mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-12 11:28:58 -05:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e319b44f0f | |||
| d29cfc8880 | |||
| 19178ca94d | |||
| 45040c4754 | |||
| f79fe1490e | |||
| 5cfbc671c5 | |||
| e6e9419b93 | |||
| 90eb78e571 | |||
| 991866f549 | |||
| 6d1b3475d4 | |||
| 5e967a2b67 | |||
| 23a8fd6a47 | |||
| 0686eb3cbb | |||
| 6d9ab315c2 | |||
| 4128731c5f | |||
| ef96426ca0 | |||
| ce1dbe8b00 | |||
| 444f043140 | |||
| 2d32c0d671 | |||
| 8dc70a5e30 | |||
| 3e4e55fbf1 | |||
| fcfedd6e15 | |||
| 6c4342690f | |||
| b8c361fcf3 | |||
| 8771a0ec91 | |||
| fc33c52133 | |||
| 75cf9293b1 | |||
| e489c6a346 | |||
| cefc2bdf60 | |||
| 78473bf3d0 | |||
| 15403c6a92 | |||
| 35b98863a4 | |||
| 65f5968fb1 | |||
| 2dfea4d72f | |||
| ff77118932 | |||
| 79a773432a | |||
| d53869f1df | |||
| fc9ddb2b0d | |||
| 6fcb6863bd | |||
| b1cee91ad9 | |||
| 60bd5cbeff | |||
| b6a3a15379 | |||
| c68f214eff | |||
| c90ee84483 | |||
| dc1ee72594 | |||
| 924132287e | |||
| e6f347aa07 | |||
| 367bc23dd4 | |||
| a1a11b2bb8 | |||
| 0653c6a59f | |||
| b6d793e109 | |||
| 439dd0b44e | |||
| 2556f5e15d | |||
| cc0eec3bf0 | |||
| 4b009a8eb4 | |||
| 2aaddf7306 | |||
| fb5d6145d0 | |||
| 59310bac93 |
@@ -20,12 +20,12 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Cache Build
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
id: cache-build
|
||||
env:
|
||||
cache-name: prod-build
|
||||
@@ -43,7 +43,7 @@ runs:
|
||||
shell: bash
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
@@ -53,7 +53,7 @@ runs:
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Run Chromatic
|
||||
uses: chromaui/action@4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694 # v13.3.4
|
||||
|
||||
+37
-48
@@ -57,7 +57,7 @@ jobs:
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 22.x
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
shell: bash
|
||||
|
||||
- name: create .env
|
||||
@@ -85,65 +85,48 @@ jobs:
|
||||
echo "S3_REGION=us-east-1" >> .env
|
||||
echo "S3_BUCKET_NAME=formbricks-e2e" >> .env
|
||||
echo "S3_ENDPOINT_URL=http://localhost:9000" >> .env
|
||||
echo "S3_ACCESS_KEY=devminio" >> .env
|
||||
echo "S3_SECRET_KEY=devminio123" >> .env
|
||||
echo "S3_ACCESS_KEY=devrustfs-service" >> .env
|
||||
echo "S3_SECRET_KEY=devrustfs-service123" >> .env
|
||||
echo "S3_FORCE_PATH_STYLE=1" >> .env
|
||||
shell: bash
|
||||
|
||||
- name: Install MinIO client (mc)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MC_VERSION="RELEASE.2025-08-13T08-35-41Z"
|
||||
MC_BASE="https://dl.min.io/client/mc/release/linux-amd64/archive"
|
||||
MC_BIN="mc.${MC_VERSION}"
|
||||
MC_SUM="${MC_BIN}.sha256sum"
|
||||
|
||||
curl -fsSL "${MC_BASE}/${MC_BIN}" -o "${MC_BIN}"
|
||||
curl -fsSL "${MC_BASE}/${MC_SUM}" -o "${MC_SUM}"
|
||||
|
||||
sha256sum -c "${MC_SUM}"
|
||||
|
||||
chmod +x "${MC_BIN}"
|
||||
sudo mv "${MC_BIN}" /usr/local/bin/mc
|
||||
|
||||
- name: Start MinIO Server
|
||||
- name: Start RustFS Server
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Start MinIO server in background
|
||||
# Start RustFS server in background
|
||||
docker run -d \
|
||||
--name minio-server \
|
||||
--name rustfs-server \
|
||||
-p 9000:9000 \
|
||||
-p 9001:9001 \
|
||||
-e MINIO_ROOT_USER=devminio \
|
||||
-e MINIO_ROOT_PASSWORD=devminio123 \
|
||||
minio/minio:RELEASE.2025-09-07T16-13-09Z \
|
||||
server /data --console-address :9001
|
||||
-e RUSTFS_ACCESS_KEY=devrustfs \
|
||||
-e RUSTFS_SECRET_KEY=devrustfs123 \
|
||||
-e RUSTFS_ADDRESS=:9000 \
|
||||
-e RUSTFS_CONSOLE_ENABLE=true \
|
||||
-e RUSTFS_CONSOLE_ADDRESS=:9001 \
|
||||
rustfs/rustfs:1.0.0-alpha.93 \
|
||||
/data
|
||||
|
||||
echo "MinIO server started"
|
||||
echo "RustFS server started"
|
||||
|
||||
- name: Wait for MinIO and create S3 bucket
|
||||
- name: Bootstrap RustFS bucket and browser upload CORS
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Waiting for MinIO to be ready..."
|
||||
ready=0
|
||||
for i in {1..60}; do
|
||||
if curl -fsS http://localhost:9000/minio/health/live >/dev/null; then
|
||||
echo "MinIO is up after ${i} seconds"
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ "$ready" -ne 1 ]; then
|
||||
echo "::error::MinIO did not become ready within 60 seconds"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mc alias set local http://localhost:9000 devminio devminio123
|
||||
mc mb --ignore-existing local/formbricks-e2e
|
||||
docker run --rm \
|
||||
--network host \
|
||||
--entrypoint /bin/sh \
|
||||
-e RUSTFS_ENDPOINT_URL=http://127.0.0.1:9000 \
|
||||
-e RUSTFS_ADMIN_USER=devrustfs \
|
||||
-e RUSTFS_ADMIN_PASSWORD=devrustfs123 \
|
||||
-e RUSTFS_SERVICE_USER=devrustfs-service \
|
||||
-e RUSTFS_SERVICE_PASSWORD=devrustfs-service123 \
|
||||
-e RUSTFS_BUCKET_NAME=formbricks-e2e \
|
||||
-e RUSTFS_POLICY_NAME=formbricks-e2e-policy \
|
||||
-e RUSTFS_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 \
|
||||
-v "$PWD/docker/rustfs-init.sh:/tmp/rustfs-init.sh:ro" \
|
||||
minio/mc@sha256:95b5f3f7969a5c5a9f3a700ba72d5c84172819e13385aaf916e237cf111ab868 \
|
||||
/tmp/rustfs-init.sh
|
||||
|
||||
- name: Build App
|
||||
run: |
|
||||
@@ -242,8 +225,14 @@ jobs:
|
||||
if: failure()
|
||||
with:
|
||||
name: app-logs
|
||||
if-no-files-found: ignore
|
||||
path: app.log
|
||||
|
||||
- name: Output App Logs
|
||||
if: failure()
|
||||
run: cat app.log
|
||||
run: |
|
||||
if [ -f app.log ]; then
|
||||
cat app.log
|
||||
else
|
||||
echo "app.log not found because the Run App step did not execute or failed before log creation."
|
||||
fi
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
- name: Setup Node.js 22.x
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
@@ -2,6 +2,7 @@ name: Translation Validation
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js 22.x
|
||||
if: steps.changes.outputs.translations == 'true'
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22.x
|
||||
|
||||
@@ -49,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.translations == 'true'
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
run: pnpm install --frozen-lockfile --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Validate translation keys
|
||||
if: steps.changes.outputs.translations == 'true'
|
||||
|
||||
+12
-12
@@ -11,19 +11,19 @@
|
||||
"clean": "rimraf .turbo node_modules dist storybook-static"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@storybook/addon-a11y": "10.2.17",
|
||||
"@storybook/addon-links": "10.2.17",
|
||||
"@storybook/addon-onboarding": "10.2.17",
|
||||
"@storybook/react-vite": "10.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@tailwindcss/vite": "4.2.1",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@chromatic-com/storybook": "5.0.2",
|
||||
"@storybook/addon-a11y": "10.3.5",
|
||||
"@storybook/addon-docs": "10.3.5",
|
||||
"@storybook/addon-links": "10.3.5",
|
||||
"@storybook/addon-onboarding": "10.3.5",
|
||||
"@storybook/react-vite": "10.3.5",
|
||||
"@tailwindcss/vite": "4.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"@vitejs/plugin-react": "5.1.4",
|
||||
"eslint-plugin-react-refresh": "0.4.26",
|
||||
"eslint-plugin-storybook": "10.2.17",
|
||||
"storybook": "10.2.17",
|
||||
"vite": "7.3.1",
|
||||
"@storybook/addon-docs": "10.2.17"
|
||||
"eslint-plugin-storybook": "10.3.5",
|
||||
"storybook": "10.3.5",
|
||||
"vite": "7.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
@@ -32,7 +32,7 @@ describe("getTeamsByOrganizationId", () => {
|
||||
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
new PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getTeamsByOrganizationId("org1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
@@ -27,7 +27,7 @@ export const getTeamsByOrganizationId = reactCache(
|
||||
name: team.name,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
if (error instanceof PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
|
||||
@@ -409,16 +409,22 @@ export const MainNavigation = ({
|
||||
: `/environments/${environment.id}/surveys/`;
|
||||
|
||||
const handleProjectChange = (projectId: string) => {
|
||||
if (projectId === project.id) return;
|
||||
const targetPath =
|
||||
projectId === project.id ? `/environments/${environment.id}/surveys` : `/workspaces/${projectId}/`;
|
||||
startTransition(() => {
|
||||
router.push(`/workspaces/${projectId}/`);
|
||||
setIsWorkspaceDropdownOpen(false);
|
||||
router.push(targetPath);
|
||||
});
|
||||
};
|
||||
|
||||
const handleOrganizationChange = (organizationId: string) => {
|
||||
if (organizationId === organization.id) return;
|
||||
const targetPath =
|
||||
organizationId === organization.id
|
||||
? `/environments/${environment.id}/settings/general`
|
||||
: `/organizations/${organizationId}/`;
|
||||
startTransition(() => {
|
||||
router.push(`/organizations/${organizationId}/`);
|
||||
setIsOrganizationDropdownOpen(false);
|
||||
router.push(targetPath);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -475,7 +481,7 @@ export const MainNavigation = ({
|
||||
);
|
||||
|
||||
const switcherIconClasses =
|
||||
"flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-slate-600";
|
||||
"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-600";
|
||||
const isInitialProjectsLoading = isWorkspaceDropdownOpen && !hasInitializedProjects && !workspaceLoadError;
|
||||
|
||||
return (
|
||||
|
||||
+5
-1
@@ -114,8 +114,12 @@ export const OrganizationBreadcrumb = ({
|
||||
}
|
||||
|
||||
const handleOrganizationChange = (organizationId: string) => {
|
||||
if (organizationId === currentOrganizationId) return;
|
||||
startTransition(() => {
|
||||
setIsOrganizationDropdownOpen(false);
|
||||
if (organizationId === currentOrganizationId && currentEnvironmentId) {
|
||||
router.push(`/environments/${currentEnvironmentId}/settings/general`);
|
||||
return;
|
||||
}
|
||||
router.push(`/organizations/${organizationId}/`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -152,9 +152,13 @@ export const ProjectBreadcrumb = ({
|
||||
}
|
||||
|
||||
const handleProjectChange = (projectId: string) => {
|
||||
if (projectId === currentProjectId) return;
|
||||
const targetPath =
|
||||
projectId === currentProjectId
|
||||
? `/environments/${currentEnvironmentId}/surveys`
|
||||
: `/workspaces/${projectId}/`;
|
||||
startTransition(() => {
|
||||
router.push(`/workspaces/${projectId}/`);
|
||||
setIsProjectDropdownOpen(false);
|
||||
router.push(targetPath);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+8
-11
@@ -3,25 +3,22 @@
|
||||
import { InboxIcon, PresentationIcon } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { useEnvironment } from "@/app/(app)/environments/[environmentId]/context/environment-context";
|
||||
import { revalidateSurveyIdPath } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions";
|
||||
import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
|
||||
import { SecondaryNavigation } from "@/modules/ui/components/secondary-navigation";
|
||||
|
||||
interface SurveyAnalysisNavigationProps {
|
||||
environmentId: string;
|
||||
survey: TSurvey;
|
||||
activeId: string;
|
||||
}
|
||||
|
||||
export const SurveyAnalysisNavigation = ({
|
||||
environmentId,
|
||||
survey,
|
||||
activeId,
|
||||
}: SurveyAnalysisNavigationProps) => {
|
||||
export const SurveyAnalysisNavigation = ({ activeId }: SurveyAnalysisNavigationProps) => {
|
||||
const pathname = usePathname();
|
||||
const { t } = useTranslation();
|
||||
const { environment } = useEnvironment();
|
||||
const { survey } = useSurvey();
|
||||
|
||||
const url = `/environments/${environmentId}/surveys/${survey.id}`;
|
||||
const url = `/environments/${environment.id}/surveys/${survey.id}`;
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
@@ -31,7 +28,7 @@ export const SurveyAnalysisNavigation = ({
|
||||
href: `${url}/summary?referer=true`,
|
||||
current: pathname?.includes("/summary"),
|
||||
onClick: () => {
|
||||
revalidateSurveyIdPath(environmentId, survey.id);
|
||||
revalidateSurveyIdPath(environment.id, survey.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -41,7 +38,7 @@ export const SurveyAnalysisNavigation = ({
|
||||
href: `${url}/responses?referer=true`,
|
||||
current: pathname?.includes("/responses"),
|
||||
onClick: () => {
|
||||
revalidateSurveyIdPath(environmentId, survey.id);
|
||||
revalidateSurveyIdPath(environment.id, survey.id);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
+41
-15
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useCallback, useContext, useState } from "react";
|
||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ElementOption,
|
||||
ElementOptions,
|
||||
@@ -30,7 +30,7 @@ interface SelectedFilterOptions {
|
||||
|
||||
export interface DateRange {
|
||||
from: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
to?: Date;
|
||||
}
|
||||
|
||||
interface FilterDateContextProps {
|
||||
@@ -41,6 +41,8 @@ interface FilterDateContextProps {
|
||||
dateRange: DateRange;
|
||||
setDateRange: React.Dispatch<React.SetStateAction<DateRange>>;
|
||||
resetState: () => void;
|
||||
refreshAnalysisData: () => Promise<void>;
|
||||
registerAnalysisRefreshHandler: (handler: () => Promise<void>) => () => void;
|
||||
}
|
||||
|
||||
const ResponseFilterContext = createContext<FilterDateContextProps | undefined>(undefined);
|
||||
@@ -61,6 +63,7 @@ const ResponseFilterProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
from: undefined,
|
||||
to: getTodayDate(),
|
||||
});
|
||||
const refreshHandlerRef = useRef<(() => Promise<void>) | null>(null);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setDateRange({
|
||||
@@ -73,20 +76,43 @@ const ResponseFilterProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ResponseFilterContext.Provider
|
||||
value={{
|
||||
setSelectedFilter,
|
||||
selectedFilter,
|
||||
selectedOptions,
|
||||
setSelectedOptions,
|
||||
dateRange,
|
||||
setDateRange,
|
||||
resetState,
|
||||
}}>
|
||||
{children}
|
||||
</ResponseFilterContext.Provider>
|
||||
const refreshAnalysisData = useCallback(async () => {
|
||||
await refreshHandlerRef.current?.();
|
||||
}, []);
|
||||
|
||||
const registerAnalysisRefreshHandler = useCallback((handler: () => Promise<void>) => {
|
||||
refreshHandlerRef.current = handler;
|
||||
|
||||
return () => {
|
||||
if (refreshHandlerRef.current === handler) {
|
||||
refreshHandlerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
setSelectedFilter,
|
||||
selectedFilter,
|
||||
selectedOptions,
|
||||
setSelectedOptions,
|
||||
dateRange,
|
||||
setDateRange,
|
||||
resetState,
|
||||
refreshAnalysisData,
|
||||
registerAnalysisRefreshHandler,
|
||||
}),
|
||||
[
|
||||
dateRange,
|
||||
refreshAnalysisData,
|
||||
registerAnalysisRefreshHandler,
|
||||
resetState,
|
||||
selectedFilter,
|
||||
selectedOptions,
|
||||
]
|
||||
);
|
||||
|
||||
return <ResponseFilterContext.Provider value={contextValue}>{children}</ResponseFilterContext.Provider>;
|
||||
};
|
||||
|
||||
const useResponseFilter = () => {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { SkeletonLoader } from "@/modules/ui/components/skeleton-loader";
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle="" />
|
||||
<div className="flex h-9 animate-pulse gap-2">
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
</div>
|
||||
<SkeletonLoader type="summary" />
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
+35
-2
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurveyQuota } from "@formbricks/types/quota";
|
||||
import { TResponseWithQuotas } from "@formbricks/types/responses";
|
||||
@@ -13,6 +15,7 @@ import { useResponseFilter } from "@/app/(app)/environments/[environmentId]/surv
|
||||
import { ResponseDataView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseDataView";
|
||||
import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter";
|
||||
import { getFormattedFilters } from "@/app/lib/surveys/surveys";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
|
||||
interface ResponsePageProps {
|
||||
@@ -46,8 +49,8 @@ export const ResponsePage = ({
|
||||
const [page, setPage] = useState<number | null>(null);
|
||||
const [hasMore, setHasMore] = useState<boolean>(initialResponses.length >= responsesPerPage);
|
||||
const [isFetchingFirstPage, setIsFetchingFirstPage] = useState<boolean>(false);
|
||||
const { selectedFilter, dateRange, resetState } = useResponseFilter();
|
||||
|
||||
const { selectedFilter, dateRange, resetState, registerAnalysisRefreshHandler } = useResponseFilter();
|
||||
const { t } = useTranslation();
|
||||
const filters = useMemo(
|
||||
() => getFormattedFilters(survey, selectedFilter, dateRange),
|
||||
|
||||
@@ -86,6 +89,34 @@ export const ResponsePage = ({
|
||||
setResponses((prev) => prev.map((r) => (r.id === responseId ? updatedResponse : r)));
|
||||
};
|
||||
|
||||
const refetchResponses = useCallback(async () => {
|
||||
setIsFetchingFirstPage(true);
|
||||
|
||||
try {
|
||||
const getResponsesActionResponse = await getResponsesAction({
|
||||
surveyId,
|
||||
limit: responsesPerPage,
|
||||
offset: 0,
|
||||
filterCriteria: filters,
|
||||
});
|
||||
|
||||
if (getResponsesActionResponse?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(getResponsesActionResponse) ?? t("common.something_went_wrong"));
|
||||
}
|
||||
|
||||
const freshResponses = getResponsesActionResponse?.data ?? [];
|
||||
setResponses(freshResponses);
|
||||
setPage(1);
|
||||
setHasMore(freshResponses.length >= responsesPerPage);
|
||||
} finally {
|
||||
setIsFetchingFirstPage(false);
|
||||
}
|
||||
}, [filters, responsesPerPage, surveyId]);
|
||||
|
||||
useEffect(() => {
|
||||
return registerAnalysisRefreshHandler(refetchResponses);
|
||||
}, [refetchResponses, registerAnalysisRefreshHandler]);
|
||||
|
||||
const surveyMemoized = useMemo(() => {
|
||||
return replaceHeadlineRecall(survey, "default");
|
||||
}, [survey]);
|
||||
@@ -134,6 +165,8 @@ export const ResponsePage = ({
|
||||
}
|
||||
};
|
||||
fetchFilteredResponses();
|
||||
// page is intentionally omitted to avoid refetching after the initial page setup.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters, responsesPerPage, selectedFilter, dateRange, surveyId]);
|
||||
|
||||
return (
|
||||
|
||||
+1
-1
@@ -1,5 +1,4 @@
|
||||
import { TFunction } from "i18next";
|
||||
import { capitalize } from "lodash";
|
||||
import {
|
||||
AirplayIcon,
|
||||
ArrowUpFromDotIcon,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
SmartphoneIcon,
|
||||
} from "lucide-react";
|
||||
import { TResponseMeta } from "@formbricks/types/responses";
|
||||
import { capitalize } from "@/lib/utils/object";
|
||||
|
||||
export const getAddressFieldLabel = (field: string, t: TFunction) => {
|
||||
switch (field) {
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { SkeletonLoader } from "@/modules/ui/components/skeleton-loader";
|
||||
|
||||
const Loading = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.responses")} />
|
||||
<div className="flex h-9 animate-pulse gap-1.5">
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
<div className="h-9 w-36 rounded-full bg-slate-200" />
|
||||
</div>
|
||||
<SkeletonLoader type="responseTable" />
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
+1
-3
@@ -64,8 +64,6 @@ const Page = async (props: { params: Promise<{ environmentId: string; surveyId:
|
||||
pageTitle={survey.name}
|
||||
cta={
|
||||
<SurveyAnalysisCTA
|
||||
environment={environment}
|
||||
survey={survey}
|
||||
isReadOnly={isReadOnly}
|
||||
user={user}
|
||||
publicDomain={publicDomain}
|
||||
@@ -76,7 +74,7 @@ const Page = async (props: { params: Promise<{ environmentId: string; surveyId:
|
||||
isStorageConfigured={IS_STORAGE_CONFIGURED}
|
||||
/>
|
||||
}>
|
||||
<SurveyAnalysisNavigation environmentId={environment.id} survey={survey} activeId="responses" />
|
||||
<SurveyAnalysisNavigation activeId="responses" />
|
||||
</PageHeader>
|
||||
<ResponsePage
|
||||
environment={environment}
|
||||
|
||||
+5
-8
@@ -4,16 +4,13 @@ import { useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { useEnvironment } from "@/app/(app)/environments/[environmentId]/context/environment-context";
|
||||
import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
|
||||
import { Confetti } from "@/modules/ui/components/confetti";
|
||||
|
||||
interface SummaryMetadataProps {
|
||||
environment: TEnvironment;
|
||||
survey: TSurvey;
|
||||
}
|
||||
|
||||
export const SuccessMessage = ({ environment, survey }: SummaryMetadataProps) => {
|
||||
export const SuccessMessage = () => {
|
||||
const { environment } = useEnvironment();
|
||||
const { survey } = useSurvey();
|
||||
const { t } = useTranslation();
|
||||
const searchParams = useSearchParams();
|
||||
const [confetti, setConfetti] = useState(false);
|
||||
|
||||
+35
-19
@@ -71,7 +71,7 @@ export const SummaryPage = ({
|
||||
const [tab, setTab] = useState<"dropOffs" | "quotas" | "impressions" | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = useState(!initialSurveySummary);
|
||||
|
||||
const { selectedFilter, dateRange, resetState } = useResponseFilter();
|
||||
const { selectedFilter, dateRange, resetState, registerAnalysisRefreshHandler } = useResponseFilter();
|
||||
|
||||
const [displays, setDisplays] = useState<TDisplayWithContact[]>([]);
|
||||
const [isDisplaysLoading, setIsDisplaysLoading] = useState(false);
|
||||
@@ -111,7 +111,7 @@ export const SummaryPage = ({
|
||||
} finally {
|
||||
setIsDisplaysLoading(false);
|
||||
}
|
||||
}, [fetchDisplays, t]);
|
||||
}, [fetchDisplays]);
|
||||
|
||||
const handleLoadMoreDisplays = useCallback(async () => {
|
||||
try {
|
||||
@@ -131,13 +131,39 @@ export const SummaryPage = ({
|
||||
}
|
||||
}, [tab, loadInitialDisplays]);
|
||||
|
||||
const fetchSummary = useCallback(async () => {
|
||||
const currentFilters = getFormattedFilters(survey, selectedFilter, dateRange);
|
||||
const updatedSurveySummary = await getSurveySummaryAction({
|
||||
surveyId,
|
||||
filterCriteria: currentFilters,
|
||||
});
|
||||
|
||||
if (updatedSurveySummary?.serverError) {
|
||||
throw new Error(getFormattedErrorMessage(updatedSurveySummary));
|
||||
}
|
||||
|
||||
setSurveySummary(updatedSurveySummary?.data ?? defaultSurveySummary);
|
||||
}, [dateRange, selectedFilter, survey, surveyId]);
|
||||
|
||||
const refreshSummary = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await Promise.all([fetchSummary(), tab === "impressions" ? loadInitialDisplays() : Promise.resolve()]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [fetchSummary, loadInitialDisplays, tab]);
|
||||
|
||||
useEffect(() => {
|
||||
return registerAnalysisRefreshHandler(refreshSummary);
|
||||
}, [refreshSummary, registerAnalysisRefreshHandler]);
|
||||
|
||||
// Only fetch data when filters change or when there's no initial data
|
||||
useEffect(() => {
|
||||
// If we have initial data and no filters are applied, don't fetch
|
||||
const hasNoFilters =
|
||||
(!selectedFilter ||
|
||||
Object.keys(selectedFilter).length === 0 ||
|
||||
(selectedFilter.filter && selectedFilter.filter.length === 0)) &&
|
||||
(!selectedFilter || Object.keys(selectedFilter).length === 0 || selectedFilter.filter?.length === 0) &&
|
||||
(!dateRange || (!dateRange.from && !dateRange.to));
|
||||
|
||||
if (initialSurveySummary && hasNoFilters) {
|
||||
@@ -145,21 +171,11 @@ export const SummaryPage = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchSummary = async () => {
|
||||
const fetchFilteredSummary = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Recalculate filters inside the effect to ensure we have the latest values
|
||||
const currentFilters = getFormattedFilters(survey, selectedFilter, dateRange);
|
||||
let updatedSurveySummary;
|
||||
|
||||
updatedSurveySummary = await getSurveySummaryAction({
|
||||
surveyId,
|
||||
filterCriteria: currentFilters,
|
||||
});
|
||||
|
||||
const surveySummary = updatedSurveySummary?.data ?? defaultSurveySummary;
|
||||
setSurveySummary(surveySummary);
|
||||
await fetchSummary();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -167,8 +183,8 @@ export const SummaryPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
fetchSummary();
|
||||
}, [selectedFilter, dateRange, survey, surveyId, initialSurveySummary]);
|
||||
fetchFilteredSummary();
|
||||
}, [selectedFilter, dateRange, initialSurveySummary, fetchSummary]);
|
||||
|
||||
const surveyMemoized = useMemo(() => {
|
||||
return replaceHeadlineRecall(survey, "default");
|
||||
|
||||
+29
-11
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { BellRing, Eye, ListRestart, SquarePenIcon } from "lucide-react";
|
||||
import { BellRing, Eye, ListRestart, RefreshCcwIcon, SquarePenIcon } from "lucide-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSegment } from "@formbricks/types/segment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
import { useEnvironment } from "@/app/(app)/environments/[environmentId]/context/environment-context";
|
||||
import { useResponseFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/response-filter-context";
|
||||
import { SuccessMessage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage";
|
||||
import { ShareSurveyModal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal";
|
||||
import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown";
|
||||
import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { EditPublicSurveyAlertDialog } from "@/modules/survey/components/edit-public-survey-alert-dialog";
|
||||
import { useSingleUseId } from "@/modules/survey/hooks/useSingleUseId";
|
||||
@@ -23,8 +23,6 @@ import { IconBar } from "@/modules/ui/components/iconbar";
|
||||
import { resetSurveyAction } from "../actions";
|
||||
|
||||
interface SurveyAnalysisCTAProps {
|
||||
survey: TSurvey;
|
||||
environment: TEnvironment;
|
||||
isReadOnly: boolean;
|
||||
user: TUser;
|
||||
publicDomain: string;
|
||||
@@ -41,8 +39,6 @@ interface ModalState {
|
||||
}
|
||||
|
||||
export const SurveyAnalysisCTA = ({
|
||||
survey,
|
||||
environment,
|
||||
isReadOnly,
|
||||
user,
|
||||
publicDomain,
|
||||
@@ -63,9 +59,12 @@ export const SurveyAnalysisCTA = ({
|
||||
});
|
||||
const [isResetModalOpen, setIsResetModalOpen] = useState(false);
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const { project } = useEnvironment();
|
||||
const { environment, project } = useEnvironment();
|
||||
const { survey } = useSurvey();
|
||||
const { refreshSingleUseId } = useSingleUseId(survey, isReadOnly);
|
||||
const { refreshAnalysisData } = useResponseFilter();
|
||||
|
||||
const appSetupCompleted = survey.type === "app" && environment.appSetupCompleted;
|
||||
|
||||
@@ -77,7 +76,7 @@ export const SurveyAnalysisCTA = ({
|
||||
}, [searchParams]);
|
||||
|
||||
const handleShareModalToggle = (open: boolean) => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const params = new URLSearchParams(globalThis.location.search);
|
||||
const currentShareParam = params.get("share") === "true";
|
||||
|
||||
if (open && !currentShareParam) {
|
||||
@@ -147,6 +146,25 @@ export const SurveyAnalysisCTA = ({
|
||||
};
|
||||
|
||||
const iconActions = [
|
||||
{
|
||||
icon: RefreshCcwIcon,
|
||||
tooltip: t("common.refresh"),
|
||||
onClick: async () => {
|
||||
if (isRefreshing) return;
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await refreshAnalysisData();
|
||||
toast.success(t("common.data_refreshed_successfully"));
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : t("common.something_went_wrong");
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
},
|
||||
disabled: isRefreshing,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
icon: BellRing,
|
||||
tooltip: t("environments.surveys.summary.configure_alerts"),
|
||||
@@ -183,7 +201,7 @@ export const SurveyAnalysisCTA = ({
|
||||
return (
|
||||
<div className="hidden justify-end gap-x-1.5 sm:flex">
|
||||
{!isReadOnly && (appSetupCompleted || survey.type === "link") && survey.status !== "draft" && (
|
||||
<SurveyStatusDropdown environment={environment} survey={survey} />
|
||||
<SurveyStatusDropdown />
|
||||
)}
|
||||
|
||||
<IconBar actions={iconActions} />
|
||||
@@ -215,7 +233,7 @@ export const SurveyAnalysisCTA = ({
|
||||
projectCustomScripts={project.customHeadScripts}
|
||||
/>
|
||||
)}
|
||||
<SuccessMessage environment={environment} survey={survey} />
|
||||
<SuccessMessage />
|
||||
|
||||
{responseCount > 0 && (
|
||||
<EditPublicSurveyAlertDialog
|
||||
|
||||
+18
-3
@@ -16,13 +16,19 @@ export const WebsiteEmbedTab = ({ surveyUrl }: WebsiteEmbedTabProps) => {
|
||||
const [embedModeEnabled, setEmbedModeEnabled] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const iframeCode = `<div style="position: relative; height:80dvh; overflow:auto;">
|
||||
<iframe
|
||||
src="${surveyUrl}${embedModeEnabled ? "?embed=true" : ""}"
|
||||
const separator = surveyUrl.includes("?") ? "&" : "?";
|
||||
|
||||
const iframeSrc = embedModeEnabled ? `${surveyUrl}${separator}embed=true` : surveyUrl;
|
||||
|
||||
const iframeCode = `<div style="position: relative; height:80dvh; overflow:auto;">
|
||||
<iframe
|
||||
src="${iframeSrc}"
|
||||
frameborder="0" style="position: absolute; left:0; top:0; width:100%; height:100%; border:0;">
|
||||
</iframe>
|
||||
</div>`;
|
||||
|
||||
const previewSrc = `${iframeSrc}${iframeSrc.includes("?") ? "&" : "?"}preview=true`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CodeBlock language="html" noMargin>
|
||||
@@ -48,6 +54,15 @@ export const WebsiteEmbedTab = ({ surveyUrl }: WebsiteEmbedTabProps) => {
|
||||
{t("common.copy_code")}
|
||||
<CopyIcon />
|
||||
</Button>
|
||||
|
||||
<p className="text-base font-medium text-slate-800">{t("common.preview")}</p>
|
||||
<div className="relative h-[500px] w-full overflow-hidden rounded-lg border border-slate-300">
|
||||
<iframe
|
||||
title={t("common.preview")}
|
||||
src={previewSrc}
|
||||
className="absolute inset-0 h-full w-full border-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+55
@@ -191,6 +191,61 @@ describe("getSurveySummaryMeta", () => {
|
||||
expect(meta.dropOffPercentage).toBe(0);
|
||||
expect(meta.ttcAverage).toBe(0);
|
||||
});
|
||||
|
||||
test("uses block-level TTC to avoid multiplying by number of elements", () => {
|
||||
const surveyWithOneBlockThreeElements: TSurvey = {
|
||||
...mockBaseSurvey,
|
||||
blocks: [
|
||||
{
|
||||
id: "block1",
|
||||
name: "Block 1",
|
||||
elements: [
|
||||
{
|
||||
id: "q1",
|
||||
type: TSurveyElementTypeEnum.OpenText,
|
||||
headline: { default: "Q1" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
charLimit: { enabled: false },
|
||||
},
|
||||
{
|
||||
id: "q2",
|
||||
type: TSurveyElementTypeEnum.OpenText,
|
||||
headline: { default: "Q2" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
charLimit: { enabled: false },
|
||||
},
|
||||
{
|
||||
id: "q3",
|
||||
type: TSurveyElementTypeEnum.OpenText,
|
||||
headline: { default: "Q3" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
charLimit: { enabled: false },
|
||||
},
|
||||
] as TSurveyElement[],
|
||||
},
|
||||
],
|
||||
questions: [],
|
||||
};
|
||||
|
||||
const responses = [
|
||||
{
|
||||
id: "r1",
|
||||
data: { q1: "a", q2: "b", q3: "c" },
|
||||
updatedAt: new Date(),
|
||||
contact: null,
|
||||
contactAttributes: {},
|
||||
language: "en",
|
||||
ttc: { q1: 5000, q2: 5000, q3: 4800, _total: 14800 },
|
||||
finished: true,
|
||||
},
|
||||
] as any;
|
||||
|
||||
const meta = getSurveySummaryMeta(surveyWithOneBlockThreeElements, responses, 1, mockQuotas);
|
||||
expect(meta.ttcAverage).toBe(5000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSurveySummaryDropOff", () => {
|
||||
|
||||
+7
-1
@@ -1094,7 +1094,9 @@ export const getResponsesForSummary = reactCache(
|
||||
const transformedResponses: TSurveySummaryResponse[] = await Promise.all(
|
||||
responses.map((responsePrisma) => {
|
||||
return {
|
||||
...responsePrisma,
|
||||
id: responsePrisma.id,
|
||||
data: (responsePrisma.data ?? {}) as TResponseData,
|
||||
updatedAt: responsePrisma.updatedAt,
|
||||
contact: responsePrisma.contact
|
||||
? {
|
||||
id: responsePrisma.contact.id as string,
|
||||
@@ -1103,6 +1105,10 @@ export const getResponsesForSummary = reactCache(
|
||||
)?.value as string,
|
||||
}
|
||||
: null,
|
||||
contactAttributes: (responsePrisma.contactAttributes ?? {}) as TResponseContactAttributes,
|
||||
language: responsePrisma.language,
|
||||
ttc: (responsePrisma.ttc ?? {}) as TResponseTtc,
|
||||
finished: responsePrisma.finished,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
+1
-3
@@ -66,8 +66,6 @@ const SurveyPage = async (props: { params: Promise<{ environmentId: string; surv
|
||||
pageTitle={survey.name}
|
||||
cta={
|
||||
<SurveyAnalysisCTA
|
||||
environment={environment}
|
||||
survey={survey}
|
||||
isReadOnly={isReadOnly}
|
||||
user={user}
|
||||
publicDomain={publicDomain}
|
||||
@@ -78,7 +76,7 @@ const SurveyPage = async (props: { params: Promise<{ environmentId: string; surv
|
||||
isStorageConfigured={IS_STORAGE_CONFIGURED}
|
||||
/>
|
||||
}>
|
||||
<SurveyAnalysisNavigation environmentId={environment.id} survey={survey} activeId="summary" />
|
||||
<SurveyAnalysisNavigation activeId="summary" />
|
||||
</PageHeader>
|
||||
<SummaryPage
|
||||
environment={environment}
|
||||
|
||||
+5
-16
@@ -3,8 +3,9 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { useEnvironment } from "@/app/(app)/environments/[environmentId]/context/environment-context";
|
||||
import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { updateSurveyAction } from "@/modules/survey/editor/actions";
|
||||
import {
|
||||
@@ -16,17 +17,9 @@ import {
|
||||
} from "@/modules/ui/components/select";
|
||||
import { SurveyStatusIndicator } from "@/modules/ui/components/survey-status-indicator";
|
||||
|
||||
interface SurveyStatusDropdownProps {
|
||||
environment: TEnvironment;
|
||||
updateLocalSurveyStatus?: (status: TSurvey["status"]) => void;
|
||||
survey: TSurvey;
|
||||
}
|
||||
|
||||
export const SurveyStatusDropdown = ({
|
||||
environment,
|
||||
updateLocalSurveyStatus,
|
||||
survey,
|
||||
}: SurveyStatusDropdownProps) => {
|
||||
export const SurveyStatusDropdown = () => {
|
||||
const { environment } = useEnvironment();
|
||||
const { survey } = useSurvey();
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -46,10 +39,6 @@ export const SurveyStatusDropdown = ({
|
||||
toast.success(toastMessage);
|
||||
}
|
||||
|
||||
if (updateLocalSurveyStatus) {
|
||||
updateLocalSurveyStatus(resultingStatus);
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(updateSurveyActionResponse);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { SurveysQueryClientProvider } from "./query-client-provider";
|
||||
|
||||
const SurveysLayout = ({ children }: { children: ReactNode }) => {
|
||||
return <SurveysQueryClientProvider>{children}</SurveysQueryClientProvider>;
|
||||
};
|
||||
|
||||
export default SurveysLayout;
|
||||
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { type ReactNode, useState } from "react";
|
||||
|
||||
export const SurveysQueryClientProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [queryClient] = useState(() => new QueryClient());
|
||||
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { captureSurveyResponsePostHogEvent } from "./posthog";
|
||||
|
||||
vi.mock("@/lib/posthog", () => ({
|
||||
capturePostHogEvent: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("captureSurveyResponsePostHogEvent", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const makeParams = (responseCount: number) => ({
|
||||
organizationId: "org-1",
|
||||
surveyId: "survey-1",
|
||||
surveyType: "link",
|
||||
environmentId: "env-1",
|
||||
responseCount,
|
||||
});
|
||||
|
||||
test("fires on 1st response with milestone 'first'", async () => {
|
||||
const { capturePostHogEvent } = await import("@/lib/posthog");
|
||||
|
||||
captureSurveyResponsePostHogEvent(makeParams(1));
|
||||
|
||||
expect(capturePostHogEvent).toHaveBeenCalledWith("org-1", "survey_response_received", {
|
||||
survey_id: "survey-1",
|
||||
survey_type: "link",
|
||||
organization_id: "org-1",
|
||||
environment_id: "env-1",
|
||||
response_count: 1,
|
||||
is_first_response: true,
|
||||
milestone: "first",
|
||||
});
|
||||
});
|
||||
|
||||
test("fires on every 100th response", async () => {
|
||||
const { capturePostHogEvent } = await import("@/lib/posthog");
|
||||
|
||||
for (const count of [100, 200, 300, 500, 1000, 5000]) {
|
||||
captureSurveyResponsePostHogEvent(makeParams(count));
|
||||
}
|
||||
|
||||
expect(capturePostHogEvent).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
|
||||
test("does NOT fire for 2nd through 99th responses", async () => {
|
||||
const { capturePostHogEvent } = await import("@/lib/posthog");
|
||||
|
||||
for (const count of [2, 5, 10, 50, 99]) {
|
||||
captureSurveyResponsePostHogEvent(makeParams(count));
|
||||
}
|
||||
|
||||
expect(capturePostHogEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("does NOT fire for non-100th counts above 100", async () => {
|
||||
const { capturePostHogEvent } = await import("@/lib/posthog");
|
||||
|
||||
for (const count of [101, 150, 250, 499, 501]) {
|
||||
captureSurveyResponsePostHogEvent(makeParams(count));
|
||||
}
|
||||
|
||||
expect(capturePostHogEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("sets milestone to count string for non-first milestones", async () => {
|
||||
const { capturePostHogEvent } = await import("@/lib/posthog");
|
||||
|
||||
captureSurveyResponsePostHogEvent(makeParams(200));
|
||||
|
||||
expect(capturePostHogEvent).toHaveBeenCalledWith(
|
||||
"org-1",
|
||||
"survey_response_received",
|
||||
expect.objectContaining({
|
||||
is_first_response: false,
|
||||
milestone: "200",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -185,4 +185,20 @@ describe("auth route audit logging", () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("does not log a completed sign-in for the intermediate SSO recovery verification step", async () => {
|
||||
const authOptions = await getWrappedAuthOptions("req-sso-recovery");
|
||||
const user = {
|
||||
id: "user_4",
|
||||
email: "user4@example.com",
|
||||
authFlowPurpose: "sso_recovery",
|
||||
};
|
||||
const account = { provider: "token" };
|
||||
|
||||
await expect(authOptions.callbacks.signIn({ user, account })).resolves.toBe(true);
|
||||
await authOptions.events.signIn({ user, account, isNewUser: false });
|
||||
|
||||
expect(mocks.baseEventSignIn).not.toHaveBeenCalled();
|
||||
expect(mocks.queueAuditEventBackground).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,12 @@ const getAuthMethod = (account: Account | null) => {
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
const isSsoRecoveryVerificationFlow = (account: Account | null, user: User | AdapterUser) =>
|
||||
account?.provider === "token" &&
|
||||
"authFlowPurpose" in user &&
|
||||
typeof user.authFlowPurpose === "string" &&
|
||||
user.authFlowPurpose === "sso_recovery";
|
||||
|
||||
const handler = async (req: Request, ctx: any) => {
|
||||
const eventId = req.headers.get("x-request-id") ?? undefined;
|
||||
|
||||
@@ -117,6 +123,10 @@ const handler = async (req: Request, ctx: any) => {
|
||||
events: {
|
||||
...baseAuthOptions.events,
|
||||
async signIn({ user, account, isNewUser }: any) {
|
||||
if (isSsoRecoveryVerificationFlow(account, user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await baseAuthOptions.events?.signIn?.({ user, account, isNewUser });
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextResponse } from "next/server";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { verifySsoRelinkIntent } from "@/lib/jwt";
|
||||
import { deleteSessionBySessionToken } from "@/modules/auth/lib/auth-session-repository";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
NEXT_AUTH_SESSION_COOKIE_NAMES,
|
||||
getSessionTokenFromCookieHeader,
|
||||
} from "@/modules/auth/lib/session-cookie";
|
||||
import { completeSsoRecovery, getSsoRecoveryFailureRedirectUrl } from "@/modules/ee/sso/lib/sso-recovery";
|
||||
|
||||
const clearSessionCookies = (response: NextResponse) => {
|
||||
for (const cookieName of NEXT_AUTH_SESSION_COOKIE_NAMES) {
|
||||
response.cookies.set({
|
||||
name: cookieName,
|
||||
value: "",
|
||||
expires: new Date(0),
|
||||
path: "/",
|
||||
secure: cookieName.startsWith("__Secure-"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buildFailedRecoveryResponse = async (request: Request, callbackUrl?: string) => {
|
||||
const response = NextResponse.redirect(getSsoRecoveryFailureRedirectUrl(callbackUrl));
|
||||
clearSessionCookies(response);
|
||||
|
||||
const sessionToken = getSessionTokenFromCookieHeader(request.headers.get("cookie"));
|
||||
if (!sessionToken) {
|
||||
return response;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteSessionBySessionToken(sessionToken);
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to delete SSO recovery session after recovery completion error");
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export const GET = async (request: Request) => {
|
||||
const url = new URL(request.url);
|
||||
const intentToken = url.searchParams.get("intent");
|
||||
|
||||
if (!intentToken) {
|
||||
return NextResponse.redirect(getSsoRecoveryFailureRedirectUrl());
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
const callbackUrl = await completeSsoRecovery({
|
||||
intentToken,
|
||||
sessionUserId: session?.user.id,
|
||||
});
|
||||
|
||||
return NextResponse.redirect(callbackUrl);
|
||||
} catch {
|
||||
try {
|
||||
const intent = verifySsoRelinkIntent(intentToken);
|
||||
return await buildFailedRecoveryResponse(request, intent.callbackUrl);
|
||||
} catch {
|
||||
return await buildFailedRecoveryResponse(request);
|
||||
}
|
||||
}
|
||||
};
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { getResponseIdByDisplayId } from "./response";
|
||||
|
||||
vi.mock("@/lib/utils/validate", () => ({
|
||||
validateInputs: vi.fn((inputs: [unknown, unknown][]) =>
|
||||
inputs.map((input: [unknown, unknown]) => input[0])
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
display: {
|
||||
findFirst: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("getResponseIdByDisplayId", () => {
|
||||
const environmentId = "env1234567890123456789012";
|
||||
const displayId = "display1234567890123456789";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns the linked responseId when a response exists", async () => {
|
||||
vi.mocked(prisma.display.findFirst).mockResolvedValue({
|
||||
response: {
|
||||
id: "response123456789012345678",
|
||||
},
|
||||
} as any);
|
||||
|
||||
const result = await getResponseIdByDisplayId(environmentId, displayId);
|
||||
|
||||
expect(validateInputs).toHaveBeenCalledWith(
|
||||
[environmentId, expect.any(Object)],
|
||||
[displayId, expect.any(Object)]
|
||||
);
|
||||
expect(prisma.display.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: displayId,
|
||||
survey: {
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
response: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({ responseId: "response123456789012345678" });
|
||||
});
|
||||
|
||||
test("returns null when the display exists but has no response", async () => {
|
||||
vi.mocked(prisma.display.findFirst).mockResolvedValue({
|
||||
response: null,
|
||||
} as any);
|
||||
|
||||
await expect(getResponseIdByDisplayId(environmentId, displayId)).resolves.toEqual({
|
||||
responseId: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("throws ResourceNotFoundError when the display does not exist in the environment", async () => {
|
||||
vi.mocked(prisma.display.findFirst).mockResolvedValue(null);
|
||||
|
||||
await expect(getResponseIdByDisplayId(environmentId, displayId)).rejects.toThrow(
|
||||
new ResourceNotFoundError("Display", displayId)
|
||||
);
|
||||
});
|
||||
|
||||
test("throws ValidationError when input validation fails", async () => {
|
||||
const validationError = new ValidationError("Validation failed");
|
||||
vi.mocked(validateInputs).mockImplementation(() => {
|
||||
throw validationError;
|
||||
});
|
||||
|
||||
await expect(getResponseIdByDisplayId(environmentId, displayId)).rejects.toThrow(ValidationError);
|
||||
expect(prisma.display.findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("throws DatabaseError on Prisma request errors", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Database error", {
|
||||
code: "P2002",
|
||||
clientVersion: "test",
|
||||
});
|
||||
vi.mocked(prisma.display.findFirst).mockRejectedValue(prismaError);
|
||||
|
||||
await expect(getResponseIdByDisplayId(environmentId, displayId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { getResponseIdByDisplayId } from "./lib/response";
|
||||
import { GET } from "./route";
|
||||
|
||||
vi.mock("@/app/lib/api/with-api-logging", async () => {
|
||||
return {
|
||||
withV1ApiWrapper:
|
||||
({ handler }: { handler: any }) =>
|
||||
async (req: NextRequest, props: any) => {
|
||||
const result = await handler({ req, props });
|
||||
return result.response;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./lib/response", () => ({
|
||||
getResponseIdByDisplayId: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("GET /api/v1/client/[environmentId]/displays/[displayId]/response", () => {
|
||||
const req = new NextRequest("http://localhost/api/v1/client/env/displays/display/response");
|
||||
const props = {
|
||||
params: Promise.resolve({
|
||||
environmentId: "env1234567890123456789012",
|
||||
displayId: "display1234567890123456789",
|
||||
}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns the responseId when a linked response exists", async () => {
|
||||
vi.mocked(getResponseIdByDisplayId).mockResolvedValue({ responseId: "response123456789012345678" });
|
||||
|
||||
const response = await GET(req, props);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
data: {
|
||||
responseId: "response123456789012345678",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns null when the display exists without a response", async () => {
|
||||
vi.mocked(getResponseIdByDisplayId).mockResolvedValue({ responseId: null });
|
||||
|
||||
const response = await GET(req, props);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
data: {
|
||||
responseId: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 404 when the display is missing for the environment", async () => {
|
||||
vi.mocked(getResponseIdByDisplayId).mockRejectedValue(
|
||||
new ResourceNotFoundError("Display", "display1234567890123456789")
|
||||
);
|
||||
|
||||
const response = await GET(req, props);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -70,6 +70,7 @@ const mockEnvironmentData = {
|
||||
displayOption: "displayOnce",
|
||||
hiddenFields: { enabled: false },
|
||||
isBackButtonHidden: false,
|
||||
isAutoProgressingEnabled: true,
|
||||
triggers: [],
|
||||
displayPercentage: null,
|
||||
delay: 0,
|
||||
@@ -122,6 +123,13 @@ describe("getEnvironmentStateData", () => {
|
||||
surveys: expect.any(Object),
|
||||
}),
|
||||
});
|
||||
|
||||
const prismaCall = vi.mocked(prisma.environment.findUnique).mock.calls[0][0];
|
||||
expect(prismaCall.select.surveys.select).toEqual(
|
||||
expect.objectContaining({
|
||||
isAutoProgressingEnabled: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should throw ResourceNotFoundError when environment is not found", async () => {
|
||||
|
||||
@@ -121,6 +121,7 @@ export const getEnvironmentStateData = async (environmentId: string): Promise<En
|
||||
displayOption: true,
|
||||
hiddenFields: true,
|
||||
isBackButtonHidden: true,
|
||||
isAutoProgressingEnabled: true,
|
||||
triggers: {
|
||||
select: {
|
||||
actionClass: {
|
||||
|
||||
@@ -10,6 +10,8 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { THandlerParams, withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { ENCRYPTION_KEY } from "@/lib/constants";
|
||||
import { symmetricDecrypt } from "@/lib/crypto";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
|
||||
import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
|
||||
@@ -123,17 +125,118 @@ export const POST = withV1ApiWrapper({
|
||||
}
|
||||
if (survey.environmentId !== environmentId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Survey is part of another environment",
|
||||
{
|
||||
"survey.environmentId": survey.environmentId,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
response: responses.badRequestResponse("Survey does not belong to this environment", undefined, true),
|
||||
};
|
||||
}
|
||||
|
||||
if (survey.type === "link" && survey.singleUse?.enabled) {
|
||||
if (!responseInputData.singleUseId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Missing single use id",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (!responseInputData.meta?.url) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Missing or invalid URL in response metadata",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(responseInputData.meta.url);
|
||||
} catch (error) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Invalid URL in response metadata",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
error: error instanceof Error ? error.message : "Unknown error occurred",
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const suId = url.searchParams.get("suId");
|
||||
if (!suId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Missing single use id",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (survey.singleUse.isEncrypted) {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
logger.error({ url: req.url, surveyId: survey.id, environmentId }, "ENCRYPTION_KEY is not set");
|
||||
return {
|
||||
response: responses.internalServerErrorResponse("An unexpected error occurred.", true),
|
||||
};
|
||||
}
|
||||
|
||||
let decryptedSuId: string;
|
||||
try {
|
||||
decryptedSuId = symmetricDecrypt(suId, ENCRYPTION_KEY);
|
||||
} catch {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Invalid single use id",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (decryptedSuId !== responseInputData.singleUseId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Invalid single use id",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
} else if (responseInputData.singleUseId !== suId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Invalid single use id",
|
||||
{
|
||||
surveyId: survey.id,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!validateFileUploads(responseInputData.data, survey.questions)) {
|
||||
return {
|
||||
response: responses.badRequestResponse("Invalid file upload response"),
|
||||
|
||||
@@ -75,11 +75,7 @@ export const POST = withV1ApiWrapper({
|
||||
|
||||
if (survey.environmentId !== environmentId) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Survey does not belong to the environment",
|
||||
{ surveyId, environmentId },
|
||||
true
|
||||
),
|
||||
response: responses.badRequestResponse("Survey does not belong to this environment", undefined, true),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { publicUserSelect } from "@/lib/user/public-user";
|
||||
import { GET } from "./route";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
headers: vi.fn(),
|
||||
getSessionUser: vi.fn(),
|
||||
parseApiKeyV2: vi.fn(),
|
||||
hashSha256: vi.fn(),
|
||||
verifySecret: vi.fn(),
|
||||
applyRateLimit: vi.fn(),
|
||||
notAuthenticatedResponse: vi.fn(
|
||||
() => new Response(JSON.stringify({ message: "Not authenticated" }), { status: 401 })
|
||||
),
|
||||
tooManyRequestsResponse: vi.fn(
|
||||
(message: string) => new Response(JSON.stringify({ message }), { status: 429 })
|
||||
),
|
||||
badRequestResponse: vi.fn((message: string) => new Response(JSON.stringify({ message }), { status: 400 })),
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
headers: mocks.headers,
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
apiKey: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/app/api/v1/management/me/lib/utils", () => ({
|
||||
getSessionUser: mocks.getSessionUser,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/lib/api/response", () => ({
|
||||
responses: {
|
||||
notAuthenticatedResponse: mocks.notAuthenticatedResponse,
|
||||
tooManyRequestsResponse: mocks.tooManyRequestsResponse,
|
||||
badRequestResponse: mocks.badRequestResponse,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/crypto", () => ({
|
||||
hashSha256: mocks.hashSha256,
|
||||
parseApiKeyV2: mocks.parseApiKeyV2,
|
||||
verifySecret: mocks.verifySecret,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/core/rate-limit/helpers", () => ({
|
||||
applyRateLimit: mocks.applyRateLimit,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/core/rate-limit/rate-limit-configs", () => ({
|
||||
rateLimitConfigs: {
|
||||
api: {
|
||||
v1: { windowMs: 60_000, max: 1000 },
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const getMockHeaders = (apiKey: string | null) => ({
|
||||
get: (headerName: string) => (headerName === "x-api-key" ? apiKey : null),
|
||||
});
|
||||
|
||||
describe("v1 management me route", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.headers.mockResolvedValue(getMockHeaders(null));
|
||||
mocks.getSessionUser.mockResolvedValue(undefined);
|
||||
mocks.parseApiKeyV2.mockReturnValue(null);
|
||||
mocks.hashSha256.mockReturnValue("hashed-api-key");
|
||||
mocks.verifySecret.mockResolvedValue(false);
|
||||
mocks.applyRateLimit.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
test("returns a sanitized authenticated user for session-based requests", async () => {
|
||||
const publicUser = {
|
||||
id: "user_123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
emailVerified: new Date("2025-04-17T20:11:54.947Z"),
|
||||
createdAt: new Date("2025-04-17T20:09:14.021Z"),
|
||||
updatedAt: new Date("2026-04-22T22:12:39.104Z"),
|
||||
twoFactorEnabled: false,
|
||||
identityProvider: "email" as const,
|
||||
notificationSettings: {
|
||||
alert: {},
|
||||
unsubscribedOrganizationIds: [],
|
||||
},
|
||||
locale: "en-US" as const,
|
||||
lastLoginAt: new Date("2026-04-22T22:12:39.104Z"),
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
mocks.getSessionUser.mockResolvedValue({ id: publicUser.id });
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue(publicUser as never);
|
||||
|
||||
const response = await GET();
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(responseBody).toStrictEqual(JSON.parse(JSON.stringify(publicUser)));
|
||||
expect(responseBody).not.toHaveProperty("password");
|
||||
expect(responseBody).not.toHaveProperty("twoFactorSecret");
|
||||
expect(responseBody).not.toHaveProperty("backupCodes");
|
||||
expect(responseBody).not.toHaveProperty("identityProviderAccountId");
|
||||
expect(prisma.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: publicUser.id },
|
||||
select: publicUserSelect,
|
||||
});
|
||||
expect(mocks.applyRateLimit).toHaveBeenCalledWith(expect.any(Object), publicUser.id);
|
||||
});
|
||||
|
||||
test("returns the existing unauthenticated response when no session is present", async () => {
|
||||
const response = await GET();
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(responseBody).toEqual({ message: "Not authenticated" });
|
||||
expect(mocks.notAuthenticatedResponse).toHaveBeenCalled();
|
||||
expect(prisma.user.findUnique).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("preserves the API key response path", async () => {
|
||||
const apiKeyData = {
|
||||
id: "api_key_123",
|
||||
organizationId: "org_123",
|
||||
hashedKey: "stored-hash",
|
||||
lastUsedAt: new Date(),
|
||||
apiKeyEnvironments: [
|
||||
{
|
||||
permission: "manage",
|
||||
environment: {
|
||||
id: "env_123",
|
||||
type: "development",
|
||||
createdAt: new Date("2025-01-01T00:00:00.000Z"),
|
||||
updatedAt: new Date("2025-01-02T00:00:00.000Z"),
|
||||
projectId: "project_123",
|
||||
appSetupCompleted: true,
|
||||
project: {
|
||||
id: "project_123",
|
||||
name: "My Project",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mocks.headers.mockResolvedValue(getMockHeaders("api-key"));
|
||||
vi.mocked(prisma.apiKey.findFirst).mockResolvedValue(apiKeyData as never);
|
||||
|
||||
const response = await GET();
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(responseBody).toStrictEqual({
|
||||
id: "env_123",
|
||||
type: "development",
|
||||
createdAt: "2025-01-01T00:00:00.000Z",
|
||||
updatedAt: "2025-01-02T00:00:00.000Z",
|
||||
appSetupCompleted: true,
|
||||
project: {
|
||||
id: "project_123",
|
||||
name: "My Project",
|
||||
},
|
||||
});
|
||||
expect(mocks.getSessionUser).not.toHaveBeenCalled();
|
||||
expect(mocks.applyRateLimit).toHaveBeenCalledWith(expect.any(Object), apiKeyData.id);
|
||||
});
|
||||
});
|
||||
@@ -96,14 +96,7 @@ const validateSurvey = async (responseInput: TResponseInput, environmentId: stri
|
||||
}
|
||||
if (survey.environmentId !== environmentId) {
|
||||
return {
|
||||
error: responses.badRequestResponse(
|
||||
"Survey is part of another environment",
|
||||
{
|
||||
"survey.environmentId": survey.environmentId,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
),
|
||||
error: responses.badRequestResponse("Survey does not belong to this environment", undefined, true),
|
||||
};
|
||||
}
|
||||
return { survey };
|
||||
|
||||
@@ -1,47 +1,19 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { deleteSurvey } from "./surveys";
|
||||
|
||||
vi.mock("@/lib/utils/validate", () => ({
|
||||
validateInputs: vi.fn(),
|
||||
const { mockDeleteSharedSurvey } = vi.hoisted(() => ({
|
||||
mockDeleteSharedSurvey: vi.fn(),
|
||||
}));
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
survey: {
|
||||
delete: vi.fn(),
|
||||
},
|
||||
segment: {
|
||||
delete: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
vi.mock("@formbricks/logger", () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
|
||||
vi.mock("@/modules/survey/lib/surveys", () => ({
|
||||
deleteSurvey: mockDeleteSharedSurvey,
|
||||
}));
|
||||
|
||||
const surveyId = "clq5n7p1q0000m7z0h5p6g3r2";
|
||||
const environmentId = "clq5n7p1q0000m7z0h5p6g3r3";
|
||||
const segmentId = "clq5n7p1q0000m7z0h5p6g3r4";
|
||||
const actionClassId1 = "clq5n7p1q0000m7z0h5p6g3r5";
|
||||
const actionClassId2 = "clq5n7p1q0000m7z0h5p6g3r6";
|
||||
|
||||
const mockDeletedSurveyAppPrivateSegment = {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
type: "app",
|
||||
segment: { id: segmentId, isPrivate: true },
|
||||
triggers: [{ actionClass: { id: actionClassId1 } }, { actionClass: { id: actionClassId2 } }],
|
||||
};
|
||||
|
||||
const mockDeletedSurveyLink = {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
environmentId: "clq5n7p1q0000m7z0h5p6g3r3",
|
||||
type: "link",
|
||||
segment: null,
|
||||
triggers: [],
|
||||
@@ -56,66 +28,20 @@ describe("deleteSurvey", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should delete a link survey without a segment and revalidate caches", async () => {
|
||||
vi.mocked(prisma.survey.delete).mockResolvedValue(mockDeletedSurveyLink as any);
|
||||
test("delegates survey deletion to the shared service", async () => {
|
||||
mockDeleteSharedSurvey.mockResolvedValue(mockDeletedSurveyLink);
|
||||
|
||||
const deletedSurvey = await deleteSurvey(surveyId);
|
||||
|
||||
expect(validateInputs).toHaveBeenCalledWith([surveyId, expect.any(Object)]);
|
||||
expect(prisma.survey.delete).toHaveBeenCalledWith({
|
||||
where: { id: surveyId },
|
||||
include: {
|
||||
segment: true,
|
||||
triggers: { include: { actionClass: true } },
|
||||
},
|
||||
});
|
||||
expect(prisma.segment.delete).not.toHaveBeenCalled();
|
||||
|
||||
expect(mockDeleteSharedSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(deletedSurvey).toEqual(mockDeletedSurveyLink);
|
||||
});
|
||||
|
||||
test("should handle PrismaClientKnownRequestError during survey deletion", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Record not found", {
|
||||
code: "P2025",
|
||||
clientVersion: "4.0.0",
|
||||
});
|
||||
vi.mocked(prisma.survey.delete).mockRejectedValue(prismaError);
|
||||
|
||||
await expect(deleteSurvey(surveyId)).rejects.toThrow(DatabaseError);
|
||||
expect(logger.error).toHaveBeenCalledWith({ error: prismaError, surveyId }, "Error deleting survey");
|
||||
expect(prisma.segment.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should handle PrismaClientKnownRequestError during segment deletion", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Foreign key constraint failed", {
|
||||
code: "P2003",
|
||||
clientVersion: "4.0.0",
|
||||
});
|
||||
vi.mocked(prisma.survey.delete).mockResolvedValue(mockDeletedSurveyAppPrivateSegment as any);
|
||||
vi.mocked(prisma.segment.delete).mockRejectedValue(prismaError);
|
||||
|
||||
await expect(deleteSurvey(surveyId)).rejects.toThrow(DatabaseError);
|
||||
expect(logger.error).toHaveBeenCalledWith({ error: prismaError, surveyId }, "Error deleting survey");
|
||||
expect(prisma.segment.delete).toHaveBeenCalledWith({ where: { id: segmentId } });
|
||||
});
|
||||
|
||||
test("should handle generic errors during deletion", async () => {
|
||||
test("rethrows shared delete service errors", async () => {
|
||||
const genericError = new Error("Something went wrong");
|
||||
vi.mocked(prisma.survey.delete).mockRejectedValue(genericError);
|
||||
mockDeleteSharedSurvey.mockRejectedValue(genericError);
|
||||
|
||||
await expect(deleteSurvey(surveyId)).rejects.toThrow(genericError);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(prisma.segment.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw validation error for invalid surveyId", async () => {
|
||||
const invalidSurveyId = "invalid-id";
|
||||
const validationError = new Error("Validation failed");
|
||||
vi.mocked(validateInputs).mockImplementation(() => {
|
||||
throw validationError;
|
||||
});
|
||||
|
||||
await expect(deleteSurvey(invalidSurveyId)).rejects.toThrow(validationError);
|
||||
expect(prisma.survey.delete).not.toHaveBeenCalled();
|
||||
expect(mockDeleteSharedSurvey).toHaveBeenCalledWith(surveyId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { deleteSurvey as deleteSharedSurvey } from "@/modules/survey/lib/surveys";
|
||||
|
||||
export const deleteSurvey = async (surveyId: string) => {
|
||||
validateInputs([surveyId, z.cuid2()]);
|
||||
|
||||
try {
|
||||
const deletedSurvey = await prisma.survey.delete({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
include: {
|
||||
segment: true,
|
||||
triggers: {
|
||||
include: {
|
||||
actionClass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (deletedSurvey.type === "app" && deletedSurvey.segment?.isPrivate) {
|
||||
await prisma.segment.delete({
|
||||
where: {
|
||||
id: deletedSurvey.segment.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return deletedSurvey;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
logger.error({ error, surveyId }, "Error deleting survey");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export const deleteSurvey = async (surveyId: string) => deleteSharedSurvey(surveyId);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TAuthenticationApiKey } from "@formbricks/types/auth";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZSurveyUpdateInput } from "@formbricks/types/surveys/types";
|
||||
import { handleErrorResponse } from "@/app/api/v1/auth";
|
||||
import { deleteSurvey } from "@/app/api/v1/management/surveys/[surveyId]/lib/surveys";
|
||||
@@ -70,6 +71,12 @@ export const GET = withV1ApiWrapper({
|
||||
response: responses.successResponse(resolveStorageUrlsInObject(result.survey)),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ResourceNotFoundError) {
|
||||
return {
|
||||
response: responses.notFoundResponse("Survey", params.surveyId),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
response: handleErrorResponse(error),
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { DatabaseError, ResourceNotFoundError, UniqueConstraintError } from "@formbricks/types/errors";
|
||||
import { TResponseWithQuotaFull, TSurveyQuota } from "@formbricks/types/quota";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
@@ -175,10 +175,34 @@ describe("createResponse V2", () => {
|
||||
).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on Prisma known request error", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", {
|
||||
test("should throw UniqueConstraintError on P2002 with singleUseId target", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
clientVersion: "test",
|
||||
meta: { target: ["surveyId", "singleUseId"] },
|
||||
});
|
||||
vi.mocked(mockTx.response.create).mockRejectedValue(prismaError);
|
||||
await expect(
|
||||
createResponse(mockResponseInput, mockTx as unknown as Prisma.TransactionClient)
|
||||
).rejects.toThrow(UniqueConstraintError);
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on P2002 without singleUseId target", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
|
||||
code: "P2002",
|
||||
clientVersion: "test",
|
||||
meta: { target: ["displayId"] },
|
||||
});
|
||||
vi.mocked(mockTx.response.create).mockRejectedValue(prismaError);
|
||||
await expect(
|
||||
createResponse(mockResponseInput, mockTx as unknown as Prisma.TransactionClient)
|
||||
).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
test("should throw DatabaseError on non-P2002 Prisma known request error", async () => {
|
||||
const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", {
|
||||
code: "P2025",
|
||||
clientVersion: "test",
|
||||
});
|
||||
vi.mocked(mockTx.response.create).mockRejectedValue(prismaError);
|
||||
await expect(
|
||||
|
||||
@@ -2,7 +2,7 @@ import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { DatabaseError, ResourceNotFoundError, UniqueConstraintError } from "@formbricks/types/errors";
|
||||
import { TResponseWithQuotaFull } from "@formbricks/types/quota";
|
||||
import { TResponse, ZResponseInput } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
@@ -129,6 +129,13 @@ export const createResponse = async (
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
if (error.code === "P2002") {
|
||||
const target = (error.meta?.target as string[]) ?? [];
|
||||
if (target?.includes("singleUseId")) {
|
||||
throw new UniqueConstraintError("Response already submitted for this single-use link");
|
||||
}
|
||||
}
|
||||
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,11 +124,8 @@ describe("checkSurveyValidity", () => {
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result?.status).toBe(400);
|
||||
expect(responses.badRequestResponse).toHaveBeenCalledWith(
|
||||
"Survey is part of another environment",
|
||||
{
|
||||
"survey.environmentId": "env-2",
|
||||
environmentId: "env-1",
|
||||
},
|
||||
"Survey does not belong to this environment",
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,14 +17,7 @@ export const checkSurveyValidity = async (
|
||||
responseInput: TResponseInputV2
|
||||
): Promise<Response | null> => {
|
||||
if (survey.environmentId !== environmentId) {
|
||||
return responses.badRequestResponse(
|
||||
"Survey is part of another environment",
|
||||
{
|
||||
"survey.environmentId": survey.environmentId,
|
||||
environmentId,
|
||||
},
|
||||
true
|
||||
);
|
||||
return responses.badRequestResponse("Survey does not belong to this environment", undefined, true);
|
||||
}
|
||||
|
||||
if (survey.type === "link" && survey.singleUse?.enabled) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { ZEnvironmentId } from "@formbricks/types/environment";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { InvalidInputError, UniqueConstraintError } from "@formbricks/types/errors";
|
||||
import { TResponseWithQuotaFull } from "@formbricks/types/quota";
|
||||
import { checkSurveyValidity } from "@/app/api/v2/client/[environmentId]/responses/lib/utils";
|
||||
import { reportApiError } from "@/app/lib/api/api-error-reporter";
|
||||
@@ -177,6 +177,10 @@ const createResponseForRequest = async ({
|
||||
return responses.badRequestResponse(error.message, undefined, true);
|
||||
}
|
||||
|
||||
if (error instanceof UniqueConstraintError) {
|
||||
return responses.conflictResponse(error.message, undefined, true);
|
||||
}
|
||||
|
||||
const response = getUnexpectedPublicErrorResponse();
|
||||
reportApiError({
|
||||
request,
|
||||
|
||||
@@ -9,6 +9,22 @@ const { mockAuthenticateRequest, mockGetServerSession } = vi.hoisted(() => ({
|
||||
mockGetServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockQueueAuditEvent, mockBuildAuditLogBaseObject } = vi.hoisted(() => ({
|
||||
mockQueueAuditEvent: vi.fn().mockImplementation(async () => undefined),
|
||||
mockBuildAuditLogBaseObject: vi.fn((action: string, targetType: string, apiUrl: string) => ({
|
||||
action,
|
||||
targetType,
|
||||
userId: "unknown",
|
||||
targetId: "unknown",
|
||||
organizationId: "unknown",
|
||||
status: "failure",
|
||||
oldObject: undefined,
|
||||
newObject: undefined,
|
||||
userType: "api",
|
||||
apiUrl,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("next-auth", () => ({
|
||||
getServerSession: mockGetServerSession,
|
||||
}));
|
||||
@@ -25,6 +41,14 @@ vi.mock("@/modules/core/rate-limit/helpers", () => ({
|
||||
applyRateLimit: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/audit-logs/lib/handler", () => ({
|
||||
queueAuditEvent: mockQueueAuditEvent,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/lib/api/with-api-logging", () => ({
|
||||
buildAuditLogBaseObject: mockBuildAuditLogBaseObject,
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/logger", () => ({
|
||||
logger: {
|
||||
withContext: vi.fn(() => ({
|
||||
@@ -45,6 +69,114 @@ describe("withV3ApiWrapper", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("passes an audit log to the handler and queues success after the response", async () => {
|
||||
const { queueAuditEvent } = await import("@/modules/ee/audit-logs/lib/handler");
|
||||
|
||||
mockGetServerSession.mockResolvedValue({
|
||||
user: { id: "user_1", name: "Test", email: "t@example.com" },
|
||||
expires: "2026-01-01",
|
||||
});
|
||||
|
||||
const handler = vi.fn(async ({ auditLog }) => {
|
||||
expect(auditLog).toEqual(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
userId: "user_1",
|
||||
userType: "user",
|
||||
status: "failure",
|
||||
})
|
||||
);
|
||||
|
||||
if (auditLog) {
|
||||
auditLog.targetId = "survey_1";
|
||||
auditLog.organizationId = "org_1";
|
||||
auditLog.oldObject = { id: "survey_1" };
|
||||
}
|
||||
|
||||
return Response.json({ ok: true });
|
||||
});
|
||||
|
||||
const wrapped = withV3ApiWrapper({
|
||||
auth: "both",
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
handler,
|
||||
});
|
||||
|
||||
const response = await wrapped(
|
||||
new NextRequest("http://localhost/api/v3/surveys/survey_1", {
|
||||
method: "DELETE",
|
||||
headers: { "x-request-id": "req-audit" },
|
||||
}),
|
||||
{} as never
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(queueAuditEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
targetId: "survey_1",
|
||||
organizationId: "org_1",
|
||||
userId: "user_1",
|
||||
userType: "user",
|
||||
status: "success",
|
||||
oldObject: { id: "survey_1" },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("queues a failure audit log when the handler returns a non-ok response", async () => {
|
||||
const { queueAuditEvent } = await import("@/modules/ee/audit-logs/lib/handler");
|
||||
|
||||
mockAuthenticateRequest.mockResolvedValue({
|
||||
type: "apiKey",
|
||||
apiKeyId: "key_1",
|
||||
organizationId: "org_1",
|
||||
organizationAccess: { accessControl: { read: true, write: true } },
|
||||
environmentPermissions: [],
|
||||
});
|
||||
|
||||
const wrapped = withV3ApiWrapper({
|
||||
auth: "both",
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
handler: async ({ auditLog }) => {
|
||||
if (auditLog) {
|
||||
auditLog.targetId = "survey_2";
|
||||
}
|
||||
|
||||
return new Response("forbidden", { status: 403 });
|
||||
},
|
||||
});
|
||||
|
||||
const response = await wrapped(
|
||||
new NextRequest("http://localhost/api/v3/surveys/survey_2", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"x-request-id": "req-failure-audit",
|
||||
"x-api-key": "fbk_test",
|
||||
},
|
||||
}),
|
||||
{} as never
|
||||
);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(queueAuditEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
targetId: "survey_2",
|
||||
organizationId: "org_1",
|
||||
userId: "key_1",
|
||||
userType: "api",
|
||||
status: "failure",
|
||||
eventId: "req-failure-audit",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("uses session auth first in both mode and injects request id into plain responses", async () => {
|
||||
const { applyRateLimit } = await import("@/modules/core/rate-limit/helpers");
|
||||
mockGetServerSession.mockResolvedValue({
|
||||
|
||||
@@ -4,10 +4,13 @@ import { z } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TooManyRequestsError } from "@formbricks/types/errors";
|
||||
import { authenticateRequest } from "@/app/api/v1/auth";
|
||||
import { buildAuditLogBaseObject } from "@/app/lib/api/with-api-logging";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { applyRateLimit } from "@/modules/core/rate-limit/helpers";
|
||||
import { rateLimitConfigs } from "@/modules/core/rate-limit/rate-limit-configs";
|
||||
import type { TRateLimitConfig } from "@/modules/core/rate-limit/types/rate-limit";
|
||||
import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler";
|
||||
import { TAuditAction, TAuditTarget } from "@/modules/ee/audit-logs/types/audit-log";
|
||||
import {
|
||||
type InvalidParam,
|
||||
problemBadRequest,
|
||||
@@ -15,7 +18,7 @@ import {
|
||||
problemTooManyRequests,
|
||||
problemUnauthorized,
|
||||
} from "./response";
|
||||
import type { TV3Authentication } from "./types";
|
||||
import type { TV3AuditLog, TV3Authentication } from "./types";
|
||||
|
||||
type TV3Schema = z.ZodTypeAny;
|
||||
type MaybePromise<T> = T | Promise<T>;
|
||||
@@ -38,6 +41,7 @@ export type TV3HandlerParams<TParsedInput = Record<string, never>, TProps = unkn
|
||||
req: NextRequest;
|
||||
props: TProps;
|
||||
authentication: TV3Authentication;
|
||||
auditLog?: TV3AuditLog;
|
||||
parsedInput: TParsedInput;
|
||||
requestId: string;
|
||||
instance: string;
|
||||
@@ -48,6 +52,8 @@ export type TWithV3ApiWrapperParams<S extends TV3Schemas | undefined, TProps = u
|
||||
schemas?: S;
|
||||
rateLimit?: boolean;
|
||||
customRateLimitConfig?: TRateLimitConfig;
|
||||
action?: TAuditAction;
|
||||
targetType?: TAuditTarget;
|
||||
handler: (params: TV3HandlerParams<TV3ParsedInput<S>, TProps>) => MaybePromise<Response>;
|
||||
};
|
||||
|
||||
@@ -293,10 +299,61 @@ async function applyV3RateLimitOrRespond(params: {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildV3AuditLog(
|
||||
authentication: TV3Authentication,
|
||||
action?: TAuditAction,
|
||||
targetType?: TAuditTarget,
|
||||
apiUrl?: string
|
||||
): TV3AuditLog | undefined {
|
||||
if (!authentication || !action || !targetType || !apiUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const auditLog = buildAuditLogBaseObject(action, targetType, apiUrl);
|
||||
|
||||
if ("user" in authentication && authentication.user?.id) {
|
||||
auditLog.userId = authentication.user.id;
|
||||
auditLog.userType = "user";
|
||||
} else if ("apiKeyId" in authentication) {
|
||||
auditLog.userId = authentication.apiKeyId;
|
||||
auditLog.userType = "api";
|
||||
auditLog.organizationId = authentication.organizationId;
|
||||
}
|
||||
|
||||
return auditLog;
|
||||
}
|
||||
|
||||
async function queueV3AuditLog(
|
||||
auditLog: TV3AuditLog | undefined,
|
||||
requestId: string,
|
||||
log: ReturnType<typeof logger.withContext>
|
||||
): Promise<void> {
|
||||
if (!auditLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await queueAuditEvent({
|
||||
...auditLog,
|
||||
...(auditLog.status === "failure" ? { eventId: auditLog.eventId ?? requestId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
log.error({ error }, "Failed to queue V3 audit event");
|
||||
}
|
||||
}
|
||||
|
||||
export const withV3ApiWrapper = <S extends TV3Schemas | undefined, TProps = unknown>(
|
||||
params: TWithV3ApiWrapperParams<S, TProps>
|
||||
): ((req: NextRequest, props: TProps) => Promise<Response>) => {
|
||||
const { auth = "both", schemas, rateLimit = true, customRateLimitConfig, handler } = params;
|
||||
const {
|
||||
auth = "both",
|
||||
schemas,
|
||||
rateLimit = true,
|
||||
customRateLimitConfig,
|
||||
handler,
|
||||
action,
|
||||
targetType,
|
||||
} = params;
|
||||
|
||||
return async (req: NextRequest, props: TProps): Promise<Response> => {
|
||||
const requestId = req.headers.get("x-request-id") ?? crypto.randomUUID();
|
||||
@@ -306,6 +363,7 @@ export const withV3ApiWrapper = <S extends TV3Schemas | undefined, TProps = unkn
|
||||
method: req.method,
|
||||
path: instance,
|
||||
});
|
||||
let auditLog: TV3AuditLog | undefined;
|
||||
|
||||
try {
|
||||
const authResult = await authenticateV3RequestOrRespond(req, auth, requestId, instance);
|
||||
@@ -331,17 +389,33 @@ export const withV3ApiWrapper = <S extends TV3Schemas | undefined, TProps = unkn
|
||||
return rateLimitResponse;
|
||||
}
|
||||
|
||||
auditLog = buildV3AuditLog(authResult.authentication, action, targetType, req.url);
|
||||
|
||||
const response = await handler({
|
||||
req,
|
||||
props,
|
||||
authentication: authResult.authentication,
|
||||
auditLog,
|
||||
parsedInput: parsedInputResult.parsedInput,
|
||||
requestId,
|
||||
instance,
|
||||
});
|
||||
|
||||
if (auditLog) {
|
||||
if (response.ok) {
|
||||
auditLog.status = "success";
|
||||
} else {
|
||||
auditLog.eventId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
await queueV3AuditLog(auditLog, requestId, log);
|
||||
return ensureRequestIdHeader(response, requestId);
|
||||
} catch (error) {
|
||||
if (auditLog) {
|
||||
auditLog.eventId = requestId;
|
||||
await queueV3AuditLog(auditLog, requestId, log);
|
||||
}
|
||||
log.error({ error, statusCode: 500 }, "V3 API unexpected error");
|
||||
return problemInternalError(requestId, "An unexpected error occurred.", instance);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
problemTooManyRequests,
|
||||
problemUnauthorized,
|
||||
successListResponse,
|
||||
successResponse,
|
||||
} from "./response";
|
||||
|
||||
describe("v3 problem responses", () => {
|
||||
@@ -93,3 +94,27 @@ describe("successListResponse", () => {
|
||||
expect(res.headers.get("Cache-Control")).toBe("private, max-age=0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("successResponse", () => {
|
||||
test("wraps the payload in a data envelope", async () => {
|
||||
const res = successResponse({ id: "survey_1" }, { requestId: "req-success" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("X-Request-Id")).toBe("req-success");
|
||||
expect(res.headers.get("Cache-Control")).toContain("no-store");
|
||||
expect(await res.json()).toEqual({
|
||||
data: { id: "survey_1" },
|
||||
});
|
||||
});
|
||||
|
||||
test("allows custom status and cache headers", async () => {
|
||||
const res = successResponse(
|
||||
{ ok: true },
|
||||
{
|
||||
cache: "private, max-age=60",
|
||||
status: 202,
|
||||
}
|
||||
);
|
||||
expect(res.status).toBe(202);
|
||||
expect(res.headers.get("Cache-Control")).toBe("private, max-age=60");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,3 +147,27 @@ export function successListResponse<T, TMeta extends Record<string, unknown>>(
|
||||
}
|
||||
return Response.json({ data, meta }, { status: 200, headers });
|
||||
}
|
||||
|
||||
export function successResponse<T>(
|
||||
data: T,
|
||||
options?: { requestId?: string; cache?: string; status?: number }
|
||||
): Response {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": options?.cache ?? CACHE_NO_STORE,
|
||||
};
|
||||
|
||||
if (options?.requestId) {
|
||||
headers["X-Request-Id"] = options.requestId;
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
data,
|
||||
},
|
||||
{
|
||||
status: options?.status ?? 200,
|
||||
headers,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Session } from "next-auth";
|
||||
import type { TAuthenticationApiKey } from "@formbricks/types/auth";
|
||||
import type { TApiAuditLog } from "@/app/lib/api/with-api-logging";
|
||||
|
||||
export type TV3Authentication = TAuthenticationApiKey | Session | null;
|
||||
export type TV3AuditLog = TApiAuditLog;
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
import { ApiKeyPermission, EnvironmentType } from "@prisma/client";
|
||||
import { NextRequest } from "next/server";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { requireV3WorkspaceAccess } from "@/app/api/v3/lib/auth";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { deleteSurvey } from "@/modules/survey/lib/surveys";
|
||||
import { DELETE } from "./route";
|
||||
|
||||
const { mockAuthenticateRequest } = vi.hoisted(() => ({
|
||||
mockAuthenticateRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockQueueAuditEvent, mockBuildAuditLogBaseObject } = vi.hoisted(() => ({
|
||||
mockQueueAuditEvent: vi.fn().mockImplementation(async () => undefined),
|
||||
mockBuildAuditLogBaseObject: vi.fn((action: string, targetType: string, apiUrl: string) => ({
|
||||
action,
|
||||
targetType,
|
||||
userId: "unknown",
|
||||
targetId: "unknown",
|
||||
organizationId: "unknown",
|
||||
status: "failure",
|
||||
oldObject: undefined,
|
||||
newObject: undefined,
|
||||
userType: "api",
|
||||
apiUrl,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("next-auth", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/api/v1/auth", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@/app/api/v1/auth")>();
|
||||
return { ...actual, authenticateRequest: mockAuthenticateRequest };
|
||||
});
|
||||
|
||||
vi.mock("@/modules/core/rate-limit/helpers", () => ({
|
||||
applyRateLimit: vi.fn().mockResolvedValue(undefined),
|
||||
applyIPRateLimit: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/constants", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@/lib/constants")>();
|
||||
return { ...actual, AUDIT_LOG_ENABLED: false };
|
||||
});
|
||||
|
||||
vi.mock("@/app/api/v3/lib/auth", () => ({
|
||||
requireV3WorkspaceAccess: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/survey/service", () => ({
|
||||
getSurvey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/survey/lib/surveys", () => ({
|
||||
deleteSurvey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/audit-logs/lib/handler", () => ({
|
||||
queueAuditEvent: mockQueueAuditEvent,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/lib/api/with-api-logging", () => ({
|
||||
buildAuditLogBaseObject: mockBuildAuditLogBaseObject,
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/logger", () => ({
|
||||
logger: {
|
||||
withContext: vi.fn(() => ({
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
const getServerSession = vi.mocked((await import("next-auth")).getServerSession);
|
||||
const queueAuditEvent = vi.mocked((await import("@/modules/ee/audit-logs/lib/handler")).queueAuditEvent);
|
||||
|
||||
const surveyId = "clxx1234567890123456789012";
|
||||
const environmentId = "clzz9876543210987654321098";
|
||||
|
||||
function createRequest(url: string, requestId?: string, extraHeaders?: Record<string, string>): NextRequest {
|
||||
const headers: Record<string, string> = { ...extraHeaders };
|
||||
if (requestId) {
|
||||
headers["x-request-id"] = requestId;
|
||||
}
|
||||
|
||||
return new NextRequest(url, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
const apiKeyAuth = {
|
||||
type: "apiKey" as const,
|
||||
apiKeyId: "key_1",
|
||||
organizationId: "org_1",
|
||||
organizationAccess: {
|
||||
accessControl: { read: true, write: true },
|
||||
},
|
||||
environmentPermissions: [
|
||||
{
|
||||
environmentId,
|
||||
environmentType: EnvironmentType.development,
|
||||
projectId: "proj_1",
|
||||
projectName: "P",
|
||||
permission: ApiKeyPermission.write,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("DELETE /api/v3/surveys/[surveyId]", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
getServerSession.mockResolvedValue({
|
||||
user: { id: "user_1", name: "User", email: "u@example.com" },
|
||||
expires: "2026-01-01",
|
||||
} as any);
|
||||
mockAuthenticateRequest.mockResolvedValue(null);
|
||||
vi.mocked(getSurvey).mockResolvedValue({
|
||||
id: surveyId,
|
||||
name: "Delete me",
|
||||
environmentId,
|
||||
type: "link",
|
||||
status: "draft",
|
||||
createdAt: new Date("2026-04-15T10:00:00.000Z"),
|
||||
updatedAt: new Date("2026-04-15T10:00:00.000Z"),
|
||||
responseCount: 0,
|
||||
creator: { name: "User" },
|
||||
singleUse: null,
|
||||
} as any);
|
||||
vi.mocked(deleteSurvey).mockResolvedValue({
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
type: "link",
|
||||
segment: null,
|
||||
triggers: [],
|
||||
} as any);
|
||||
vi.mocked(requireV3WorkspaceAccess).mockResolvedValue({
|
||||
environmentId,
|
||||
projectId: "proj_1",
|
||||
organizationId: "org_1",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns 401 when no session and no API key", async () => {
|
||||
getServerSession.mockResolvedValue(null);
|
||||
mockAuthenticateRequest.mockResolvedValue(null);
|
||||
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(vi.mocked(getSurvey)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns 200 with session auth and deletes the survey", async () => {
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-delete"), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(requireV3WorkspaceAccess).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ user: expect.any(Object) }),
|
||||
environmentId,
|
||||
"readWrite",
|
||||
"req-delete",
|
||||
`/api/v3/surveys/${surveyId}`
|
||||
);
|
||||
expect(deleteSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(await res.json()).toEqual({
|
||||
data: {
|
||||
id: surveyId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 200 with x-api-key when the key can delete in the survey workspace", async () => {
|
||||
getServerSession.mockResolvedValue(null);
|
||||
mockAuthenticateRequest.mockResolvedValue(apiKeyAuth as any);
|
||||
|
||||
const res = await DELETE(
|
||||
createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-api-key", {
|
||||
"x-api-key": "fbk_test",
|
||||
}),
|
||||
{
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never
|
||||
);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(requireV3WorkspaceAccess).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ apiKeyId: "key_1" }),
|
||||
environmentId,
|
||||
"readWrite",
|
||||
"req-api-key",
|
||||
`/api/v3/surveys/${surveyId}`
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 400 when surveyId is invalid", async () => {
|
||||
const res = await DELETE(createRequest("http://localhost/api/v3/surveys/not-a-cuid"), {
|
||||
params: Promise.resolve({ surveyId: "not-a-cuid" }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(vi.mocked(getSurvey)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns 403 when the survey does not exist", async () => {
|
||||
vi.mocked(getSurvey).mockResolvedValueOnce(null);
|
||||
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(deleteSurvey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns 403 when the user lacks readWrite workspace access", async () => {
|
||||
vi.mocked(requireV3WorkspaceAccess).mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
title: "Forbidden",
|
||||
status: 403,
|
||||
detail: "You are not authorized to access this resource",
|
||||
requestId: "req-forbidden",
|
||||
}),
|
||||
{ status: 403, headers: { "Content-Type": "application/problem+json" } }
|
||||
)
|
||||
);
|
||||
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-forbidden"), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(deleteSurvey).not.toHaveBeenCalled();
|
||||
expect(queueAuditEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
targetId: "unknown",
|
||||
organizationId: "unknown",
|
||||
userId: "user_1",
|
||||
userType: "user",
|
||||
status: "failure",
|
||||
oldObject: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 500 when survey deletion fails", async () => {
|
||||
vi.mocked(deleteSurvey).mockRejectedValueOnce(new DatabaseError("db down"));
|
||||
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-db"), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(500);
|
||||
const body = await res.json();
|
||||
expect(body.code).toBe("internal_server_error");
|
||||
});
|
||||
|
||||
test("returns 403 when the survey is deleted after authorization succeeds", async () => {
|
||||
vi.mocked(deleteSurvey).mockRejectedValueOnce(new ResourceNotFoundError("Survey", surveyId));
|
||||
|
||||
const res = await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-race"), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
const body = await res.json();
|
||||
expect(body.code).toBe("forbidden");
|
||||
expect(queueAuditEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
targetId: surveyId,
|
||||
organizationId: "org_1",
|
||||
userId: "user_1",
|
||||
userType: "user",
|
||||
status: "failure",
|
||||
oldObject: expect.objectContaining({
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("queues an audit log with target, actor, organization, and old object", async () => {
|
||||
await DELETE(createRequest(`http://localhost/api/v3/surveys/${surveyId}`, "req-audit"), {
|
||||
params: Promise.resolve({ surveyId }),
|
||||
} as never);
|
||||
|
||||
expect(queueAuditEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
targetId: surveyId,
|
||||
organizationId: "org_1",
|
||||
userId: "user_1",
|
||||
userType: "user",
|
||||
status: "success",
|
||||
oldObject: expect.objectContaining({
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { z } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { withV3ApiWrapper } from "@/app/api/v3/lib/api-wrapper";
|
||||
import { requireV3WorkspaceAccess } from "@/app/api/v3/lib/auth";
|
||||
import { problemForbidden, problemInternalError, successResponse } from "@/app/api/v3/lib/response";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { deleteSurvey } from "@/modules/survey/lib/surveys";
|
||||
|
||||
export const DELETE = withV3ApiWrapper({
|
||||
auth: "both",
|
||||
action: "deleted",
|
||||
targetType: "survey",
|
||||
schemas: {
|
||||
params: z.object({
|
||||
surveyId: z.cuid2(),
|
||||
}),
|
||||
},
|
||||
handler: async ({ parsedInput, authentication, requestId, instance, auditLog }) => {
|
||||
const surveyId = parsedInput.params.surveyId;
|
||||
const log = logger.withContext({ requestId, surveyId });
|
||||
|
||||
try {
|
||||
const survey = await getSurvey(surveyId);
|
||||
|
||||
if (!survey) {
|
||||
log.warn({ statusCode: 403 }, "Survey not found or not accessible");
|
||||
return problemForbidden(requestId, "You are not authorized to access this resource", instance);
|
||||
}
|
||||
|
||||
const authResult = await requireV3WorkspaceAccess(
|
||||
authentication,
|
||||
survey.environmentId,
|
||||
"readWrite",
|
||||
requestId,
|
||||
instance
|
||||
);
|
||||
|
||||
if (authResult instanceof Response) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
if (auditLog) {
|
||||
auditLog.targetId = survey.id;
|
||||
auditLog.organizationId = authResult.organizationId;
|
||||
auditLog.oldObject = survey;
|
||||
}
|
||||
|
||||
const deletedSurvey = await deleteSurvey(surveyId);
|
||||
|
||||
return successResponse(
|
||||
{
|
||||
id: deletedSurvey.id,
|
||||
},
|
||||
{ requestId }
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof ResourceNotFoundError) {
|
||||
log.warn({ errorCode: error.name, statusCode: 403 }, "Survey not found or not accessible");
|
||||
return problemForbidden(requestId, "You are not authorized to access this resource", instance);
|
||||
}
|
||||
|
||||
if (error instanceof DatabaseError) {
|
||||
log.error({ error, statusCode: 500 }, "Database error");
|
||||
return problemInternalError(requestId, "An unexpected error occurred.", instance);
|
||||
}
|
||||
|
||||
log.error({ error, statusCode: 500 }, "V3 survey delete unexpected error");
|
||||
return problemInternalError(requestId, "An unexpected error occurred.", instance);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe("parseV3SurveysListQuery", () => {
|
||||
expect(r.invalid_params[0]).toEqual({
|
||||
name: "foo",
|
||||
reason:
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, includeTotalCount, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("parseV3SurveysListQuery", () => {
|
||||
expect(r.invalid_params[0]).toEqual({
|
||||
name: "after",
|
||||
reason:
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, includeTotalCount, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -57,7 +57,7 @@ describe("parseV3SurveysListQuery", () => {
|
||||
expect(r.invalid_params[0]).toEqual({
|
||||
name: "name",
|
||||
reason:
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, includeTotalCount, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -68,11 +68,20 @@ describe("parseV3SurveysListQuery", () => {
|
||||
if (r.ok) {
|
||||
expect(r.limit).toBe(20);
|
||||
expect(r.cursor).toBeNull();
|
||||
expect(r.includeTotalCount).toBe(true);
|
||||
expect(r.sortBy).toBe("updatedAt");
|
||||
expect(r.filterCriteria).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("parses includeTotalCount=false", () => {
|
||||
const r = parseV3SurveysListQuery(params(`workspaceId=${wid}&includeTotalCount=false`));
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) {
|
||||
expect(r.includeTotalCount).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
test("builds filter from explicit operator params", () => {
|
||||
const r = parseV3SurveysListQuery(
|
||||
params(
|
||||
@@ -102,7 +111,7 @@ describe("parseV3SurveysListQuery", () => {
|
||||
expect(r.invalid_params[0]).toEqual({
|
||||
name: "filter[createdBy][in]",
|
||||
reason:
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
"Unsupported query parameter. Use only workspaceId, limit, cursor, includeTotalCount, filter[name][contains], filter[status][in], filter[type][in], sortBy.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ const SUPPORTED_QUERY_PARAMS = [
|
||||
"workspaceId",
|
||||
"limit",
|
||||
"cursor",
|
||||
"includeTotalCount",
|
||||
FILTER_NAME_CONTAINS_QUERY_PARAM,
|
||||
FILTER_STATUS_IN_QUERY_PARAM,
|
||||
FILTER_TYPE_IN_QUERY_PARAM,
|
||||
@@ -53,6 +54,11 @@ const ZV3SurveysListQuery = z.object({
|
||||
workspaceId: ZId,
|
||||
limit: z.coerce.number().int().min(1).max(V3_SURVEYS_MAX_LIMIT).default(V3_SURVEYS_DEFAULT_LIMIT),
|
||||
cursor: z.string().min(1).optional(),
|
||||
includeTotalCount: z
|
||||
.enum(["true", "false"])
|
||||
.optional()
|
||||
.transform((value) => value !== "false")
|
||||
.default(true),
|
||||
[FILTER_NAME_CONTAINS_QUERY_PARAM]: z
|
||||
.string()
|
||||
.max(512)
|
||||
@@ -71,6 +77,7 @@ export type TV3SurveysListQueryParseResult =
|
||||
workspaceId: string;
|
||||
limit: number;
|
||||
cursor: TSurveyListPageCursor | null;
|
||||
includeTotalCount: boolean;
|
||||
sortBy: TSurveyListSort;
|
||||
filterCriteria: TSurveyFilterCriteria | undefined;
|
||||
}
|
||||
@@ -111,6 +118,7 @@ export function parseV3SurveysListQuery(searchParams: URLSearchParams): TV3Surve
|
||||
workspaceId: searchParams.get("workspaceId"),
|
||||
limit: searchParams.get("limit") ?? undefined,
|
||||
cursor: searchParams.get("cursor")?.trim() || undefined,
|
||||
includeTotalCount: searchParams.get("includeTotalCount")?.trim() || undefined,
|
||||
[FILTER_NAME_CONTAINS_QUERY_PARAM]: searchParams.get(FILTER_NAME_CONTAINS_QUERY_PARAM) ?? undefined,
|
||||
[FILTER_STATUS_IN_QUERY_PARAM]: statusVals.length > 0 ? statusVals : undefined,
|
||||
[FILTER_TYPE_IN_QUERY_PARAM]: typeVals.length > 0 ? typeVals : undefined,
|
||||
@@ -153,6 +161,7 @@ export function parseV3SurveysListQuery(searchParams: URLSearchParams): TV3Surve
|
||||
workspaceId: q.workspaceId,
|
||||
limit: q.limit,
|
||||
cursor,
|
||||
includeTotalCount: q.includeTotalCount,
|
||||
sortBy,
|
||||
filterCriteria: buildFilterCriteria(q),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { requireV3WorkspaceAccess } from "@/app/api/v3/lib/auth";
|
||||
import { getSurveyCount } from "@/modules/survey/list/lib/survey";
|
||||
import { getSurveyListPage } from "@/modules/survey/list/lib/survey-page";
|
||||
import { encodeSurveyListPageCursor, getSurveyListPage } from "@/modules/survey/list/lib/survey-page";
|
||||
import { GET } from "./route";
|
||||
|
||||
const { mockAuthenticateRequest } = vi.hoisted(() => ({
|
||||
@@ -257,6 +257,29 @@ describe("GET /api/v3/surveys", () => {
|
||||
expect(getSurveyCount).toHaveBeenCalledWith(resolvedEnvironmentId, undefined);
|
||||
});
|
||||
|
||||
test("skips totalCount when includeTotalCount=false", async () => {
|
||||
vi.mocked(getSurveyListPage).mockResolvedValue({
|
||||
surveys: [],
|
||||
nextCursor: null,
|
||||
});
|
||||
const cursor = encodeSurveyListPageCursor({
|
||||
version: 1,
|
||||
sortBy: "updatedAt",
|
||||
value: "2026-04-15T10:00:00.000Z",
|
||||
id: "survey_1",
|
||||
});
|
||||
|
||||
const req = createRequest(
|
||||
`http://localhost/api/v3/surveys?workspaceId=${validWorkspaceId}&cursor=${cursor}&includeTotalCount=false`
|
||||
);
|
||||
const res = await GET(req, {} as any);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.meta).toEqual({ limit: 20, nextCursor: null, totalCount: null });
|
||||
expect(getSurveyCount).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("passes filter query to getSurveyListPage", async () => {
|
||||
const filterCriteria = { status: ["inProgress"] };
|
||||
const req = createRequest(
|
||||
@@ -321,11 +344,11 @@ describe("GET /api/v3/surveys", () => {
|
||||
const res = await GET(req, {} as any);
|
||||
const body = await res.json();
|
||||
expect(body.data[0]).not.toHaveProperty("blocks");
|
||||
expect(body.data[0]).not.toHaveProperty("singleUse");
|
||||
expect(body.data[0]).not.toHaveProperty("_count");
|
||||
expect(body.data[0]).not.toHaveProperty("environmentId");
|
||||
expect(body.data[0].id).toBe("s1");
|
||||
expect(body.data[0].workspaceId).toBe("env_1");
|
||||
expect(body.data[0].singleUse).toBeNull();
|
||||
});
|
||||
|
||||
test("returns 403 when getSurveyListPage throws ResourceNotFoundError", async () => {
|
||||
|
||||
@@ -46,21 +46,22 @@ export const GET = withV3ApiWrapper({
|
||||
|
||||
const { environmentId } = authResult;
|
||||
|
||||
const [{ surveys, nextCursor }, totalCount] = await Promise.all([
|
||||
getSurveyListPage(environmentId, {
|
||||
limit: parsed.limit,
|
||||
cursor: parsed.cursor,
|
||||
sortBy: parsed.sortBy,
|
||||
filterCriteria: parsed.filterCriteria,
|
||||
}),
|
||||
getSurveyCount(environmentId, parsed.filterCriteria),
|
||||
]);
|
||||
const surveyPagePromise = getSurveyListPage(environmentId, {
|
||||
limit: parsed.limit,
|
||||
cursor: parsed.cursor,
|
||||
sortBy: parsed.sortBy,
|
||||
filterCriteria: parsed.filterCriteria,
|
||||
});
|
||||
const totalCountPromise = parsed.includeTotalCount
|
||||
? getSurveyCount(environmentId, parsed.filterCriteria)
|
||||
: Promise.resolve(null);
|
||||
const [surveyPage, totalCount] = await Promise.all([surveyPagePromise, totalCountPromise]);
|
||||
|
||||
return successListResponse(
|
||||
surveys.map(serializeV3SurveyListItem),
|
||||
surveyPage.surveys.map(serializeV3SurveyListItem),
|
||||
{
|
||||
limit: parsed.limit,
|
||||
nextCursor,
|
||||
nextCursor: surveyPage.nextCursor,
|
||||
totalCount,
|
||||
},
|
||||
{ requestId, cache: "private, no-store" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TSurvey } from "@/modules/survey/list/types/surveys";
|
||||
|
||||
export type TV3SurveyListItem = Omit<TSurvey, "environmentId" | "singleUse"> & {
|
||||
export type TV3SurveyListItem = Omit<TSurvey, "environmentId"> & {
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export type TV3SurveyListItem = Omit<TSurvey, "environmentId" | "singleUse"> & {
|
||||
* Internally surveys are still scoped by environmentId; externally v3 exposes workspaceId.
|
||||
*/
|
||||
export function serializeV3SurveyListItem(survey: TSurvey): TV3SurveyListItem {
|
||||
const { environmentId, singleUse: _omitSingleUse, ...rest } = survey;
|
||||
const { environmentId, ...rest } = survey;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
|
||||
@@ -339,6 +339,56 @@ describe("API Response Utilities", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("conflictResponse", () => {
|
||||
test("should return a conflict response", () => {
|
||||
const message = "Resource already exists";
|
||||
const details = { field: "singleUseId" };
|
||||
const response = responses.conflictResponse(message, details);
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
|
||||
return response.json().then((body) => {
|
||||
expect(body).toEqual({
|
||||
code: "conflict",
|
||||
message,
|
||||
details,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should handle undefined details", () => {
|
||||
const message = "Resource already exists";
|
||||
const response = responses.conflictResponse(message);
|
||||
|
||||
expect(response.status).toBe(409);
|
||||
|
||||
return response.json().then((body) => {
|
||||
expect(body).toEqual({
|
||||
code: "conflict",
|
||||
message,
|
||||
details: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should include CORS headers when cors is true", () => {
|
||||
const message = "Resource already exists";
|
||||
const response = responses.conflictResponse(message, undefined, true);
|
||||
|
||||
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, POST, PUT, DELETE, OPTIONS");
|
||||
expect(response.headers.get("Access-Control-Allow-Headers")).toBe("Content-Type, Authorization");
|
||||
});
|
||||
|
||||
test("should use custom cache control header when provided", () => {
|
||||
const message = "Resource already exists";
|
||||
const customCache = "no-cache";
|
||||
const response = responses.conflictResponse(message, undefined, false, customCache);
|
||||
|
||||
expect(response.headers.get("Cache-Control")).toBe(customCache);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tooManyRequestsResponse", () => {
|
||||
test("should return a too many requests response", () => {
|
||||
const message = "Rate limit exceeded";
|
||||
|
||||
@@ -16,7 +16,8 @@ interface ApiErrorResponse {
|
||||
| "method_not_allowed"
|
||||
| "not_authenticated"
|
||||
| "forbidden"
|
||||
| "too_many_requests";
|
||||
| "too_many_requests"
|
||||
| "conflict";
|
||||
message: string;
|
||||
details: {
|
||||
[key: string]: string | string[] | number | number[] | boolean | boolean[];
|
||||
@@ -236,6 +237,30 @@ const internalServerErrorResponse = (
|
||||
);
|
||||
};
|
||||
|
||||
const conflictResponse = (
|
||||
message: string,
|
||||
details?: { [key: string]: string },
|
||||
cors: boolean = false,
|
||||
cache: string = "private, no-store"
|
||||
) => {
|
||||
const headers = {
|
||||
...(cors && corsHeaders),
|
||||
"Cache-Control": cache,
|
||||
};
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
code: "conflict",
|
||||
message,
|
||||
details: details || {},
|
||||
} as ApiErrorResponse,
|
||||
{
|
||||
status: 409,
|
||||
headers,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const tooManyRequestsResponse = (
|
||||
message: string,
|
||||
cors: boolean = false,
|
||||
@@ -270,4 +295,5 @@ export const responses = {
|
||||
successResponse,
|
||||
tooManyRequestsResponse,
|
||||
forbiddenResponse,
|
||||
conflictResponse,
|
||||
};
|
||||
|
||||
@@ -971,13 +971,13 @@ const improveTrialConversion = (t: TFunction): TTemplate => {
|
||||
elements: [
|
||||
buildOpenTextElement({
|
||||
id: reusableElementIds[2],
|
||||
headline: t("templates.improve_trial_conversion_question_2_headline"),
|
||||
headline: t("templates.improve_trial_conversion_question_3_headline"),
|
||||
required: true,
|
||||
inputType: "text",
|
||||
}),
|
||||
],
|
||||
logic: [createBlockJumpLogic(reusableElementIds[2], block6Id, "isSubmitted")],
|
||||
buttonLabel: t("templates.improve_trial_conversion_question_2_button_label"),
|
||||
buttonLabel: t("templates.improve_trial_conversion_question_3_button_label"),
|
||||
t,
|
||||
}),
|
||||
buildBlock({
|
||||
@@ -1647,14 +1647,14 @@ const identifyCustomerGoals = (t: TFunction): TTemplate => {
|
||||
elements: [
|
||||
buildMultipleChoiceElement({
|
||||
type: TSurveyElementTypeEnum.MultipleChoiceSingle,
|
||||
headline: "What's your primary goal for using $[projectName]?",
|
||||
headline: t("templates.identify_customer_goals_question_1_headline"),
|
||||
required: true,
|
||||
shuffleOption: "none",
|
||||
choices: [
|
||||
"Understand my user base deeply",
|
||||
"Identify upselling opportunities",
|
||||
"Build the best possible product",
|
||||
"Rule the world to make everyone breakfast brussels sprouts.",
|
||||
t("templates.identify_customer_goals_question_1_choice_1"),
|
||||
t("templates.identify_customer_goals_question_1_choice_2"),
|
||||
t("templates.identify_customer_goals_question_1_choice_3"),
|
||||
t("templates.identify_customer_goals_question_1_choice_4"),
|
||||
],
|
||||
}),
|
||||
],
|
||||
@@ -4926,6 +4926,7 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => {
|
||||
showLanguageSwitch: false,
|
||||
followUps: [],
|
||||
isBackButtonHidden: false,
|
||||
isAutoProgressingEnabled: true,
|
||||
isCaptureIpEnabled: false,
|
||||
metadata: {},
|
||||
questions: [], // Required for build-time type checking (Zod defaults to [] at runtime)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"ro-RO",
|
||||
"ru-RU",
|
||||
"sv-SE",
|
||||
"tr-TR",
|
||||
"zh-Hans-CN",
|
||||
"zh-Hant-TW"
|
||||
]
|
||||
|
||||
+41
-29
@@ -125,6 +125,7 @@ checksums:
|
||||
common/centered_modal: 982ff411cb7e91e30300c2ed56b7e507
|
||||
common/change_organization: 3b2c873962509445ff2cb8cde5ad913b
|
||||
common/change_workspace: 489cbcf7eef9b9b960e426fbf4da318f
|
||||
common/choice_n: ee41eb382bae7289a221d959f3046965
|
||||
common/choices: 8a7a77a71ec6eebc363c5dc0f8490a4d
|
||||
common/choose_environment: 5762cd499529815fc3e6a7feea39f90b
|
||||
common/choose_organization: a8f5db68012323bfbb1a0ad0fb194603
|
||||
@@ -138,6 +139,7 @@ checksums:
|
||||
common/close: 2c2e22f8424a1031de89063bd0022e16
|
||||
common/code: 343bc5386149b97cece2b093c39034b2
|
||||
common/collapse_rows: 24988527f9180f37aa55d2aa183ccb21
|
||||
common/column_n: 550955aee6a92d8ccc96989300add693
|
||||
common/completed: 0e4bbce9985f25eb673d9a054c8d5334
|
||||
common/configuration: 923ec0502721489202f6222dd4107163
|
||||
common/confirm: 90930b51154032f119fa75c1bd422d8b
|
||||
@@ -168,6 +170,7 @@ checksums:
|
||||
common/created_by: 6775c2fa7d495fea48f1ad816daea93b
|
||||
common/customer_success: 2b0c99a5f57e1d16cf0a998f9bb116c4
|
||||
common/dark_overlay: 173e84b526414dbc70dbf9737e443b60
|
||||
common/data_refreshed_successfully: 85728c61a9e1a16e46af69ddf0dbcda6
|
||||
common/date: 56f41c5d30a76295bb087b20b7bee4c3
|
||||
common/days: c95fe8aedde21a0b5653dbd0b3c58b48
|
||||
common/default: d9c6dc5c412fe94143dfd1d332ec81d4
|
||||
@@ -209,6 +212,7 @@ checksums:
|
||||
common/failed_to_copy_to_clipboard: de836a7d628d36c832809252f188f784
|
||||
common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e
|
||||
common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4
|
||||
common/field_placeholder: ec26d96643d86da164162204ec6c650f
|
||||
common/filter: 626325a05e4c8800f7ede7012b0cadaf
|
||||
common/finish: ffa7a10f71182b48fefed7135bee24fa
|
||||
common/first_name: cf040a5d6a9fd696be400380cc99f54b
|
||||
@@ -220,10 +224,12 @@ checksums:
|
||||
common/generate: 0345bf322c191e70d01fd6607ec5c2f8
|
||||
common/go_back: b917ea82facb90c88c523b255d29f84b
|
||||
common/go_to_dashboard: a6efa97d25e36fedc0af794f6ba610f2
|
||||
common/headline: 0023cbe059bbadcc77312825cbbce5ac
|
||||
common/hidden: fa290c6ada5869d744ed35e9cca64699
|
||||
common/hidden_field: 3ed5c58d0ed359e558cdf7bd33606d2d
|
||||
common/hidden_fields: 3de6cfd308293a826cb8679fd1d49972
|
||||
common/hide_column: 23ce94db148f2d8e4a0923defead6cf1
|
||||
common/html: f750870203043349d570d8f5865ca0f8
|
||||
common/id: c8886d38aeea2ed5f785aba4fc96784b
|
||||
common/image: 048ba7a239de0fbd883ade8558415830
|
||||
common/images: 9305827c28694866f49db42b4c51831f
|
||||
@@ -271,7 +277,6 @@ checksums:
|
||||
common/months: da74749fbe80394fa0f72973d7b0964a
|
||||
common/move_down: 4f4de55743043355ad4a839aff2c48ff
|
||||
common/move_up: 69f25b205c677abdb26cbb69d97cd10b
|
||||
common/multiple_languages: 7d8ddd4b40d32fcd7bd6f7bac6485b1f
|
||||
common/my_product: ad022177062f9ef6e9acf33b13e889aa
|
||||
common/name: 9368b5a047572b6051f334af5aa76819
|
||||
common/new: 126d036fae5fb6b629728ecb97e6195b
|
||||
@@ -286,6 +291,7 @@ checksums:
|
||||
common/no_result_found: fedddbc0149972ea072a9e063198a16d
|
||||
common/no_results: 0e9b73265c6542240f5a3bf6b43e9280
|
||||
common/no_surveys_found: 7b74706fe4f4aacd7d858e19e444fe85
|
||||
common/no_text_found: 27350f35bdd57b3701c7ec578a1a0e11
|
||||
common/none_of_the_above: e007f0b1e046d5ddbbcfbd87940456ee
|
||||
common/not_authenticated: fed6c62208524ea6782b5f9c07a95a4f
|
||||
common/not_authorized: 4be80383fe1a6f52c61138f1aa8d01d4
|
||||
@@ -309,7 +315,7 @@ checksums:
|
||||
common/organization_settings: 11528aa89ae9935e55dcb54478058775
|
||||
common/other: 79acaa6cd481262bea4e743a422529d2
|
||||
common/other_filters: 20b09213c131db47eb8b23e72d0c4bea
|
||||
common/others: 39160224ce0e35eb4eb252c997edf4d8
|
||||
common/other_placeholder: f3a0fa2eaaf75aa92b290449c928c081
|
||||
common/overlay_color: 4b72073285d13fff93d094aabffe05ac
|
||||
common/overview: 30c54e4dc4ce599b87d94be34a8617f5
|
||||
common/password: 223a61cf906ab9c40d22612c588dff48
|
||||
@@ -327,7 +333,6 @@ checksums:
|
||||
common/please_upgrade_your_plan: 03d54a21ecd27723c72a13644837e5ed
|
||||
common/powered_by_formbricks: 1c3e19894583292bfaf686cac84a4960
|
||||
common/preview: 3173ee1f0f1d4e50665ca4a84c38e15d
|
||||
common/preview_survey: 7409e9c118e3e5d5f2a86201c2b354f2
|
||||
common/privacy: 7459744a63ef8af4e517a09024bd7c08
|
||||
common/product_manager: dfeadc96e6d3de22a884ee97974b505e
|
||||
common/production: 226e0ce83b49700bc1b1c08c4c3ed23a
|
||||
@@ -342,6 +347,7 @@ checksums:
|
||||
common/quotas_description: a2caa44fa74664b3b6007e813f31a754
|
||||
common/read_docs: d06513c266fdd9056e0500eab838ebac
|
||||
common/recipients: f90e7f266be3f5a724858f21a9fd855e
|
||||
common/refresh: c0aec3f31be4c984bae9a482572d2857
|
||||
common/remove: dba2fe5fe9f83f8078c687f28cba4b52
|
||||
common/remove_from_team: 69bcc7a1001c3017f9de578ee22cffd6
|
||||
common/reorder_and_hide_columns: a5e3d7c0c7ef879211d05a37be1c5069
|
||||
@@ -354,6 +360,7 @@ checksums:
|
||||
common/responses: 14bb6c69f906d7bbd1359f7ef1bb3c28
|
||||
common/restart: bab6232e89f24e3129f8e48268739d5b
|
||||
common/role: 53743bbb6ca938f5b893552e839d067f
|
||||
common/row_n: eb5bb04b244fadd7a6962aa58bf6bd17
|
||||
common/saas: f01686245bcfb35a3590ab56db677bdb
|
||||
common/sales: 38758eb50094cd8190a71fe67be4d647
|
||||
common/save: f7a2929f33bc420195e59ac5a8bcd454
|
||||
@@ -392,6 +399,7 @@ checksums:
|
||||
common/storage_not_configured: b0c3e339f6d71f23fdd189e7bcb076f6
|
||||
common/string: 4ddccc1974775ed7357f9beaf9361cec
|
||||
common/styling: 240fc91eb03c52d46b137f82e7aec2a1
|
||||
common/subheader: 73a37d57cb9807e574a42bd0c7e334ed
|
||||
common/submit: 7c91ef5f747eea9f77a9c4f23e19fb2e
|
||||
common/summary: 13eb7b8a239fb4702dfdaee69100a220
|
||||
common/survey: b659d270a53dada994d926e0cc6e9a54
|
||||
@@ -460,7 +468,6 @@ checksums:
|
||||
common/workspace_name_placeholder: 8a9e30ab01666af13c44a73b82c37ec1
|
||||
common/workspaces: 8ba082a84aa35cf851af1cf874b853e2
|
||||
common/years: eb4f5fdd2b320bf13e200fd6a6c1abff
|
||||
common/you: db2a4a796b70cc1430d1b21f6ffb6dcb
|
||||
common/you_are_downgraded_to_the_community_edition: e3ae56502ff787109cae0997519f628e
|
||||
common/you_are_not_authorized_to_perform_this_action: 1b3255ab740582ddff016a399f8bf302
|
||||
common/you_have_reached_your_limit_of_workspace_limit: 54d754c3267036742f23fb05fd3fcc45
|
||||
@@ -633,8 +640,6 @@ checksums:
|
||||
environments/contacts/attributes_msg_new_attribute_created: 5cba6158c4305c05104814ec1479267c
|
||||
environments/contacts/attributes_msg_userid_already_exists: 9c695538befc152806c460f52a73821a
|
||||
environments/contacts/contact_deleted_successfully: c5b64a42a50e055f9e27ec49e20e03fa
|
||||
environments/contacts/contacts_table_refresh: 6a959475991dd4ab28ad881bae569a09
|
||||
environments/contacts/contacts_table_refresh_success: 40951396e88e5c8fdafa0b3bb4fadca8
|
||||
environments/contacts/create_attribute: 87320615901f95b4f35ee83c290a3a6c
|
||||
environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f
|
||||
environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26
|
||||
@@ -1132,7 +1137,7 @@ checksums:
|
||||
environments/settings/general/organization_invite_link_ready: e54b37c4ec2e5a9ea9f6bc6e5b512b0b
|
||||
environments/settings/general/organization_name: 73c9b31c9032a22bd84a07881942bb04
|
||||
environments/settings/general/organization_name_description: ff517b4749a332b94a26110d7c7e771f
|
||||
environments/settings/general/organization_name_placeholder: fc91de3ddc89ab77f30d555778312380
|
||||
environments/settings/general/organization_name_placeholder: abcee7d91a848e573b63a763cfaf6a08
|
||||
environments/settings/general/organization_name_updated_successfully: d36ba3a4f614d30b10e696d25da22432
|
||||
environments/settings/general/organization_settings: d31952131ad5f0ec72ad96f1ed11bef6
|
||||
environments/settings/general/please_add_a_logo: 66d6f97a2e7b27efc04bd653240ee813
|
||||
@@ -1234,15 +1239,9 @@ checksums:
|
||||
environments/settings/teams/you_are_a_member: cf5af638d5371c8fbc337e92519e5150
|
||||
environments/surveys/all_set_time_to_create_first_survey: 21d3bb74c3b9642b3195d17c17346399
|
||||
environments/surveys/alphabetical: 5fcfeff9c5fd28714f0a390e0ddaaaee
|
||||
environments/surveys/copy_survey: de8142b45e7bca61f2dca0069a62b417
|
||||
environments/surveys/copy_survey_description: 66d0aadf192ad5790fbf3f55f3bb5485
|
||||
environments/surveys/copy_survey_error: 74cab7d84ea8b669e106d4c326cac005
|
||||
environments/surveys/copy_survey_link_to_clipboard: 77387e3d3de4be07a2a34963f73cd7e8
|
||||
environments/surveys/copy_survey_partially_success: a436a5fb7167b95c2308794d35aab070
|
||||
environments/surveys/copy_survey_success: a829e645fe034b3e712d0b8572a5edc4
|
||||
environments/surveys/delete_survey_and_responses_warning: 3320c91c1fd27378b7f3d6abc003f2ae
|
||||
environments/surveys/edit/1_choose_the_default_language_for_this_survey: d22759857c1bb3d6b337e8e9d501dad7
|
||||
environments/surveys/edit/2_activate_translation_for_specific_languages: 9f23cb81ad301073df45ae36f0d94f9e
|
||||
environments/surveys/edit/activate_translations: af127c1bed2b47e2012e3a23e489ecb8
|
||||
environments/surveys/edit/add: 5196f5cd4ba3a6ac8edef91345e17f66
|
||||
environments/surveys/edit/add_a_delay_or_auto_close_the_survey: b5fa358bf3ff324014060eb0baf6dd2f
|
||||
environments/surveys/edit/add_a_four_digit_pin: 953cb3673d2135923e3b4474d33ffb2c
|
||||
@@ -1291,6 +1290,8 @@ checksums:
|
||||
environments/surveys/edit/assign: e80715ab64bf7cf463abb3a9fd1ad516
|
||||
environments/surveys/edit/audience: a4d9fab4214a641e2d358fbb28f010e0
|
||||
environments/surveys/edit/auto_close_on_inactivity: 093db516799315ccd4242a3675693012
|
||||
environments/surveys/edit/auto_progress_rating_and_nps: 76b98e95a5b850850baa0ccc3c7fbf7c
|
||||
environments/surveys/edit/auto_progress_rating_and_nps_description: 2a992dd8a5b9532f178f9a21881feb9a
|
||||
environments/surveys/edit/auto_save_disabled: f7411fb0dcfb8f7b19b85f0be54f2231
|
||||
environments/surveys/edit/auto_save_disabled_tooltip: 77322e1e866b7d29f7641a88bbd3b681
|
||||
environments/surveys/edit/auto_save_on: 1524d466830b00c5d727c701db404963
|
||||
@@ -1336,6 +1337,7 @@ checksums:
|
||||
environments/surveys/edit/caution_text: 3291e962c0e4c4656832837ddc512918
|
||||
environments/surveys/edit/change_anyway: 6377497d40373f6d0f082670194981ab
|
||||
environments/surveys/edit/change_background: fa71a993869f7d3ac553c547c12c3e9b
|
||||
environments/surveys/edit/change_default: 6236a6c8a28489ba7c4cad7426806859
|
||||
environments/surveys/edit/change_question_type: 2d555ae48df8dbedfc6a4e1ad492f4aa
|
||||
environments/surveys/edit/change_survey_type: c26322043a476da6d94adb8b4efe1e93
|
||||
environments/surveys/edit/change_the_background_to_a_color_image_or_animation: f1b9c9eb61497dd91b2550dd50c77836
|
||||
@@ -1348,6 +1350,7 @@ checksums:
|
||||
environments/surveys/edit/choose_where_to_run_the_survey: ad87bcae97c445f1fd9ac110ea24f117
|
||||
environments/surveys/edit/city: 1831f32e1babbb29af27fac3053504a2
|
||||
environments/surveys/edit/close_survey_on_response_limit: 256d0bccdbcbb3d20e39aabc5b376e5e
|
||||
environments/surveys/edit/code: 343bc5386149b97cece2b093c39034b2
|
||||
environments/surveys/edit/color: 9d53d1d120e8b8954bcae9a322573748
|
||||
environments/surveys/edit/column_used_in_logic_error: deffbd3e8f4bd71a5e522682e8ee60dd
|
||||
environments/surveys/edit/columns: 14896556dc1535d70198854757f704ec
|
||||
@@ -1372,6 +1375,7 @@ checksums:
|
||||
environments/surveys/edit/customize_survey_logo: 7f7e26026c88a727228f2d7a00d914e2
|
||||
environments/surveys/edit/darken_or_lighten_background_of_your_choice: 304a64a8050ebf501d195e948cd25b6f
|
||||
environments/surveys/edit/days_before_showing_this_survey_again: 9ee757e5c3a07844b12ceb406dc65b04
|
||||
environments/surveys/edit/default_language: 06d01d2598419e36ba97d2d8719f849b
|
||||
environments/surveys/edit/delete_anyways: cc8683ab625280eefc9776bd381dc2e8
|
||||
environments/surveys/edit/delete_block: c00617cb0724557e486304276063807a
|
||||
environments/surveys/edit/delete_choice: fd750208d414b9ad8c980c161a0199e1
|
||||
@@ -1391,7 +1395,6 @@ checksums:
|
||||
environments/surveys/edit/duplicate_question: 910751de01fdd327165968214717711b
|
||||
environments/surveys/edit/edit_link: 40ba9e15beac77a46c5baf30be84ac54
|
||||
environments/surveys/edit/edit_recall: 38a4a7378d02453e35d06f2532eef318
|
||||
environments/surveys/edit/edit_translations: 2b21bea4b53e88342559272701e9fbf3
|
||||
environments/surveys/edit/element_not_found: 196777ff6811dd177971ffc8e27a72c1
|
||||
environments/surveys/edit/enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey: c70466147d49dcbb3686452f35c46428
|
||||
environments/surveys/edit/enable_recaptcha_to_protect_your_survey_from_spam: 4483a5763718d201ac97caa1e1216e13
|
||||
@@ -1527,11 +1530,13 @@ checksums:
|
||||
environments/surveys/edit/long_answer: 3a97f8d2e90aba6e679917a0c5670c53
|
||||
environments/surveys/edit/long_answer_toggle_description: 86bcdfeb74d9825c2f2d5a215e92d111
|
||||
environments/surveys/edit/lower_label: 45985bca022d4370bd6e013af75d5160
|
||||
environments/surveys/edit/manage_languages: 9c56d5afee8a73dfc283a452470f3a10
|
||||
environments/surveys/edit/manage_languages: fe82303bc27b55ccfc076b527b185e39
|
||||
environments/surveys/edit/manage_translations: 09b01c5c251e6dbc3dc6cd8b33fb6301
|
||||
environments/surveys/edit/matrix_all_fields: 187240509163b2f52a400a565e57c67f
|
||||
environments/surveys/edit/matrix_rows: 8f41f34e6ca28221cf1ebd948af4c151
|
||||
environments/surveys/edit/max_file_size: 3d35a22048f4d22e24da698fb5fb77d7
|
||||
environments/surveys/edit/max_file_size_limit_is: 78998639cde3587cecb272ba47e05f9e
|
||||
environments/surveys/edit/missing_first: a0c8802636ade7bac86a0dacba00b8d4
|
||||
environments/surveys/edit/move_question_to_block: e8d7ef1e2f727921cb7f5788849492ad
|
||||
environments/surveys/edit/multiply: 89a0bb629167f97750ae1645a46ced0d
|
||||
environments/surveys/edit/needed_for_self_hosted_cal_com_instance: d241e72f0332177d32ce6c35070757dc
|
||||
@@ -1539,7 +1544,7 @@ checksums:
|
||||
environments/surveys/edit/next_button_label: 39f1e82ae1dea5e400e8ed7c98c6ad9c
|
||||
environments/surveys/edit/no_hidden_fields_yet_add_first_one_below: 9cc6cab3a6a42dbf835215897b5b8516
|
||||
environments/surveys/edit/no_images_found_for: 7dabcbcc7084f59c6ec0971895dfcd29
|
||||
environments/surveys/edit/no_languages_found_add_first_one_to_get_started: 22d7782c8504daf693cab3cf7135d6e3
|
||||
environments/surveys/edit/no_languages_found_add_first_one_to_get_started: 4e66397232da6a463708220dc020bf42
|
||||
environments/surveys/edit/no_option_found: a1a3aa7e6c13b6bb8df20a1a104c7c04
|
||||
environments/surveys/edit/no_recall_items_found: 729e2b02e412cdc79f5ad94b1918620c
|
||||
environments/surveys/edit/no_variables_yet_add_first_one_below: c8704b9ebc9c26c0e9dd50c099ba88cd
|
||||
@@ -1566,6 +1571,7 @@ checksums:
|
||||
environments/surveys/edit/please_enter_a_valid_url: 25d43dfb802c31cb59dc88453ea72fc4
|
||||
environments/surveys/edit/please_set_a_survey_trigger: 0358142df37dd1724f629008a1db453a
|
||||
environments/surveys/edit/please_specify: e1faa6cd085144f7339c7e74dc6fb366
|
||||
environments/surveys/edit/present_your_survey_in_multiple_languages: 37f28b0a092d68322fedbc2e0c221ef3
|
||||
environments/surveys/edit/prevent_double_submission: afc502baa2da81d9c9618da1c3b5a57a
|
||||
environments/surveys/edit/prevent_double_submission_description: ef7d2aa22d43bdc6ccebb076c6aa9ce5
|
||||
environments/surveys/edit/progress_saved: d7bfc189571f08bbb4d0240cb9363ffa
|
||||
@@ -1655,6 +1661,7 @@ checksums:
|
||||
environments/surveys/edit/seven_points: 4ead50fdfda45e8710767e1b1a84bf42
|
||||
environments/surveys/edit/show_block_settings: bad99d99c9908874e45f5c350a88cc79
|
||||
environments/surveys/edit/show_button: 6b364aac9d7ac71f34a438607c9693bc
|
||||
environments/surveys/edit/show_in_order: 15784a59572eb8a6dba6b918c31a9493
|
||||
environments/surveys/edit/show_language_switch: b6915a7f26d7079f2d4d844d74440413
|
||||
environments/surveys/edit/show_multiple_times: 05239c532c9c05ef5d2990ba6ce12f60
|
||||
environments/surveys/edit/show_only_once: 31858baf60ebcf193c7e35d9084af0af
|
||||
@@ -1686,7 +1693,6 @@ checksums:
|
||||
environments/surveys/edit/survey_preview: 33644451073149383d3ace08be930739
|
||||
environments/surveys/edit/survey_styling: 7f96d6563e934e65687b74374a33b1dc
|
||||
environments/surveys/edit/survey_trigger: f0c7014a684ca566698b87074fad5579
|
||||
environments/surveys/edit/switch_multi_language_on_to_get_started: cca0ef91ee49095da30cd1e3f26c406f
|
||||
environments/surveys/edit/target_block_not_found: 0a0c401017ab32364fec2fcbf815d832
|
||||
environments/surveys/edit/targeted: ca615f1fc3b490d5a2187b27fb4a2073
|
||||
environments/surveys/edit/ten_points: a1317b82003859f77fb3138c55450d63
|
||||
@@ -1694,9 +1700,11 @@ checksums:
|
||||
environments/surveys/edit/the_survey_will_be_shown_once_even_if_person_doesnt_respond: e45beba7ae126775f4966776c982a3b4
|
||||
environments/surveys/edit/then: 5e941fb7dd51a18651fcfb865edd5ba6
|
||||
environments/surveys/edit/this_action_will_remove_all_the_translations_from_this_survey: 3340c89696f10bdc01b9a1047ff0b987
|
||||
environments/surveys/edit/this_will_remove_the_language_and_all_its_translations: 6a71ae70abbd61f13f15323d825a47f6
|
||||
environments/surveys/edit/three_points: d7f299aec752d7d690ef0ab6373327ae
|
||||
environments/surveys/edit/times: 5ab156c13df6bfd75c0b17ad0a92c78a
|
||||
environments/surveys/edit/to_keep_the_placement_over_all_surveys_consistent_you_can: 7a078e6a39d4c30b465137d2b6ef3e67
|
||||
environments/surveys/edit/translated: 5b9d805410310b726f12bacb06da44e3
|
||||
environments/surveys/edit/trigger_survey_when_one_of_the_actions_is_fired: 8570291668ec9879d204f10e861112db
|
||||
environments/surveys/edit/try_lollipop_or_mountain: c550a0f07b3ae40a237e30a4314a249c
|
||||
environments/surveys/edit/type_field_id: 714b845806236bb8a9d6a09933b836e9
|
||||
@@ -1769,6 +1777,7 @@ checksums:
|
||||
environments/surveys/edit/verify_email_before_submission_description: 434ab3ee6134367513b633a9d4f7d772
|
||||
environments/surveys/edit/visibility_and_recontact: c27cb4ff3a4262266902a335c3ad5d84
|
||||
environments/surveys/edit/visibility_and_recontact_description: 2969ab679e1f6111dd96e95cee26e219
|
||||
environments/surveys/edit/visible: 54ea1310fe55664c24a712eb17070fbd
|
||||
environments/surveys/edit/wait: 014d18ade977bf08d75b995076596708
|
||||
environments/surveys/edit/wait_a_few_seconds_after_the_trigger_before_showing_the_survey: 13d5521cf73be5afeba71f5db5847919
|
||||
environments/surveys/edit/waiting_time_across_surveys: 6873c18d51830e2cadef67cce6a2c95c
|
||||
@@ -1951,7 +1960,6 @@ checksums:
|
||||
environments/surveys/summary/downloading_qr_code: 3c46bf636e617848a4fca9b6c5b51dac
|
||||
environments/surveys/summary/drop_offs: 605ee950f82110132d6c5780926af109
|
||||
environments/surveys/summary/drop_offs_tooltip: 2a01683380be45f17636365886cf3452
|
||||
environments/surveys/summary/failed_to_copy_link: 4e891c757c80e770674e8e74d1c08487
|
||||
environments/surveys/summary/filter_added_successfully: e247f65020cd87454bcec0da6f0fd034
|
||||
environments/surveys/summary/filter_updated_successfully: 01146bc7e6394e271836be2f1b3a257b
|
||||
environments/surveys/summary/filtered_responses_csv: aad66a98be6a09cac8bef9e4db4a75cf
|
||||
@@ -2036,7 +2044,6 @@ checksums:
|
||||
environments/surveys/summary/youre_not_plugged_in_yet: f19da3cd474b9a3cf28e956fd811fb00
|
||||
environments/surveys/survey_deleted_successfully: a6b654cc914b344a4475fd2fd4a98cc5
|
||||
environments/surveys/survey_duplicated_successfully: 91e244f1e7a33640bb4817166a01ff46
|
||||
environments/surveys/survey_duplication_error: 35994330aed844ce37d8b4f09df24581
|
||||
environments/surveys/templates/all_channels: 6be67a82fc7326dc2304b23ab3348b87
|
||||
environments/surveys/templates/all_industries: c7354412fe34585526ff2232aadace41
|
||||
environments/surveys/templates/all_roles: 6582ccd0a2349c162a7ae1574cdf76be
|
||||
@@ -2113,7 +2120,6 @@ checksums:
|
||||
environments/workspace/languages/duplicate_language_or_language_id: 0e17e3794b24e2428ca6ffadae0d08f3
|
||||
environments/workspace/languages/edit_languages: c9d36f6b28557cc7d54e87c37dc18fdd
|
||||
environments/workspace/languages/identifier: 7d8ade6b85e96216bcd73adeeeeecd8c
|
||||
environments/workspace/languages/incomplete_translations: d82908b5725f18f5849c7876ad497ebc
|
||||
environments/workspace/languages/language: 277fd1a41cc237a437cd1d5e4a80463b
|
||||
environments/workspace/languages/language_deleted_successfully: 4a805d030491f3fe608d2371b0cfcd83
|
||||
environments/workspace/languages/languages_updated_successfully: 60de474c99c5059c0458cddd0b016c15
|
||||
@@ -2124,7 +2130,6 @@ checksums:
|
||||
environments/workspace/languages/remove_language: 1a64563b0f37109f97b78eddd493e381
|
||||
environments/workspace/languages/remove_language_from_surveys_to_remove_it_from_workspace: 61bc96f9db31a29a649cc9ecd684bc39
|
||||
environments/workspace/languages/search_items: b54b751c8b075200be579d6c8e58096b
|
||||
environments/workspace/languages/translate: 59f9803b27e2030ba7323ed239116cf7
|
||||
environments/workspace/look/add_background_color: 9be512ee1246e32d3958c56097d202d9
|
||||
environments/workspace/look/add_background_color_description: adb6fcb392862b3d0e9420d9b5405ddb
|
||||
environments/workspace/look/advanced_styling_field_border_radius: 63b8f3541a9792d705e67d5aca7b6451
|
||||
@@ -2182,12 +2187,12 @@ checksums:
|
||||
environments/workspace/look/advanced_styling_field_track_bg_description: 8a56258273dfe49e83fe752ea9e8daed
|
||||
environments/workspace/look/advanced_styling_field_track_height: 9ce57cb4583039c224a37e013efb6b8f
|
||||
environments/workspace/look/advanced_styling_field_track_height_description: 90243a4374e15d9118ad0fd93d5f3614
|
||||
environments/workspace/look/advanced_styling_field_upper_label_color: 65d75c60dfdba88e5fed38bcb24a0a5d
|
||||
environments/workspace/look/advanced_styling_field_upper_label_color_description: ae2276506807c7ceeb7a8b87723a8dd4
|
||||
environments/workspace/look/advanced_styling_field_upper_label_size: ea0ca9a3ffa1650f97a31df453b0afc7
|
||||
environments/workspace/look/advanced_styling_field_upper_label_size_description: 061668625be0f7a68ebc2e2ebe49e5a9
|
||||
environments/workspace/look/advanced_styling_field_upper_label_weight: 946c5836d2cfaaee21e494424d550887
|
||||
environments/workspace/look/advanced_styling_field_upper_label_weight_description: 916b03c719a8dead351679336aabcf53
|
||||
environments/workspace/look/advanced_styling_field_upper_label_color: 2767a5db32742073a01aac16488e93dc
|
||||
environments/workspace/look/advanced_styling_field_upper_label_color_description: 58f43ce21b7f6539cc937aa80c7e8060
|
||||
environments/workspace/look/advanced_styling_field_upper_label_size: 3342babd1df61a3bdf7a3284137f7c24
|
||||
environments/workspace/look/advanced_styling_field_upper_label_size_description: 867a89a79ed7ac7f1c6b0f3481a67f26
|
||||
environments/workspace/look/advanced_styling_field_upper_label_weight: a9a0de9e840518d282cfdbcb02d059b5
|
||||
environments/workspace/look/advanced_styling_field_upper_label_weight_description: 3cee88e1c8e75548dcb6004f0e44f31c
|
||||
environments/workspace/look/advanced_styling_section_buttons: 3b44d6e2800e7bf3f133f1bce435f4c2
|
||||
environments/workspace/look/advanced_styling_section_headlines: 6def704c0ac2ecb5951400c806856a41
|
||||
environments/workspace/look/advanced_styling_section_inputs: 76bbeb561122a72fd3ec8c49eff7c563
|
||||
@@ -2720,6 +2725,11 @@ checksums:
|
||||
templates/gauge_feature_satisfaction_question_2_headline: 0fcbefbfcf5c21e42de8a36cb2cad854
|
||||
templates/identify_customer_goals_description: c30d06df9e5c76334e4c3d470ee6e4d8
|
||||
templates/identify_customer_goals_name: f8123dbfa22e169517a811fae7496595
|
||||
templates/identify_customer_goals_question_1_choice_1: a6803cfbdbd6208eedf5c691f9e106a5
|
||||
templates/identify_customer_goals_question_1_choice_2: 7461749517d62030ec2e3915cf1d223b
|
||||
templates/identify_customer_goals_question_1_choice_3: 725eb3ee0d4f2d229fcf588c21e66a86
|
||||
templates/identify_customer_goals_question_1_choice_4: 3985521036afaf1cbd2bdc7a4d86d351
|
||||
templates/identify_customer_goals_question_1_headline: bd9cd414fb723110d7f0a786bbf89d6c
|
||||
templates/identify_sign_up_barriers_description: 5b2fbee8c425d7a4d0706ec3628cea11
|
||||
templates/identify_sign_up_barriers_name: 3bbc5352dfa7a9c237bc2c6b21b608dd
|
||||
templates/identify_sign_up_barriers_question_1_button_label: 080fd22c580f56ffdcea6c3d60448b84
|
||||
@@ -2794,12 +2804,14 @@ checksums:
|
||||
templates/improve_trial_conversion_question_1_subheader: 67c7047ba2365d461df14dbed3f9506d
|
||||
templates/improve_trial_conversion_question_2_button_label: 89ddbcf710eba274963494f312bdc8a9
|
||||
templates/improve_trial_conversion_question_2_headline: 05dd4820f60b9d267a9affc7e662f029
|
||||
templates/improve_trial_conversion_question_3_button_label: 89ddbcf710eba274963494f312bdc8a9
|
||||
templates/improve_trial_conversion_question_3_headline: 3daeccf3dfc7bf8e9868c10fb3ea0b19
|
||||
templates/improve_trial_conversion_question_4_button_label: d94a6a11cfdf4ebde4c5332e585e2e96
|
||||
templates/improve_trial_conversion_question_4_headline: 9b07341f65574c4165086ec107cebb45
|
||||
templates/improve_trial_conversion_question_4_html: 8ce95691eeeae7ad61c4d2f867b918ca
|
||||
templates/improve_trial_conversion_question_5_button_label: 89ddbcf710eba274963494f312bdc8a9
|
||||
templates/improve_trial_conversion_question_5_headline: dbd99e216fcbf8693b8e77fbd77e1c84
|
||||
templates/improve_trial_conversion_question_5_subheader: b9b478e967930358b0c74324a7c18fc8
|
||||
templates/improve_trial_conversion_question_5_subheader: 859876a442a633f4aa0d78fd0ee4ab4c
|
||||
templates/improve_trial_conversion_question_6_headline: f15239ecc4f1a6bd8bea77a38b39c844
|
||||
templates/improve_trial_conversion_question_6_subheader: e147ddbb609fff6e6fc78fb1f4add0ac
|
||||
templates/integration_setup_survey_description: 696ccab07d7098cdb79c224fa1208889
|
||||
|
||||
@@ -182,6 +182,7 @@ export const AVAILABLE_LOCALES: TUserLocale[] = [
|
||||
"ro-RO",
|
||||
"ru-RU",
|
||||
"sv-SE",
|
||||
"tr-TR",
|
||||
"zh-Hans-CN",
|
||||
"zh-Hant-TW",
|
||||
];
|
||||
|
||||
@@ -213,6 +213,13 @@ export const appLanguages = [
|
||||
native: "Svenska",
|
||||
},
|
||||
},
|
||||
{
|
||||
code: "tr-TR",
|
||||
label: {
|
||||
"en-US": "Turkish",
|
||||
native: "Türkçe",
|
||||
},
|
||||
},
|
||||
{
|
||||
code: "zh-Hans-CN",
|
||||
label: {
|
||||
|
||||
@@ -6,11 +6,13 @@ import {
|
||||
createEmailChangeToken,
|
||||
createEmailToken,
|
||||
createInviteToken,
|
||||
createSsoRelinkIntent,
|
||||
createToken,
|
||||
createTokenForLinkSurvey,
|
||||
getEmailFromEmailToken,
|
||||
verifyEmailChangeToken,
|
||||
verifyInviteToken,
|
||||
verifySsoRelinkIntent,
|
||||
verifyToken,
|
||||
verifyTokenForLinkSurvey,
|
||||
} from "./jwt";
|
||||
@@ -380,6 +382,7 @@ describe("JWT Functions - Comprehensive Security Tests", () => {
|
||||
expect(verified).toEqual({
|
||||
id: mockUser.id, // Returns the decrypted user ID
|
||||
email: mockUser.email,
|
||||
purpose: "email_verification",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -414,6 +417,7 @@ describe("JWT Functions - Comprehensive Security Tests", () => {
|
||||
expect(verified).toEqual({
|
||||
id: mockUser.id, // Returns the raw ID from payload
|
||||
email: mockUser.email,
|
||||
purpose: "email_verification",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -425,6 +429,7 @@ describe("JWT Functions - Comprehensive Security Tests", () => {
|
||||
expect(verified).toEqual({
|
||||
id: mockUser.id, // Returns the decrypted user ID
|
||||
email: mockUser.email,
|
||||
purpose: "email_verification",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1004,5 +1009,78 @@ describe("JWT Functions - Comprehensive Security Tests", () => {
|
||||
expect(results.every((result: any) => result.id === mockUser.id)).toBe(true); // Returns decrypted user ID
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO recovery support", () => {
|
||||
test("creates verification tokens that preserve the recovery purpose", async () => {
|
||||
const token = createToken(mockUser.id, { purpose: "sso_recovery", expiresIn: "15m" });
|
||||
|
||||
await expect(verifyToken(token)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: mockUser.id,
|
||||
email: mockUser.email,
|
||||
purpose: "sso_recovery",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("defaults legacy verification tokens to email_verification when purpose is missing", async () => {
|
||||
const legacyToken = jwt.sign({ id: `encrypted_${mockUser.id}` }, TEST_NEXTAUTH_SECRET);
|
||||
|
||||
await expect(verifyToken(legacyToken)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: mockUser.id,
|
||||
email: mockUser.email,
|
||||
purpose: "email_verification",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("round-trips SSO relink intents without losing callback state", () => {
|
||||
const intent = createSsoRelinkIntent({
|
||||
userId: mockUser.id,
|
||||
email: mockUser.email,
|
||||
provider: "google",
|
||||
providerAccountId: "provider-123",
|
||||
callbackUrl: "http://localhost:3000/invite?token=invite-token",
|
||||
});
|
||||
|
||||
expect(verifySsoRelinkIntent(intent)).toEqual({
|
||||
userId: mockUser.id,
|
||||
email: mockUser.email,
|
||||
provider: "google",
|
||||
providerAccountId: "provider-123",
|
||||
callbackUrl: "http://localhost:3000/invite?token=invite-token",
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects expired SSO relink intents", () => {
|
||||
const expiredIntent = jwt.sign(
|
||||
{
|
||||
userId: crypto.symmetricEncrypt(mockUser.id, TEST_ENCRYPTION_KEY),
|
||||
email: crypto.symmetricEncrypt(mockUser.email, TEST_ENCRYPTION_KEY),
|
||||
provider: "google",
|
||||
providerAccountId: crypto.symmetricEncrypt("provider-123", TEST_ENCRYPTION_KEY),
|
||||
callbackUrl: crypto.symmetricEncrypt("http://localhost:3000", TEST_ENCRYPTION_KEY),
|
||||
exp: Math.floor(Date.now() / 1000) - 3600,
|
||||
},
|
||||
TEST_NEXTAUTH_SECRET
|
||||
);
|
||||
|
||||
expect(() => verifySsoRelinkIntent(expiredIntent)).toThrow();
|
||||
});
|
||||
|
||||
test("rejects tampered SSO relink intents", () => {
|
||||
const intent = createSsoRelinkIntent({
|
||||
userId: mockUser.id,
|
||||
email: mockUser.email,
|
||||
provider: "google",
|
||||
providerAccountId: "provider-123",
|
||||
callbackUrl: "http://localhost:3000",
|
||||
});
|
||||
|
||||
const tamperedIntent = `${intent.slice(0, -1)}x`;
|
||||
expect(() => verifySsoRelinkIntent(tamperedIntent)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+108
-5
@@ -1,4 +1,4 @@
|
||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
import jwt, { JwtPayload, SignOptions } from "jsonwebtoken";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ENCRYPTION_KEY, NEXTAUTH_SECRET } from "@/lib/constants";
|
||||
@@ -13,7 +13,39 @@ const decryptWithFallback = (encryptedText: string, key: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createToken = (userId: string, options = {}): string => {
|
||||
export const VERIFICATION_TOKEN_PURPOSES = ["email_verification", "sso_recovery"] as const;
|
||||
|
||||
export type TVerificationTokenPurpose = (typeof VERIFICATION_TOKEN_PURPOSES)[number];
|
||||
|
||||
export type TVerifyTokenPayload = JwtPayload & {
|
||||
id: string;
|
||||
email: string;
|
||||
purpose: TVerificationTokenPurpose;
|
||||
};
|
||||
|
||||
type TVerificationTokenOptions = SignOptions & {
|
||||
purpose?: TVerificationTokenPurpose;
|
||||
};
|
||||
|
||||
type TSsoRelinkIntentPayload = {
|
||||
callbackUrl: string;
|
||||
email: string;
|
||||
provider: string;
|
||||
providerAccountId: string;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
const DEFAULT_VERIFICATION_TOKEN_PURPOSE: TVerificationTokenPurpose = "email_verification";
|
||||
|
||||
const getVerificationTokenPurpose = (purpose: unknown): TVerificationTokenPurpose => {
|
||||
if (purpose && VERIFICATION_TOKEN_PURPOSES.includes(purpose as TVerificationTokenPurpose)) {
|
||||
return purpose as TVerificationTokenPurpose;
|
||||
}
|
||||
|
||||
return DEFAULT_VERIFICATION_TOKEN_PURPOSE;
|
||||
};
|
||||
|
||||
export const createToken = (userId: string, options: TVerificationTokenOptions = {}): string => {
|
||||
if (!NEXTAUTH_SECRET) {
|
||||
throw new Error("NEXTAUTH_SECRET is not set");
|
||||
}
|
||||
@@ -23,7 +55,9 @@ export const createToken = (userId: string, options = {}): string => {
|
||||
}
|
||||
|
||||
const encryptedUserId = symmetricEncrypt(userId, ENCRYPTION_KEY);
|
||||
return jwt.sign({ id: encryptedUserId }, NEXTAUTH_SECRET, options);
|
||||
const { purpose = DEFAULT_VERIFICATION_TOKEN_PURPOSE, ...jwtOptions } = options;
|
||||
|
||||
return jwt.sign({ id: encryptedUserId, purpose }, NEXTAUTH_SECRET, jwtOptions);
|
||||
};
|
||||
export const createTokenForLinkSurvey = (surveyId: string, userEmail: string): string => {
|
||||
if (!NEXTAUTH_SECRET) {
|
||||
@@ -224,7 +258,72 @@ const getUserEmailForLegacyVerification = async (
|
||||
return { userId: decryptedId, userEmail: foundUser.email };
|
||||
};
|
||||
|
||||
export const verifyToken = async (token: string): Promise<JwtPayload> => {
|
||||
const DEFAULT_SSO_RELINK_INTENT_OPTIONS: SignOptions = {
|
||||
expiresIn: "15m",
|
||||
};
|
||||
|
||||
export const createSsoRelinkIntent = (
|
||||
payload: TSsoRelinkIntentPayload,
|
||||
options: SignOptions = DEFAULT_SSO_RELINK_INTENT_OPTIONS
|
||||
): string => {
|
||||
if (!NEXTAUTH_SECRET) {
|
||||
throw new Error("NEXTAUTH_SECRET is not set");
|
||||
}
|
||||
|
||||
if (!ENCRYPTION_KEY) {
|
||||
throw new Error("ENCRYPTION_KEY is not set");
|
||||
}
|
||||
|
||||
return jwt.sign(
|
||||
{
|
||||
userId: symmetricEncrypt(payload.userId, ENCRYPTION_KEY),
|
||||
email: symmetricEncrypt(payload.email, ENCRYPTION_KEY),
|
||||
provider: payload.provider,
|
||||
providerAccountId: symmetricEncrypt(payload.providerAccountId, ENCRYPTION_KEY),
|
||||
callbackUrl: symmetricEncrypt(payload.callbackUrl, ENCRYPTION_KEY),
|
||||
},
|
||||
NEXTAUTH_SECRET,
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
export const verifySsoRelinkIntent = (token: string): TSsoRelinkIntentPayload => {
|
||||
if (!NEXTAUTH_SECRET) {
|
||||
throw new Error("NEXTAUTH_SECRET is not set");
|
||||
}
|
||||
|
||||
if (!ENCRYPTION_KEY) {
|
||||
throw new Error("ENCRYPTION_KEY is not set");
|
||||
}
|
||||
|
||||
const payload = jwt.verify(token, NEXTAUTH_SECRET, { algorithms: ["HS256"] }) as JwtPayload & {
|
||||
userId: string;
|
||||
email: string;
|
||||
provider: string;
|
||||
providerAccountId: string;
|
||||
callbackUrl: string;
|
||||
};
|
||||
|
||||
if (
|
||||
!payload?.userId ||
|
||||
!payload?.email ||
|
||||
!payload?.provider ||
|
||||
!payload?.providerAccountId ||
|
||||
!payload?.callbackUrl
|
||||
) {
|
||||
throw new Error("Token is invalid or missing required fields");
|
||||
}
|
||||
|
||||
return {
|
||||
userId: decryptWithFallback(payload.userId, ENCRYPTION_KEY),
|
||||
email: decryptWithFallback(payload.email, ENCRYPTION_KEY),
|
||||
provider: payload.provider,
|
||||
providerAccountId: decryptWithFallback(payload.providerAccountId, ENCRYPTION_KEY),
|
||||
callbackUrl: decryptWithFallback(payload.callbackUrl, ENCRYPTION_KEY),
|
||||
};
|
||||
};
|
||||
|
||||
export const verifyToken = async (token: string): Promise<TVerifyTokenPayload> => {
|
||||
if (!NEXTAUTH_SECRET) {
|
||||
throw new Error("NEXTAUTH_SECRET is not set");
|
||||
}
|
||||
@@ -263,7 +362,11 @@ export const verifyToken = async (token: string): Promise<JwtPayload> => {
|
||||
// Get user email if we don't have it yet
|
||||
userData ??= await getUserEmailForLegacyVerification(token, payload.id);
|
||||
|
||||
return { id: userData.userId, email: userData.userEmail };
|
||||
return {
|
||||
id: userData.userId,
|
||||
email: userData.userEmail,
|
||||
purpose: getVerificationTokenPurpose(payload.purpose),
|
||||
};
|
||||
};
|
||||
|
||||
export const verifyInviteToken = (token: string): { inviteId: string; email: string } => {
|
||||
|
||||
@@ -63,7 +63,7 @@ const mapOrganizationBilling = (billing: TOrganizationWithBilling["billing"]): T
|
||||
stripeCustomerId: billing.stripeCustomerId,
|
||||
limits: billing.limits,
|
||||
usageCycleAnchor: billing.usageCycleAnchor,
|
||||
...(billing.stripe === undefined ? {} : { stripe: billing.stripe }),
|
||||
...(billing.stripe == null ? {} : { stripe: billing.stripe }),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import structuredClonePolyfill from "@ungap/structured-clone";
|
||||
|
||||
const structuredCloneExport =
|
||||
typeof structuredClone === "undefined" ? structuredClonePolyfill : structuredClone;
|
||||
const structuredCloneExport = globalThis.structuredClone;
|
||||
|
||||
export { structuredCloneExport as structuredClone };
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getFeatureFlag: vi.fn(),
|
||||
posthog: {
|
||||
__loaded: false,
|
||||
getFeatureFlag: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("posthog-js", () => ({
|
||||
default: mocks.posthog,
|
||||
}));
|
||||
|
||||
describe("getPostHogClientFeatureFlag", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.posthog.__loaded = false;
|
||||
mocks.posthog.getFeatureFlag = mocks.getFeatureFlag;
|
||||
});
|
||||
|
||||
test("returns false before PostHog is initialized", async () => {
|
||||
const { getPostHogClientFeatureFlag } = await import("./client");
|
||||
|
||||
expect(getPostHogClientFeatureFlag("test-flag")).toBe(false);
|
||||
expect(mocks.getFeatureFlag).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns true from posthog.getFeatureFlag", async () => {
|
||||
mocks.posthog.__loaded = true;
|
||||
mocks.getFeatureFlag.mockReturnValue(true);
|
||||
|
||||
const { getPostHogClientFeatureFlag } = await import("./client");
|
||||
|
||||
expect(getPostHogClientFeatureFlag("test-flag")).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false from posthog.getFeatureFlag", async () => {
|
||||
mocks.posthog.__loaded = true;
|
||||
mocks.getFeatureFlag.mockReturnValue(false);
|
||||
|
||||
const { getPostHogClientFeatureFlag } = await import("./client");
|
||||
|
||||
expect(getPostHogClientFeatureFlag("test-flag")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns variant string from posthog.getFeatureFlag", async () => {
|
||||
mocks.posthog.__loaded = true;
|
||||
mocks.getFeatureFlag.mockReturnValue("variant-a");
|
||||
|
||||
const { getPostHogClientFeatureFlag } = await import("./client");
|
||||
|
||||
expect(getPostHogClientFeatureFlag("test-flag")).toBe("variant-a");
|
||||
});
|
||||
|
||||
test("coerces undefined to false", async () => {
|
||||
mocks.posthog.__loaded = true;
|
||||
mocks.getFeatureFlag.mockReturnValue(undefined);
|
||||
|
||||
const { getPostHogClientFeatureFlag } = await import("./client");
|
||||
|
||||
expect(getPostHogClientFeatureFlag("test-flag")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import posthog from "posthog-js";
|
||||
import type { TPostHogFeatureFlagValue } from "./types";
|
||||
|
||||
export const getPostHogClientFeatureFlag = (flagKey: string): TPostHogFeatureFlagValue => {
|
||||
if (!posthog.__loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const featureFlagValue = posthog.getFeatureFlag(flagKey);
|
||||
return featureFlagValue ?? false;
|
||||
};
|
||||
|
||||
export type { TPostHogFeatureFlagContext, TPostHogFeatureFlagValue } from "./types";
|
||||
@@ -0,0 +1,131 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getFeatureFlag: vi.fn(),
|
||||
loggerWarn: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("getPostHogFeatureFlag", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test("returns false when PostHog is not configured", async () => {
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: undefined }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: { getFeatureFlag: mocks.getFeatureFlag },
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(getPostHogFeatureFlag("user123", "test-flag")).resolves.toBe(false);
|
||||
expect(mocks.getFeatureFlag).not.toHaveBeenCalled();
|
||||
expect(mocks.loggerWarn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false when posthogServerClient is null", async () => {
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: "phc_test_key" }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: null,
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(getPostHogFeatureFlag("user123", "test-flag")).resolves.toBe(false);
|
||||
expect(mocks.getFeatureFlag).not.toHaveBeenCalled();
|
||||
expect(mocks.loggerWarn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("forwards distinctId, flagKey, and mapped groups to PostHog", async () => {
|
||||
mocks.getFeatureFlag.mockResolvedValue(true);
|
||||
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: "phc_test_key" }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: { getFeatureFlag: mocks.getFeatureFlag },
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(
|
||||
getPostHogFeatureFlag("user123", "experiment-flag", {
|
||||
organizationId: "org_123",
|
||||
workspaceId: "ws_456",
|
||||
})
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(mocks.getFeatureFlag).toHaveBeenCalledWith("experiment-flag", "user123", {
|
||||
groups: {
|
||||
organization: "org_123",
|
||||
workspace: "ws_456",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("preserves variant string responses", async () => {
|
||||
mocks.getFeatureFlag.mockResolvedValue("variant-a");
|
||||
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: "phc_test_key" }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: { getFeatureFlag: mocks.getFeatureFlag },
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(getPostHogFeatureFlag("user123", "experiment-flag")).resolves.toBe("variant-a");
|
||||
});
|
||||
|
||||
test("coerces undefined to false", async () => {
|
||||
mocks.getFeatureFlag.mockResolvedValue(undefined);
|
||||
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: "phc_test_key" }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: { getFeatureFlag: mocks.getFeatureFlag },
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(getPostHogFeatureFlag("user123", "experiment-flag")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test("logs and returns false when PostHog throws", async () => {
|
||||
mocks.getFeatureFlag.mockRejectedValue(new Error("network error"));
|
||||
|
||||
vi.doMock("server-only", () => ({}));
|
||||
vi.doMock("@formbricks/logger", () => ({
|
||||
logger: { warn: mocks.loggerWarn },
|
||||
}));
|
||||
vi.doMock("@/lib/constants", () => ({ POSTHOG_KEY: "phc_test_key" }));
|
||||
vi.doMock("./server", () => ({
|
||||
posthogServerClient: { getFeatureFlag: mocks.getFeatureFlag },
|
||||
}));
|
||||
|
||||
const { getPostHogFeatureFlag } = await import("./get-feature-flag");
|
||||
|
||||
await expect(getPostHogFeatureFlag("user123", "experiment-flag")).resolves.toBe(false);
|
||||
expect(mocks.loggerWarn).toHaveBeenCalledWith(
|
||||
{ error: expect.any(Error), flagKey: "experiment-flag" },
|
||||
"Failed to evaluate PostHog feature flag"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import "server-only";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { POSTHOG_KEY } from "@/lib/constants";
|
||||
import { posthogServerClient } from "./server";
|
||||
import type { TPostHogFeatureFlagContext, TPostHogFeatureFlagValue } from "./types";
|
||||
|
||||
const buildPostHogGroups = (context?: TPostHogFeatureFlagContext): Record<string, string> | undefined => {
|
||||
const groups = {
|
||||
...(context?.organizationId ? { organization: context.organizationId } : {}),
|
||||
...(context?.workspaceId ? { workspace: context.workspaceId } : {}),
|
||||
};
|
||||
|
||||
return Object.keys(groups).length > 0 ? groups : undefined;
|
||||
};
|
||||
|
||||
export const getPostHogFeatureFlag = async (
|
||||
distinctId: string,
|
||||
flagKey: string,
|
||||
context?: TPostHogFeatureFlagContext
|
||||
): Promise<TPostHogFeatureFlagValue> => {
|
||||
if (!POSTHOG_KEY || !posthogServerClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const featureFlagValue = await posthogServerClient.getFeatureFlag(flagKey, distinctId, {
|
||||
groups: buildPostHogGroups(context),
|
||||
});
|
||||
|
||||
return featureFlagValue ?? false;
|
||||
} catch (error) {
|
||||
logger.warn({ error, flagKey }, "Failed to evaluate PostHog feature flag");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
export { capturePostHogEvent } from "./capture";
|
||||
export { getPostHogFeatureFlag } from "./get-feature-flag";
|
||||
export type { TPostHogFeatureFlagContext, TPostHogFeatureFlagValue } from "./types";
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type TPostHogFeatureFlagValue = boolean | string;
|
||||
|
||||
export type TPostHogFeatureFlagContext = {
|
||||
organizationId?: string;
|
||||
workspaceId?: string;
|
||||
};
|
||||
@@ -190,6 +190,14 @@ const mockWelcomeCard: TSurveyWelcomeCard = {
|
||||
showResponseCount: false,
|
||||
};
|
||||
|
||||
const mockBlocks = [
|
||||
{
|
||||
id: "block1",
|
||||
name: "Block 1",
|
||||
elements: [mockQuestion],
|
||||
},
|
||||
];
|
||||
|
||||
const baseSurveyProperties = {
|
||||
id: mockId,
|
||||
name: "Mock Survey",
|
||||
@@ -201,14 +209,9 @@ const baseSurveyProperties = {
|
||||
displayLimit: 3,
|
||||
welcomeCard: mockWelcomeCard,
|
||||
questions: [],
|
||||
blocks: [
|
||||
{
|
||||
id: "block1",
|
||||
name: "Block 1",
|
||||
elements: [mockQuestion],
|
||||
},
|
||||
],
|
||||
blocks: mockBlocks as unknown as SurveyMock["blocks"],
|
||||
isBackButtonHidden: false,
|
||||
isAutoProgressingEnabled: false,
|
||||
isCaptureIpEnabled: false,
|
||||
endings: [
|
||||
{
|
||||
@@ -303,6 +306,7 @@ export const createSurveyInput: TSurveyCreateInput = {
|
||||
displayOption: "respondMultiple",
|
||||
triggers: [{ actionClass: mockActionClass }],
|
||||
...baseSurveyProperties,
|
||||
blocks: mockBlocks,
|
||||
};
|
||||
|
||||
export const updateSurveyInput: TSurvey = {
|
||||
@@ -325,6 +329,7 @@ export const updateSurveyInput: TSurvey = {
|
||||
followUps: [],
|
||||
...baseSurveyProperties,
|
||||
...commonMockProperties,
|
||||
blocks: mockBlocks,
|
||||
slug: null,
|
||||
customHeadScripts: null,
|
||||
customHeadScriptsMode: null,
|
||||
|
||||
@@ -5,7 +5,8 @@ import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId, ZOptionalNumber } from "@formbricks/types/common";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TSegment, ZSegmentFilters } from "@formbricks/types/segment";
|
||||
import { ZSegmentFilters } from "@formbricks/types/segment";
|
||||
import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
|
||||
import { TSurvey, TSurveyCreateInput, ZSurvey, ZSurveyCreateInput } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
getOrganizationByEnvironmentId,
|
||||
@@ -48,6 +49,7 @@ export const selectSurvey = {
|
||||
isVerifyEmailEnabled: true,
|
||||
isSingleResponsePerEmailEnabled: true,
|
||||
isBackButtonHidden: true,
|
||||
isAutoProgressingEnabled: true,
|
||||
isCaptureIpEnabled: true,
|
||||
redirectUrl: true,
|
||||
projectOverwrites: true,
|
||||
@@ -557,22 +559,7 @@ export const updateSurveyInternal = async (
|
||||
select: selectSurvey,
|
||||
});
|
||||
|
||||
let surveySegment: TSegment | null = null;
|
||||
if (prismaSurvey.segment) {
|
||||
surveySegment = {
|
||||
...prismaSurvey.segment,
|
||||
surveys: prismaSurvey.segment.surveys.map((survey) => survey.id),
|
||||
};
|
||||
}
|
||||
|
||||
const modifiedSurvey: TSurvey = {
|
||||
...prismaSurvey, // Properties from prismaSurvey
|
||||
displayPercentage: Number(prismaSurvey.displayPercentage) || null,
|
||||
segment: surveySegment,
|
||||
customHeadScriptsMode: prismaSurvey.customHeadScriptsMode,
|
||||
};
|
||||
|
||||
return modifiedSurvey;
|
||||
return transformPrismaSurvey<TSurvey>(prismaSurvey);
|
||||
} catch (error) {
|
||||
logger.error(error, "Error updating survey");
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -647,8 +634,8 @@ export const createSurvey = async (
|
||||
}
|
||||
|
||||
// Validate and prepare blocks for persistence
|
||||
if (data.blocks && data.blocks.length > 0) {
|
||||
data.blocks = validateMediaAndPrepareBlocks(data.blocks);
|
||||
if (Array.isArray(data.blocks) && data.blocks.length > 0) {
|
||||
data.blocks = validateMediaAndPrepareBlocks(data.blocks as unknown as TSurveyBlock[]);
|
||||
}
|
||||
|
||||
const survey = await prisma.survey.create({
|
||||
@@ -772,21 +759,7 @@ export const loadNewSegmentInSurvey = async (surveyId: string, newSegmentId: str
|
||||
});
|
||||
}
|
||||
|
||||
let surveySegment: TSegment | null = null;
|
||||
if (prismaSurvey.segment) {
|
||||
surveySegment = {
|
||||
...prismaSurvey.segment,
|
||||
surveys: prismaSurvey.segment.surveys.map((survey) => survey.id),
|
||||
};
|
||||
}
|
||||
|
||||
const modifiedSurvey = {
|
||||
...prismaSurvey,
|
||||
segment: surveySegment,
|
||||
customHeadScriptsMode: prismaSurvey.customHeadScriptsMode,
|
||||
};
|
||||
|
||||
return modifiedSurvey as TSurvey;
|
||||
return transformPrismaSurvey<TSurvey>(prismaSurvey);
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type Locale, formatDistance } from "date-fns";
|
||||
import { de, enUS, es, fr, hu, ja, nl, pt, ptBR, ro, ru, sv, zhCN, zhTW } from "date-fns/locale";
|
||||
import { de, enUS, es, fr, hu, ja, nl, pt, ptBR, ro, ru, sv, tr, zhCN, zhTW } from "date-fns/locale";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { formatDateForDisplay } from "./utils/datetime";
|
||||
|
||||
@@ -17,6 +17,7 @@ const TIME_SINCE_LOCALES: Record<TUserLocale, Locale> = {
|
||||
"ro-RO": ro,
|
||||
"ru-RU": ru,
|
||||
"sv-SE": sv,
|
||||
"tr-TR": tr,
|
||||
"zh-Hans-CN": zhCN,
|
||||
"zh-Hant-TW": zhTW,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export type DebouncedFunction<T extends (...args: any[]) => void> = ((...args: Parameters<T>) => void) & {
|
||||
cancel: () => void;
|
||||
};
|
||||
|
||||
export const debounce = <T extends (...args: any[]) => void>(
|
||||
callback: T,
|
||||
delay: number
|
||||
): DebouncedFunction<T> => {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const debounced = ((...args: Parameters<T>) => {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => callback(...args), delay);
|
||||
}) as DebouncedFunction<T>;
|
||||
|
||||
debounced.cancel = () => {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return debounced;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
export const capitalize = (value: string): string => {
|
||||
if (!value) return "";
|
||||
|
||||
return `${value.charAt(0).toUpperCase()}${value.slice(1).toLowerCase()}`;
|
||||
};
|
||||
|
||||
export const isDeepEqual = (left: unknown, right: unknown): boolean => {
|
||||
if (Object.is(left, right)) return true;
|
||||
|
||||
if (left instanceof Date && right instanceof Date) {
|
||||
return left.getTime() === right.getTime();
|
||||
}
|
||||
|
||||
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(left) || Array.isArray(right)) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
||||
|
||||
return left.every((item, index) => isDeepEqual(item, right[index]));
|
||||
}
|
||||
|
||||
const leftRecord = left as Record<string, unknown>;
|
||||
const rightRecord = right as Record<string, unknown>;
|
||||
const leftKeys = Object.keys(leftRecord);
|
||||
const rightKeys = Object.keys(rightRecord);
|
||||
|
||||
if (leftKeys.length !== rightKeys.length) return false;
|
||||
|
||||
return leftKeys.every(
|
||||
(key) =>
|
||||
Object.prototype.hasOwnProperty.call(rightRecord, key) && isDeepEqual(leftRecord[key], rightRecord[key])
|
||||
);
|
||||
};
|
||||
@@ -54,7 +54,7 @@ export const findRecallInfoById = (text: string, id: string): string | null => {
|
||||
return match ? match[0] : null;
|
||||
};
|
||||
|
||||
const getRecallItemLabel = <T extends TSurvey>(
|
||||
export const getRecallItemLabel = <T extends TSurvey>(
|
||||
recallItemId: string,
|
||||
survey: T,
|
||||
languageCode: string
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { isSafeIdentifier } from "./safe-identifier";
|
||||
import { isSafeIdentifier, toSafeIdentifier } from "./safe-identifier";
|
||||
|
||||
describe("safe-identifier", () => {
|
||||
describe("isSafeIdentifier", () => {
|
||||
@@ -32,4 +32,23 @@ describe("safe-identifier", () => {
|
||||
expect(isSafeIdentifier("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toSafeIdentifier", () => {
|
||||
test("normalizes free-form labels into safe identifiers", () => {
|
||||
expect(toSafeIdentifier("Date of Birth")).toBe("date_of_birth");
|
||||
expect(toSafeIdentifier("Customer-ID")).toBe("customer_id");
|
||||
expect(toSafeIdentifier(" Preferred Language ")).toBe("preferred_language");
|
||||
expect(toSafeIdentifier("city__name")).toBe("city_name");
|
||||
});
|
||||
|
||||
test("strips invalid leading characters until first lowercase letter", () => {
|
||||
expect(toSafeIdentifier("123 Date")).toBe("date");
|
||||
expect(toSafeIdentifier("__name")).toBe("name");
|
||||
expect(toSafeIdentifier("99")).toBe("");
|
||||
});
|
||||
|
||||
test("keeps already safe identifiers unchanged", () => {
|
||||
expect(toSafeIdentifier("country_code")).toBe("country_code");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,44 @@ export const isSafeIdentifier = (value: string): boolean => {
|
||||
return /^[a-z0-9_]+$/.test(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a free-form string to a safe identifier candidate.
|
||||
* The output only contains lowercase letters, numbers, and underscores.
|
||||
* It also ensures the identifier starts with a lowercase letter by stripping invalid leading chars.
|
||||
*/
|
||||
export const toSafeIdentifier = (value: string): string => {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
let safeIdentifier = "";
|
||||
let shouldInsertUnderscore = false;
|
||||
|
||||
for (const char of normalized) {
|
||||
const isLowercaseLetter = char >= "a" && char <= "z";
|
||||
const isDigit = char >= "0" && char <= "9";
|
||||
|
||||
if (isLowercaseLetter || isDigit) {
|
||||
if (shouldInsertUnderscore && safeIdentifier.length > 0) {
|
||||
safeIdentifier += "_";
|
||||
}
|
||||
safeIdentifier += char;
|
||||
shouldInsertUnderscore = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (safeIdentifier.length > 0) {
|
||||
shouldInsertUnderscore = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < safeIdentifier.length; i++) {
|
||||
const char = safeIdentifier[i];
|
||||
if (char >= "a" && char <= "z") {
|
||||
return safeIdentifier.slice(i);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a snake_case string to Title Case for display as a label.
|
||||
* Example: "job_description" -> "Job Description"
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Zentriertes Modalfenster",
|
||||
"change_organization": "Organisation wechseln",
|
||||
"change_workspace": "Workspace wechseln",
|
||||
"choice_n": "Auswahl {{n}}",
|
||||
"choices": "Entscheidungen",
|
||||
"choose_environment": "Umgebung auswählen",
|
||||
"choose_organization": "Organisation auswählen",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Schließen",
|
||||
"code": "Code",
|
||||
"collapse_rows": "Zeilen einklappen",
|
||||
"column_n": "Spalte {{n}}",
|
||||
"completed": "Abgeschlossen",
|
||||
"configuration": "Konfiguration",
|
||||
"confirm": "Bestätigen",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Erstellt von",
|
||||
"customer_success": "Kundenerfolg",
|
||||
"dark_overlay": "Dunkle Überlagerung",
|
||||
"data_refreshed_successfully": "Daten erfolgreich aktualisiert",
|
||||
"date": "Datum",
|
||||
"days": "Tage",
|
||||
"default": "Standard",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Fehler beim Kopieren in die Zwischenablage",
|
||||
"failed_to_load_organizations": "Fehler beim Laden der Organisationen",
|
||||
"failed_to_load_workspaces": "Projekte konnten nicht geladen werden",
|
||||
"field_placeholder": "{{field}}-Platzhalter",
|
||||
"filter": "Filter",
|
||||
"finish": "Fertigstellen",
|
||||
"first_name": "Vorname",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Generieren",
|
||||
"go_back": "Geh zurück",
|
||||
"go_to_dashboard": "Zum Dashboard gehen",
|
||||
"headline": "Überschrift",
|
||||
"hidden": "Versteckt",
|
||||
"hidden_field": "Verstecktes Feld",
|
||||
"hidden_fields": "Versteckte Felder",
|
||||
"hide_column": "Spalte ausblenden",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Bild",
|
||||
"images": "Bilder",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "Monate",
|
||||
"move_down": "Nach unten bewegen",
|
||||
"move_up": "Nach oben bewegen",
|
||||
"multiple_languages": "Mehrsprachigkeit",
|
||||
"my_product": "mein Produkt",
|
||||
"name": "Name",
|
||||
"new": "Neu",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Kein Ergebnis gefunden",
|
||||
"no_results": "Keine Ergebnisse",
|
||||
"no_surveys_found": "Keine Umfragen gefunden.",
|
||||
"no_text_found": "Kein Text gefunden",
|
||||
"none_of_the_above": "Keine der oben genannten Optionen",
|
||||
"not_authenticated": "Du bist nicht authentifiziert, um diese Aktion durchzuführen.",
|
||||
"not_authorized": "Nicht berechtigt",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Organisationseinstellungen",
|
||||
"other": "Andere",
|
||||
"other_filters": "Weitere Filter",
|
||||
"others": "Andere",
|
||||
"other_placeholder": "Sonstiger Platzhalter",
|
||||
"overlay_color": "Overlay-Farbe",
|
||||
"overview": "Überblick",
|
||||
"password": "Passwort",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Bitte aktualisieren Sie Ihren Plan",
|
||||
"powered_by_formbricks": "Bereitgestellt von Formbricks",
|
||||
"preview": "Vorschau",
|
||||
"preview_survey": "Umfragevorschau",
|
||||
"privacy": "Datenschutz",
|
||||
"product_manager": "Produktmanager",
|
||||
"production": "Produktion",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Begrenze die Anzahl der Antworten, die du von Teilnehmern erhältst, die bestimmte Kriterien erfüllen.",
|
||||
"read_docs": "Dokumentation lesen",
|
||||
"recipients": "Empfänger",
|
||||
"refresh": "Aktualisieren",
|
||||
"remove": "Entfernen",
|
||||
"remove_from_team": "Aus Team entfernen",
|
||||
"reorder_and_hide_columns": "Spalten neu anordnen und ausblenden",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Antworten",
|
||||
"restart": "Neustart",
|
||||
"role": "Rolle",
|
||||
"row_n": "Zeile {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Vertrieb",
|
||||
"save": "Speichern",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Dateispeicher nicht eingerichtet, Uploads werden wahrscheinlich fehlschlagen",
|
||||
"string": "Text",
|
||||
"styling": "Styling",
|
||||
"subheader": "Unterüberschrift",
|
||||
"submit": "Abschicken",
|
||||
"summary": "Zusammenfassung",
|
||||
"survey": "Umfrage",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "z. B. Formbricks",
|
||||
"workspaces": "Projekte",
|
||||
"years": "Jahre",
|
||||
"you": "Du",
|
||||
"you_are_downgraded_to_the_community_edition": "Du wurdest auf die Community Edition herabgestuft.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Du bist nicht berechtigt, diese Aktion durchzuführen.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Sie haben Ihr Limit von {projectLimit} Workspaces erreicht.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Neues Attribut “{key}” mit Typ “{dataType}” erstellt",
|
||||
"attributes_msg_userid_already_exists": "Die Benutzer-ID existiert bereits für diese Umgebung und wurde nicht aktualisiert.",
|
||||
"contact_deleted_successfully": "Kontakt erfolgreich gelöscht",
|
||||
"contacts_table_refresh": "Kontakte aktualisieren",
|
||||
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
|
||||
"create_attribute": "Attribut erstellen",
|
||||
"create_new_attribute": "Neues Attribut erstellen",
|
||||
"create_new_attribute_description": "Erstellen Sie ein neues Attribut für Segmentierungszwecke.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Dein Einladungslink für die Organisation ist fertig!",
|
||||
"organization_name": "Organisationsname",
|
||||
"organization_name_description": "Gib deiner Organisation einen Namen.",
|
||||
"organization_name_placeholder": "z. B. Powerpuff Girls",
|
||||
"organization_name_placeholder": "z. B. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Organisationsname erfolgreich aktualisiert",
|
||||
"organization_settings": "Organisationseinstellungen",
|
||||
"please_add_a_logo": "Bitte füge ein Logo hinzu",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Alles klar! Zeit, deine erste Umfrage zu erstellen",
|
||||
"alphabetical": "alphabetisch",
|
||||
"copy_survey": "Umfrage kopieren",
|
||||
"copy_survey_description": "Kopiere diese Umfrage in eine andere Umgebung",
|
||||
"copy_survey_error": "Kopieren der Umfrage fehlgeschlagen",
|
||||
"copy_survey_link_to_clipboard": "Umfragelink in die Zwischenablage kopieren",
|
||||
"copy_survey_partially_success": "{success} Umfragen erfolgreich kopiert, {error} fehlgeschlagen.",
|
||||
"copy_survey_success": "Umfrage erfolgreich kopiert!",
|
||||
"delete_survey_and_responses_warning": "Bist Du sicher, dass Du diese Umfrage und alle ihre Antworten löschen möchtest?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Wähle die Standardsprache für diese Umfrage:",
|
||||
"2_activate_translation_for_specific_languages": "2. Übersetzung für bestimmte Sprachen aktivieren:",
|
||||
"activate_translations": "Übersetzungen aktivieren",
|
||||
"add": "+ hinzufügen",
|
||||
"add_a_delay_or_auto_close_the_survey": "Füge eine Verzögerung hinzu oder schließe die Umfrage automatisch.",
|
||||
"add_a_four_digit_pin": "Füge eine vierstellige PIN hinzu",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Zuweisen =",
|
||||
"audience": "Publikum",
|
||||
"auto_close_on_inactivity": "Automatisches Schließen bei Inaktivität",
|
||||
"auto_progress_rating_and_nps": "Bewertungs- und NPS-Fragen automatisch fortsetzen",
|
||||
"auto_progress_rating_and_nps_description": "Automatisches Weitergehen bei Einzelfragen-Blöcken. Pflichtfragen blenden Weiter aus, außer wenn \"Sonstiges\" ausgewählt ist.",
|
||||
"auto_save_disabled": "Automatisches Speichern deaktiviert",
|
||||
"auto_save_disabled_tooltip": "Ihre Umfrage wird nur im Entwurfsmodus automatisch gespeichert. So wird sichergestellt, dass öffentliche Umfragen nicht unbeabsichtigt aktualisiert werden.",
|
||||
"auto_save_on": "Automatisches Speichern an",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Änderungen werden zu Inkonsistenzen führen",
|
||||
"change_anyway": "Trotzdem ändern",
|
||||
"change_background": "Hintergrund ändern",
|
||||
"change_default": "Standard ändern",
|
||||
"change_question_type": "Fragetyp ändern",
|
||||
"change_survey_type": "Die Änderung des Umfragetypen kann vorhandenen Zugriff beeinträchtigen",
|
||||
"change_the_background_to_a_color_image_or_animation": "Hintergrund zu einer Farbe, einem Bild oder einer Animation ändern.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Wähle, wo die Umfrage durchgeführt werden soll.",
|
||||
"city": "Stadt",
|
||||
"close_survey_on_response_limit": "Umfrage bei Erreichen des Antwortlimits schließen",
|
||||
"code": "Code",
|
||||
"color": "Farbe",
|
||||
"column_used_in_logic_error": "Diese Spalte wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.",
|
||||
"columns": "Spalten",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Umfragelogo anpassen",
|
||||
"darken_or_lighten_background_of_your_choice": "Hintergrund deiner Wahl abdunkeln oder aufhellen.",
|
||||
"days_before_showing_this_survey_again": "oder mehr Tage müssen zwischen der zuletzt angezeigten Umfrage und der Anzeige dieser Umfrage vergehen.",
|
||||
"default_language": "Standardsprache",
|
||||
"delete_anyways": "Trotzdem löschen",
|
||||
"delete_block": "Block löschen",
|
||||
"delete_choice": "Auswahl löschen",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Frage duplizieren",
|
||||
"edit_link": "Bearbeitungslink",
|
||||
"edit_recall": "Erinnerung bearbeiten",
|
||||
"edit_translations": "{lang} -Übersetzungen bearbeiten",
|
||||
"element_not_found": "Frage nicht gefunden",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Befragten erlauben, die Sprache jederzeit zu wechseln. Benötigt mind. 2 aktive Sprachen.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Spamschutz verwendet reCAPTCHA v3, um Spam-Antworten herauszufiltern.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Ermöglichen Sie den Befragten, längere Antworten über mehrere Zeilen zu schreiben.",
|
||||
"lower_label": "Unteres Label",
|
||||
"manage_languages": "Sprachen verwalten",
|
||||
"manage_translations": "Übersetzungen verwalten",
|
||||
"matrix_all_fields": "Alle Felder",
|
||||
"matrix_rows": "Zeilen",
|
||||
"max_file_size": "Maximale Dateigröße",
|
||||
"max_file_size_limit_is": "Die maximale Dateigrößenbeschränkung beträgt",
|
||||
"missing_first": "Fehlende zuerst",
|
||||
"move_question_to_block": "Frage in Block verschieben",
|
||||
"multiply": "Multiplizieren *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Benötigt für eine selbstgehostete Cal.com-Instanz",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Beschriftung der Schaltfläche \"Weiter\"",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Noch keine versteckten Felder. Füge das erste unten hinzu.",
|
||||
"no_images_found_for": "Keine Bilder gefunden für ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Keine Sprachen gefunden. Füge die erste hinzu, um loszulegen.",
|
||||
"no_languages_found_add_first_one_to_get_started": "In diesem Workspace wurden keine Umfragesprachen gefunden. Füge bitte eine hinzu, um loszulegen.",
|
||||
"no_option_found": "Keine Option gefunden",
|
||||
"no_recall_items_found": "Keine Recall-Elemente gefunden",
|
||||
"no_variables_yet_add_first_one_below": "Noch keine Variablen. Füge die erste hinzu.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Bitte geben Sie eine gültige URL ein (z. B. https://beispiel.de)",
|
||||
"please_set_a_survey_trigger": "Bitte richte einen Umfrage-Trigger ein",
|
||||
"please_specify": "Bitte angeben",
|
||||
"present_your_survey_in_multiple_languages": "Präsentiere deine Umfrage in mehreren Sprachen",
|
||||
"prevent_double_submission": "Doppeltes Anbschicken verhindern",
|
||||
"prevent_double_submission_description": "Nur eine Antwort pro E-Mail-Adresse zulassen (beta)",
|
||||
"progress_saved": "Fortschritt gespeichert",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 Punkte",
|
||||
"show_block_settings": "Block-Einstellungen anzeigen",
|
||||
"show_button": "Button anzeigen",
|
||||
"show_in_order": "In Reihenfolge anzeigen",
|
||||
"show_language_switch": "Sprachwechsel anzeigen",
|
||||
"show_multiple_times": "Begrenzte Anzahl von Malen anzeigen",
|
||||
"show_only_once": "Nur einmal anzeigen",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Umfragevorschau 👀",
|
||||
"survey_styling": "Umfrage Styling",
|
||||
"survey_trigger": "Auslöser der Umfrage",
|
||||
"switch_multi_language_on_to_get_started": "Aktiviere Mehrsprachigkeit, um loszulegen 👉",
|
||||
"target_block_not_found": "Zielblock nicht gefunden",
|
||||
"targeted": "Gezielt",
|
||||
"ten_points": "10 Punkte",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Einmal anzeigen, auch wenn sie nicht antworten.",
|
||||
"then": "dann",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Diese Aktion entfernt alle Übersetzungen aus dieser Umfrage.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Dies entfernt diese Sprache und alle zugehörigen Übersetzungen aus dieser Umfrage. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"three_points": "3 Punkte",
|
||||
"times": "Zeiten",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Um die Platzierung über alle Umfragen hinweg konsistent zu halten, kannst du",
|
||||
"translated": "Übersetzt",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Umfrage auslösen, wenn eine der Aktionen ausgeführt wird...",
|
||||
"try_lollipop_or_mountain": "Versuch 'Lolli' oder 'Berge'...",
|
||||
"type_field_id": "Feld-ID eingeben",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Lass nur Leute mit einer echten E-Mail antworten.",
|
||||
"visibility_and_recontact": "Sichtbarkeit & erneute Kontaktaufnahme",
|
||||
"visibility_and_recontact_description": "Steuern Sie, wann diese Umfrage erscheinen kann und wie oft sie erneut erscheinen kann.",
|
||||
"visible": "Sichtbar",
|
||||
"wait": "Warte",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Warte ein paar Sekunden nach dem Auslöser, bevor Du die Umfrage anzeigst",
|
||||
"waiting_time_across_surveys": "Abkühlphase (umfrageübergreifend)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "QR-Code wird heruntergeladen",
|
||||
"drop_offs": "Drop-Off Rate",
|
||||
"drop_offs_tooltip": "So oft wurde die Umfrage gestartet, aber nicht abgeschlossen.",
|
||||
"failed_to_copy_link": "Kopieren des Links fehlgeschlagen",
|
||||
"filter_added_successfully": "Filter erfolgreich hinzugefügt",
|
||||
"filter_updated_successfully": "Filter erfolgreich aktualisiert",
|
||||
"filtered_responses_csv": "Gefilterte Antworten (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Umfrage erfolgreich gelöscht",
|
||||
"survey_duplicated_successfully": "Umfrage erfolgreich dupliziert",
|
||||
"survey_duplication_error": "Duplizieren der Umfrage fehlgeschlagen",
|
||||
"templates": {
|
||||
"all_channels": "Alle Kanäle",
|
||||
"all_industries": "Alle Branchen",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Doppelte Sprache oder Sprach-ID",
|
||||
"edit_languages": "Sprachen bearbeiten",
|
||||
"identifier": "Kennung (ISO)",
|
||||
"incomplete_translations": "Unvollständige Übersetzungen",
|
||||
"language": "Sprache",
|
||||
"language_deleted_successfully": "Sprache erfolgreich gelöscht",
|
||||
"languages_updated_successfully": "Sprachen erfolgreich aktualisiert",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Bitte wähle eine Sprache aus",
|
||||
"remove_language": "Sprache entfernen",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Bitte entferne die Sprache aus diesen Umfragen, um sie aus dem Workspace zu entfernen.",
|
||||
"search_items": "Elemente suchen",
|
||||
"translate": "Übersetzen"
|
||||
"search_items": "Elemente suchen"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Hintergrundfarbe hinzufügen",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Färbt den nicht ausgefüllten Teil des Balkens.",
|
||||
"advanced_styling_field_track_height": "Track-Höhe",
|
||||
"advanced_styling_field_track_height_description": "Steuert die Dicke des Fortschrittsbalkens.",
|
||||
"advanced_styling_field_upper_label_color": "Farbe des oberen Labels",
|
||||
"advanced_styling_field_upper_label_color_description": "Färbt die kleine Beschriftung über Eingabefeldern.",
|
||||
"advanced_styling_field_upper_label_size": "Schriftgröße des oberen Labels",
|
||||
"advanced_styling_field_upper_label_size_description": "Skaliert die kleine Beschriftung über Eingabefeldern.",
|
||||
"advanced_styling_field_upper_label_weight": "Schriftstärke des oberen Labels",
|
||||
"advanced_styling_field_upper_label_weight_description": "Macht die Beschriftung leichter oder fetter.",
|
||||
"advanced_styling_field_upper_label_color": "Labelfarbe",
|
||||
"advanced_styling_field_upper_label_color_description": "Färbt die kleinen Labels über Eingabefeldern und Skalenbeschriftungen ein.",
|
||||
"advanced_styling_field_upper_label_size": "Label-Schriftgröße",
|
||||
"advanced_styling_field_upper_label_size_description": "Skaliert die kleinen Labels über Eingabefeldern und Skalenbeschriftungen.",
|
||||
"advanced_styling_field_upper_label_weight": "Label-Schriftstärke",
|
||||
"advanced_styling_field_upper_label_weight_description": "Macht die Labels dünner oder fetter.",
|
||||
"advanced_styling_section_buttons": "Buttons",
|
||||
"advanced_styling_section_headlines": "Überschriften & Beschreibungen",
|
||||
"advanced_styling_section_inputs": "Eingabefelder",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Was könnten wir besser machen?",
|
||||
"identify_customer_goals_description": "Besser verstehen, ob deine Botschaften die richtigen Erwartungen an dein Produkt schaffen.",
|
||||
"identify_customer_goals_name": "Kundenziele identifizieren",
|
||||
"identify_customer_goals_question_1_choice_1": "Meine Nutzerbasis tiefgehend verstehen",
|
||||
"identify_customer_goals_question_1_choice_2": "Upselling-Möglichkeiten identifizieren",
|
||||
"identify_customer_goals_question_1_choice_3": "Das bestmögliche Produkt entwickeln",
|
||||
"identify_customer_goals_question_1_choice_4": "Die Welt beherrschen, um allen Rosenkohl zum Frühstück zu servieren",
|
||||
"identify_customer_goals_question_1_headline": "Was ist Ihr Hauptziel bei der Nutzung von $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Biete einen Rabatt an, um Einblicke in Anmeldebarrieren zu gewinnen.",
|
||||
"identify_sign_up_barriers_name": "Identifiziere Anmeldebarrieren",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Erhalte 10% Rabatt",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Hilf uns, Dich besser zu verstehen:",
|
||||
"improve_trial_conversion_question_2_button_label": "Weiter",
|
||||
"improve_trial_conversion_question_2_headline": "Das tut mir leid zu hören. Was war das größte Problem bei der Nutzung von $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Weiter",
|
||||
"improve_trial_conversion_question_3_headline": "Was haben Sie von $[projectName] erwartet?",
|
||||
"improve_trial_conversion_question_4_button_label": "Erhalte 20% Rabatt",
|
||||
"improve_trial_conversion_question_4_headline": "Das tut mir leid zu hören! Erhalte 20% Rabatt im ersten Jahr.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Wir freuen uns, dir einen 20% Rabatt auf einen Jahresplan anzubieten.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Weiter",
|
||||
"improve_trial_conversion_question_5_headline": "Was möchtest Du erreichen?",
|
||||
"improve_trial_conversion_question_5_subheader": "Bitte wähle eine der folgenden Optionen aus:",
|
||||
"improve_trial_conversion_question_5_subheader": "Bitte beschreibe unten:",
|
||||
"improve_trial_conversion_question_6_headline": "Wie löst Du dein Problem heutzutage?",
|
||||
"improve_trial_conversion_question_6_subheader": "Bitte nenne alternative Lösungen:",
|
||||
"integration_setup_survey_description": "Bewerte, wie einfach Nutzer Integrationen zu deinem Produkt hinzufügen können.",
|
||||
|
||||
+42
-30
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Centered Modal",
|
||||
"change_organization": "Change organization",
|
||||
"change_workspace": "Change workspace",
|
||||
"choice_n": "Choice {{n}}",
|
||||
"choices": "Choices",
|
||||
"choose_environment": "Choose environment",
|
||||
"choose_organization": "Choose organization",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Close",
|
||||
"code": "Code",
|
||||
"collapse_rows": "Collapse rows",
|
||||
"column_n": "Column {{n}}",
|
||||
"completed": "Completed",
|
||||
"configuration": "Configuration",
|
||||
"confirm": "Confirm",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Created by",
|
||||
"customer_success": "Customer Success",
|
||||
"dark_overlay": "Dark overlay",
|
||||
"data_refreshed_successfully": "Data refreshed successfully",
|
||||
"date": "Date",
|
||||
"days": "days",
|
||||
"default": "Default",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Failed to copy to clipboard",
|
||||
"failed_to_load_organizations": "Failed to load organizations",
|
||||
"failed_to_load_workspaces": "Failed to load workspaces",
|
||||
"field_placeholder": "{{field}} Placeholder",
|
||||
"filter": "Filter",
|
||||
"finish": "Finish",
|
||||
"first_name": "First Name",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Generate",
|
||||
"go_back": "Go Back",
|
||||
"go_to_dashboard": "Go to Dashboard",
|
||||
"headline": "Headline",
|
||||
"hidden": "Hidden",
|
||||
"hidden_field": "Hidden field",
|
||||
"hidden_fields": "Hidden fields",
|
||||
"hide_column": "Hide column",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Image",
|
||||
"images": "Images",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "months",
|
||||
"move_down": "Move down",
|
||||
"move_up": "Move up",
|
||||
"multiple_languages": "Multiple languages",
|
||||
"my_product": "my Product",
|
||||
"name": "Name",
|
||||
"new": "New",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "No result found",
|
||||
"no_results": "No results",
|
||||
"no_surveys_found": "No surveys found.",
|
||||
"no_text_found": "No text found",
|
||||
"none_of_the_above": "None of the above",
|
||||
"not_authenticated": "You are not authenticated to perform this action.",
|
||||
"not_authorized": "Not authorized",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Organization settings",
|
||||
"other": "Other",
|
||||
"other_filters": "Other Filters",
|
||||
"others": "Others",
|
||||
"other_placeholder": "Other Placeholder",
|
||||
"overlay_color": "Overlay color",
|
||||
"overview": "Overview",
|
||||
"password": "Password",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Please upgrade your plan",
|
||||
"powered_by_formbricks": "Powered by Formbricks",
|
||||
"preview": "Preview",
|
||||
"preview_survey": "Preview Survey",
|
||||
"privacy": "Privacy Policy",
|
||||
"product_manager": "Product Manager",
|
||||
"production": "Production",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limit the amount of responses you receive from participants who meet certain criteria.",
|
||||
"read_docs": "Read docs",
|
||||
"recipients": "Recipients",
|
||||
"refresh": "Refresh",
|
||||
"remove": "Remove",
|
||||
"remove_from_team": "Remove from team",
|
||||
"reorder_and_hide_columns": "Reorder and hide columns",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Responses",
|
||||
"restart": "Restart",
|
||||
"role": "Role",
|
||||
"row_n": "Row {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Sales",
|
||||
"save": "Save",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "File storage not set up, uploads will likely fail",
|
||||
"string": "Text",
|
||||
"styling": "Styling",
|
||||
"subheader": "Subheader",
|
||||
"submit": "Submit",
|
||||
"summary": "Summary",
|
||||
"survey": "Survey",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "e.g. Formbricks",
|
||||
"workspaces": "Workspaces",
|
||||
"years": "years",
|
||||
"you": "You",
|
||||
"you_are_downgraded_to_the_community_edition": "You are downgraded to the Community Edition.",
|
||||
"you_are_not_authorized_to_perform_this_action": "You are not authorized to perform this action.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "You have reached your limit of {projectLimit} workspaces.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Created new attribute “{key}” with type “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "The user ID already exists for this environment and was not updated.",
|
||||
"contact_deleted_successfully": "Contact deleted successfully",
|
||||
"contacts_table_refresh": "Refresh contacts",
|
||||
"contacts_table_refresh_success": "Contacts refreshed successfully",
|
||||
"create_attribute": "Create attribute",
|
||||
"create_new_attribute": "Create new attribute",
|
||||
"create_new_attribute_description": "Create a new attribute for segmentation purposes.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Your organization invite link is ready!",
|
||||
"organization_name": "Organization Name",
|
||||
"organization_name_description": "Give your organization a descriptive name.",
|
||||
"organization_name_placeholder": "e.g. Power Puff Girls",
|
||||
"organization_name_placeholder": "e.g. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Organization name updated successfully",
|
||||
"organization_settings": "Organization Settings",
|
||||
"please_add_a_logo": "Please add a logo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "You are all set! Time to create your first survey",
|
||||
"alphabetical": "Alphabetical",
|
||||
"copy_survey": "Copy survey",
|
||||
"copy_survey_description": "Copy this survey to another environment",
|
||||
"copy_survey_error": "Failed to copy survey",
|
||||
"copy_survey_link_to_clipboard": "Copy survey link to clipboard",
|
||||
"copy_survey_partially_success": "{success} surveys copied successfully, {error} failed.",
|
||||
"copy_survey_success": "Survey copied successfully",
|
||||
"delete_survey_and_responses_warning": "Are you sure you want to delete this survey and all of its responses?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Choose the default language for this survey:",
|
||||
"2_activate_translation_for_specific_languages": "2. Activate translation for specific languages:",
|
||||
"activate_translations": "Activate translations",
|
||||
"add": "Add +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Add a delay or auto-close the survey",
|
||||
"add_a_four_digit_pin": "Add a four digit PIN",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Assign =",
|
||||
"audience": "Audience",
|
||||
"auto_close_on_inactivity": "Auto close on inactivity",
|
||||
"auto_progress_rating_and_nps": "Auto-progress rating and NPS questions",
|
||||
"auto_progress_rating_and_nps_description": "Auto-advance in single-question blocks. Required questions hide Next, except when \"Other\" is selected.",
|
||||
"auto_save_disabled": "Auto-save disabled",
|
||||
"auto_save_disabled_tooltip": "Your survey is only auto-saved when in draft. This assures public surveys are not unintentionally updated.",
|
||||
"auto_save_on": "Auto-save on",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Changes will lead to inconsistencies",
|
||||
"change_anyway": "Change anyway",
|
||||
"change_background": "Change background",
|
||||
"change_default": "Change default",
|
||||
"change_question_type": "Change question type",
|
||||
"change_survey_type": "Switching survey type affects existing access",
|
||||
"change_the_background_to_a_color_image_or_animation": "Change the background to a color, image or animation.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Choose where to run the survey.",
|
||||
"city": "City",
|
||||
"close_survey_on_response_limit": "Close survey on response limit",
|
||||
"code": "Code",
|
||||
"color": "Color",
|
||||
"column_used_in_logic_error": "This column is used in logic of question {questionIndex}. Please remove it from logic first.",
|
||||
"columns": "Columns",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Customize the survey logo",
|
||||
"darken_or_lighten_background_of_your_choice": "Darken or lighten background of your choice.",
|
||||
"days_before_showing_this_survey_again": "or more days to pass between the last shown survey and showing this survey.",
|
||||
"default_language": "Default language",
|
||||
"delete_anyways": "Delete anyways",
|
||||
"delete_block": "Delete block",
|
||||
"delete_choice": "Delete choice",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplicate question",
|
||||
"edit_link": "Edit link",
|
||||
"edit_recall": "Edit Recall",
|
||||
"edit_translations": "Edit {lang} translations",
|
||||
"element_not_found": "Question not found",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Allow respondents to switch language at any time. Needs min. 2 active languages.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Spam protection uses reCAPTCHA v3 to filter out the spam responses.",
|
||||
@@ -1599,11 +1602,13 @@
|
||||
"long_answer": "Long answer",
|
||||
"long_answer_toggle_description": "Allow respondents to write longer, multi-line answers.",
|
||||
"lower_label": "Lower Label",
|
||||
"manage_languages": "Manage Languages",
|
||||
"manage_languages": "Manage languages",
|
||||
"manage_translations": "Manage translations",
|
||||
"matrix_all_fields": "All fields",
|
||||
"matrix_rows": "Rows",
|
||||
"max_file_size": "Max file size",
|
||||
"max_file_size_limit_is": "Max file size limit is",
|
||||
"missing_first": "Missing first",
|
||||
"move_question_to_block": "Move question to block",
|
||||
"multiply": "Multiply *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Needed for a self-hosted Cal.com instance",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "“Next” button label",
|
||||
"no_hidden_fields_yet_add_first_one_below": "No hidden fields yet. Add the first one below.",
|
||||
"no_images_found_for": "No images found for “{query}”",
|
||||
"no_languages_found_add_first_one_to_get_started": "No languages found. Add the first one to get started.",
|
||||
"no_languages_found_add_first_one_to_get_started": "No survey languages found in this workspace. Please add one to get started.",
|
||||
"no_option_found": "No option found",
|
||||
"no_recall_items_found": "No recall items found",
|
||||
"no_variables_yet_add_first_one_below": "No variables yet. Add the first one below.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Please enter a valid URL (e.g., https://example.com)",
|
||||
"please_set_a_survey_trigger": "Please set a survey trigger",
|
||||
"please_specify": "Please specify",
|
||||
"present_your_survey_in_multiple_languages": "Present your survey in multiple languages",
|
||||
"prevent_double_submission": "Prevent double submission",
|
||||
"prevent_double_submission_description": "Only allow 1 response per email address",
|
||||
"progress_saved": "Progress saved",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 points",
|
||||
"show_block_settings": "Show Block settings",
|
||||
"show_button": "Show Button",
|
||||
"show_in_order": "Show in order",
|
||||
"show_language_switch": "Show language switch",
|
||||
"show_multiple_times": "Show a limited number of times",
|
||||
"show_only_once": "Show only once",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Survey Preview 👀",
|
||||
"survey_styling": "Survey styling",
|
||||
"survey_trigger": "Survey Trigger",
|
||||
"switch_multi_language_on_to_get_started": "Switch multi-language on to get started 👉",
|
||||
"target_block_not_found": "Target block not found",
|
||||
"targeted": "Targeted",
|
||||
"ten_points": "10 points",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Show a single time, even if they do not respond.",
|
||||
"then": "Then",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "This action will remove all the translations from this survey.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "This will remove this language and all its translations from this survey. This action cannot be undone.",
|
||||
"three_points": "3 points",
|
||||
"times": "times",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "To keep the placement over all surveys consistent, you can",
|
||||
"translated": "Translated",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Trigger survey when one of the actions is fired…",
|
||||
"try_lollipop_or_mountain": "Try “lollipop” or “mountain”…",
|
||||
"type_field_id": "Type field id",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Only let people with a real email respond.",
|
||||
"visibility_and_recontact": "Visibility & Recontact",
|
||||
"visibility_and_recontact_description": "Control when this survey can appear and how often it can reappear.",
|
||||
"visible": "Visible",
|
||||
"wait": "Wait",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Wait a few seconds after the trigger before showing the survey",
|
||||
"waiting_time_across_surveys": "Cooldown Period (across surveys)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Downloading QR code",
|
||||
"drop_offs": "Drop-Offs",
|
||||
"drop_offs_tooltip": "Number of times the survey has been started but not completed.",
|
||||
"failed_to_copy_link": "Failed to copy link",
|
||||
"filter_added_successfully": "Filter added successfully",
|
||||
"filter_updated_successfully": "Filter updated successfully",
|
||||
"filtered_responses_csv": "Filtered responses (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Survey deleted successfully",
|
||||
"survey_duplicated_successfully": "Survey duplicated successfully",
|
||||
"survey_duplication_error": "Failed to duplicate the survey.",
|
||||
"templates": {
|
||||
"all_channels": "All channels",
|
||||
"all_industries": "All industries",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Duplicate language or language ID",
|
||||
"edit_languages": "Edit languages",
|
||||
"identifier": "Identifier (ISO)",
|
||||
"incomplete_translations": "Incomplete translations",
|
||||
"language": "Language",
|
||||
"language_deleted_successfully": "Language deleted successfully",
|
||||
"languages_updated_successfully": "Languages updated successfully",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Please select a language",
|
||||
"remove_language": "Remove Language",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Please remove the language from these surveys in order to remove it from the workspace.",
|
||||
"search_items": "Search items",
|
||||
"translate": "Translate"
|
||||
"search_items": "Search items"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Add background color",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colors the unfilled portion of the bar.",
|
||||
"advanced_styling_field_track_height": "Track Height",
|
||||
"advanced_styling_field_track_height_description": "Controls the progress bar thickness.",
|
||||
"advanced_styling_field_upper_label_color": "Headline Label Color",
|
||||
"advanced_styling_field_upper_label_color_description": "Colors the small label above inputs.",
|
||||
"advanced_styling_field_upper_label_size": "Headline Label Font Size",
|
||||
"advanced_styling_field_upper_label_size_description": "Scales the small label above inputs.",
|
||||
"advanced_styling_field_upper_label_weight": "Headline Label Font Weight",
|
||||
"advanced_styling_field_upper_label_weight_description": "Makes the label lighter or bolder.",
|
||||
"advanced_styling_field_upper_label_color": "Label Color",
|
||||
"advanced_styling_field_upper_label_color_description": "Colors the small labels above inputs and scale labels.",
|
||||
"advanced_styling_field_upper_label_size": "Label Font Size",
|
||||
"advanced_styling_field_upper_label_size_description": "Scales the small labels above inputs and scale labels.",
|
||||
"advanced_styling_field_upper_label_weight": "Label Font Weight",
|
||||
"advanced_styling_field_upper_label_weight_description": "Makes the labels lighter or bolder.",
|
||||
"advanced_styling_section_buttons": "Buttons",
|
||||
"advanced_styling_section_headlines": "Headlines & Descriptions",
|
||||
"advanced_styling_section_inputs": "Inputs",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "What is one thing we could do better?",
|
||||
"identify_customer_goals_description": "Better understand if your messaging creates the right expectations of the value your product provides.",
|
||||
"identify_customer_goals_name": "Identify Customer Goals",
|
||||
"identify_customer_goals_question_1_choice_1": "Understand my user base deeply",
|
||||
"identify_customer_goals_question_1_choice_2": "Identify upselling opportunities",
|
||||
"identify_customer_goals_question_1_choice_3": "Build the best possible product",
|
||||
"identify_customer_goals_question_1_choice_4": "Rule the world to make everyone breakfast brussels sprouts",
|
||||
"identify_customer_goals_question_1_headline": "What is your primary goal for using $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Offer a discount to gather insights about sign up barriers.",
|
||||
"identify_sign_up_barriers_name": "Identify Sign Up Barriers",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Get 10% discount",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Help us understand you better:",
|
||||
"improve_trial_conversion_question_2_button_label": "Next",
|
||||
"improve_trial_conversion_question_2_headline": "Sorry to hear. What was the biggest problem using $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Next",
|
||||
"improve_trial_conversion_question_3_headline": "What did you expect $[projectName] to do?",
|
||||
"improve_trial_conversion_question_4_button_label": "Get 20% off",
|
||||
"improve_trial_conversion_question_4_headline": "Sorry to hear! Get 20% off the first year.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We are happy to offer you a 20% discount on a yearly plan.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Next",
|
||||
"improve_trial_conversion_question_5_headline": "What would you like to achieve?",
|
||||
"improve_trial_conversion_question_5_subheader": "Please select one of the following options:",
|
||||
"improve_trial_conversion_question_5_subheader": "Please describe below:",
|
||||
"improve_trial_conversion_question_6_headline": "How are you solving your problem now?",
|
||||
"improve_trial_conversion_question_6_subheader": "Please name alternative solutions:",
|
||||
"integration_setup_survey_description": "Evaluate how easily users can add integrations to your product. Find blind spots.",
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Modal centrado",
|
||||
"change_organization": "Cambiar organización",
|
||||
"change_workspace": "Cambiar espacio de trabajo",
|
||||
"choice_n": "Opción {{n}}",
|
||||
"choices": "Opciones",
|
||||
"choose_environment": "Elegir entorno",
|
||||
"choose_organization": "Elegir organización",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Cerrar",
|
||||
"code": "Código",
|
||||
"collapse_rows": "Contraer filas",
|
||||
"column_n": "Columna {{n}}",
|
||||
"completed": "Completado",
|
||||
"configuration": "Configuración",
|
||||
"confirm": "Confirmar",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Creado por",
|
||||
"customer_success": "Éxito del cliente",
|
||||
"dark_overlay": "Superposición oscura",
|
||||
"data_refreshed_successfully": "Datos actualizados correctamente",
|
||||
"date": "Fecha",
|
||||
"days": "días",
|
||||
"default": "Predeterminado",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Error al copiar al portapapeles",
|
||||
"failed_to_load_organizations": "Error al cargar organizaciones",
|
||||
"failed_to_load_workspaces": "Error al cargar los proyectos",
|
||||
"field_placeholder": "Marcador de posición de {{field}}",
|
||||
"filter": "Filtro",
|
||||
"finish": "Finalizar",
|
||||
"first_name": "Nombre",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Generar",
|
||||
"go_back": "Volver",
|
||||
"go_to_dashboard": "Ir al panel de control",
|
||||
"headline": "Titular",
|
||||
"hidden": "Oculto",
|
||||
"hidden_field": "Campo oculto",
|
||||
"hidden_fields": "Campos ocultos",
|
||||
"hide_column": "Ocultar columna",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Imagen",
|
||||
"images": "Imágenes",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "meses",
|
||||
"move_down": "Mover hacia abajo",
|
||||
"move_up": "Mover hacia arriba",
|
||||
"multiple_languages": "Múltiples idiomas",
|
||||
"my_product": "mi producto",
|
||||
"name": "Nombre",
|
||||
"new": "Nuevo",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "No se encontró resultado",
|
||||
"no_results": "Sin resultados",
|
||||
"no_surveys_found": "No se encontraron encuestas.",
|
||||
"no_text_found": "No se encontró texto",
|
||||
"none_of_the_above": "Ninguna de las anteriores",
|
||||
"not_authenticated": "No estás autenticado para realizar esta acción.",
|
||||
"not_authorized": "No autorizado",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Ajustes de la organización",
|
||||
"other": "Otro",
|
||||
"other_filters": "Otros Filtros",
|
||||
"others": "Otros",
|
||||
"other_placeholder": "Otro marcador de posición",
|
||||
"overlay_color": "Color de superposición",
|
||||
"overview": "Resumen",
|
||||
"password": "Contraseña",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Por favor, actualiza tu plan",
|
||||
"powered_by_formbricks": "Desarrollado por Formbricks",
|
||||
"preview": "Vista previa",
|
||||
"preview_survey": "Vista previa de la encuesta",
|
||||
"privacy": "Política de privacidad",
|
||||
"product_manager": "Gestor de producto",
|
||||
"production": "Producción",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limita la cantidad de respuestas que recibes de participantes que cumplen ciertos criterios.",
|
||||
"read_docs": "Leer documentación",
|
||||
"recipients": "Destinatarios",
|
||||
"refresh": "Actualizar",
|
||||
"remove": "Eliminar",
|
||||
"remove_from_team": "Eliminar del equipo",
|
||||
"reorder_and_hide_columns": "Reordenar y ocultar columnas",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Respuestas",
|
||||
"restart": "Reiniciar",
|
||||
"role": "Rol",
|
||||
"row_n": "Fila {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Ventas",
|
||||
"save": "Guardar",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Almacenamiento de archivos no configurado, es probable que fallen las subidas",
|
||||
"string": "Texto",
|
||||
"styling": "Estilo",
|
||||
"subheader": "Subtítulo",
|
||||
"submit": "Enviar",
|
||||
"summary": "Resumen",
|
||||
"survey": "Encuesta",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "p. ej. Formbricks",
|
||||
"workspaces": "Proyectos",
|
||||
"years": "años",
|
||||
"you": "Tú",
|
||||
"you_are_downgraded_to_the_community_edition": "Has sido degradado a la edición Community.",
|
||||
"you_are_not_authorized_to_perform_this_action": "No tienes autorización para realizar esta acción.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Has alcanzado tu límite de {projectLimit} espacios de trabajo.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Se creó el atributo nuevo “{key}” con el tipo “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "El ID de usuario ya existe para este entorno y no se actualizó.",
|
||||
"contact_deleted_successfully": "Contacto eliminado correctamente",
|
||||
"contacts_table_refresh": "Actualizar contactos",
|
||||
"contacts_table_refresh_success": "Contactos actualizados correctamente",
|
||||
"create_attribute": "Crear atributo",
|
||||
"create_new_attribute": "Crear atributo nuevo",
|
||||
"create_new_attribute_description": "Crea un atributo nuevo para fines de segmentación.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "¡Tu enlace de invitación a la organización está listo!",
|
||||
"organization_name": "Nombre de la organización",
|
||||
"organization_name_description": "Dale a tu organización un nombre descriptivo.",
|
||||
"organization_name_placeholder": "p. ej. Power Puff Girls",
|
||||
"organization_name_placeholder": "p. ej. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Nombre de la organización actualizado correctamente",
|
||||
"organization_settings": "Configuración de la organización",
|
||||
"please_add_a_logo": "Por favor, añade un logotipo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "¡Todo listo! Es hora de crear tu primera encuesta",
|
||||
"alphabetical": "Alfabético",
|
||||
"copy_survey": "Copiar encuesta",
|
||||
"copy_survey_description": "Copia esta encuesta a otro entorno",
|
||||
"copy_survey_error": "Error al copiar la encuesta",
|
||||
"copy_survey_link_to_clipboard": "Copiar enlace de la encuesta al portapapeles",
|
||||
"copy_survey_partially_success": "{success} encuestas copiadas correctamente, {error} fallidas.",
|
||||
"copy_survey_success": "¡Encuesta copiada correctamente!",
|
||||
"delete_survey_and_responses_warning": "¿Estás seguro de que quieres eliminar esta encuesta y todas sus respuestas?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Elige el idioma predeterminado para esta encuesta:",
|
||||
"2_activate_translation_for_specific_languages": "2. Activa la traducción para idiomas específicos:",
|
||||
"activate_translations": "Activar traducciones",
|
||||
"add": "Añadir +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Añadir un retraso o cerrar automáticamente la encuesta",
|
||||
"add_a_four_digit_pin": "Añadir un PIN de cuatro dígitos",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Asignar =",
|
||||
"audience": "Audiencia",
|
||||
"auto_close_on_inactivity": "Cierre automático por inactividad",
|
||||
"auto_progress_rating_and_nps": "Avanzar automáticamente en preguntas de valoración y NPS",
|
||||
"auto_progress_rating_and_nps_description": "Avance automático en bloques de una sola pregunta. Las preguntas obligatorias ocultan Siguiente, excepto cuando se selecciona \"Otro\".",
|
||||
"auto_save_disabled": "Guardado automático desactivado",
|
||||
"auto_save_disabled_tooltip": "Su encuesta solo se guarda automáticamente cuando está en borrador. Esto asegura que las encuestas públicas no se actualicen involuntariamente.",
|
||||
"auto_save_on": "Guardado automático activado",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Los cambios provocarán inconsistencias",
|
||||
"change_anyway": "Cambiar de todos modos",
|
||||
"change_background": "Cambiar fondo",
|
||||
"change_default": "Cambiar predeterminado",
|
||||
"change_question_type": "Cambiar tipo de pregunta",
|
||||
"change_survey_type": "Cambiar el tipo de encuesta afecta al acceso existente",
|
||||
"change_the_background_to_a_color_image_or_animation": "Cambiar el fondo a un color, imagen o animación.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Elige dónde ejecutar la encuesta.",
|
||||
"city": "Ciudad",
|
||||
"close_survey_on_response_limit": "Cerrar encuesta al alcanzar el límite de respuestas",
|
||||
"code": "Código",
|
||||
"color": "Color",
|
||||
"column_used_in_logic_error": "Esta columna se usa en la lógica de la pregunta {questionIndex}. Por favor, elimínala de la lógica primero.",
|
||||
"columns": "Columnas",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Personalizar el logotipo de la encuesta",
|
||||
"darken_or_lighten_background_of_your_choice": "Oscurece o aclara el fondo de tu elección.",
|
||||
"days_before_showing_this_survey_again": "o más días deben transcurrir entre la última encuesta mostrada y la visualización de esta encuesta.",
|
||||
"default_language": "Idioma predeterminado",
|
||||
"delete_anyways": "Eliminar de todos modos",
|
||||
"delete_block": "Eliminar bloque",
|
||||
"delete_choice": "Eliminar opción",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplicar pregunta",
|
||||
"edit_link": "Editar enlace",
|
||||
"edit_recall": "Editar recuperación",
|
||||
"edit_translations": "Editar traducciones de {lang}",
|
||||
"element_not_found": "Pregunta no encontrada",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir a los participantes cambiar el idioma de la encuesta en cualquier momento durante la encuesta.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "La protección contra spam utiliza reCAPTCHA v3 para filtrar las respuestas spam.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Permitir a los encuestados escribir respuestas más largas y de varias líneas.",
|
||||
"lower_label": "Etiqueta inferior",
|
||||
"manage_languages": "Gestionar idiomas",
|
||||
"manage_translations": "Gestionar traducciones",
|
||||
"matrix_all_fields": "Todos los campos",
|
||||
"matrix_rows": "Filas",
|
||||
"max_file_size": "Tamaño máximo de archivo",
|
||||
"max_file_size_limit_is": "El límite de tamaño máximo de archivo es",
|
||||
"missing_first": "Faltantes primero",
|
||||
"move_question_to_block": "Mover pregunta al bloque",
|
||||
"multiply": "Multiplicar *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Necesario para una instancia Cal.com autohospedada",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Etiqueta del botón \"Siguiente\"",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Aún no hay campos ocultos. Añade el primero a continuación.",
|
||||
"no_images_found_for": "No se encontraron imágenes para ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "No se encontraron idiomas. Añade el primero para comenzar.",
|
||||
"no_languages_found_add_first_one_to_get_started": "No se encontraron idiomas de encuesta en este espacio de trabajo. Por favor, añade uno para comenzar.",
|
||||
"no_option_found": "No se encontró ninguna opción",
|
||||
"no_recall_items_found": "No se encontraron elementos de recuperación",
|
||||
"no_variables_yet_add_first_one_below": "No hay variables todavía. Añade la primera a continuación.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Por favor, introduce una URL válida (p. ej., https://example.com)",
|
||||
"please_set_a_survey_trigger": "Establece un disparador de encuesta",
|
||||
"please_specify": "Por favor, especifica",
|
||||
"present_your_survey_in_multiple_languages": "Presenta tu encuesta en varios idiomas",
|
||||
"prevent_double_submission": "Evitar envío duplicado",
|
||||
"prevent_double_submission_description": "Permitir solo 1 respuesta por dirección de correo electrónico",
|
||||
"progress_saved": "Progreso guardado",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 puntos",
|
||||
"show_block_settings": "Mostrar ajustes del bloque",
|
||||
"show_button": "Mostrar botón",
|
||||
"show_in_order": "Mostrar en orden",
|
||||
"show_language_switch": "Mostrar cambio de idioma",
|
||||
"show_multiple_times": "Mostrar un número limitado de veces",
|
||||
"show_only_once": "Mostrar solo una vez",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Vista previa de la encuesta 👀",
|
||||
"survey_styling": "Estilo del formulario",
|
||||
"survey_trigger": "Activador de la encuesta",
|
||||
"switch_multi_language_on_to_get_started": "Activa el modo multiidioma para comenzar 👉",
|
||||
"target_block_not_found": "Bloque objetivo no encontrado",
|
||||
"targeted": "Dirigido",
|
||||
"ten_points": "10 puntos",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar una sola vez, incluso si no responden.",
|
||||
"then": "Entonces",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Esta acción eliminará todas las traducciones de esta encuesta.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Esto eliminará este idioma y todas sus traducciones de esta encuesta. Esta acción no se puede deshacer.",
|
||||
"three_points": "3 puntos",
|
||||
"times": "veces",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para mantener la ubicación coherente en todas las encuestas, puedes",
|
||||
"translated": "Traducido",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Activar encuesta cuando se dispare una de las acciones...",
|
||||
"try_lollipop_or_mountain": "Prueba 'piruleta' o 'montaña'...",
|
||||
"type_field_id": "Escribe el id del campo",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Solo permite responder a personas con un correo electrónico real.",
|
||||
"visibility_and_recontact": "Visibilidad y recontacto",
|
||||
"visibility_and_recontact_description": "Controla cuándo puede aparecer esta encuesta y con qué frecuencia puede volver a aparecer.",
|
||||
"visible": "Visible",
|
||||
"wait": "Esperar",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Esperar unos segundos después del disparador antes de mostrar la encuesta",
|
||||
"waiting_time_across_surveys": "Periodo de espera (entre encuestas)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Descargando código QR",
|
||||
"drop_offs": "Abandonos",
|
||||
"drop_offs_tooltip": "Número de veces que se ha iniciado la encuesta pero no se ha completado.",
|
||||
"failed_to_copy_link": "Error al copiar el enlace",
|
||||
"filter_added_successfully": "Filtro añadido correctamente",
|
||||
"filter_updated_successfully": "Filtro actualizado correctamente",
|
||||
"filtered_responses_csv": "Respuestas filtradas (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "¡Encuesta eliminada correctamente!",
|
||||
"survey_duplicated_successfully": "Encuesta duplicada correctamente.",
|
||||
"survey_duplication_error": "Error al duplicar la encuesta.",
|
||||
"templates": {
|
||||
"all_channels": "Todos los canales",
|
||||
"all_industries": "Todas las industrias",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Idioma o ID de idioma duplicado",
|
||||
"edit_languages": "Editar idiomas",
|
||||
"identifier": "Identificador (ISO)",
|
||||
"incomplete_translations": "Traducciones incompletas",
|
||||
"language": "Idioma",
|
||||
"language_deleted_successfully": "Idioma eliminado correctamente",
|
||||
"languages_updated_successfully": "Idiomas actualizados correctamente",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Por favor, selecciona un idioma",
|
||||
"remove_language": "Eliminar idioma",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Por favor, elimina el idioma de estas encuestas para poder eliminarlo del espacio de trabajo.",
|
||||
"search_items": "Buscar elementos",
|
||||
"translate": "Traducir"
|
||||
"search_items": "Buscar elementos"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Añadir color de fondo",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colorea la parte no rellenada de la barra.",
|
||||
"advanced_styling_field_track_height": "Altura de la pista",
|
||||
"advanced_styling_field_track_height_description": "Controla el grosor de la barra de progreso.",
|
||||
"advanced_styling_field_upper_label_color": "Color de la etiqueta del titular",
|
||||
"advanced_styling_field_upper_label_color_description": "Colorea la etiqueta pequeña sobre los campos de entrada.",
|
||||
"advanced_styling_field_upper_label_size": "Tamaño de fuente de la etiqueta del titular",
|
||||
"advanced_styling_field_upper_label_size_description": "Escala la etiqueta pequeña sobre los campos de entrada.",
|
||||
"advanced_styling_field_upper_label_weight": "Grosor de fuente de la etiqueta del titular",
|
||||
"advanced_styling_field_upper_label_weight_description": "Hace que la etiqueta sea más ligera o más gruesa.",
|
||||
"advanced_styling_field_upper_label_color": "Color de etiqueta",
|
||||
"advanced_styling_field_upper_label_color_description": "Colorea las pequeñas etiquetas sobre los campos de entrada y las etiquetas de escala.",
|
||||
"advanced_styling_field_upper_label_size": "Tamaño de fuente de etiqueta",
|
||||
"advanced_styling_field_upper_label_size_description": "Escala las pequeñas etiquetas sobre los campos de entrada y las etiquetas de escala.",
|
||||
"advanced_styling_field_upper_label_weight": "Grosor de fuente de etiqueta",
|
||||
"advanced_styling_field_upper_label_weight_description": "Hace que las etiquetas sean más ligeras o más gruesas.",
|
||||
"advanced_styling_section_buttons": "Botones",
|
||||
"advanced_styling_section_headlines": "Títulos y descripciones",
|
||||
"advanced_styling_section_inputs": "Campos de entrada",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "¿Qué es una cosa que podríamos mejorar?",
|
||||
"identify_customer_goals_description": "Comprende mejor si tus mensajes crean las expectativas correctas sobre el valor que proporciona tu producto.",
|
||||
"identify_customer_goals_name": "Identificar objetivos del cliente",
|
||||
"identify_customer_goals_question_1_choice_1": "Comprender en profundidad a mi base de usuarios",
|
||||
"identify_customer_goals_question_1_choice_2": "Identificar oportunidades de venta adicional",
|
||||
"identify_customer_goals_question_1_choice_3": "Construir el mejor producto posible",
|
||||
"identify_customer_goals_question_1_choice_4": "Conquistar el mundo para que todos desayunen coles de Bruselas",
|
||||
"identify_customer_goals_question_1_headline": "¿Cuál es tu objetivo principal al usar $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Ofrece un descuento para obtener información sobre las barreras de registro.",
|
||||
"identify_sign_up_barriers_name": "Identificar barreras de registro",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Obtener 10 % de descuento",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Ayúdanos a entenderte mejor:",
|
||||
"improve_trial_conversion_question_2_button_label": "Siguiente",
|
||||
"improve_trial_conversion_question_2_headline": "Lamentamos oír eso. ¿Cuál fue el mayor problema al usar $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Siguiente",
|
||||
"improve_trial_conversion_question_3_headline": "¿Qué esperabas que hiciera $[projectName]?",
|
||||
"improve_trial_conversion_question_4_button_label": "Obtener 20 % de descuento",
|
||||
"improve_trial_conversion_question_4_headline": "¡Sentimos oírlo! Obtén un 20 % de descuento en el primer año.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Nos complace ofrecerte un 20 % de descuento en un plan anual.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Siguiente",
|
||||
"improve_trial_conversion_question_5_headline": "¿Qué te gustaría conseguir?",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, selecciona una de las siguientes opciones:",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, describe a continuación:",
|
||||
"improve_trial_conversion_question_6_headline": "¿Cómo estás solucionando tu problema ahora?",
|
||||
"improve_trial_conversion_question_6_subheader": "Por favor, nombra soluciones alternativas:",
|
||||
"integration_setup_survey_description": "Evalúa con qué facilidad los usuarios pueden añadir integraciones a tu producto. Encuentra puntos ciegos.",
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Au centre",
|
||||
"change_organization": "Changer d'organisation",
|
||||
"change_workspace": "Changer d'espace de travail",
|
||||
"choice_n": "Choix {{n}}",
|
||||
"choices": "Choix",
|
||||
"choose_environment": "Choisir l'environnement",
|
||||
"choose_organization": "Choisir l'organisation",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Fermer",
|
||||
"code": "Code",
|
||||
"collapse_rows": "Réduire les lignes",
|
||||
"column_n": "Colonne {{n}}",
|
||||
"completed": "Terminé",
|
||||
"configuration": "Configuration",
|
||||
"confirm": "Confirmer",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Créé par",
|
||||
"customer_success": "Succès Client",
|
||||
"dark_overlay": "Foncée",
|
||||
"data_refreshed_successfully": "Données actualisées avec succès",
|
||||
"date": "Date",
|
||||
"days": "jours",
|
||||
"default": "Par défaut",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Échec de la copie dans le presse-papiers",
|
||||
"failed_to_load_organizations": "Échec du chargement des organisations",
|
||||
"failed_to_load_workspaces": "Échec du chargement des projets",
|
||||
"field_placeholder": "Espace réservé {{field}}",
|
||||
"filter": "Filtre",
|
||||
"finish": "Terminer",
|
||||
"first_name": "Prénom",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Générer",
|
||||
"go_back": "Retourner",
|
||||
"go_to_dashboard": "Aller au tableau de bord",
|
||||
"headline": "Titre principal",
|
||||
"hidden": "Caché",
|
||||
"hidden_field": "Champ caché",
|
||||
"hidden_fields": "Champs cachés",
|
||||
"hide_column": "Cacher la colonne",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Image",
|
||||
"images": "Images",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "mois",
|
||||
"move_down": "Déplacer vers le bas",
|
||||
"move_up": "Déplacer vers le haut",
|
||||
"multiple_languages": "Plusieurs langues",
|
||||
"my_product": "mon produit",
|
||||
"name": "Nom",
|
||||
"new": "Nouveau",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Aucun résultat trouvé",
|
||||
"no_results": "Aucun résultat",
|
||||
"no_surveys_found": "Aucun sondage trouvé.",
|
||||
"no_text_found": "Aucun texte trouvé",
|
||||
"none_of_the_above": "Aucun des éléments ci-dessus",
|
||||
"not_authenticated": "Vous n'êtes pas authentifié pour effectuer cette action.",
|
||||
"not_authorized": "Non autorisé",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Paramètres de l'organisation",
|
||||
"other": "Autre",
|
||||
"other_filters": "Autres filtres",
|
||||
"others": "Autres",
|
||||
"other_placeholder": "Autre espace réservé",
|
||||
"overlay_color": "Couleur de superposition",
|
||||
"overview": "Aperçu",
|
||||
"password": "Mot de passe",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Veuillez mettre à niveau votre plan",
|
||||
"powered_by_formbricks": "Propulsé par Formbricks",
|
||||
"preview": "Aperçu",
|
||||
"preview_survey": "Aperçu de l'enquête",
|
||||
"privacy": "Politique de confidentialité",
|
||||
"product_manager": "Chef de produit",
|
||||
"production": "Production",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limitez le nombre de réponses que vous recevez de la part des participants répondant à certains critères.",
|
||||
"read_docs": "Lire la documentation",
|
||||
"recipients": "Destinataires",
|
||||
"refresh": "Actualiser",
|
||||
"remove": "Retirer",
|
||||
"remove_from_team": "Retirer de l'équipe",
|
||||
"reorder_and_hide_columns": "Réorganiser et masquer des colonnes",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Réponses",
|
||||
"restart": "Recommencer",
|
||||
"role": "Rôle",
|
||||
"row_n": "Ligne {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Ventes",
|
||||
"save": "Enregistrer",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Stockage de fichiers non configuré, les téléchargements risquent d'échouer",
|
||||
"string": "Texte",
|
||||
"styling": "Style",
|
||||
"subheader": "Sous-titre",
|
||||
"submit": "Soumettre",
|
||||
"summary": "Résumé",
|
||||
"survey": "Enquête",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "par ex. Formbricks",
|
||||
"workspaces": "Projets",
|
||||
"years": "années",
|
||||
"you": "Vous",
|
||||
"you_are_downgraded_to_the_community_edition": "Vous êtes rétrogradé à l'édition communautaire.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Vous n'êtes pas autorisé à effectuer cette action.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Vous avez atteint votre limite de {projectLimit} espaces de travail.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Nouvel attribut “{key}” créé avec le type “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "L'identifiant utilisateur existe déjà pour cet environnement et n'a pas été mis à jour.",
|
||||
"contact_deleted_successfully": "Contact supprimé avec succès",
|
||||
"contacts_table_refresh": "Actualiser les contacts",
|
||||
"contacts_table_refresh_success": "Contacts rafraîchis avec succès",
|
||||
"create_attribute": "Créer un attribut",
|
||||
"create_new_attribute": "Créer un nouvel attribut",
|
||||
"create_new_attribute_description": "Créez un nouvel attribut à des fins de segmentation.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Le lien d'invitation de votre organisation est prêt !",
|
||||
"organization_name": "Nom de l'organisation",
|
||||
"organization_name_description": "Attribuez un nom descriptif à votre organisation.",
|
||||
"organization_name_placeholder": "e.g. Power Puff Girls",
|
||||
"organization_name_placeholder": "e.g. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Nom de l'organisation mis à jour avec succès",
|
||||
"organization_settings": "Paramètres de l'organisation",
|
||||
"please_add_a_logo": "Veuillez ajouter un logo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Vous êtes prêt ! Il est temps de créer votre première enquête.",
|
||||
"alphabetical": "Alphabétique",
|
||||
"copy_survey": "Copier l'enquête",
|
||||
"copy_survey_description": "Copier cette enquête dans un autre environnement",
|
||||
"copy_survey_error": "Échec de la copie du sondage",
|
||||
"copy_survey_link_to_clipboard": "Copier le lien du sondage dans le presse-papiers",
|
||||
"copy_survey_partially_success": "{success} enquêtes copiées avec succès, {error} échouées.",
|
||||
"copy_survey_success": "Enquête copiée avec succès !",
|
||||
"delete_survey_and_responses_warning": "Êtes-vous sûr de vouloir supprimer cette enquête et toutes ses réponses?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Choisissez la langue par défaut pour ce sondage :",
|
||||
"2_activate_translation_for_specific_languages": "2. Activer la traduction pour des langues spécifiques:",
|
||||
"activate_translations": "Activer les traductions",
|
||||
"add": "Ajouter +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Ajouter un délai ou fermer automatiquement l'enquête",
|
||||
"add_a_four_digit_pin": "Ajoutez un code PIN à quatre chiffres.",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Attribuer =",
|
||||
"audience": "Public",
|
||||
"auto_close_on_inactivity": "Fermeture automatique en cas d'inactivité",
|
||||
"auto_progress_rating_and_nps": "Progression automatique pour les questions d'évaluation et NPS",
|
||||
"auto_progress_rating_and_nps_description": "Avancement automatique dans les blocs à question unique. Les questions obligatoires masquent le bouton Suivant, sauf lorsque « Autre » est sélectionné.",
|
||||
"auto_save_disabled": "Sauvegarde automatique désactivée",
|
||||
"auto_save_disabled_tooltip": "Votre sondage n'est sauvegardé automatiquement que lorsqu'il est en brouillon. Cela garantit que les sondages publics ne sont pas mis à jour involontairement.",
|
||||
"auto_save_on": "Sauvegarde automatique activée",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Les changements entraîneront des incohérences.",
|
||||
"change_anyway": "Changer de toute façon",
|
||||
"change_background": "Changer l'arrière-plan",
|
||||
"change_default": "Modifier la langue par défaut",
|
||||
"change_question_type": "Changer le type de question",
|
||||
"change_survey_type": "Le changement de type de sondage affecte l'accès existant",
|
||||
"change_the_background_to_a_color_image_or_animation": "Changez l'arrière-plan en une couleur, une image ou une animation.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Choisissez où réaliser l'enquête.",
|
||||
"city": "Ville",
|
||||
"close_survey_on_response_limit": "Fermer l'enquête sur la limite de réponse",
|
||||
"code": "Code",
|
||||
"color": "Couleur",
|
||||
"column_used_in_logic_error": "Cette colonne est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.",
|
||||
"columns": "Colonnes",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Personnaliser le logo de l'enquête",
|
||||
"darken_or_lighten_background_of_your_choice": "Assombrir ou éclaircir l'arrière-plan de votre choix.",
|
||||
"days_before_showing_this_survey_again": "ou plus de jours doivent s'écouler entre le dernier sondage affiché et l'affichage de ce sondage.",
|
||||
"default_language": "Langue par défaut",
|
||||
"delete_anyways": "Supprimer quand même",
|
||||
"delete_block": "Supprimer le bloc",
|
||||
"delete_choice": "Supprimer l'option",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Dupliquer la question",
|
||||
"edit_link": "Modifier le lien",
|
||||
"edit_recall": "Modifier le rappel",
|
||||
"edit_translations": "Modifier les traductions {lang}",
|
||||
"element_not_found": "Question non trouvée",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permettre aux répondants de changer de langue à tout moment. Nécessite au moins 2 langues actives.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "La protection contre le spam utilise reCAPTCHA v3 pour filtrer les réponses indésirables.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Permettre aux répondants d'écrire des réponses plus longues et sur plusieurs lignes.",
|
||||
"lower_label": "Étiquette inférieure",
|
||||
"manage_languages": "Gérer les langues",
|
||||
"manage_translations": "Gérer les traductions",
|
||||
"matrix_all_fields": "Tous les champs",
|
||||
"matrix_rows": "Lignes",
|
||||
"max_file_size": "Taille maximale du fichier",
|
||||
"max_file_size_limit_is": "La limite de taille maximale du fichier est",
|
||||
"missing_first": "Manquantes en premier",
|
||||
"move_question_to_block": "Déplacer la question vers le bloc",
|
||||
"multiply": "Multiplier *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Nécessaire pour une instance Cal.com auto-hébergée",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Libellé du bouton « Suivant »",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Aucun champ caché pour le moment. Ajoutez le premier ci-dessous.",
|
||||
"no_images_found_for": "Aucune image trouvée pour ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Aucune langue trouvée. Ajoutez la première pour commencer.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Aucune langue d'enquête trouvée dans cet espace de travail. Veuillez en ajouter une pour commencer.",
|
||||
"no_option_found": "Aucune option trouvée",
|
||||
"no_recall_items_found": "Aucun élément de rappel trouvé",
|
||||
"no_variables_yet_add_first_one_below": "Aucune variable pour le moment. Ajoutez la première ci-dessous.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Veuillez entrer une URL valide (par exemple, https://example.com)",
|
||||
"please_set_a_survey_trigger": "Veuillez définir un déclencheur d'enquête.",
|
||||
"please_specify": "Veuillez préciser",
|
||||
"present_your_survey_in_multiple_languages": "Présente ton questionnaire dans plusieurs langues",
|
||||
"prevent_double_submission": "Empêcher la double soumission",
|
||||
"prevent_double_submission_description": "Autoriser uniquement 1 réponse par adresse e-mail",
|
||||
"progress_saved": "Progression enregistrée",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 points",
|
||||
"show_block_settings": "Afficher les paramètres du bloc",
|
||||
"show_button": "Afficher le bouton",
|
||||
"show_in_order": "Afficher dans l'ordre",
|
||||
"show_language_switch": "Afficher le changement de langue",
|
||||
"show_multiple_times": "Afficher un nombre limité de fois",
|
||||
"show_only_once": "Afficher une seule fois",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Aperçu du sondage 👀",
|
||||
"survey_styling": "Style de formulaire",
|
||||
"survey_trigger": "Déclencheur d'enquête",
|
||||
"switch_multi_language_on_to_get_started": "Activez le mode multilingue pour commencer 👉",
|
||||
"target_block_not_found": "Bloc cible non trouvé",
|
||||
"targeted": "Ciblé",
|
||||
"ten_points": "10 points",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Afficher une seule fois, même si la personne ne répond pas.",
|
||||
"then": "Alors",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Cette action supprimera toutes les traductions de cette enquête.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Cela supprimera cette langue et toutes ses traductions de ce questionnaire. Cette action est irréversible.",
|
||||
"three_points": "3 points",
|
||||
"times": "fois",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Pour maintenir la cohérence du placement sur tous les sondages, vous pouvez",
|
||||
"translated": "Traduit",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Déclencher l'enquête lorsqu'une des actions est déclenchée...",
|
||||
"try_lollipop_or_mountain": "Essayez 'sucette' ou 'montagne'...",
|
||||
"type_field_id": "Identifiant de champ de type",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Ne laissez répondre que les personnes ayant une véritable adresse e-mail.",
|
||||
"visibility_and_recontact": "Visibilité et recontact",
|
||||
"visibility_and_recontact_description": "Contrôlez quand cette enquête peut apparaître et à quelle fréquence elle peut réapparaître.",
|
||||
"visible": "Visible",
|
||||
"wait": "Attendre",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Attendez quelques secondes après le déclencheur avant de montrer l'enquête.",
|
||||
"waiting_time_across_surveys": "Période de refroidissement (entre les sondages)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Téléchargement du code QR",
|
||||
"drop_offs": "Dépôts",
|
||||
"drop_offs_tooltip": "Nombre de fois que l'enquête a été commencée mais non terminée.",
|
||||
"failed_to_copy_link": "Échec de la copie du lien",
|
||||
"filter_added_successfully": "Filtre ajouté avec succès",
|
||||
"filter_updated_successfully": "Filtre mis à jour avec succès",
|
||||
"filtered_responses_csv": "Réponses filtrées (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Enquête supprimée avec succès !",
|
||||
"survey_duplicated_successfully": "Enquête dupliquée avec succès.",
|
||||
"survey_duplication_error": "Échec de la duplication de l'enquête.",
|
||||
"templates": {
|
||||
"all_channels": "Tous les canaux",
|
||||
"all_industries": "Tous les secteurs",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Langue ou identifiant de langue en double",
|
||||
"edit_languages": "Modifier les langues",
|
||||
"identifier": "Identifiant (ISO)",
|
||||
"incomplete_translations": "Traductions incomplètes",
|
||||
"language": "Langue",
|
||||
"language_deleted_successfully": "Langue supprimée avec succès",
|
||||
"languages_updated_successfully": "Langues mises à jour avec succès",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Veuillez sélectionner une langue",
|
||||
"remove_language": "Supprimer la langue",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Veuillez supprimer la langue de ces sondages afin de la retirer de l'espace de travail.",
|
||||
"search_items": "Rechercher des éléments",
|
||||
"translate": "Traduire"
|
||||
"search_items": "Rechercher des éléments"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Ajouter une couleur d'arrière-plan",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colore la partie non remplie de la barre.",
|
||||
"advanced_styling_field_track_height": "Hauteur de la piste",
|
||||
"advanced_styling_field_track_height_description": "Contrôle l'épaisseur de la barre de progression.",
|
||||
"advanced_styling_field_upper_label_color": "Couleur de l'étiquette du titre",
|
||||
"advanced_styling_field_upper_label_color_description": "Colore le petit libellé au-dessus des champs de saisie.",
|
||||
"advanced_styling_field_upper_label_size": "Taille de police de l'étiquette du titre",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajuste la taille du petit libellé au-dessus des champs de saisie.",
|
||||
"advanced_styling_field_upper_label_weight": "Graisse de police de l'étiquette du titre",
|
||||
"advanced_styling_field_upper_label_weight_description": "Rend le libellé plus léger ou plus gras.",
|
||||
"advanced_styling_field_upper_label_color": "Couleur de l'étiquette",
|
||||
"advanced_styling_field_upper_label_color_description": "Colore les petites étiquettes au-dessus des champs de saisie et des échelles.",
|
||||
"advanced_styling_field_upper_label_size": "Taille de police de l'étiquette",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajuste la taille des petites étiquettes au-dessus des champs de saisie et des échelles.",
|
||||
"advanced_styling_field_upper_label_weight": "Graisse de police de l'étiquette",
|
||||
"advanced_styling_field_upper_label_weight_description": "Rend les étiquettes plus légères ou plus grasses.",
|
||||
"advanced_styling_section_buttons": "Boutons",
|
||||
"advanced_styling_section_headlines": "Titres et descriptions",
|
||||
"advanced_styling_section_inputs": "Champs de saisie",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Quelle est une chose que nous pourrions améliorer ?",
|
||||
"identify_customer_goals_description": "Mieux comprendre si votre message crée les bonnes attentes quant à la valeur que votre produit apporte.",
|
||||
"identify_customer_goals_name": "Identifier les objectifs des clients",
|
||||
"identify_customer_goals_question_1_choice_1": "Comprendre ma base d'utilisateurs en profondeur",
|
||||
"identify_customer_goals_question_1_choice_2": "Identifier des opportunités de montée en gamme",
|
||||
"identify_customer_goals_question_1_choice_3": "Créer le meilleur produit possible",
|
||||
"identify_customer_goals_question_1_choice_4": "Conquérir le monde pour imposer des choux de Bruxelles au petit-déjeuner à tout le monde",
|
||||
"identify_customer_goals_question_1_headline": "Quel est votre objectif principal pour l'utilisation de $[projectName] ?",
|
||||
"identify_sign_up_barriers_description": "Offrir une remise pour recueillir des informations sur les obstacles à l'inscription.",
|
||||
"identify_sign_up_barriers_name": "Identifier les obstacles à l'inscription",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Obtenez 10 % de réduction",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Aidez-nous à mieux vous comprendre :",
|
||||
"improve_trial_conversion_question_2_button_label": "Suivant",
|
||||
"improve_trial_conversion_question_2_headline": "Désolé d'apprendre cela. Quel était le plus gros problème rencontré avec $[projectName] ?",
|
||||
"improve_trial_conversion_question_3_button_label": "Suivant",
|
||||
"improve_trial_conversion_question_3_headline": "Qu'attendiez-vous de $[projectName] ?",
|
||||
"improve_trial_conversion_question_4_button_label": "Obtenez 20 % de réduction",
|
||||
"improve_trial_conversion_question_4_headline": "Désolé d'apprendre cela ! Bénéficiez de 20 % de réduction sur la première année.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Nous sommes heureux de vous offrir une remise de 20 % sur un plan annuel.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Suivant",
|
||||
"improve_trial_conversion_question_5_headline": "Que souhaitez-vous accomplir ?",
|
||||
"improve_trial_conversion_question_5_subheader": "Veuillez sélectionner l'une des options suivantes :",
|
||||
"improve_trial_conversion_question_5_subheader": "Merci de décrire ci-dessous :",
|
||||
"improve_trial_conversion_question_6_headline": "Comment résolvez-vous votre problème maintenant ?",
|
||||
"improve_trial_conversion_question_6_subheader": "Veuillez nommer des solutions alternatives :",
|
||||
"integration_setup_survey_description": "Évaluez la facilité avec laquelle les utilisateurs peuvent ajouter des intégrations à votre produit. Identifiez les points aveugles.",
|
||||
|
||||
+86
-74
@@ -63,8 +63,8 @@
|
||||
"login_with_email": "Bejelentkezés e-mail-címmel",
|
||||
"lost_access": "Elvesztette a hozzáférést?",
|
||||
"new_to_formbricks": "Új a Formbicksen?",
|
||||
"oauth_account_not_linked_description": "This SSO provider is not linked to an existing Formbricks account. Please sign in with the method you used originally. If that was email and password, complete email verification first if you are prompted.",
|
||||
"oauth_account_not_linked_title": "This SSO sign-in could not be linked",
|
||||
"oauth_account_not_linked_description": "Ez az SSO-szolgáltató nincs összekapcsolva egy meglévő Formbricks-fiókkal. Jelentkezzen be az eredetileg használt módszerrel. Ha ez e-mail és jelszó páros volt, akkor először végezze el az e-mail-ellenőrzést, ha a rendszer erre kéri.",
|
||||
"oauth_account_not_linked_title": "Ezt az SSO-bejelentkezést nem sikerült összekapcsolni",
|
||||
"use_a_backup_code": "Visszaszerzési kód használata"
|
||||
},
|
||||
"saml_connection_error": "Valami probléma történt. A további részletekért nézze meg az alkalmazás konzolját.",
|
||||
@@ -150,8 +150,9 @@
|
||||
"bottom_right": "Jobbra lent",
|
||||
"cancel": "Mégse",
|
||||
"centered_modal": "Középre helyezett kizárólagos",
|
||||
"change_organization": "Szervezet módosítása",
|
||||
"change_workspace": "Munkaterület módosítása",
|
||||
"change_organization": "Szervezet megváltoztatása",
|
||||
"change_workspace": "Munkaterület megváltoztatása",
|
||||
"choice_n": "{{n}}. választás",
|
||||
"choices": "Választási lehetőségek",
|
||||
"choose_environment": "Környezet kiválasztása",
|
||||
"choose_organization": "Szervezet kiválasztása",
|
||||
@@ -165,13 +166,14 @@
|
||||
"close": "Bezárás",
|
||||
"code": "Kód",
|
||||
"collapse_rows": "Sorok összecsukása",
|
||||
"column_n": "{{n}}. oszlop",
|
||||
"completed": "Befejezve",
|
||||
"configuration": "Beállítás",
|
||||
"confirm": "Megerősítés",
|
||||
"connect": "Kapcsolódás",
|
||||
"connect_formbricks": "Kapcsolódás a Formbrickshez",
|
||||
"connected": "Kapcsolódva",
|
||||
"contact": "Kapcsolat",
|
||||
"contact": "Partner",
|
||||
"contacts": "Partnerek",
|
||||
"continue": "Folytatás",
|
||||
"copied": "Másolva",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Létrehozta",
|
||||
"customer_success": "Ügyfélsiker",
|
||||
"dark_overlay": "Sötét rávetítés",
|
||||
"data_refreshed_successfully": "Az adatok sikeresen frissítve lettek",
|
||||
"date": "Dátum",
|
||||
"days": "nap",
|
||||
"default": "Alapértelmezett",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Nem sikerült másolni a vágólapra",
|
||||
"failed_to_load_organizations": "Nem sikerült betölteni a szervezeteket",
|
||||
"failed_to_load_workspaces": "Nem sikerült a munkaterületek betöltése",
|
||||
"field_placeholder": "{{field}} helykitöltője",
|
||||
"filter": "Szűrő",
|
||||
"finish": "Befejezés",
|
||||
"first_name": "Keresztnév",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Előállítás",
|
||||
"go_back": "Vissza",
|
||||
"go_to_dashboard": "Ugrás a vezérlőpultra",
|
||||
"headline": "Főcím",
|
||||
"hidden": "Rejtett",
|
||||
"hidden_field": "Rejtett mező",
|
||||
"hidden_fields": "Rejtett mezők",
|
||||
"hide_column": "Oszlop elrejtése",
|
||||
"html": "HTML",
|
||||
"id": "Azonosító",
|
||||
"image": "Kép",
|
||||
"images": "Képek",
|
||||
@@ -267,7 +273,7 @@
|
||||
"invite": "Meghívás",
|
||||
"invite_them": "Meghívó nekik",
|
||||
"javascript_required": "JavaScript szükséges",
|
||||
"javascript_required_description": "A Formbricks használatához JavaScript szükséges. Kérjük, engedélyezze a JavaScriptet a böngésző beállításaiban a folytatáshoz.",
|
||||
"javascript_required_description": "A Formbricks megfelelő működéséhez JavaScript szükséges. Engedélyezze a JavaScriptet a böngésző beállításaiban a folytatáshoz.",
|
||||
"key": "Kulcs",
|
||||
"label": "Címke",
|
||||
"language": "Nyelv",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "hónap",
|
||||
"move_down": "Mozgatás le",
|
||||
"move_up": "Mozgatás fel",
|
||||
"multiple_languages": "Több nyelv",
|
||||
"my_product": "saját termék",
|
||||
"name": "Név",
|
||||
"new": "Új",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Nem található eredmény",
|
||||
"no_results": "Nincs találat",
|
||||
"no_surveys_found": "Nem találhatók kérdőívek.",
|
||||
"no_text_found": "Nem található szöveg",
|
||||
"none_of_the_above": "A fentiek közül egyik sem",
|
||||
"not_authenticated": "Nincs jogosultsága ennek a műveletnek a végrehajtásához.",
|
||||
"not_authorized": "Nincs felhatalmazva",
|
||||
@@ -321,9 +327,9 @@
|
||||
"notifications": "Értesítések",
|
||||
"number": "Szám",
|
||||
"off": "Ki",
|
||||
"offline_all_responses_synced": "Az Ön válasza sikeresen mentésre került.",
|
||||
"offline_syncing_responses": "Az Ön válaszainak szinkronizálása folyamatban…",
|
||||
"offline_you_are_offline": "Ön offline állapotban van. Az Ön válasza a böngészőjében tárolásra került, és mentésre kerül, amint ismét online lesz.",
|
||||
"offline_all_responses_synced": "A válasz sikeresen el lett mentve.",
|
||||
"offline_syncing_responses": "Válaszok szinkronizálása…",
|
||||
"offline_you_are_offline": "Ön nem érhető el. A válasza a böngészőjében van tárolva, és akkor lesz elmentve, ha újra elérhető lesz.",
|
||||
"on": "Be",
|
||||
"only_one_file_allowed": "Csak egy fájl engedélyezett",
|
||||
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Csak tulajdonosok és kezelők hajthatják végre ezt a műveletet.",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Szervezet beállításai",
|
||||
"other": "Egyéb",
|
||||
"other_filters": "Egyéb szűrők",
|
||||
"others": "Mások",
|
||||
"other_placeholder": "Egyéb helykitöltő",
|
||||
"overlay_color": "Rávetítés színe",
|
||||
"overview": "Áttekintés",
|
||||
"password": "Jelszó",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Váltson magasabb csomagra",
|
||||
"powered_by_formbricks": "A gépházban: Formbricks",
|
||||
"preview": "Előnézet",
|
||||
"preview_survey": "Kérdőív előnézete",
|
||||
"privacy": "Adatvédelmi irányelvek",
|
||||
"product_manager": "Termékmenedzser",
|
||||
"production": "Produktív",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "A bizonyos feltételeknek megfelelő résztvevőktől kapott válaszok számának korlátozása.",
|
||||
"read_docs": "Dokumentáció elolvasása",
|
||||
"recipients": "Címzettek",
|
||||
"refresh": "Frissítés",
|
||||
"remove": "Eltávolítás",
|
||||
"remove_from_team": "Eltávolítás a csapatból",
|
||||
"reorder_and_hide_columns": "Oszlopok átrendezése és elrejtése",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Válaszok",
|
||||
"restart": "Újraindítás",
|
||||
"role": "Szerep",
|
||||
"row_n": "{{n}}. sor",
|
||||
"saas": "SaaS",
|
||||
"sales": "Értékesítés",
|
||||
"save": "Mentés",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "A fájltároló nincs beállítva, a feltöltések valószínűleg sikertelenek lesznek",
|
||||
"string": "Szöveg",
|
||||
"styling": "Stíluskészítés",
|
||||
"subheader": "Alcím",
|
||||
"submit": "Elküldés",
|
||||
"summary": "Összegzés",
|
||||
"survey": "Kérdőív",
|
||||
@@ -440,7 +448,7 @@
|
||||
"team_name": "Csapat neve",
|
||||
"team_role": "Csapatszerep",
|
||||
"teams": "Csapatok",
|
||||
"terms_of_service": "Felhasználási feltételek",
|
||||
"terms_of_service": "Használati feltételek",
|
||||
"text": "Szöveg",
|
||||
"time": "Idő",
|
||||
"time_to_finish": "Idő a befejezésig",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "például Formbricks",
|
||||
"workspaces": "Munkaterületek",
|
||||
"years": "év",
|
||||
"you": "Ön",
|
||||
"you_are_downgraded_to_the_community_edition": "Visszaváltott a közösségi kiadásra.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Nincs felhatalmazva ennek a műveletnek a végrehajtásához.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Elérte a(z) {projectLimit} munkaterületből álló korlátot.",
|
||||
@@ -550,7 +557,7 @@
|
||||
"verification_email_heading": "Már majdnem kész vagyunk!",
|
||||
"verification_email_hey": "Helló 👋",
|
||||
"verification_email_if_expired_request_new_token": "Ha lejárt, kérjen új tokent itt:",
|
||||
"verification_email_link_valid_for_24_hours": "A hivatkozás 24 órán keresztül érvényes.",
|
||||
"verification_email_link_valid_for_24_hours": "A hivatkozás 24 óráig érvényes.",
|
||||
"verification_email_request_new_verification": "Új ellenőrzés kérése",
|
||||
"verification_email_subject": "Ellenőrizze az e-mail-címét a Formbricks használatához",
|
||||
"verification_email_survey_name": "Kérdőív neve",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Az új „{dataType}” típusú „{key}” attribútum létrehozva",
|
||||
"attributes_msg_userid_already_exists": "A felhasználó-azonosító már létezik ennél a környezetnél, és nem lett frissítve.",
|
||||
"contact_deleted_successfully": "A partner sikeresen törölve",
|
||||
"contacts_table_refresh": "Partnerek frissítése",
|
||||
"contacts_table_refresh_success": "A partnerek sikeresen frissítve",
|
||||
"create_attribute": "Attribútum létrehozása",
|
||||
"create_new_attribute": "Új attribútum létrehozása",
|
||||
"create_new_attribute_description": "Új attribútum létrehozása szakaszolási célokhoz.",
|
||||
@@ -862,16 +867,16 @@
|
||||
"created_by_third_party": "Harmadik fél által létrehozva",
|
||||
"discord_webhook_not_supported": "A Discord webhorgok jelenleg nem támogatottak.",
|
||||
"empty_webhook_message": "A webhorgai itt fognak megjelenni, amint hozzáadja azokat. ⏲️",
|
||||
"endpoint_bad_gateway_error": "Hibás átjáró (502): Proxy-/átjáróhiba, a szolgáltatás nem érhető el",
|
||||
"endpoint_gateway_timeout_error": "Átjáró időtúllépés (504): Átjáró időtúllépés, a szolgáltatás nem érhető el",
|
||||
"endpoint_internal_server_error": "Belső szerverhiba (500): A szolgáltatás váratlan hibába ütközött",
|
||||
"endpoint_method_not_allowed_error": "A metódus nem engedélyezett (405): A végpont létezik, de nem fogad POST kéréseket",
|
||||
"endpoint_not_found_error": "Nem található (404): A végpont nem létezik",
|
||||
"endpoint_bad_gateway_error": "Hibás átjáró (502): proxy- vagy átjáróhiba, a szolgáltatás nem érhető el",
|
||||
"endpoint_gateway_timeout_error": "Átjáró időtúllépés (504): átjáró időtúllépés, a szolgáltatás nem érhető el",
|
||||
"endpoint_internal_server_error": "Belső kiszolgálóhiba (500): a szolgáltatás váratlan hibába ütközött",
|
||||
"endpoint_method_not_allowed_error": "A módszer nem engedélyezett (405): a végpont létezik, de nem fogad POST-kéréseket",
|
||||
"endpoint_not_found_error": "Nem található (404): a végpont nem létezik",
|
||||
"endpoint_pinged": "Hurrá! Képesek vagyunk pingelni a webhorgot!",
|
||||
"endpoint_pinged_error": "Nem lehet pingelni a webhorgot!",
|
||||
"endpoint_service_unavailable_error": "A szolgáltatás nem érhető el (503): A szolgáltatás átmenetileg nem elérhető",
|
||||
"endpoint_service_unavailable_error": "A szolgáltatás nem érhető el (503): a szolgáltatás átmenetileg nem érhető el",
|
||||
"learn_to_verify": "Tudja meg, hogy kell ellenőrizni a webhorog aláírásait",
|
||||
"no_triggers": "Nincsenek Triggerek",
|
||||
"no_triggers": "Nincsenek aktiválók",
|
||||
"please_check_console": "További részletekért nézze meg a konzolt",
|
||||
"please_enter_a_url": "Adjon meg egy URL-t",
|
||||
"response_created": "Válasz létrehozva",
|
||||
@@ -887,7 +892,7 @@
|
||||
"webhook_created": "Webhorog létrehozva",
|
||||
"webhook_delete_confirmation": "Biztosan törölni szeretné ezt a webhorgot? Ez le fogja állítani a jövőbeli értesítések küldését.",
|
||||
"webhook_deleted_successfully": "A webhorog sikeresen törölve",
|
||||
"webhook_name_placeholder": "Választható: címkézze meg a webhorgot az egyszerű azonosításért",
|
||||
"webhook_name_placeholder": "Elhagyható: címkézze meg a webhorgot az egyszerű azonosításért",
|
||||
"webhook_test_failed_due_to": "A webhorog tesztelése sikertelen a következő miatt:",
|
||||
"webhook_updated_successfully": "A webhorog sikeresen frissítve",
|
||||
"webhook_url_placeholder": "Illessze be azt az URL-t, amelyen az eseményt aktiválni szeretné"
|
||||
@@ -1013,9 +1018,9 @@
|
||||
"plan_change_applied": "A csomag sikeresen frissítve.",
|
||||
"plan_change_scheduled": "A csomagváltoztatás sikeresen ütemezve.",
|
||||
"plan_custom": "Egyéni",
|
||||
"plan_feature_everything_in_hobby": "Minden a Hobbi csomagban",
|
||||
"plan_feature_everything_in_hobby": "Minden a Hobby csomagban",
|
||||
"plan_feature_everything_in_pro": "Minden a Pro csomagban",
|
||||
"plan_hobby": "Hobbi",
|
||||
"plan_hobby": "Hobby",
|
||||
"plan_hobby_description": "Magánszemélyeknek és kis csapatoknak, akik most teszik meg a kezdeti lépéseket a Formbricks Cloud szolgáltatással.",
|
||||
"plan_hobby_feature_responses": "250 válasz/hónap",
|
||||
"plan_hobby_feature_workspaces": "1 munkaterület",
|
||||
@@ -1023,11 +1028,11 @@
|
||||
"plan_pro_description": "Növekvő csapatoknak, akiknek magasabb korlátokra, automatizálásra és dinamikus túllépési lehetőségekre van szükségük.",
|
||||
"plan_pro_feature_responses": "2000 válasz/hónap (dinamikus túllépés)",
|
||||
"plan_pro_feature_workspaces": "3 munkaterület",
|
||||
"plan_scale": "Méretezés",
|
||||
"plan_scale": "Scale",
|
||||
"plan_scale_description": "Nagyobb csapatoknak, amelyeknek több kapacitásra, erősebb irányításra és nagyobb válaszmennyiségre van szükségük.",
|
||||
"plan_scale_feature_responses": "5000 válasz/hónap (dinamikus túllépés)",
|
||||
"plan_scale_feature_workspaces": "5 munkaterület",
|
||||
"plan_selection_description": "Hobbi, Pro és Méretezés csomagok összehasonlítása, majd csomagok közötti váltás közvetlenül a Formbricksben.",
|
||||
"plan_selection_description": "Hobby, Pro és Scale csomagok összehasonlítása, majd csomagok közötti váltás közvetlenül a Formbricksben.",
|
||||
"plan_selection_title": "Csomag kiválasztása",
|
||||
"plan_unknown": "Ismeretlen",
|
||||
"remove_branding": "Márkajel eltávolítása",
|
||||
@@ -1035,7 +1040,7 @@
|
||||
"select_plan_header_subtitle": "Nincs szükség hitelkártyára, nincs kötöttség.",
|
||||
"select_plan_header_title": "Zökkenőmentesen integrált kérdőívek, 100%-ban az Ön márkájához igazítva.",
|
||||
"status_trialing": "Próbaidőszak",
|
||||
"stay_on_hobby_plan": "A Hobbi csomagnál szeretnék maradni",
|
||||
"stay_on_hobby_plan": "A Hobby csomagnál szeretnék maradni",
|
||||
"stripe_setup_incomplete": "A számlázási beállítás befejezetlen",
|
||||
"stripe_setup_incomplete_description": "A számlázási beállítás nem fejeződött be sikeresen. Próbálja meg újra aktiválni az előfizetését.",
|
||||
"subscription": "Előfizetés",
|
||||
@@ -1140,17 +1145,17 @@
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "A Formbricks teljes erejének feloldása. 30 napig ingyen."
|
||||
},
|
||||
"general": {
|
||||
"ai_data_analysis_disabled_for_organization": "Az MI-alapú adatelemzés és adatgazdagítás ki van kapcsolva ennél a szervezetnél.",
|
||||
"ai_data_analysis_enabled": "Adatgazdagítás és elemzés (AI)",
|
||||
"ai_data_analysis_enabled_description": "AI segítségével többet hozhat ki az adataiból, irányítópultokat, diagramokat, jelentéseket és egyebeket állíthat be. Hozzáfér az élményekhez kapcsolódó adatokhoz.",
|
||||
"ai_enabled": "Formbricks AI",
|
||||
"ai_enabled_description": "AI-alapú funkciók kezelése ehhez a szervezethez.",
|
||||
"ai_data_analysis_disabled_for_organization": "Az MI-adatelemzés le van tiltva ennél a szervezetnél.",
|
||||
"ai_data_analysis_enabled": "Adatgazdagítás és -elemzés (MI)",
|
||||
"ai_data_analysis_enabled_description": "Mesterséges intelligencia ahhoz, hogy többet hozzon ki az adataiból. Vezérlőpultok, diagramok, jelentések és még sok más beállítása. Az élményadatokra is kiterjed.",
|
||||
"ai_enabled": "Formbricks MI",
|
||||
"ai_enabled_description": "MI-alapú funkciók kezelése ennél a szervezetnél.",
|
||||
"ai_features_not_enabled_for_organization": "Az MI-funkciók nincsenek engedélyezve ennél a szervezetnél.",
|
||||
"ai_instance_not_configured": "Az MI példányszinten, környezeti változókkal van konfigurálva. Kérd meg a rendszergazdát, hogy állítsa be az AI_PROVIDER értékét, a szolgáltató hitelesítő adatait és a megfelelő modelllistát, mielőtt engedélyezné az MI-funkciókat.",
|
||||
"ai_settings_updated_successfully": "AI beállítások sikeresen frissítve",
|
||||
"ai_smart_tools_disabled_for_organization": "Az MI intelligens funkciói ki vannak kapcsolva ennél a szervezetnél.",
|
||||
"ai_smart_tools_enabled": "Intelligens funkciók (AI)",
|
||||
"ai_smart_tools_enabled_description": "AI segítségével kevesebb idő alatt többet érhet el. Soha nem fér hozzá a Formbricks által gyűjtött adatokhoz. Csak például felmérések más nyelvekre történő fordításához használatos.",
|
||||
"ai_instance_not_configured": "Az MI példányszinten van beállítva környezeti változókon keresztül. Kérje meg az adminisztrátort, hogy állítsa be az AI_PROVIDER, AI_MODEL és a hozzájuk tartozó szolgáltató hitelesítési adatait, mielőtt engedélyezné az MI-funkciókat.",
|
||||
"ai_settings_updated_successfully": "Az MI-beállítások sikeresen frissítve",
|
||||
"ai_smart_tools_disabled_for_organization": "Az MI intelligens eszközei le vannak tiltva ennél a szervezetnél.",
|
||||
"ai_smart_tools_enabled": "Intelligens funkcionalitás (MI)",
|
||||
"ai_smart_tools_enabled_description": "Mesterséges intelligencia ahhoz, hogy segítsen Önnek többet elérni kevesebb idő alatt. Soha sem érinti a Formbricks segítségével gyűjtött adatokat. Csak például a kérdőívek más nyelvekre történő fordításához kerül felhasználásra.",
|
||||
"bulk_invite_warning_description": "Az ingyenes csomagban az összes szervezeti tag mindig a „Tulajdonos” szerephez van hozzárendelve.",
|
||||
"cannot_delete_only_organization": "Ez az egyetlen szervezete, nem lehet törölni. Először hozzon létre egy új szervezetet.",
|
||||
"cannot_leave_only_organization": "Nem hagyhatja el ezt a szervezetet, mivel ez az egyetlen szervezete. Először hozzon létre egy új szervezetet.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "A szervezete meghívási hivatkozása készen áll!",
|
||||
"organization_name": "Szervezet neve",
|
||||
"organization_name_description": "Adjon a szervezetének egy leíró nevet.",
|
||||
"organization_name_placeholder": "például Pindúr pandúrok",
|
||||
"organization_name_placeholder": "például Acme Inc.",
|
||||
"organization_name_updated_successfully": "A szervezet neve sikeresen frissítve",
|
||||
"organization_settings": "Szervezet beállításai",
|
||||
"please_add_a_logo": "Adjon hozzá egy logót",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Mindent beállított! Ideje létrehozni az első kérdőívet",
|
||||
"alphabetical": "Ábécé-sorrend",
|
||||
"copy_survey": "Kérdőív másolása",
|
||||
"copy_survey_description": "A kérdőív másolása egy másik környezetbe",
|
||||
"copy_survey_error": "Nem sikerült másolni a kérdőívet",
|
||||
"copy_survey_link_to_clipboard": "Kérdőív hivatkozásának másolása a vágólapra",
|
||||
"copy_survey_partially_success": "{success} kérdőív sikeresen másolva, {error} sikertelen.",
|
||||
"copy_survey_success": "A kérdőív sikeresen másolva",
|
||||
"delete_survey_and_responses_warning": "Biztosan törölni szeretné ezt a kérdőívet és az összes válaszát?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Válassza ki a kérdőív alapértelmezett nyelvét:",
|
||||
"2_activate_translation_for_specific_languages": "2. Aktiválja a fordítást bizonyos nyelvekhez:",
|
||||
"activate_translations": "Fordítások aktiválása",
|
||||
"add": "Hozzáadás +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Késleltetés hozzáadása vagy a kérdőív automatikus lezárása",
|
||||
"add_a_four_digit_pin": "Négy számjegyű PIN-kód hozzáadása",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "= hozzárendelése",
|
||||
"audience": "Közönség",
|
||||
"auto_close_on_inactivity": "Automatikus lezárás tétlenségnél",
|
||||
"auto_progress_rating_and_nps": "Értékelés és valós ügyfél-támogatottsági érték kérdések automatikus feldolgozása",
|
||||
"auto_progress_rating_and_nps_description": "Automatikus továbblépés az egykérdéses blokkokban. A kötelező kérdések elrejtik a „Tovább” gombot, kivéve ha az „Egyéb” van kiválasztva.",
|
||||
"auto_save_disabled": "Az automatikus mentés letiltva",
|
||||
"auto_save_disabled_tooltip": "A kérdőív csak akkor kerül automatikusan mentésre, ha piszkozatban van. Ez biztosítja, hogy a nyilvános kérdőívek ne legyenek véletlenül frissítve.",
|
||||
"auto_save_on": "Automatikus mentés bekapcsolva",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "A változtatások következetlenségekhez vezetnek",
|
||||
"change_anyway": "Változtatás mindenképp",
|
||||
"change_background": "Háttér megváltoztatása",
|
||||
"change_default": "Alapértelmezett megváltoztatása",
|
||||
"change_question_type": "Kérdés típusának megváltoztatása",
|
||||
"change_survey_type": "A kérdőív típusának megváltoztatása befolyásolja a meglévő hozzáférést",
|
||||
"change_the_background_to_a_color_image_or_animation": "A háttér megváltoztatása színre, képre vagy animációra.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Annak kiválasztása, hogy hol fusson a kérdőív.",
|
||||
"city": "Város",
|
||||
"close_survey_on_response_limit": "Kérdőív lezárása a válaszkorlátnál",
|
||||
"code": "Kód",
|
||||
"color": "Szín",
|
||||
"column_used_in_logic_error": "Ez az oszlop használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.",
|
||||
"columns": "Oszlopok",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "A kérdőív logójának személyre szabása",
|
||||
"darken_or_lighten_background_of_your_choice": "A választási lehetőség hátterének sötétítése vagy világosítása.",
|
||||
"days_before_showing_this_survey_again": "vagy több napnak kell eltelnie az utolsó megjelenített kérdőív és ezen kérdőív megjelenése között.",
|
||||
"default_language": "Alapértelmezett nyelv",
|
||||
"delete_anyways": "Törlés mindenképp",
|
||||
"delete_block": "Blokk törlése",
|
||||
"delete_choice": "Választási lehetőség törlése",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Kérdés kettőzése",
|
||||
"edit_link": "Hivatkozás szerkesztése",
|
||||
"edit_recall": "Visszahívás szerkesztése",
|
||||
"edit_translations": "{lang} fordítások szerkesztése",
|
||||
"element_not_found": "A kérdés nem található",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Lehetővé tétel a válaszadóknak, hogy bármikor nyelvet váltsanak. Legalább 2 aktív nyelvet igényel.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "A szemét elleni védekezés a reCAPTCHA v3-at használja a kéretlen válaszok kiszűréséhez.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Lehetővé tétel a válaszadóknak, hogy hosszabb, többsoros válaszokat írjanak.",
|
||||
"lower_label": "Alsó címke",
|
||||
"manage_languages": "Nyelvek kezelése",
|
||||
"manage_translations": "Fordítások kezelése",
|
||||
"matrix_all_fields": "Összes mező",
|
||||
"matrix_rows": "Sorok",
|
||||
"max_file_size": "Legnagyobb fájlméret",
|
||||
"max_file_size_limit_is": "A legnagyobb fájlméretkorlát",
|
||||
"missing_first": "Hiányzik az első",
|
||||
"move_question_to_block": "Kérdés áthelyezése egy blokkba",
|
||||
"multiply": "Szorzás *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Saját üzemeltetésű Cal.com-példányhoz szükséges",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "A „Következő” gomb címkéje",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Még nincsenek rejtett mezők. Adja hozzá az elsőt lent.",
|
||||
"no_images_found_for": "Nem találhatók képek a(z) „{query}” lekérdezéshez",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nem találhatók nyelvek. Adja hozzá az elsőt a kezdéshez.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nem találhatók kérdőívnyelvek ezen a munkaterületen. Adja hozzá egyet a kezdéshez.",
|
||||
"no_option_found": "Nem található lehetőség",
|
||||
"no_recall_items_found": "Nem találhatók visszahívási elemek",
|
||||
"no_variables_yet_add_first_one_below": "Még nincsenek változók. Adja hozzá az elsőt lent.",
|
||||
@@ -1622,7 +1627,7 @@
|
||||
"only_people_who_match_your_targeting_can_be_surveyed": "Csak azok a személyek kérdezhetők meg, akik megfelelnek a célcsoportnak.",
|
||||
"option_idx": "{choiceIndex}. lehetőség",
|
||||
"option_used_in_logic_error": "Ez a lehetőség használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.",
|
||||
"optional": "Választható",
|
||||
"optional": "Elhagyható",
|
||||
"options": "Beállítások*",
|
||||
"options_used_in_logic_bulk_error": "A következő lehetőségek használatban vannak a logikában: {questionIndexes}. Először távolítsa el azokat a logikából.",
|
||||
"override_theme_with_individual_styles_for_this_survey": "A téma felülírása egyéni stílusokkal ennél a kérdőívnél.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Adjon meg egy érvényes URL-t (például https://example.com)",
|
||||
"please_set_a_survey_trigger": "Állítson be kérdőív-aktiválót",
|
||||
"please_specify": "Adja meg",
|
||||
"present_your_survey_in_multiple_languages": "A kérdőív bemutatása több nyelven",
|
||||
"prevent_double_submission": "Kettős beküldés megakadályozása",
|
||||
"prevent_double_submission_description": "E-mail-címenként csak 1 válasz engedélyezése",
|
||||
"progress_saved": "Folyamat elmentve",
|
||||
@@ -1706,8 +1712,8 @@
|
||||
"response_limit_needs_to_exceed_number_of_received_responses": "A válaszkorlátnak meg kell haladnia a kapott válaszok számát ({responseCount}).",
|
||||
"response_limits_redirections_and_more": "Válaszkorlátok, átirányítások és egyebek.",
|
||||
"response_options": "Válasz beállításai",
|
||||
"reverse_order_occasionally": "Sorrend alkalmi megfordítása",
|
||||
"reverse_order_occasionally_except_last": "Sorrend alkalmi megfordítása az utolsó kivételével",
|
||||
"reverse_order_occasionally": "Időnként fordított sorrendben",
|
||||
"reverse_order_occasionally_except_last": "Időnként fordított sorrendben, kivéve az utolsó",
|
||||
"roundness": "Kerekesség",
|
||||
"roundness_description": "Annak vezérlése, hogy a sarkok mennyire legyenek lekerekítve.",
|
||||
"row_used_in_logic_error": "Ez a sor használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 pont",
|
||||
"show_block_settings": "Blokkbeállítások megjelenítése",
|
||||
"show_button": "Gomb megjelenítése",
|
||||
"show_in_order": "Megjelenítés sorrendben",
|
||||
"show_language_switch": "Nyelvválasztó megjelenítése",
|
||||
"show_multiple_times": "Megjelenítés korlátozott számú alkalommal",
|
||||
"show_only_once": "Megjelenítés csak egyszer",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Kérdőív előnézete 👀",
|
||||
"survey_styling": "Kérdőív stílusának beállítása",
|
||||
"survey_trigger": "Kérdőív aktiválója",
|
||||
"switch_multi_language_on_to_get_started": "Kapcsolja be a többnyelvűséget a kezdéshez 👉",
|
||||
"target_block_not_found": "A célblokk nem található",
|
||||
"targeted": "Célzott",
|
||||
"ten_points": "10 pont",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Megjelenítés egyetlen alkalommal, még akkor is, ha nem válaszolnak.",
|
||||
"then": "Azután",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Ez a művelet eltávolítja az összes fordítást ebből a kérdőívből.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Ez el fogja távolítani ezt a nyelvet és annak összes fordítását ebből a kérdőívből. Ezt a műveletet nem lehet visszavonni.",
|
||||
"three_points": "3 pont",
|
||||
"times": "alkalom",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Ahhoz, hogy következetesen megtartsa az elhelyezést az összes kérdőívnél, az alábbiakat teheti:",
|
||||
"translated": "Lefordítva",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "A kérdőív aktiválása, ha a műveletek egyikét elindítják…",
|
||||
"try_lollipop_or_mountain": "A „nyalóka” vagy „hegy” kipróbálása…",
|
||||
"type_field_id": "Mezőazonosító beírása",
|
||||
@@ -1786,11 +1794,11 @@
|
||||
"upper_label": "Felső címke",
|
||||
"url_filters": "URL szűrők",
|
||||
"url_not_supported": "Az URL nem támogatott",
|
||||
"validate_id_duplicate": "A(z) {type} azonosító már létezik a kérdések, rejtett mezők vagy változók között.",
|
||||
"validate_id_empty": "Kérjük, adjon meg egy {type} azonosítót.",
|
||||
"validate_id_invalid_chars": "A(z) {type} azonosító nem engedélyezett. Kérjük, csak alfanumerikus karaktereket, kötőjeleket vagy aláhúzásjeleket használjon.",
|
||||
"validate_id_no_spaces": "A(z) {type} azonosító nem tartalmazhat szóközöket. Kérjük, távolítsa el a szóközöket.",
|
||||
"validate_id_reserved": "A(z) {type} azonosító \"{field}\" nem engedélyezett. Ez egy fenntartott kulcsszó.",
|
||||
"validate_id_duplicate": "A {type} azonosítója már létezik a kérdésekben, rejtett mezőkben vagy változókban.",
|
||||
"validate_id_empty": "Adja meg egy {type} azonosítóját.",
|
||||
"validate_id_invalid_chars": "A {type} azonosítója nem engedélyezett. Használjon csak alfanumerikus karaktereket, kötőjeleket vagy aláhúzásjeleket.",
|
||||
"validate_id_no_spaces": "A {type} azonosítója nem tartalmazhat szóközöket. Távolítsa el a szóközöket.",
|
||||
"validate_id_reserved": "A {type} „{field}” azonosítója nem engedélyezett. Ez egy foglalt kulcsszó.",
|
||||
"validation": {
|
||||
"add_validation_rule": "Ellenőrzési szabály hozzáadása",
|
||||
"answer_all_rows": "Válaszoljon az összes sorra",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Csak valódi e-mail-címmel rendelkező személyek válaszolhassanak.",
|
||||
"visibility_and_recontact": "Láthatóság és újbóli kapcsolatfelvétel",
|
||||
"visibility_and_recontact_description": "Annak vezérlése, hogy ez a kérdőív mikor jelenhet meg és milyen gyakran jelenhet meg újra.",
|
||||
"visible": "Látható",
|
||||
"wait": "Várakozás",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Várakozás néhány másodpercig az aktiválás után, mielőtt megjelenítené a kérdőívet",
|
||||
"waiting_time_across_surveys": "Várakozási időszak (kérdőívek között)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "QR-kód letöltése",
|
||||
"drop_offs": "Megszakítások",
|
||||
"drop_offs_tooltip": "A kérdőív elkezdési, de be nem fejezési alkalmainak száma.",
|
||||
"failed_to_copy_link": "Nem sikerült a hivatkozás másolása",
|
||||
"filter_added_successfully": "A szűrő sikeresen hozzáadva",
|
||||
"filter_updated_successfully": "A szűrő sikeresen frissítve",
|
||||
"filtered_responses_csv": "Szűrt válaszok (CSV)",
|
||||
@@ -2131,7 +2139,7 @@
|
||||
"this_quarter": "Ez a negyedév",
|
||||
"this_year": "Ez az év",
|
||||
"time_to_complete": "Kitöltéshez szükséges idő",
|
||||
"ttc_survey_tooltip": "A felmérés kitöltésének átlagos ideje.",
|
||||
"ttc_survey_tooltip": "A kérdőív megválaszolásának átlagos ideje.",
|
||||
"ttc_tooltip": "A kérdés megválaszolásának átlagos ideje.",
|
||||
"unknown_question_type": "Ismeretlen kérdéstípus",
|
||||
"use_personal_links": "Személyes hivatkozások használata",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "A kérdőív sikeresen törölve",
|
||||
"survey_duplicated_successfully": "A kérdőív sikeresen megkettőzve",
|
||||
"survey_duplication_error": "Nem sikerült megkettőzni a kérdőívet.",
|
||||
"templates": {
|
||||
"all_channels": "Összes csatorna",
|
||||
"all_industries": "Összes iparág",
|
||||
@@ -2221,7 +2228,7 @@
|
||||
"languages": {
|
||||
"add_language": "Nyelv hozzáadása",
|
||||
"alias": "Álnév",
|
||||
"alias_tooltip": "Az álnév egy alternatív név a hivatkozás-kérdőívekben és az SDK-ban lévő nyelv azonosításához (választható)",
|
||||
"alias_tooltip": "Az álnév egy alternatív név a hivatkozás-kérdőívekben és az SDK-ban lévő nyelv azonosításához (elhagyható)",
|
||||
"cannot_remove_language_warning": "Nem tudja eltávolítani ezt a nyelvet, mert még mindig használatban van ezekben a kérdőívekben:",
|
||||
"conflict_between_identifier_and_alias": "Ütközés van egy hozzáadott nyelv azonosítója és az álnevei egyike között. Az álnevek és az azonosítók nem lehetnek azonosak.",
|
||||
"conflict_between_selected_alias_and_another_language": "Ütközés van a kiválasztott álnév és egy másik, ezzel az azonosítóval rendelkező nyelv között. A következetlenségek elkerülése érdekében ezzel az azonosítóval adja hozzá a nyelvet a munkaterületéhez.",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Kettőzött nyelv vagy nyelvazonosító",
|
||||
"edit_languages": "Nyelvek szerkesztése",
|
||||
"identifier": "Azonosító (ISO)",
|
||||
"incomplete_translations": "Befejezetlen fordítások",
|
||||
"language": "Nyelv",
|
||||
"language_deleted_successfully": "A nyelv sikeresen törölve",
|
||||
"languages_updated_successfully": "A nyelvek sikeresen frissítve",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Válasszon egy nyelvet",
|
||||
"remove_language": "Nyelv eltávolítása",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Távolítsa el a nyelvet ezekből a kérdőívekből, hogy eltávolítsa azt a munkaterületről.",
|
||||
"search_items": "Elemek keresése",
|
||||
"translate": "Fordítás"
|
||||
"search_items": "Elemek keresése"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Háttérszín hozzáadása",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Kiszínezi a sáv kitöltetlen részét.",
|
||||
"advanced_styling_field_track_height": "Követés magassága",
|
||||
"advanced_styling_field_track_height_description": "A folyamatjelző vastagságát vezérli.",
|
||||
"advanced_styling_field_upper_label_color": "Címsor címkéjének színe",
|
||||
"advanced_styling_field_upper_label_color_description": "Kiszínezi a beviteli mezők fölötti kis címkéket.",
|
||||
"advanced_styling_field_upper_label_size": "Címsor címkéjének betűmérete",
|
||||
"advanced_styling_field_upper_label_size_description": "Átméretezi a beviteli mezők fölötti kis címkéket.",
|
||||
"advanced_styling_field_upper_label_weight": "Címsor címkéjének betűvastagsága",
|
||||
"advanced_styling_field_upper_label_weight_description": "Vékonyabbá vagy vastagabbá teszi a címkét.",
|
||||
"advanced_styling_field_upper_label_color": "Címke színe",
|
||||
"advanced_styling_field_upper_label_color_description": "Kiszínezi a beviteli mezők fölötti kis címkéket és a méretezés címkéit.",
|
||||
"advanced_styling_field_upper_label_size": "Címke betűmérete",
|
||||
"advanced_styling_field_upper_label_size_description": "Átméretezi a beviteli mezők fölötti kis címkéket és a méretezés címkéit.",
|
||||
"advanced_styling_field_upper_label_weight": "Címke betűvastagsága",
|
||||
"advanced_styling_field_upper_label_weight_description": "Vékonyabbá vagy vastagabbá teszi a címkéket.",
|
||||
"advanced_styling_section_buttons": "Gombok",
|
||||
"advanced_styling_section_headlines": "Címsorok és leírások",
|
||||
"advanced_styling_section_inputs": "Beviteli mezők",
|
||||
@@ -2642,7 +2647,7 @@
|
||||
"csat_question_1_headline": "Mennyire valószínű, hogy ezt a(z) $[projectName] projektet ajánlaná egy ismerősnek vagy kollégának?",
|
||||
"csat_question_1_lower_label": "Nem valószínű",
|
||||
"csat_question_1_upper_label": "Nagyon valószínű",
|
||||
"csat_question_2_choice_1": "Részben elégedett",
|
||||
"csat_question_2_choice_1": "Valamelyest elégedett",
|
||||
"csat_question_2_choice_2": "Nagyon elégedett",
|
||||
"csat_question_2_choice_3": "Sem elégedett, sem elégedetlen",
|
||||
"csat_question_2_choice_4": "Valamelyest elégedetlen",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Mi az egyetlen dolog, amelyet jobban csinálhatnánk?",
|
||||
"identify_customer_goals_description": "Jobban megérteni, hogy az üzenetei a termék által nyújtott érték megfelelő elvárásait keltik-e.",
|
||||
"identify_customer_goals_name": "Ügyfélcélok azonosítása",
|
||||
"identify_customer_goals_question_1_choice_1": "A felhasználói bázisom alapos megértése",
|
||||
"identify_customer_goals_question_1_choice_2": "Felülértékesítési lehetőségek azonosítása",
|
||||
"identify_customer_goals_question_1_choice_3": "A lehető legjobb termék elkészítése",
|
||||
"identify_customer_goals_question_1_choice_4": "Világuralom szerezése, hogy mindenki kelbimbót egyen reggelire",
|
||||
"identify_customer_goals_question_1_headline": "Mi az elsődleges célja a(z) $[projectName] használatával?",
|
||||
"identify_sign_up_barriers_description": "Kedvezmény felajánlása a regisztrációs akadályokkal kapcsolatos tapasztalatok gyűjtéséhez.",
|
||||
"identify_sign_up_barriers_name": "Regisztrációs akadályok azonosítása",
|
||||
"identify_sign_up_barriers_question_1_button_label": "10% kedvezmény",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Segítsen nekünk jobban megérteni Önt:",
|
||||
"improve_trial_conversion_question_2_button_label": "Következő",
|
||||
"improve_trial_conversion_question_2_headline": "Sajnálattal halljuk. Mi volt a legnagyobb probléma a(z) $[projectName] projekt használatával?",
|
||||
"improve_trial_conversion_question_3_button_label": "Következő",
|
||||
"improve_trial_conversion_question_3_headline": "Mit vár el a(z) $[projectName] projekttől?",
|
||||
"improve_trial_conversion_question_4_button_label": "20% kedvezmény",
|
||||
"improve_trial_conversion_question_4_headline": "Sajnálattal halljuk! 20% kedvezményt kap az első évre.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Boldogan felajánlunk 20% kedvezményt az éves csomagra.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Következő",
|
||||
"improve_trial_conversion_question_5_headline": "Mit szeretne elérni?",
|
||||
"improve_trial_conversion_question_5_subheader": "Válassza ki a következő lehetőségek egyikét:",
|
||||
"improve_trial_conversion_question_5_subheader": "Írja le az alábbiakban:",
|
||||
"improve_trial_conversion_question_6_headline": "Hogyan oldja meg a problémáját most?",
|
||||
"improve_trial_conversion_question_6_subheader": "Nevezzen meg alternatív megoldásokat:",
|
||||
"integration_setup_survey_description": "Annak kiértékelése, hogy a felhasználók mennyire könnyen tudnak integrációkat hozzáadni a termékéhez. A vakfoltok megtalálása.",
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "中央モーダル",
|
||||
"change_organization": "組織を変更",
|
||||
"change_workspace": "ワークスペースを変更",
|
||||
"choice_n": "選択肢 {{n}}",
|
||||
"choices": "選択肢",
|
||||
"choose_environment": "環境を選択",
|
||||
"choose_organization": "組織を選択",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "閉じる",
|
||||
"code": "コード",
|
||||
"collapse_rows": "行を非表示",
|
||||
"column_n": "列 {{n}}",
|
||||
"completed": "完了",
|
||||
"configuration": "設定",
|
||||
"confirm": "確認",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "作成者",
|
||||
"customer_success": "カスタマーサクセス",
|
||||
"dark_overlay": "暗いオーバーレイ",
|
||||
"data_refreshed_successfully": "データが正常に更新されました",
|
||||
"date": "日付",
|
||||
"days": "日",
|
||||
"default": "デフォルト",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "クリップボードへのコピーに失敗しました",
|
||||
"failed_to_load_organizations": "組織の読み込みに失敗しました",
|
||||
"failed_to_load_workspaces": "ワークスペースの読み込みに失敗しました",
|
||||
"field_placeholder": "{{field}} プレースホルダー",
|
||||
"filter": "フィルター",
|
||||
"finish": "完了",
|
||||
"first_name": "名",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "生成",
|
||||
"go_back": "戻る",
|
||||
"go_to_dashboard": "ダッシュボードへ移動",
|
||||
"headline": "見出し",
|
||||
"hidden": "非表示",
|
||||
"hidden_field": "非表示フィールド",
|
||||
"hidden_fields": "非表示フィールド",
|
||||
"hide_column": "列を非表示",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "画像",
|
||||
"images": "画像",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "ヶ月",
|
||||
"move_down": "下に移動",
|
||||
"move_up": "上に移動",
|
||||
"multiple_languages": "多言語",
|
||||
"my_product": "マイプロダクト",
|
||||
"name": "名前",
|
||||
"new": "新規",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "結果が見つかりません",
|
||||
"no_results": "結果なし",
|
||||
"no_surveys_found": "フォームが見つかりません。",
|
||||
"no_text_found": "テキストが見つかりません",
|
||||
"none_of_the_above": "いずれも該当しません",
|
||||
"not_authenticated": "このアクションを実行するための認証がされていません。",
|
||||
"not_authorized": "権限がありません",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "組織設定",
|
||||
"other": "その他",
|
||||
"other_filters": "その他のフィルター",
|
||||
"others": "その他",
|
||||
"other_placeholder": "その他のプレースホルダー",
|
||||
"overlay_color": "オーバーレイの色",
|
||||
"overview": "概要",
|
||||
"password": "パスワード",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "プランをアップグレードしてください",
|
||||
"powered_by_formbricks": "Powered by Formbricks",
|
||||
"preview": "プレビュー",
|
||||
"preview_survey": "フォームをプレビュー",
|
||||
"privacy": "プライバシーポリシー",
|
||||
"product_manager": "プロダクトマネージャー",
|
||||
"production": "本番",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "特定の基準を満たす参加者からの回答数を制限する",
|
||||
"read_docs": "ドキュメントを読む",
|
||||
"recipients": "受信者",
|
||||
"refresh": "更新",
|
||||
"remove": "削除",
|
||||
"remove_from_team": "チームから削除",
|
||||
"reorder_and_hide_columns": "列の並び替えと非表示",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "回答",
|
||||
"restart": "再開",
|
||||
"role": "役割",
|
||||
"row_n": "行 {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "セールス",
|
||||
"save": "保存",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "ファイルストレージが設定されていないため、アップロードは失敗する可能性があります",
|
||||
"string": "テキスト",
|
||||
"styling": "スタイル",
|
||||
"subheader": "小見出し",
|
||||
"submit": "送信",
|
||||
"summary": "概要",
|
||||
"survey": "フォーム",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "例: Formbricks",
|
||||
"workspaces": "ワークスペース",
|
||||
"years": "年",
|
||||
"you": "あなた",
|
||||
"you_are_downgraded_to_the_community_edition": "コミュニティ版にダウングレードされました。",
|
||||
"you_are_not_authorized_to_perform_this_action": "このアクションを実行する権限がありません。",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "ワークスペースの上限である{projectLimit}件に達しました。",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "新しい属性“{key}”を型“{dataType}”で作成しました",
|
||||
"attributes_msg_userid_already_exists": "この環境にはすでにユーザーIDが存在するため、更新されませんでした。",
|
||||
"contact_deleted_successfully": "連絡先を正常に削除しました",
|
||||
"contacts_table_refresh": "連絡先を更新",
|
||||
"contacts_table_refresh_success": "連絡先を正常に更新しました",
|
||||
"create_attribute": "属性を作成",
|
||||
"create_new_attribute": "新しい属性を作成",
|
||||
"create_new_attribute_description": "セグメンテーション用の新しい属性を作成します。",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "組織の招待リンクが準備できました!",
|
||||
"organization_name": "組織名",
|
||||
"organization_name_description": "組織に分かりやすい名前を付けます。",
|
||||
"organization_name_placeholder": "例: パワーパフガールズ",
|
||||
"organization_name_placeholder": "例: Acme Inc.",
|
||||
"organization_name_updated_successfully": "組織名を正常に更新しました",
|
||||
"organization_settings": "組織設定",
|
||||
"please_add_a_logo": "ロゴを追加してください",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "すべての準備が整いました!最初のフォームを作成しましょう",
|
||||
"alphabetical": "アルファベット順",
|
||||
"copy_survey": "フォームをコピー",
|
||||
"copy_survey_description": "このフォームを別の環境にコピー",
|
||||
"copy_survey_error": "フォームのコピーに失敗しました",
|
||||
"copy_survey_link_to_clipboard": "フォームのリンクをクリップボードにコピー",
|
||||
"copy_survey_partially_success": "{success} 個のフォームが正常にコピーされ、{error} 個が失敗しました。",
|
||||
"copy_survey_success": "フォームを正常にコピーしました!",
|
||||
"delete_survey_and_responses_warning": "本当にこのフォームとすべての回答を削除しますか?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. このフォームのデフォルト言語を選択してください:",
|
||||
"2_activate_translation_for_specific_languages": "2. 特定の言語の翻訳を有効にしてください:",
|
||||
"activate_translations": "翻訳を有効化",
|
||||
"add": "追加 +",
|
||||
"add_a_delay_or_auto_close_the_survey": "遅延を追加するか、フォームを自動的に閉じる",
|
||||
"add_a_four_digit_pin": "4桁のPINを追加",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "割り当て =",
|
||||
"audience": "オーディエンス",
|
||||
"auto_close_on_inactivity": "非アクティブ時に自動閉鎖",
|
||||
"auto_progress_rating_and_nps": "評価とNPSの質問を自動進行",
|
||||
"auto_progress_rating_and_nps_description": "単一質問ブロックで自動的に次へ進みます。必須質問では「次へ」ボタンが非表示になりますが、「その他」が選択された場合は表示されます。",
|
||||
"auto_save_disabled": "自動保存が無効",
|
||||
"auto_save_disabled_tooltip": "アンケートは下書き状態の時のみ自動保存されます。これにより、公開中のアンケートが意図せず更新されることを防ぎます。",
|
||||
"auto_save_on": "自動保存オン",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "変更は不整合を引き起こします",
|
||||
"change_anyway": "とにかく変更",
|
||||
"change_background": "背景を変更",
|
||||
"change_default": "デフォルトを変更",
|
||||
"change_question_type": "質問の種類を変更",
|
||||
"change_survey_type": "フォームの種類を変更すると、既存のアクセスに影響します",
|
||||
"change_the_background_to_a_color_image_or_animation": "背景を色、画像、またはアニメーションに変更します。",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "フォームを実行する場所を選択してください。",
|
||||
"city": "市区町村",
|
||||
"close_survey_on_response_limit": "回答数の上限でフォームを閉じる",
|
||||
"code": "コード",
|
||||
"color": "色",
|
||||
"column_used_in_logic_error": "この列は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
|
||||
"columns": "列",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "アンケートのロゴをカスタマイズする",
|
||||
"darken_or_lighten_background_of_your_choice": "お好みの背景を暗くしたり明るくしたりします。",
|
||||
"days_before_showing_this_survey_again": "最後に表示されたアンケートとこのアンケートを表示するまでに、この日数以上の期間を空ける必要があります。",
|
||||
"default_language": "デフォルト言語",
|
||||
"delete_anyways": "削除する",
|
||||
"delete_block": "ブロックを削除",
|
||||
"delete_choice": "選択肢を削除",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "質問を複製",
|
||||
"edit_link": "編集 リンク",
|
||||
"edit_recall": "リコールを編集",
|
||||
"edit_translations": "{lang} 翻訳を編集",
|
||||
"element_not_found": "質問が見つかりません",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "回答者がいつでも言語を切り替えられるようにします。最低2つのアクティブな言語が必要です。",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "スパム対策はreCAPTCHA v3を使用してスパム回答をフィルタリングします。",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "回答者が長文の複数行の回答を書けるようにします。",
|
||||
"lower_label": "下限ラベル",
|
||||
"manage_languages": "言語を管理",
|
||||
"manage_translations": "翻訳を管理",
|
||||
"matrix_all_fields": "すべてのフィールド",
|
||||
"matrix_rows": "行",
|
||||
"max_file_size": "最大ファイルサイズ",
|
||||
"max_file_size_limit_is": "最大ファイルサイズの上限は",
|
||||
"missing_first": "未翻訳を優先",
|
||||
"move_question_to_block": "質問をブロックに移動",
|
||||
"multiply": "乗算 *",
|
||||
"needed_for_self_hosted_cal_com_instance": "セルフホストのCal.comインスタンスに必要",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "「次へ」ボタンのラベル",
|
||||
"no_hidden_fields_yet_add_first_one_below": "まだ非表示フィールドがありません。以下で最初のものを追加してください。",
|
||||
"no_images_found_for": "''{query}'' の画像が見つかりません",
|
||||
"no_languages_found_add_first_one_to_get_started": "言語が見つかりません。始めるには、最初のものを追加してください。",
|
||||
"no_languages_found_add_first_one_to_get_started": "このワークスペースにはアンケート言語が見つかりませんでした。開始するには言語を追加してください。",
|
||||
"no_option_found": "オプションが見つかりません",
|
||||
"no_recall_items_found": "リコール項目が見つかりません",
|
||||
"no_variables_yet_add_first_one_below": "まだ変数がありません。以下で最初のものを追加してください。",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "有効な URL を入力してください (例:https://example.com)",
|
||||
"please_set_a_survey_trigger": "フォームのトリガーを設定してください",
|
||||
"please_specify": "具体的に指定してください",
|
||||
"present_your_survey_in_multiple_languages": "アンケートを複数の言語で表示",
|
||||
"prevent_double_submission": "二重送信を防ぐ",
|
||||
"prevent_double_submission_description": "メールアドレスごとに1つの回答のみを許可する",
|
||||
"progress_saved": "進捗を保存しました",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7点",
|
||||
"show_block_settings": "ブロック設定を表示",
|
||||
"show_button": "ボタンを表示",
|
||||
"show_in_order": "順番に表示",
|
||||
"show_language_switch": "言語切り替えを表示",
|
||||
"show_multiple_times": "限られた回数表示する",
|
||||
"show_only_once": "一度だけ表示",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "アンケートプレビュー 👀",
|
||||
"survey_styling": "フォームのスタイル",
|
||||
"survey_trigger": "フォームのトリガー",
|
||||
"switch_multi_language_on_to_get_started": "多言語機能をオンにして開始 👉",
|
||||
"target_block_not_found": "対象ブロックが見つかりません",
|
||||
"targeted": "ターゲット",
|
||||
"ten_points": "10点",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "回答がなくても1回だけ表示します。",
|
||||
"then": "その後",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "このアクションは、このフォームからすべての翻訳を削除します。",
|
||||
"this_will_remove_the_language_and_all_its_translations": "この言語とすべての翻訳がこのアンケートから削除されます。この操作は元に戻せません。",
|
||||
"three_points": "3点",
|
||||
"times": "回",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "すべてのフォームの配置を一貫させるために、",
|
||||
"translated": "翻訳済み",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "以下のアクションのいずれかが発火したときにフォームをトリガーします...",
|
||||
"try_lollipop_or_mountain": "「lollipop」や「mountain」を試してみてください...",
|
||||
"type_field_id": "フィールドIDを入力",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "有効なメールアドレスを持つ人のみが回答できるようにする",
|
||||
"visibility_and_recontact": "表示と再接触",
|
||||
"visibility_and_recontact_description": "このフォームがいつ表示され、どのくらいの頻度で再表示できるかをコントロールします。",
|
||||
"visible": "表示",
|
||||
"wait": "待つ",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "トリガーから数秒待ってからフォームを表示します",
|
||||
"waiting_time_across_surveys": "クールダウン期間(アンケート全体)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "QRコードをダウンロード中",
|
||||
"drop_offs": "離脱",
|
||||
"drop_offs_tooltip": "フォームが開始されたが完了しなかった回数。",
|
||||
"failed_to_copy_link": "リンクのコピーに失敗しました",
|
||||
"filter_added_successfully": "フィルターを正常に追加しました",
|
||||
"filter_updated_successfully": "フィルターを正常に更新しました",
|
||||
"filtered_responses_csv": "フィルター済み回答 (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "フォームを正常に削除しました!",
|
||||
"survey_duplicated_successfully": "フォームを正常に複製しました。",
|
||||
"survey_duplication_error": "フォームの複製に失敗しました。",
|
||||
"templates": {
|
||||
"all_channels": "すべてのチャネル",
|
||||
"all_industries": "すべての業界",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "重複する言語または言語ID",
|
||||
"edit_languages": "言語を編集",
|
||||
"identifier": "識別子(ISO)",
|
||||
"incomplete_translations": "未完了の翻訳",
|
||||
"language": "言語",
|
||||
"language_deleted_successfully": "言語を正常に削除しました",
|
||||
"languages_updated_successfully": "言語を正常に更新しました",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "言語を選択してください",
|
||||
"remove_language": "言語を削除",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "ワークスペースから削除するには、これらのフォームから言語を削除してください。",
|
||||
"search_items": "アイテムを検索",
|
||||
"translate": "翻訳"
|
||||
"search_items": "アイテムを検索"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "背景色を追加",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "バーの未入力部分の色を設定します。",
|
||||
"advanced_styling_field_track_height": "トラックの高さ",
|
||||
"advanced_styling_field_track_height_description": "プログレスバーの太さを調整します。",
|
||||
"advanced_styling_field_upper_label_color": "見出しラベルの色",
|
||||
"advanced_styling_field_upper_label_color_description": "入力フィールド上部の小さなラベルの色を設定します。",
|
||||
"advanced_styling_field_upper_label_size": "見出しラベルのフォントサイズ",
|
||||
"advanced_styling_field_upper_label_size_description": "入力フィールド上部の小さなラベルのサイズを調整します。",
|
||||
"advanced_styling_field_upper_label_weight": "見出しラベルのフォントの太さ",
|
||||
"advanced_styling_field_upper_label_weight_description": "ラベルを細くまたは太くします。",
|
||||
"advanced_styling_field_upper_label_color": "ラベルの色",
|
||||
"advanced_styling_field_upper_label_color_description": "入力欄の上にある小さなラベルとスケールラベルの色を設定します。",
|
||||
"advanced_styling_field_upper_label_size": "ラベルのフォントサイズ",
|
||||
"advanced_styling_field_upper_label_size_description": "入力欄の上にある小さなラベルとスケールラベルのサイズを調整します。",
|
||||
"advanced_styling_field_upper_label_weight": "ラベルのフォント太さ",
|
||||
"advanced_styling_field_upper_label_weight_description": "ラベルの太さを細くしたり太くしたりします。",
|
||||
"advanced_styling_section_buttons": "ボタン",
|
||||
"advanced_styling_section_headlines": "見出しと説明",
|
||||
"advanced_styling_section_inputs": "入力フィールド",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "私たちがもっとうまくできることは何ですか?",
|
||||
"identify_customer_goals_description": "あなたのメッセージが製品の価値に対する正しい期待を抱かせているかどうかをよりよく理解する。",
|
||||
"identify_customer_goals_name": "顧客目標の特定",
|
||||
"identify_customer_goals_question_1_choice_1": "ユーザーベースを深く理解する",
|
||||
"identify_customer_goals_question_1_choice_2": "アップセルの機会を特定する",
|
||||
"identify_customer_goals_question_1_choice_3": "最高の製品を構築する",
|
||||
"identify_customer_goals_question_1_choice_4": "世界を支配して全員に朝食に芽キャベツを食べさせる",
|
||||
"identify_customer_goals_question_1_headline": "$[projectName]を使用する主な目的は何ですか?",
|
||||
"identify_sign_up_barriers_description": "サインアップの障壁に関する洞察を得るために割引を提供する。",
|
||||
"identify_sign_up_barriers_name": "サインアップの障壁を特定する",
|
||||
"identify_sign_up_barriers_question_1_button_label": "10%割引を取得",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "私たちをよりよく理解するためにお手伝いください:",
|
||||
"improve_trial_conversion_question_2_button_label": "次へ",
|
||||
"improve_trial_conversion_question_2_headline": "残念です。$[projectName]を使う上で最も大きな問題は何でしたか?",
|
||||
"improve_trial_conversion_question_3_button_label": "次へ",
|
||||
"improve_trial_conversion_question_3_headline": "$[projectName]に何を期待していましたか?",
|
||||
"improve_trial_conversion_question_4_button_label": "20%オフを取得",
|
||||
"improve_trial_conversion_question_4_headline": "残念です!初年度20%オフをゲット。",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>年間プランで20%の割引を提供させていただきます。</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "次へ",
|
||||
"improve_trial_conversion_question_5_headline": "何を達成したいですか?",
|
||||
"improve_trial_conversion_question_5_subheader": "以下のオプションから一つ選択してください:",
|
||||
"improve_trial_conversion_question_5_subheader": "以下に詳しくご記入ください:",
|
||||
"improve_trial_conversion_question_6_headline": "今、問題をどのように解決していますか?",
|
||||
"improve_trial_conversion_question_6_subheader": "代替ソリューションを挙げてください:",
|
||||
"integration_setup_survey_description": "ユーザーが製品に統合を追加するのがどれだけ簡単かを評価する。盲点を見つける。",
|
||||
|
||||
+42
-30
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Gecentreerd modaal",
|
||||
"change_organization": "Organisatie wijzigen",
|
||||
"change_workspace": "Werkruimte wijzigen",
|
||||
"choice_n": "Keuze {{n}}",
|
||||
"choices": "Keuzes",
|
||||
"choose_environment": "Kies omgeving",
|
||||
"choose_organization": "Kies organisatie",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Dichtbij",
|
||||
"code": "Code",
|
||||
"collapse_rows": "Rijen samenvouwen",
|
||||
"column_n": "Kolom {{n}}",
|
||||
"completed": "Voltooid",
|
||||
"configuration": "Configuratie",
|
||||
"confirm": "Bevestigen",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Gemaakt door",
|
||||
"customer_success": "Klant succes",
|
||||
"dark_overlay": "Donkere overlay",
|
||||
"data_refreshed_successfully": "Gegevens succesvol vernieuwd",
|
||||
"date": "Datum",
|
||||
"days": "dagen",
|
||||
"default": "Standaard",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Kopiëren naar klembord mislukt",
|
||||
"failed_to_load_organizations": "Laden van organisaties mislukt",
|
||||
"failed_to_load_workspaces": "Laden van werkruimtes mislukt",
|
||||
"field_placeholder": "Tijdelijke aanduiding voor {{field}}",
|
||||
"filter": "Filter",
|
||||
"finish": "Finish",
|
||||
"first_name": "Voornaam",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Genereren",
|
||||
"go_back": "Ga terug",
|
||||
"go_to_dashboard": "Ga naar Dashboard",
|
||||
"headline": "Kop",
|
||||
"hidden": "Verborgen",
|
||||
"hidden_field": "Verborgen veld",
|
||||
"hidden_fields": "Verborgen velden",
|
||||
"hide_column": "Kolom verbergen",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Afbeelding",
|
||||
"images": "Afbeeldingen",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "maanden",
|
||||
"move_down": "Ga naar beneden",
|
||||
"move_up": "Ga omhoog",
|
||||
"multiple_languages": "Meerdere talen",
|
||||
"my_product": "mijn product",
|
||||
"name": "Naam",
|
||||
"new": "Nieuw",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Geen resultaat gevonden",
|
||||
"no_results": "Geen resultaten",
|
||||
"no_surveys_found": "Geen enquêtes gevonden.",
|
||||
"no_text_found": "Geen tekst gevonden",
|
||||
"none_of_the_above": "Geen van bovenstaande",
|
||||
"not_authenticated": "U bent niet geverifieerd om deze actie uit te voeren.",
|
||||
"not_authorized": "Niet geautoriseerd",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Organisatie-instellingen",
|
||||
"other": "Ander",
|
||||
"other_filters": "Overige filters",
|
||||
"others": "Anderen",
|
||||
"other_placeholder": "Andere tijdelijke aanduiding",
|
||||
"overlay_color": "Overlaykleur",
|
||||
"overview": "Overzicht",
|
||||
"password": "Wachtwoord",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Upgrade je abonnement",
|
||||
"powered_by_formbricks": "Mogelijk gemaakt door Formbricks",
|
||||
"preview": "Voorbeeld",
|
||||
"preview_survey": "Voorbeeld van enquête",
|
||||
"privacy": "Privacybeleid",
|
||||
"product_manager": "Productmanager",
|
||||
"production": "Productie",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Beperk het aantal reacties dat u ontvangt van deelnemers die aan bepaalde criteria voldoen.",
|
||||
"read_docs": "Documentatie lezen",
|
||||
"recipients": "Ontvangers",
|
||||
"refresh": "Vernieuwen",
|
||||
"remove": "Verwijderen",
|
||||
"remove_from_team": "Verwijderen uit team",
|
||||
"reorder_and_hide_columns": "Kolommen opnieuw rangschikken en verbergen",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Reacties",
|
||||
"restart": "Opnieuw opstarten",
|
||||
"role": "Rol",
|
||||
"row_n": "Rij {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Verkoop",
|
||||
"save": "Redden",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Bestandsopslag is niet ingesteld, uploads zullen waarschijnlijk mislukken",
|
||||
"string": "Tekst",
|
||||
"styling": "Styling",
|
||||
"subheader": "Subkop",
|
||||
"submit": "Indienen",
|
||||
"summary": "Samenvatting",
|
||||
"survey": "Vragenlijst",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "bijv. Formbricks",
|
||||
"workspaces": "Werkruimtes",
|
||||
"years": "jaren",
|
||||
"you": "Jij",
|
||||
"you_are_downgraded_to_the_community_edition": "Je bent gedowngraded naar de Community-editie.",
|
||||
"you_are_not_authorized_to_perform_this_action": "U bent niet geautoriseerd om deze actie uit te voeren.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Je hebt je limiet van {projectLimit} werkruimtes bereikt.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Nieuw attribuut “{key}” aangemaakt met type “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "De gebruikers-ID bestaat al voor deze omgeving en is niet bijgewerkt.",
|
||||
"contact_deleted_successfully": "Contact succesvol verwijderd",
|
||||
"contacts_table_refresh": "Vernieuw contacten",
|
||||
"contacts_table_refresh_success": "Contacten zijn vernieuwd",
|
||||
"create_attribute": "Attribuut aanmaken",
|
||||
"create_new_attribute": "Nieuw attribuut aanmaken",
|
||||
"create_new_attribute_description": "Maak een nieuw attribuut aan voor segmentatiedoeleinden.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "De uitnodigingslink voor uw organisatie is gereed!",
|
||||
"organization_name": "Organisatienaam",
|
||||
"organization_name_description": "Geef uw organisatie een beschrijvende naam.",
|
||||
"organization_name_placeholder": "bijv. Power Puff-meisjes",
|
||||
"organization_name_placeholder": "bijv. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Organisatienaam is succesvol bijgewerkt",
|
||||
"organization_settings": "Organisatie-instellingen",
|
||||
"please_add_a_logo": "Voeg een logo toe",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Je bent helemaal klaar! Tijd om uw eerste enquête te maken",
|
||||
"alphabetical": "Alfabetisch",
|
||||
"copy_survey": "Kopieer enquête",
|
||||
"copy_survey_description": "Kopieer deze enquête naar een andere omgeving",
|
||||
"copy_survey_error": "Het kopiëren van de enquête is mislukt",
|
||||
"copy_survey_link_to_clipboard": "Kopieer de enquêtelink naar het klembord",
|
||||
"copy_survey_partially_success": "{success} enquêtes zijn succesvol gekopieerd, {error} is mislukt.",
|
||||
"copy_survey_success": "Enquête succesvol gekopieerd!",
|
||||
"delete_survey_and_responses_warning": "Weet u zeker dat u deze enquête en alle antwoorden erop wilt verwijderen?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Kies de standaardtaal voor deze enquête:",
|
||||
"2_activate_translation_for_specific_languages": "2. Activeer vertaling voor specifieke talen:",
|
||||
"activate_translations": "Vertalingen activeren",
|
||||
"add": "Voeg + toe",
|
||||
"add_a_delay_or_auto_close_the_survey": "Voeg een vertraging toe of sluit de enquête automatisch",
|
||||
"add_a_four_digit_pin": "Voeg een viercijferige pincode toe",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Toewijzen =",
|
||||
"audience": "Publiek",
|
||||
"auto_close_on_inactivity": "Automatisch sluiten bij inactiviteit",
|
||||
"auto_progress_rating_and_nps": "Automatisch doorgaan bij beoordelings- en NPS-vragen",
|
||||
"auto_progress_rating_and_nps_description": "Automatisch doorgaan bij blokken met één vraag. Verplichte vragen verbergen Volgende, behalve wanneer \"Anders\" is geselecteerd.",
|
||||
"auto_save_disabled": "Automatisch opslaan uitgeschakeld",
|
||||
"auto_save_disabled_tooltip": "Uw enquête wordt alleen automatisch opgeslagen wanneer deze een concept is. Dit zorgt ervoor dat openbare enquêtes niet onbedoeld worden bijgewerkt.",
|
||||
"auto_save_on": "Automatisch opslaan aan",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Veranderingen zullen tot inconsistenties leiden",
|
||||
"change_anyway": "Hoe dan ook veranderen",
|
||||
"change_background": "Achtergrond wijzigen",
|
||||
"change_default": "Standaard wijzigen",
|
||||
"change_question_type": "Vraagtype wijzigen",
|
||||
"change_survey_type": "Als u van enquêtetype verandert, heeft dit invloed op de bestaande toegang",
|
||||
"change_the_background_to_a_color_image_or_animation": "Verander de achtergrond in een kleur, afbeelding of animatie.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Kies waar u de enquête wilt uitvoeren.",
|
||||
"city": "Stad",
|
||||
"close_survey_on_response_limit": "Sluit enquête over responslimiet",
|
||||
"code": "Code",
|
||||
"color": "Kleur",
|
||||
"column_used_in_logic_error": "Deze kolom wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.",
|
||||
"columns": "Kolommen",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Pas het enquêtelogo aan",
|
||||
"darken_or_lighten_background_of_your_choice": "Maak de achtergrond naar keuze donkerder of lichter.",
|
||||
"days_before_showing_this_survey_again": "of meer dagen moeten verstrijken tussen de laatst getoonde enquête en het tonen van deze enquête.",
|
||||
"default_language": "Standaardtaal",
|
||||
"delete_anyways": "Toch verwijderen",
|
||||
"delete_block": "Blok verwijderen",
|
||||
"delete_choice": "Keuze verwijderen",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Vraag dupliceren",
|
||||
"edit_link": "Link bewerken",
|
||||
"edit_recall": "Bewerken Terugroepen",
|
||||
"edit_translations": "Bewerk {lang} vertalingen",
|
||||
"element_not_found": "Vraag niet gevonden",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Sta respondenten toe om op elk moment van taal te wisselen. Vereist min. 2 actieve talen.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Spambeveiliging maakt gebruik van reCAPTCHA v3 om de spamreacties eruit te filteren.",
|
||||
@@ -1599,11 +1602,13 @@
|
||||
"long_answer": "Lang antwoord",
|
||||
"long_answer_toggle_description": "Sta respondenten toe om langere antwoorden met meerdere regels te schrijven.",
|
||||
"lower_label": "Lager etiket",
|
||||
"manage_languages": "Beheer talen",
|
||||
"manage_languages": "Talen beheren",
|
||||
"manage_translations": "Vertalingen beheren",
|
||||
"matrix_all_fields": "Alle velden",
|
||||
"matrix_rows": "Rijen",
|
||||
"max_file_size": "Maximale bestandsgrootte",
|
||||
"max_file_size_limit_is": "Maximale bestandsgroottelimiet is",
|
||||
"missing_first": "Ontbrekende eerst",
|
||||
"move_question_to_block": "Vraag naar blok verplaatsen",
|
||||
"multiply": "Vermenigvuldig *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Nodig voor een zelf-gehoste Cal.com-instantie",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Knoplabel 'Volgende'",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Nog geen verborgen velden. Voeg de eerste hieronder toe.",
|
||||
"no_images_found_for": "Geen afbeeldingen gevonden voor ''{query}'",
|
||||
"no_languages_found_add_first_one_to_get_started": "Geen talen gevonden. Voeg de eerste toe om aan de slag te gaan.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Geen enquêtetalen gevonden in deze werkruimte. Voeg er een toe om te beginnen.",
|
||||
"no_option_found": "Geen optie gevonden",
|
||||
"no_recall_items_found": "Geen recall-items gevonden",
|
||||
"no_variables_yet_add_first_one_below": "Nog geen variabelen. Voeg de eerste hieronder toe.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Voer een geldige URL in (bijvoorbeeld https://example.com)",
|
||||
"please_set_a_survey_trigger": "Stel een enquêtetrigger in",
|
||||
"please_specify": "Gelieve te specificeren",
|
||||
"present_your_survey_in_multiple_languages": "Toon je enquête in meerdere talen",
|
||||
"prevent_double_submission": "Voorkom dubbele indiening",
|
||||
"prevent_double_submission_description": "Er is slechts 1 reactie per e-mailadres toegestaan",
|
||||
"progress_saved": "Voortgang opgeslagen",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 punten",
|
||||
"show_block_settings": "Blokinstellingen tonen",
|
||||
"show_button": "Toon knop",
|
||||
"show_in_order": "Toon op volgorde",
|
||||
"show_language_switch": "Toon taalwissel",
|
||||
"show_multiple_times": "Toon een beperkt aantal keren",
|
||||
"show_only_once": "Slechts één keer weergeven",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Enquêtevoorbeeld 👀",
|
||||
"survey_styling": "Vorm styling",
|
||||
"survey_trigger": "Enquêtetrigger",
|
||||
"switch_multi_language_on_to_get_started": "Schakel meertaligheid in om te beginnen 👉",
|
||||
"target_block_not_found": "Doelblok niet gevonden",
|
||||
"targeted": "Gericht",
|
||||
"ten_points": "10 punten",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Toon één keer, zelfs als ze niet reageren.",
|
||||
"then": "Dan",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Met deze actie worden alle vertalingen uit deze enquête verwijderd.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Dit verwijdert deze taal en alle vertalingen uit deze enquête. Deze actie kan niet ongedaan worden gemaakt.",
|
||||
"three_points": "3 punten",
|
||||
"times": "keer",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Om de plaatsing over alle enquêtes consistent te houden, kunt u dat doen",
|
||||
"translated": "Vertaald",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Enquête activeren wanneer een van de acties wordt afgevuurd...",
|
||||
"try_lollipop_or_mountain": "Probeer 'lollipop' of 'berg'...",
|
||||
"type_field_id": "Typ veld-ID",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Laat alleen mensen met een echte e-mail reageren.",
|
||||
"visibility_and_recontact": "Zichtbaarheid & opnieuw contact",
|
||||
"visibility_and_recontact_description": "Bepaal wanneer deze enquête kan verschijnen en hoe vaak deze opnieuw kan verschijnen.",
|
||||
"visible": "Zichtbaar",
|
||||
"wait": "Wachten",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Wacht een paar seconden na de trigger voordat u de enquête weergeeft",
|
||||
"waiting_time_across_surveys": "Afkoelperiode (voor alle enquêtes)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "QR-code downloaden",
|
||||
"drop_offs": "Drop-offs",
|
||||
"drop_offs_tooltip": "Aantal keren dat de enquête is gestart maar niet is voltooid.",
|
||||
"failed_to_copy_link": "Kan de link niet kopiëren",
|
||||
"filter_added_successfully": "Filter succesvol toegevoegd",
|
||||
"filter_updated_successfully": "Filter succesvol bijgewerkt",
|
||||
"filtered_responses_csv": "Gefilterde reacties (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Enquête succesvol verwijderd!",
|
||||
"survey_duplicated_successfully": "Enquête is succesvol gedupliceerd.",
|
||||
"survey_duplication_error": "Het is niet gelukt de enquête te dupliceren.",
|
||||
"templates": {
|
||||
"all_channels": "Alle kanalen",
|
||||
"all_industries": "Alle industrieën",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Dubbele taal of taal-ID",
|
||||
"edit_languages": "Talen bewerken",
|
||||
"identifier": "Identifier (ISO)",
|
||||
"incomplete_translations": "Onvolledige vertalingen",
|
||||
"language": "Taal",
|
||||
"language_deleted_successfully": "Taal succesvol verwijderd",
|
||||
"languages_updated_successfully": "Talen succesvol bijgewerkt",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Selecteer een taal",
|
||||
"remove_language": "Taal verwijderen",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Verwijder de taal uit deze enquêtes om deze uit de werkruimte te verwijderen.",
|
||||
"search_items": "Items zoeken",
|
||||
"translate": "Vertalen"
|
||||
"search_items": "Items zoeken"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Achtergrondkleur toevoegen",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Kleurt het ongevulde gedeelte van de balk.",
|
||||
"advanced_styling_field_track_height": "Spoorhoogte",
|
||||
"advanced_styling_field_track_height_description": "Regelt de dikte van de voortgangsbalk.",
|
||||
"advanced_styling_field_upper_label_color": "Koplabelkleur",
|
||||
"advanced_styling_field_upper_label_color_description": "Kleurt het kleine label boven invoervelden.",
|
||||
"advanced_styling_field_upper_label_size": "Lettergrootte koplabel",
|
||||
"advanced_styling_field_upper_label_size_description": "Schaalt het kleine label boven invoervelden.",
|
||||
"advanced_styling_field_upper_label_weight": "Letterdikte koplabel",
|
||||
"advanced_styling_field_upper_label_weight_description": "Maakt het label lichter of vetter.",
|
||||
"advanced_styling_field_upper_label_color": "Labelkleur",
|
||||
"advanced_styling_field_upper_label_color_description": "Kleurt de kleine labels boven invoervelden en schaallabels.",
|
||||
"advanced_styling_field_upper_label_size": "Lettergrootte label",
|
||||
"advanced_styling_field_upper_label_size_description": "Past de grootte aan van de kleine labels boven invoervelden en schaallabels.",
|
||||
"advanced_styling_field_upper_label_weight": "Letterdikte label",
|
||||
"advanced_styling_field_upper_label_weight_description": "Maakt de labels lichter of dikgedrukt.",
|
||||
"advanced_styling_section_buttons": "Knoppen",
|
||||
"advanced_styling_section_headlines": "Koppen & beschrijvingen",
|
||||
"advanced_styling_section_inputs": "Invoervelden",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Wat kunnen we beter doen?",
|
||||
"identify_customer_goals_description": "Begrijp beter of uw boodschap de juiste verwachtingen wekt van de waarde die uw product biedt.",
|
||||
"identify_customer_goals_name": "Identificeer klantdoelen",
|
||||
"identify_customer_goals_question_1_choice_1": "Mijn gebruikersgroep grondig begrijpen",
|
||||
"identify_customer_goals_question_1_choice_2": "Upselling-mogelijkheden identificeren",
|
||||
"identify_customer_goals_question_1_choice_3": "Het best mogelijke product bouwen",
|
||||
"identify_customer_goals_question_1_choice_4": "De wereld regeren om iedereen spruitjes als ontbijt te geven",
|
||||
"identify_customer_goals_question_1_headline": "Wat is je primaire doel voor het gebruik van $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Bied een korting aan om inzicht te krijgen in de aanmeldingsbarrières.",
|
||||
"identify_sign_up_barriers_name": "Identificeer aanmeldingsbarrières",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Krijg 10% korting",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Help ons u beter te begrijpen:",
|
||||
"improve_trial_conversion_question_2_button_label": "Volgende",
|
||||
"improve_trial_conversion_question_2_headline": "Sorry om te horen. Wat was het grootste probleem bij het gebruik van $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Volgende",
|
||||
"improve_trial_conversion_question_3_headline": "Wat had je verwacht dat $[projectName] zou doen?",
|
||||
"improve_trial_conversion_question_4_button_label": "Krijg 20% korting",
|
||||
"improve_trial_conversion_question_4_headline": "Sorry om te horen! Krijg het eerste jaar 20% korting.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We bieden u graag 20% korting op een jaarabonnement.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Volgende",
|
||||
"improve_trial_conversion_question_5_headline": "Wat zou je graag willen bereiken?",
|
||||
"improve_trial_conversion_question_5_subheader": "Selecteer een van de volgende opties:",
|
||||
"improve_trial_conversion_question_5_subheader": "Beschrijf hieronder:",
|
||||
"improve_trial_conversion_question_6_headline": "Hoe los jij je probleem nu op?",
|
||||
"improve_trial_conversion_question_6_subheader": "Noem alternatieve oplossingen:",
|
||||
"integration_setup_survey_description": "Evalueer hoe gemakkelijk gebruikers integraties aan uw product kunnen toevoegen. Zoek blinde vlekken.",
|
||||
|
||||
+42
-30
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Modal Centralizado",
|
||||
"change_organization": "Alterar organização",
|
||||
"change_workspace": "Alterar espaço de trabalho",
|
||||
"choice_n": "Escolha {{n}}",
|
||||
"choices": "Escolhas",
|
||||
"choose_environment": "Escolher ambiente",
|
||||
"choose_organization": "Escolher organização",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Fechar",
|
||||
"code": "Código",
|
||||
"collapse_rows": "Recolher linhas",
|
||||
"column_n": "Coluna {{n}}",
|
||||
"completed": "Concluído",
|
||||
"configuration": "Configuração",
|
||||
"confirm": "Confirmar",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Criado por",
|
||||
"customer_success": "Sucesso do Cliente",
|
||||
"dark_overlay": "sobreposição escura",
|
||||
"data_refreshed_successfully": "Dados atualizados com sucesso",
|
||||
"date": "Encontro",
|
||||
"days": "dias",
|
||||
"default": "Padrão",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
|
||||
"failed_to_load_organizations": "Falha ao carregar organizações",
|
||||
"failed_to_load_workspaces": "Falha ao carregar projetos",
|
||||
"field_placeholder": "Espaço reservado de {{field}}",
|
||||
"filter": "Filtro",
|
||||
"finish": "Terminar",
|
||||
"first_name": "Primeiro nome",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Gerar",
|
||||
"go_back": "Voltar",
|
||||
"go_to_dashboard": "Ir para o Painel",
|
||||
"headline": "Título",
|
||||
"hidden": "Escondido",
|
||||
"hidden_field": "Campo oculto",
|
||||
"hidden_fields": "Campos ocultos",
|
||||
"hide_column": "Ocultar coluna",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "imagem",
|
||||
"images": "Imagens",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "meses",
|
||||
"move_down": "Descer",
|
||||
"move_up": "Subir",
|
||||
"multiple_languages": "Vários idiomas",
|
||||
"my_product": "meu produto",
|
||||
"name": "Nome",
|
||||
"new": "Novo",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Nenhum resultado encontrado",
|
||||
"no_results": "Nenhum resultado",
|
||||
"no_surveys_found": "Não foram encontradas pesquisas.",
|
||||
"no_text_found": "Nenhum texto encontrado",
|
||||
"none_of_the_above": "Nenhuma das opções acima",
|
||||
"not_authenticated": "Você não está autenticado para realizar essa ação.",
|
||||
"not_authorized": "Não autorizado",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Configurações da Organização",
|
||||
"other": "outro",
|
||||
"other_filters": "Outros Filtros",
|
||||
"others": "Outros",
|
||||
"other_placeholder": "Outro espaço reservado",
|
||||
"overlay_color": "Cor da sobreposição",
|
||||
"overview": "Visão Geral",
|
||||
"password": "Senha",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Por favor, atualize seu plano",
|
||||
"powered_by_formbricks": "Desenvolvido por Formbricks",
|
||||
"preview": "Prévia",
|
||||
"preview_survey": "Prévia da Pesquisa",
|
||||
"privacy": "Política de Privacidade",
|
||||
"product_manager": "Gerente de Produto",
|
||||
"production": "Produção",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limite a quantidade de respostas que você recebe de participantes que atendem a determinados critérios.",
|
||||
"read_docs": "Ler documentação",
|
||||
"recipients": "Destinatários",
|
||||
"refresh": "Atualizar",
|
||||
"remove": "remover",
|
||||
"remove_from_team": "Remover da equipe",
|
||||
"reorder_and_hide_columns": "Reordenar e ocultar colunas",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Respostas",
|
||||
"restart": "Reiniciar",
|
||||
"role": "Rolê",
|
||||
"row_n": "Linha {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "vendas",
|
||||
"save": "Salvar",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Armazenamento de arquivos não configurado, uploads provavelmente falharão",
|
||||
"string": "Texto",
|
||||
"styling": "Estilização",
|
||||
"subheader": "Subtítulo",
|
||||
"submit": "Enviar",
|
||||
"summary": "Resumo",
|
||||
"survey": "Pesquisa",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "ex: Formbricks",
|
||||
"workspaces": "Projetos",
|
||||
"years": "anos",
|
||||
"you": "Você",
|
||||
"you_are_downgraded_to_the_community_edition": "Você foi rebaixado para a Edição Comunitária.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Você não tem autorização para realizar essa ação.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Você atingiu seu limite de {projectLimit} espaços de trabalho.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Novo atributo “{key}” criado com tipo “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "O ID de usuário já existe para este ambiente e não foi atualizado.",
|
||||
"contact_deleted_successfully": "Contato excluído com sucesso",
|
||||
"contacts_table_refresh": "Atualizar contatos",
|
||||
"contacts_table_refresh_success": "Contatos atualizados com sucesso",
|
||||
"create_attribute": "Criar atributo",
|
||||
"create_new_attribute": "Criar novo atributo",
|
||||
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "O link de convite da sua organização está pronto!",
|
||||
"organization_name": "Nome da Organização",
|
||||
"organization_name_description": "Dê um nome descritivo pra sua organização.",
|
||||
"organization_name_placeholder": "por exemplo, Meninas Superpoderosas",
|
||||
"organization_name_placeholder": "por exemplo, Acme Inc.",
|
||||
"organization_name_updated_successfully": "Nome da organização atualizado com sucesso",
|
||||
"organization_settings": "Configurações da Organização",
|
||||
"please_add_a_logo": "Por favor, adicione um logo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Tá tudo pronto! Hora de criar sua primeira pesquisa",
|
||||
"alphabetical": "alfabético",
|
||||
"copy_survey": "Copiar pesquisa",
|
||||
"copy_survey_description": "Copiar essa pesquisa para outro ambiente",
|
||||
"copy_survey_error": "Falha ao copiar pesquisa",
|
||||
"copy_survey_link_to_clipboard": "Copiar link da pesquisa para a área de transferência",
|
||||
"copy_survey_partially_success": "{success} pesquisas copiadas com sucesso, {error} falharam.",
|
||||
"copy_survey_success": "Pesquisa copiada com sucesso!",
|
||||
"delete_survey_and_responses_warning": "Você tem certeza de que quer deletar essa pesquisa e todas as suas respostas?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Escolha o idioma padrão para essa pesquisa:",
|
||||
"2_activate_translation_for_specific_languages": "2. Ativar tradução para idiomas específicos:",
|
||||
"activate_translations": "Ativar traduções",
|
||||
"add": "Adicionar +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Adicione um atraso ou feche a pesquisa automaticamente",
|
||||
"add_a_four_digit_pin": "Adicione um PIN de quatro dígitos",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "atribuir =",
|
||||
"audience": "Público",
|
||||
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
|
||||
"auto_progress_rating_and_nps": "Avançar automaticamente em perguntas de avaliação e NPS",
|
||||
"auto_progress_rating_and_nps_description": "Avança automaticamente em blocos de pergunta única. Perguntas obrigatórias ocultam o botão Próximo, exceto quando \"Outro\" está selecionado.",
|
||||
"auto_save_disabled": "Salvamento automático desativado",
|
||||
"auto_save_disabled_tooltip": "Sua pesquisa só é salva automaticamente quando está em rascunho. Isso garante que pesquisas públicas não sejam atualizadas involuntariamente.",
|
||||
"auto_save_on": "Salvamento automático ativado",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Mudanças vão levar a inconsistências",
|
||||
"change_anyway": "Mudar mesmo assim",
|
||||
"change_background": "Mudar fundo",
|
||||
"change_default": "Alterar padrão",
|
||||
"change_question_type": "Mudar tipo de pergunta",
|
||||
"change_survey_type": "Alterar o tipo de pesquisa afeta o acesso existente",
|
||||
"change_the_background_to_a_color_image_or_animation": "Mude o fundo para uma cor, imagem ou animação.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Escolha onde realizar a pesquisa.",
|
||||
"city": "cidade",
|
||||
"close_survey_on_response_limit": "Fechar pesquisa ao atingir limite de respostas",
|
||||
"code": "Código",
|
||||
"color": "cor",
|
||||
"column_used_in_logic_error": "Esta coluna é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
|
||||
"columns": "colunas",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Personalizar o logo da pesquisa",
|
||||
"darken_or_lighten_background_of_your_choice": "Escureça ou clareie o fundo da sua escolha.",
|
||||
"days_before_showing_this_survey_again": "ou mais dias devem passar entre a última pesquisa exibida e a exibição desta pesquisa.",
|
||||
"default_language": "Idioma padrão",
|
||||
"delete_anyways": "Excluir mesmo assim",
|
||||
"delete_block": "Excluir bloco",
|
||||
"delete_choice": "Deletar opção",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplicar pergunta",
|
||||
"edit_link": "Editar link",
|
||||
"edit_recall": "Editar Lembrete",
|
||||
"edit_translations": "Editar traduções de {lang}",
|
||||
"element_not_found": "Pergunta não encontrada",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir que os respondentes alterem o idioma a qualquer momento. Necessita de no mínimo 2 idiomas ativos.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "A proteção contra spam usa o reCAPTCHA v3 para filtrar as respostas de spam.",
|
||||
@@ -1599,11 +1602,13 @@
|
||||
"long_answer": "resposta longa",
|
||||
"long_answer_toggle_description": "Permitir que os respondentes escrevam respostas mais longas e com várias linhas.",
|
||||
"lower_label": "Etiqueta Inferior",
|
||||
"manage_languages": "Gerenciar Idiomas",
|
||||
"manage_languages": "Gerenciar idiomas",
|
||||
"manage_translations": "Gerenciar traduções",
|
||||
"matrix_all_fields": "Todos os campos",
|
||||
"matrix_rows": "Linhas",
|
||||
"max_file_size": "Tamanho máximo do arquivo",
|
||||
"max_file_size_limit_is": "O limite de tamanho máximo do arquivo é",
|
||||
"missing_first": "Faltantes primeiro",
|
||||
"move_question_to_block": "Mover pergunta para o bloco",
|
||||
"multiply": "Multiplicar *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Próximo",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Ainda não há campos ocultos. Adicione o primeiro abaixo.",
|
||||
"no_images_found_for": "Nenhuma imagem encontrada para ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nenhum idioma encontrado. Adicione o primeiro para começar.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nenhum idioma de pesquisa encontrado neste espaço de trabalho. Por favor, adicione um para começar.",
|
||||
"no_option_found": "Nenhuma opção encontrada",
|
||||
"no_recall_items_found": "Nenhum item de recuperação encontrado",
|
||||
"no_variables_yet_add_first_one_below": "Ainda não há variáveis. Adicione a primeira abaixo.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Por favor, insira uma URL válida (ex.: https://example.com)",
|
||||
"please_set_a_survey_trigger": "Por favor, configure um gatilho para a pesquisa",
|
||||
"please_specify": "Por favor, especifique",
|
||||
"present_your_survey_in_multiple_languages": "Apresente sua pesquisa em vários idiomas",
|
||||
"prevent_double_submission": "Evitar envio duplicado",
|
||||
"prevent_double_submission_description": "Permitir apenas 1 resposta por endereço de email",
|
||||
"progress_saved": "Progresso salvo",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 pontos",
|
||||
"show_block_settings": "Mostrar configurações do bloco",
|
||||
"show_button": "Mostrar Botão",
|
||||
"show_in_order": "Mostrar em ordem",
|
||||
"show_language_switch": "Mostrar troca de idioma",
|
||||
"show_multiple_times": "Mostrar um número limitado de vezes",
|
||||
"show_only_once": "Mostrar só uma vez",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Prévia da pesquisa 👀",
|
||||
"survey_styling": "Estilização de Formulários",
|
||||
"survey_trigger": "Gatilho de Pesquisa",
|
||||
"switch_multi_language_on_to_get_started": "Ative o modo multilíngue para começar 👉",
|
||||
"target_block_not_found": "Bloco de destino não encontrado",
|
||||
"targeted": "direcionado",
|
||||
"ten_points": "10 pontos",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar uma única vez, mesmo que não respondam.",
|
||||
"then": "Então",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Essa ação vai remover todas as traduções dessa pesquisa.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Isso removerá este idioma e todas as suas traduções desta pesquisa. Esta ação não pode ser desfeita.",
|
||||
"three_points": "3 pontos",
|
||||
"times": "times",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para manter a colocação consistente em todas as pesquisas, você pode",
|
||||
"translated": "Traduzido",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Disparar pesquisa quando uma das ações for executada...",
|
||||
"try_lollipop_or_mountain": "Tenta 'pirulito' ou 'montanha'...",
|
||||
"type_field_id": "Digite o id do campo",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Deixe só quem tem um email real responder.",
|
||||
"visibility_and_recontact": "Visibilidade e recontato",
|
||||
"visibility_and_recontact_description": "Controle quando esta pesquisa pode aparecer e com que frequência pode reaparecer.",
|
||||
"visible": "Visível",
|
||||
"wait": "Espera",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Espera alguns segundos depois do gatilho antes de mostrar a pesquisa",
|
||||
"waiting_time_across_surveys": "Período de espera (entre pesquisas)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Baixando código QR",
|
||||
"drop_offs": "Pontos de Entrega",
|
||||
"drop_offs_tooltip": "Número de vezes que a pesquisa foi iniciada mas não concluída.",
|
||||
"failed_to_copy_link": "Falha ao copiar link",
|
||||
"filter_added_successfully": "Filtro adicionado com sucesso",
|
||||
"filter_updated_successfully": "Filtro atualizado com sucesso",
|
||||
"filtered_responses_csv": "Respostas filtradas (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Pesquisa deletada com sucesso!",
|
||||
"survey_duplicated_successfully": "Pesquisa duplicada com sucesso.",
|
||||
"survey_duplication_error": "Falha ao duplicar a pesquisa.",
|
||||
"templates": {
|
||||
"all_channels": "Todos os canais",
|
||||
"all_industries": "Todas as indústrias",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Idioma ou ID de idioma duplicado",
|
||||
"edit_languages": "Editar idiomas",
|
||||
"identifier": "Identificador (ISO)",
|
||||
"incomplete_translations": "Traduções incompletas",
|
||||
"language": "Idioma",
|
||||
"language_deleted_successfully": "Idioma excluído com sucesso",
|
||||
"languages_updated_successfully": "Idiomas atualizados com sucesso",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Por favor, selecione um idioma",
|
||||
"remove_language": "Remover idioma",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Por favor, remova o idioma dessas pesquisas para removê-lo do workspace.",
|
||||
"search_items": "Buscar itens",
|
||||
"translate": "Traduzir"
|
||||
"search_items": "Buscar itens"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Adicionar cor de fundo",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
|
||||
"advanced_styling_field_track_height": "Altura da trilha",
|
||||
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
|
||||
"advanced_styling_field_upper_label_color": "Cor do rótulo do título",
|
||||
"advanced_styling_field_upper_label_color_description": "Colore o pequeno rótulo acima dos campos de entrada.",
|
||||
"advanced_styling_field_upper_label_size": "Tamanho da fonte do rótulo do título",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho do pequeno rótulo acima dos campos de entrada.",
|
||||
"advanced_styling_field_upper_label_weight": "Peso da fonte do rótulo do título",
|
||||
"advanced_styling_field_upper_label_weight_description": "Torna o rótulo mais leve ou mais negrito.",
|
||||
"advanced_styling_field_upper_label_color": "Cor do Rótulo",
|
||||
"advanced_styling_field_upper_label_color_description": "Colore os pequenos rótulos acima dos campos de entrada e rótulos de escala.",
|
||||
"advanced_styling_field_upper_label_size": "Tamanho da Fonte do Rótulo",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho dos pequenos rótulos acima dos campos de entrada e rótulos de escala.",
|
||||
"advanced_styling_field_upper_label_weight": "Peso da Fonte do Rótulo",
|
||||
"advanced_styling_field_upper_label_weight_description": "Torna os rótulos mais leves ou mais pesados.",
|
||||
"advanced_styling_section_buttons": "Botões",
|
||||
"advanced_styling_section_headlines": "Títulos e descrições",
|
||||
"advanced_styling_section_inputs": "Campos de entrada",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "O que a gente poderia melhorar?",
|
||||
"identify_customer_goals_description": "Entenda melhor se sua mensagem cria as expectativas certas sobre o valor que seu produto oferece.",
|
||||
"identify_customer_goals_name": "Identificar Objetivos do Cliente",
|
||||
"identify_customer_goals_question_1_choice_1": "Entender profundamente minha base de usuários",
|
||||
"identify_customer_goals_question_1_choice_2": "Identificar oportunidades de upsell",
|
||||
"identify_customer_goals_question_1_choice_3": "Construir o melhor produto possível",
|
||||
"identify_customer_goals_question_1_choice_4": "Dominar o mundo para fazer todo mundo tomar couve de bruxelas no café da manhã",
|
||||
"identify_customer_goals_question_1_headline": "Qual é o seu objetivo principal ao usar $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Ofereça um desconto pra entender melhor as barreiras de cadastro.",
|
||||
"identify_sign_up_barriers_name": "Identificar Barreiras de Cadastro",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Ganhe 10% de desconto",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Ajuda a gente a te entender melhor:",
|
||||
"improve_trial_conversion_question_2_button_label": "Próximo",
|
||||
"improve_trial_conversion_question_2_headline": "Que chato ouvir isso. Qual foi o maior problema ao usar $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Próximo",
|
||||
"improve_trial_conversion_question_3_headline": "O que você esperava que $[projectName] fizesse?",
|
||||
"improve_trial_conversion_question_4_button_label": "Ganhe 20% de desconto",
|
||||
"improve_trial_conversion_question_4_headline": "Que pena ouvir isso! Ganhe 20% de desconto no primeiro ano.",
|
||||
"improve_trial_conversion_question_4_html": "Estamos felizes em te oferecer um desconto de 20% no plano anual.",
|
||||
"improve_trial_conversion_question_5_button_label": "Próximo",
|
||||
"improve_trial_conversion_question_5_headline": "O que você gostaria de alcançar?",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, escolha uma das opções a seguir:",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, descreva abaixo:",
|
||||
"improve_trial_conversion_question_6_headline": "Como você tá resolvendo seu problema agora?",
|
||||
"improve_trial_conversion_question_6_subheader": "Por favor, nomeie soluções alternativas:",
|
||||
"integration_setup_survey_description": "Avalie quão fácil é para os usuários adicionarem integrações ao seu produto. Encontre pontos cegos.",
|
||||
|
||||
+42
-30
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Modal Centralizado",
|
||||
"change_organization": "Alterar organização",
|
||||
"change_workspace": "Alterar espaço de trabalho",
|
||||
"choice_n": "Escolha {{n}}",
|
||||
"choices": "Escolhas",
|
||||
"choose_environment": "Escolha o ambiente",
|
||||
"choose_organization": "Escolher organização",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Fechar",
|
||||
"code": "Código",
|
||||
"collapse_rows": "Recolher linhas",
|
||||
"column_n": "Coluna {{n}}",
|
||||
"completed": "Concluído",
|
||||
"configuration": "Configuração",
|
||||
"confirm": "Confirmar",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Criado por",
|
||||
"customer_success": "Sucesso do Cliente",
|
||||
"dark_overlay": "Sobreposição escura",
|
||||
"data_refreshed_successfully": "Dados atualizados com sucesso",
|
||||
"date": "Data",
|
||||
"days": "dias",
|
||||
"default": "Padrão",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
|
||||
"failed_to_load_organizations": "Falha ao carregar organizações",
|
||||
"failed_to_load_workspaces": "Falha ao carregar projetos",
|
||||
"field_placeholder": "Espaço reservado de {{field}}",
|
||||
"filter": "Filtro",
|
||||
"finish": "Concluir",
|
||||
"first_name": "Primeiro nome",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Gerar",
|
||||
"go_back": "Voltar",
|
||||
"go_to_dashboard": "Ir para o Painel",
|
||||
"headline": "Título",
|
||||
"hidden": "Oculto",
|
||||
"hidden_field": "Campo oculto",
|
||||
"hidden_fields": "Campos ocultos",
|
||||
"hide_column": "Ocultar coluna",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Imagem",
|
||||
"images": "Imagens",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "meses",
|
||||
"move_down": "Mover para baixo",
|
||||
"move_up": "Mover para cima",
|
||||
"multiple_languages": "Várias línguas",
|
||||
"my_product": "o meu produto",
|
||||
"name": "Nome",
|
||||
"new": "Novo",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Nenhum resultado encontrado",
|
||||
"no_results": "Nenhum resultado",
|
||||
"no_surveys_found": "Nenhum inquérito encontrado.",
|
||||
"no_text_found": "Nenhum texto encontrado",
|
||||
"none_of_the_above": "Nenhuma das opções acima",
|
||||
"not_authenticated": "Não está autenticado para realizar esta ação.",
|
||||
"not_authorized": "Não autorizado",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Configurações da Organização",
|
||||
"other": "Outro",
|
||||
"other_filters": "Outros Filtros",
|
||||
"others": "Outros",
|
||||
"other_placeholder": "Outro espaço reservado",
|
||||
"overlay_color": "Cor da sobreposição",
|
||||
"overview": "Visão geral",
|
||||
"password": "Palavra-passe",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Por favor, atualize o seu plano",
|
||||
"powered_by_formbricks": "Desenvolvido por Formbricks",
|
||||
"preview": "Pré-visualização",
|
||||
"preview_survey": "Pré-visualização do inquérito",
|
||||
"privacy": "Política de Privacidade",
|
||||
"product_manager": "Gestor de Produto",
|
||||
"production": "Produção",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limitar a quantidade de respostas recebidas de participantes que atendem a certos critérios.",
|
||||
"read_docs": "Ler documentação",
|
||||
"recipients": "Destinatários",
|
||||
"refresh": "Atualizar",
|
||||
"remove": "Remover",
|
||||
"remove_from_team": "Remover da equipa",
|
||||
"reorder_and_hide_columns": "Reordenar e ocultar colunas",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Respostas",
|
||||
"restart": "Reiniciar",
|
||||
"role": "Função",
|
||||
"row_n": "Linha {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Vendas",
|
||||
"save": "Guardar",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Armazenamento de ficheiros não configurado, uploads provavelmente falharão",
|
||||
"string": "Texto",
|
||||
"styling": "Estilo",
|
||||
"subheader": "Subtítulo",
|
||||
"submit": "Submeter",
|
||||
"summary": "Resumo",
|
||||
"survey": "Inquérito",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "ex. Formbricks",
|
||||
"workspaces": "Projetos",
|
||||
"years": "anos",
|
||||
"you": "Você",
|
||||
"you_are_downgraded_to_the_community_edition": "Foi rebaixado para a Edição Comunitária.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Não está autorizado a realizar esta ação.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Atingiu o seu limite de {projectLimit} áreas de trabalho.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Criado novo atributo “{key}” com tipo “{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "O ID de utilizador já existe para este ambiente e não foi atualizado.",
|
||||
"contact_deleted_successfully": "Contacto eliminado com sucesso",
|
||||
"contacts_table_refresh": "Atualizar contactos",
|
||||
"contacts_table_refresh_success": "Contactos atualizados com sucesso",
|
||||
"create_attribute": "Criar atributo",
|
||||
"create_new_attribute": "Criar novo atributo",
|
||||
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "O link de convite da sua organização está pronto!",
|
||||
"organization_name": "Nome da Organização",
|
||||
"organization_name_description": "Dê à sua organização um nome descritivo.",
|
||||
"organization_name_placeholder": "por exemplo, Power Puff Girls",
|
||||
"organization_name_placeholder": "por exemplo, Acme Inc.",
|
||||
"organization_name_updated_successfully": "Nome da organização atualizado com sucesso",
|
||||
"organization_settings": "Configurações da organização",
|
||||
"please_add_a_logo": "Por favor, adicione um logótipo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Está tudo pronto! Hora de criar o seu primeiro inquérito",
|
||||
"alphabetical": "Alfabética",
|
||||
"copy_survey": "Copiar inquérito",
|
||||
"copy_survey_description": "Copiar este questionário para outro ambiente",
|
||||
"copy_survey_error": "Falha ao copiar inquérito",
|
||||
"copy_survey_link_to_clipboard": "Copiar link do inquérito para a área de transferência",
|
||||
"copy_survey_partially_success": "{success} inquéritos copiados com sucesso, {error} falharam.",
|
||||
"copy_survey_success": "Inquérito copiado com sucesso!",
|
||||
"delete_survey_and_responses_warning": "Tem a certeza de que deseja eliminar este inquérito e todas as suas respostas?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Escolha o idioma padrão para este inquérito:",
|
||||
"2_activate_translation_for_specific_languages": "2. Ativar tradução para idiomas específicos:",
|
||||
"activate_translations": "Ativar traduções",
|
||||
"add": "Adicionar +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Adicionar um atraso ou fechar automaticamente o inquérito",
|
||||
"add_a_four_digit_pin": "Adicione um PIN de quatro dígitos",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Atribuir =",
|
||||
"audience": "Público",
|
||||
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
|
||||
"auto_progress_rating_and_nps": "Avançar automaticamente em perguntas de classificação e NPS",
|
||||
"auto_progress_rating_and_nps_description": "Avança automaticamente em blocos de pergunta única. Perguntas obrigatórias ocultam o botão Seguinte, exceto quando \"Outro\" está selecionado.",
|
||||
"auto_save_disabled": "Guardar automático desativado",
|
||||
"auto_save_disabled_tooltip": "O seu inquérito só é guardado automaticamente quando está em rascunho. Isto garante que os inquéritos públicos não sejam atualizados involuntariamente.",
|
||||
"auto_save_on": "Guardar automático ativado",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "As alterações levarão a inconsistências",
|
||||
"change_anyway": "Alterar mesmo assim",
|
||||
"change_background": "Alterar fundo",
|
||||
"change_default": "Alterar predefinição",
|
||||
"change_question_type": "Alterar tipo de pergunta",
|
||||
"change_survey_type": "Alterar o tipo de inquérito afeta o acesso existente",
|
||||
"change_the_background_to_a_color_image_or_animation": "Altere o fundo para uma cor, imagem ou animação",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Escolha onde realizar o inquérito.",
|
||||
"city": "Cidade",
|
||||
"close_survey_on_response_limit": "Fechar inquérito no limite de respostas",
|
||||
"code": "Código",
|
||||
"color": "Cor",
|
||||
"column_used_in_logic_error": "Esta coluna é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
|
||||
"columns": "Colunas",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Personalizar o logótipo do inquérito",
|
||||
"darken_or_lighten_background_of_your_choice": "Escurecer ou clarear o fundo da sua escolha.",
|
||||
"days_before_showing_this_survey_again": "ou mais dias a decorrer entre o último inquérito apresentado e a apresentação deste inquérito.",
|
||||
"default_language": "Idioma predefinido",
|
||||
"delete_anyways": "Eliminar mesmo assim",
|
||||
"delete_block": "Eliminar bloco",
|
||||
"delete_choice": "Eliminar escolha",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplicar pergunta",
|
||||
"edit_link": "Editar link",
|
||||
"edit_recall": "Editar Lembrete",
|
||||
"edit_translations": "Editar traduções {lang}",
|
||||
"element_not_found": "Pergunta não encontrada",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir que os inquiridos mudem de idioma a qualquer momento. Necessita de pelo menos 2 idiomas ativos.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "A proteção contra spam usa o reCAPTCHA v3 para filtrar as respostas de spam.",
|
||||
@@ -1599,11 +1602,13 @@
|
||||
"long_answer": "Resposta longa",
|
||||
"long_answer_toggle_description": "Permitir que os inquiridos escrevam respostas mais longas e com várias linhas.",
|
||||
"lower_label": "Etiqueta Inferior",
|
||||
"manage_languages": "Gerir Idiomas",
|
||||
"manage_languages": "Gerir idiomas",
|
||||
"manage_translations": "Gerir traduções",
|
||||
"matrix_all_fields": "Todos os campos",
|
||||
"matrix_rows": "Linhas",
|
||||
"max_file_size": "Tamanho máximo de ficheiro",
|
||||
"max_file_size_limit_is": "O limite de tamanho máximo de ficheiro é",
|
||||
"missing_first": "Em falta primeiro",
|
||||
"move_question_to_block": "Mover pergunta para o bloco",
|
||||
"multiply": "Multiplicar *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Rótulo do botão \"Seguinte\"",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Ainda não há campos ocultos. Adicione o primeiro abaixo.",
|
||||
"no_images_found_for": "Não foram encontradas imagens para ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nenhuma língua encontrada. Adicione a primeira para começar.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Não foram encontrados idiomas de inquérito neste espaço de trabalho. Por favor, adiciona um para começar.",
|
||||
"no_option_found": "Nenhuma opção encontrada",
|
||||
"no_recall_items_found": "Nenhum item de recuperação encontrado",
|
||||
"no_variables_yet_add_first_one_below": "Ainda não há variáveis. Adicione a primeira abaixo.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Por favor, insira um URL válido (por exemplo, https://example.com)",
|
||||
"please_set_a_survey_trigger": "Por favor, defina um desencadeador de inquérito",
|
||||
"please_specify": "Por favor, especifique",
|
||||
"present_your_survey_in_multiple_languages": "Apresenta o teu inquérito em vários idiomas",
|
||||
"prevent_double_submission": "Impedir submissão dupla",
|
||||
"prevent_double_submission_description": "Permitir apenas 1 resposta por endereço de email",
|
||||
"progress_saved": "Progresso guardado",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 pontos",
|
||||
"show_block_settings": "Mostrar definições do bloco",
|
||||
"show_button": "Mostrar Botão",
|
||||
"show_in_order": "Mostrar por ordem",
|
||||
"show_language_switch": "Mostrar alternador de idioma",
|
||||
"show_multiple_times": "Mostrar um número limitado de vezes",
|
||||
"show_only_once": "Mostrar apenas uma vez",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Pré-visualização do questionário 👀",
|
||||
"survey_styling": "Estilo do formulário",
|
||||
"survey_trigger": "Desencadeador de Inquérito",
|
||||
"switch_multi_language_on_to_get_started": "Ative o modo multilingue para começar 👉",
|
||||
"target_block_not_found": "Bloco de destino não encontrado",
|
||||
"targeted": "Alvo",
|
||||
"ten_points": "10 pontos",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar uma única vez, mesmo que não respondam.",
|
||||
"then": "Então",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Esta ação irá remover todas as traduções deste inquérito.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Isto irá remover este idioma e todas as suas traduções deste inquérito. Esta ação não pode ser revertida.",
|
||||
"three_points": "3 pontos",
|
||||
"times": "tempos",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para manter a colocação consistente em todos os questionários, pode",
|
||||
"translated": "Traduzido",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Desencadear inquérito quando uma das ações for disparada...",
|
||||
"try_lollipop_or_mountain": "Experimente 'cão' ou 'planta'...",
|
||||
"type_field_id": "Escreva o id do campo",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Permitir apenas que pessoas com um email real respondam.",
|
||||
"visibility_and_recontact": "Visibilidade e Recontacto",
|
||||
"visibility_and_recontact_description": "Controlar quando este inquérito pode aparecer e com que frequência pode reaparecer.",
|
||||
"visible": "Visível",
|
||||
"wait": "Aguardar",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Aguarde alguns segundos após o gatilho antes de mostrar o inquérito",
|
||||
"waiting_time_across_surveys": "Período de espera (entre inquéritos)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "A transferir código QR",
|
||||
"drop_offs": "Desistências",
|
||||
"drop_offs_tooltip": "Número de vezes que o inquérito foi iniciado mas não concluído.",
|
||||
"failed_to_copy_link": "Falha ao copiar link",
|
||||
"filter_added_successfully": "Filtro adicionado com sucesso",
|
||||
"filter_updated_successfully": "Filtro atualizado com sucesso",
|
||||
"filtered_responses_csv": "Respostas filtradas (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Inquérito eliminado com sucesso!",
|
||||
"survey_duplicated_successfully": "Inquérito duplicado com sucesso.",
|
||||
"survey_duplication_error": "Falha ao duplicar o inquérito.",
|
||||
"templates": {
|
||||
"all_channels": "Todos os canais",
|
||||
"all_industries": "Todas as indústrias",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Idioma ou ID de idioma duplicado",
|
||||
"edit_languages": "Editar idiomas",
|
||||
"identifier": "Identificador (ISO)",
|
||||
"incomplete_translations": "Traduções incompletas",
|
||||
"language": "Idioma",
|
||||
"language_deleted_successfully": "Idioma eliminado com sucesso",
|
||||
"languages_updated_successfully": "Idiomas atualizados com sucesso",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Por favor, selecione um idioma",
|
||||
"remove_language": "Remover idioma",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Por favor, remova o idioma destes inquéritos para o poder remover do espaço de trabalho.",
|
||||
"search_items": "Pesquisar itens",
|
||||
"translate": "Traduzir"
|
||||
"search_items": "Pesquisar itens"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Adicionar cor de fundo",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
|
||||
"advanced_styling_field_track_height": "Altura da faixa",
|
||||
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
|
||||
"advanced_styling_field_upper_label_color": "Cor da etiqueta do título",
|
||||
"advanced_styling_field_upper_label_color_description": "Colore a pequena etiqueta acima dos campos de entrada.",
|
||||
"advanced_styling_field_upper_label_size": "Tamanho da fonte da etiqueta do título",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho da pequena etiqueta acima dos campos de entrada.",
|
||||
"advanced_styling_field_upper_label_weight": "Peso da fonte da etiqueta do título",
|
||||
"advanced_styling_field_upper_label_weight_description": "Torna a etiqueta mais leve ou mais negrito.",
|
||||
"advanced_styling_field_upper_label_color": "Cor da Etiqueta",
|
||||
"advanced_styling_field_upper_label_color_description": "Define a cor das pequenas etiquetas acima dos campos de entrada e das etiquetas de escala.",
|
||||
"advanced_styling_field_upper_label_size": "Tamanho da Fonte da Etiqueta",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho das pequenas etiquetas acima dos campos de entrada e das etiquetas de escala.",
|
||||
"advanced_styling_field_upper_label_weight": "Espessura da Fonte da Etiqueta",
|
||||
"advanced_styling_field_upper_label_weight_description": "Torna as etiquetas mais finas ou mais grossas.",
|
||||
"advanced_styling_section_buttons": "Botões",
|
||||
"advanced_styling_section_headlines": "Títulos e descrições",
|
||||
"advanced_styling_section_inputs": "Campos de entrada",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "O que é uma coisa que poderíamos fazer melhor?",
|
||||
"identify_customer_goals_description": "Compreenda melhor se a sua mensagem cria as expectativas certas sobre o valor que o seu produto oferece.",
|
||||
"identify_customer_goals_name": "Identificar Objetivos do Cliente",
|
||||
"identify_customer_goals_question_1_choice_1": "Compreender profundamente a minha base de utilizadores",
|
||||
"identify_customer_goals_question_1_choice_2": "Identificar oportunidades de upselling",
|
||||
"identify_customer_goals_question_1_choice_3": "Construir o melhor produto possível",
|
||||
"identify_customer_goals_question_1_choice_4": "Dominar o mundo para fazer couves de Bruxelas ao pequeno-almoço para todos",
|
||||
"identify_customer_goals_question_1_headline": "Qual é o seu objetivo principal ao usar $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Ofereça um desconto para obter informações sobre as barreiras de inscrição.",
|
||||
"identify_sign_up_barriers_name": "Identificar Barreiras de Inscrição",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Obtenha 10% de desconto",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Ajude-nos a compreendê-lo melhor:",
|
||||
"improve_trial_conversion_question_2_button_label": "Seguinte",
|
||||
"improve_trial_conversion_question_2_headline": "Lamentamos saber. Qual foi o maior problema ao usar $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Seguinte",
|
||||
"improve_trial_conversion_question_3_headline": "O que esperava que $[projectName] fizesse?",
|
||||
"improve_trial_conversion_question_4_button_label": "Obtenha 20% de desconto",
|
||||
"improve_trial_conversion_question_4_headline": "Lamentamos saber! Obtenha 20% de desconto no primeiro ano.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Estamos felizes por lhe oferecer um desconto de 20% num plano anual.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Seguinte",
|
||||
"improve_trial_conversion_question_5_headline": "O que gostaria de alcançar?",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, selecione uma das seguintes opções:",
|
||||
"improve_trial_conversion_question_5_subheader": "Por favor, descreve abaixo:",
|
||||
"improve_trial_conversion_question_6_headline": "Como está a resolver o seu problema agora?",
|
||||
"improve_trial_conversion_question_6_subheader": "Por favor, nomeie soluções alternativas:",
|
||||
"integration_setup_survey_description": "Avalie a facilidade com que os utilizadores podem adicionar integrações ao seu produto. Encontre pontos cegos.",
|
||||
|
||||
+42
-30
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Modală centralizată",
|
||||
"change_organization": "Schimbă organizația",
|
||||
"change_workspace": "Schimbă spațiul de lucru",
|
||||
"choice_n": "Opțiunea {{n}}",
|
||||
"choices": "Alegeri",
|
||||
"choose_environment": "Alege mediul",
|
||||
"choose_organization": "Alege organizația",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Închide",
|
||||
"code": "Cod",
|
||||
"collapse_rows": "Restrânge rânduri",
|
||||
"column_n": "Coloana {{n}}",
|
||||
"completed": "Completat",
|
||||
"configuration": "Configurare",
|
||||
"confirm": "Confirmare",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Creat de",
|
||||
"customer_success": "Succesul Clientului",
|
||||
"dark_overlay": "Suprapunere întunecată",
|
||||
"data_refreshed_successfully": "Datele au fost actualizate cu succes",
|
||||
"date": "Dată",
|
||||
"days": "zile",
|
||||
"default": "Implicit",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Nu s-a reușit copierea în clipboard",
|
||||
"failed_to_load_organizations": "Nu s-a reușit încărcarea organizațiilor",
|
||||
"failed_to_load_workspaces": "Nu s-au putut încărca workspaces",
|
||||
"field_placeholder": "Substituent {{field}}",
|
||||
"filter": "Filtru",
|
||||
"finish": "Finalizează",
|
||||
"first_name": "Prenume",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Generează",
|
||||
"go_back": "Înapoi",
|
||||
"go_to_dashboard": "Mergi la Tablou de Bord",
|
||||
"headline": "Titlu",
|
||||
"hidden": "Ascuns",
|
||||
"hidden_field": "Câmp ascuns",
|
||||
"hidden_fields": "Câmpuri ascunse",
|
||||
"hide_column": "Ascunde coloana",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Imagine",
|
||||
"images": "Imagini",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "luni",
|
||||
"move_down": "Mută în jos",
|
||||
"move_up": "Mută sus",
|
||||
"multiple_languages": "Mai multe limbi",
|
||||
"my_product": "produsul meu",
|
||||
"name": "Nume",
|
||||
"new": "Nou",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Niciun rezultat găsit",
|
||||
"no_results": "Nicio rezultat",
|
||||
"no_surveys_found": "Nu au fost găsite sondaje.",
|
||||
"no_text_found": "Niciun text găsit",
|
||||
"none_of_the_above": "Niciuna dintre cele de mai sus",
|
||||
"not_authenticated": "Nu sunteți autentificat pentru a efectua această acțiune.",
|
||||
"not_authorized": "Neautorizat",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Setări Organizație",
|
||||
"other": "Altele",
|
||||
"other_filters": "Alte Filtre",
|
||||
"others": "Altele",
|
||||
"other_placeholder": "Alt substituent",
|
||||
"overlay_color": "Culoare overlay",
|
||||
"overview": "Prezentare generală",
|
||||
"password": "Parolă",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Vă rugăm să faceți upgrade la planul dumneavoastră",
|
||||
"powered_by_formbricks": "Oferit de Formbricks",
|
||||
"preview": "Previzualizare",
|
||||
"preview_survey": "Previzualizare Chestionar",
|
||||
"privacy": "Politica de Confidențialitate",
|
||||
"product_manager": "Manager de Produs",
|
||||
"production": "Producție",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Limitați numărul de răspunsuri primite de la participanții care îndeplinesc anumite criterii.",
|
||||
"read_docs": "Citește documentația",
|
||||
"recipients": "Destinatari",
|
||||
"refresh": "Actualizează",
|
||||
"remove": "Șterge",
|
||||
"remove_from_team": "Elimină din echipă",
|
||||
"reorder_and_hide_columns": "Reordonați și ascundeți coloanele",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Răspunsuri",
|
||||
"restart": "Repornește",
|
||||
"role": "Rolul",
|
||||
"row_n": "Rândul {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Vânzări",
|
||||
"save": "Salvează",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Stocarea fișierelor neconfigurată, upload-urile vor eșua probabil",
|
||||
"string": "Text",
|
||||
"styling": "Stilizare",
|
||||
"subheader": "Subtitlu",
|
||||
"submit": "Trimite",
|
||||
"summary": "Sumar",
|
||||
"survey": "Chestionar",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "ex: Formbricks",
|
||||
"workspaces": "Workspaces",
|
||||
"years": "ani",
|
||||
"you": "Tu",
|
||||
"you_are_downgraded_to_the_community_edition": "Ai fost retrogradat la ediția Community.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Nu sunteți autorizat să efectuați această acțiune.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Ați atins limita de {projectLimit} spații de lucru.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "A fost creat un nou atribut „{key}” cu tipul „{dataType}”",
|
||||
"attributes_msg_userid_already_exists": "ID-ul de utilizator există deja pentru acest mediu și nu a fost actualizat.",
|
||||
"contact_deleted_successfully": "Contact șters cu succes",
|
||||
"contacts_table_refresh": "Reîmprospătare contacte",
|
||||
"contacts_table_refresh_success": "Contactele au fost actualizate cu succes",
|
||||
"create_attribute": "Creează atribut",
|
||||
"create_new_attribute": "Creează atribut nou",
|
||||
"create_new_attribute_description": "Creează un atribut nou pentru segmentare.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Linkul de invitație al organizației tale este gata!",
|
||||
"organization_name": "Nume Organizație",
|
||||
"organization_name_description": "Oferiți organizației dumneavoastră un nume descriptiv.",
|
||||
"organization_name_placeholder": "ex. Power Puff Girls",
|
||||
"organization_name_placeholder": "ex. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Numele organizației actualizat cu succes",
|
||||
"organization_settings": "Setări Organizație",
|
||||
"please_add_a_logo": "Adaugă un logo",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"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",
|
||||
"copy_survey_error": "Nu s-a putut copia sondajul",
|
||||
"copy_survey_link_to_clipboard": "Copiază linkul chestionarului în clipboard",
|
||||
"copy_survey_partially_success": "\"{success} sondaje copiate cu succes, {error} eșuate.\"",
|
||||
"copy_survey_success": "\"Sondaj copiat cu succes!\"",
|
||||
"delete_survey_and_responses_warning": "Sigur doriți să ștergeți acest sondaj și toate răspunsurile sale?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Alege limba implicită pentru acest sondaj:",
|
||||
"2_activate_translation_for_specific_languages": "2. Activați traducerea pentru anumite limbi:",
|
||||
"activate_translations": "Activează traducerile",
|
||||
"add": "Adaugă +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Adăugați o întârziere sau închideți automat sondajul",
|
||||
"add_a_four_digit_pin": "Adăugați un cod PIN din patru cifre",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Atribuire =",
|
||||
"audience": "Public",
|
||||
"auto_close_on_inactivity": "Închidere automată la inactivitate",
|
||||
"auto_progress_rating_and_nps": "Avansare automată pentru întrebări de rating și NPS",
|
||||
"auto_progress_rating_and_nps_description": "Avansare automată în blocurile cu o singură întrebare. Întrebările obligatorii ascund butonul Următorul, cu excepția cazului în care este selectată opțiunea „Altele“.",
|
||||
"auto_save_disabled": "Salvare automată dezactivată",
|
||||
"auto_save_disabled_tooltip": "Chestionarul dvs. este salvat automat doar când este în ciornă. Acest lucru asigură că sondajele publice nu sunt actualizate neintenționat.",
|
||||
"auto_save_on": "Salvare automată activată",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Schimbările vor duce la inconsecvențe",
|
||||
"change_anyway": "Schimbă oricum",
|
||||
"change_background": "Schimbați fundalul",
|
||||
"change_default": "Schimbă implicit",
|
||||
"change_question_type": "Schimbă tipul întrebării",
|
||||
"change_survey_type": "Schimbarea tipului chestionarului afectează accesul existent",
|
||||
"change_the_background_to_a_color_image_or_animation": "Schimbați fundalul cu o culoare, imagine sau animație.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Alegeți unde să rulați chestionarul.",
|
||||
"city": "Oraș",
|
||||
"close_survey_on_response_limit": "Închideți sondajul la limită de răspunsuri",
|
||||
"code": "Cod",
|
||||
"color": "Culoare",
|
||||
"column_used_in_logic_error": "Această coloană este folosită în logica întrebării {questionIndex}. Vă rugăm să o eliminați din logică mai întâi.",
|
||||
"columns": "Coloane",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Personalizează logo-ul chestionarului",
|
||||
"darken_or_lighten_background_of_your_choice": "Întunecați sau luminați fundalul după preferințe.",
|
||||
"days_before_showing_this_survey_again": "sau mai multe zile să treacă între ultima afișare a sondajului și afișarea acestui sondaj.",
|
||||
"default_language": "Limba implicită",
|
||||
"delete_anyways": "Șterge oricum",
|
||||
"delete_block": "Șterge blocul",
|
||||
"delete_choice": "Șterge alegerea",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplică întrebarea",
|
||||
"edit_link": "Editare legătură",
|
||||
"edit_recall": "Editează Referințele",
|
||||
"edit_translations": "Editează traducerile {lang}",
|
||||
"element_not_found": "Întrebarea nu a fost găsită",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permite respondenților să schimbe limba în orice moment. Necesită minimum 2 limbi active.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Protecția împotriva spamului folosește reCAPTCHA v3 pentru a filtra răspunsurile de spam.",
|
||||
@@ -1599,11 +1602,13 @@
|
||||
"long_answer": "Răspuns lung",
|
||||
"long_answer_toggle_description": "Permite respondenților să scrie răspunsuri mai lungi, pe mai multe rânduri.",
|
||||
"lower_label": "Etichetă inferioară",
|
||||
"manage_languages": "Gestionați limbile",
|
||||
"manage_languages": "Gestionează limbile",
|
||||
"manage_translations": "Gestionează traducerile",
|
||||
"matrix_all_fields": "Toate câmpurile",
|
||||
"matrix_rows": "Rânduri",
|
||||
"max_file_size": "Dimensiune maximă fișier",
|
||||
"max_file_size_limit_is": "Limita maximă pentru dimensiunea fișierului este",
|
||||
"missing_first": "Lipsă întâi",
|
||||
"move_question_to_block": "Mută întrebarea în bloc",
|
||||
"multiply": "Multiplicare",
|
||||
"needed_for_self_hosted_cal_com_instance": "Necesar pentru un exemplu autogăzduit Cal.com",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Etichetă buton \"Următorul\"",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Nu există încă câmpuri ascunse. Adăugați primul mai jos.",
|
||||
"no_images_found_for": "Nicio imagine găsită pentru ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nu s-au găsit limbi. Adaugă prima pentru a începe.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Nu s-au găsit limbi de chestionar în acest spațiu de lucru. Te rugăm să adaugi una pentru a începe.",
|
||||
"no_option_found": "Nicio opțiune găsită",
|
||||
"no_recall_items_found": "Nu au fost găsite elemente de reamintire",
|
||||
"no_variables_yet_add_first_one_below": "Nu există variabile încă. Adăugați prima mai jos.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Vă rugăm să introduceți un URL valid (de exemplu, https://example.com)",
|
||||
"please_set_a_survey_trigger": "Vă rugăm să setați un declanșator sondaj",
|
||||
"please_specify": "Vă rugăm să specificați",
|
||||
"present_your_survey_in_multiple_languages": "Prezintă chestionarul tău în mai multe limbi",
|
||||
"prevent_double_submission": "Prevenire trimitere dublă",
|
||||
"prevent_double_submission_description": "Permite doar 1 răspuns per adresă de email.",
|
||||
"progress_saved": "Progres salvat",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 puncte",
|
||||
"show_block_settings": "Afișează setările blocului",
|
||||
"show_button": "Afișează butonul",
|
||||
"show_in_order": "Afișează în ordine",
|
||||
"show_language_switch": "Afișează comutatorul de limbă",
|
||||
"show_multiple_times": "Afișează de mai multe ori",
|
||||
"show_only_once": "Afișează doar o dată",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Previzualizare chestionar 👀",
|
||||
"survey_styling": "Stilizare formular",
|
||||
"survey_trigger": "Declanșator sondaj",
|
||||
"switch_multi_language_on_to_get_started": "Activați opțiunea multi-limbă pentru a începe 👉",
|
||||
"target_block_not_found": "Blocul țintă nu a fost găsit",
|
||||
"targeted": "Ţintite",
|
||||
"ten_points": "10 puncte",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Afișează o singură dată, chiar dacă persoana nu răspunde.",
|
||||
"then": "Apoi",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Această acțiune va elimina toate traducerile din acest sondaj.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Aceasta va elimina această limbă și toate traducerile ei din acest chestionar. Această acțiune nu poate fi anulată.",
|
||||
"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",
|
||||
"translated": "Tradus",
|
||||
"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",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Permite doar persoanelor cu un email real să răspundă.",
|
||||
"visibility_and_recontact": "Vizibilitate și recontactare",
|
||||
"visibility_and_recontact_description": "Controlează când poate apărea acest sondaj și cât de des poate reapărea.",
|
||||
"visible": "Vizibil",
|
||||
"wait": "Așteptați",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Așteptați câteva secunde după declanșare înainte de a afișa sondajul",
|
||||
"waiting_time_across_surveys": "Perioadă de răcire (între sondaje)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Se descarcă codul QR",
|
||||
"drop_offs": "Renunțări",
|
||||
"drop_offs_tooltip": "Număr de ori când sondajul a fost început dar nu a fost finalizat.",
|
||||
"failed_to_copy_link": "Nu s-a putut copia legătura",
|
||||
"filter_added_successfully": "Filtru adăugat cu succes",
|
||||
"filter_updated_successfully": "Filtru actualizat cu succes",
|
||||
"filtered_responses_csv": "Răspunsuri filtrate (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "\"Sondaj șters cu succes!\"",
|
||||
"survey_duplicated_successfully": "\"Sondaj duplicat cu succes!\"",
|
||||
"survey_duplication_error": "Eșec la duplicarea sondajului.",
|
||||
"templates": {
|
||||
"all_channels": "Toate canalele",
|
||||
"all_industries": "Toate industriile",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Limbă sau ID de limbă duplicat",
|
||||
"edit_languages": "Editați limbile",
|
||||
"identifier": "Identificator (ISO)",
|
||||
"incomplete_translations": "Traduceri incomplete",
|
||||
"language": "Limba",
|
||||
"language_deleted_successfully": "Limba a fost ștearsă cu succes",
|
||||
"languages_updated_successfully": "Limbile au fost actualizate cu succes",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Vă rugăm să selectați o limbă",
|
||||
"remove_language": "Eliminați limba",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Vă rugăm să eliminați limba din aceste sondaje pentru a o elimina din spațiul de lucru.",
|
||||
"search_items": "Căutați elemente",
|
||||
"translate": "Traduceți"
|
||||
"search_items": "Căutați elemente"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Adăugați culoare de fundal",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Colorează partea necompletată a barei.",
|
||||
"advanced_styling_field_track_height": "Înălțime track",
|
||||
"advanced_styling_field_track_height_description": "Controlează grosimea barei de progres.",
|
||||
"advanced_styling_field_upper_label_color": "Culoare etichetă titlu",
|
||||
"advanced_styling_field_upper_label_color_description": "Colorează eticheta mică de deasupra câmpurilor.",
|
||||
"advanced_styling_field_upper_label_size": "Mărime font etichetă titlu",
|
||||
"advanced_styling_field_upper_label_size_description": "Redimensionează eticheta mică de deasupra câmpurilor.",
|
||||
"advanced_styling_field_upper_label_weight": "Grosime font etichetă titlu",
|
||||
"advanced_styling_field_upper_label_weight_description": "Face eticheta mai subțire sau mai îngroșată.",
|
||||
"advanced_styling_field_upper_label_color": "Culoare etichetă",
|
||||
"advanced_styling_field_upper_label_color_description": "Colorează etichetele mici de deasupra câmpurilor de introducere și etichetele de scală.",
|
||||
"advanced_styling_field_upper_label_size": "Dimensiune font etichetă",
|
||||
"advanced_styling_field_upper_label_size_description": "Ajustează dimensiunea etichetelor mici de deasupra câmpurilor de introducere și a etichetelor de scală.",
|
||||
"advanced_styling_field_upper_label_weight": "Grosime font etichetă",
|
||||
"advanced_styling_field_upper_label_weight_description": "Face etichetele mai subțiri sau mai îngroșate.",
|
||||
"advanced_styling_section_buttons": "Butoane",
|
||||
"advanced_styling_section_headlines": "Titluri și descrieri",
|
||||
"advanced_styling_section_inputs": "Inputuri",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Care este acel lucru pe care l-am putea îmbunătăți?",
|
||||
"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_customer_goals_question_1_choice_1": "Să îmi înțeleg în profunzime baza de utilizatori",
|
||||
"identify_customer_goals_question_1_choice_2": "Să identific oportunități de upselling",
|
||||
"identify_customer_goals_question_1_choice_3": "Să construiesc cel mai bun produs posibil",
|
||||
"identify_customer_goals_question_1_choice_4": "Să cuceresc lumea pentru a-i face tuturor la micul dejun varză de Bruxelles",
|
||||
"identify_customer_goals_question_1_headline": "Care este obiectivul tău principal pentru utilizarea $[projectName]?",
|
||||
"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_question_1_button_label": "Obține reducere de 10%",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Ajută-ne să te înțelegem mai bine:",
|
||||
"improve_trial_conversion_question_2_button_label": "Următorul",
|
||||
"improve_trial_conversion_question_2_headline": "Ne pare rău să auzim asta. Care a fost cea mai mare problemă folosind $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Următorul",
|
||||
"improve_trial_conversion_question_3_headline": "Ce ați fi așteptat de la $[projectName]?",
|
||||
"improve_trial_conversion_question_4_button_label": "Obțineți 20% reducere",
|
||||
"improve_trial_conversion_question_4_headline": "Ne pare rău să auzim asta! Obțineți 20% reducere în primul an.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Suntem bucuroși să vă oferim o reducere de 20% la un plan anual.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Următorul",
|
||||
"improve_trial_conversion_question_5_headline": "Ce ați dori să obțineți?",
|
||||
"improve_trial_conversion_question_5_subheader": "Vă rugăm să selectați una dintre următoarele opțiuni:",
|
||||
"improve_trial_conversion_question_5_subheader": "Te rugăm să descrii mai jos:",
|
||||
"improve_trial_conversion_question_6_headline": "Cum rezolvați acum problema dumneavoastră?",
|
||||
"improve_trial_conversion_question_6_subheader": "Vă rugăm să numiți soluțiile alternative:",
|
||||
"integration_setup_survey_description": "Evaluați cât de ușor pot utilizatorii să adauge integrări la produsul dvs. Identificați punctele oarbe.",
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Центрированное модальное окно",
|
||||
"change_organization": "Сменить организацию",
|
||||
"change_workspace": "Сменить рабочее пространство",
|
||||
"choice_n": "Вариант {{n}}",
|
||||
"choices": "Варианты",
|
||||
"choose_environment": "Выберите среду",
|
||||
"choose_organization": "Выберите организацию",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Закрыть",
|
||||
"code": "Код",
|
||||
"collapse_rows": "Свернуть строки",
|
||||
"column_n": "Колонка {{n}}",
|
||||
"completed": "Завершено",
|
||||
"configuration": "Конфигурация",
|
||||
"confirm": "Подтвердить",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Создано пользователем",
|
||||
"customer_success": "Customer Success",
|
||||
"dark_overlay": "Тёмный оверлей",
|
||||
"data_refreshed_successfully": "Данные успешно обновлены",
|
||||
"date": "Дата",
|
||||
"days": "дни",
|
||||
"default": "По умолчанию",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Не удалось скопировать в буфер обмена",
|
||||
"failed_to_load_organizations": "Не удалось загрузить организации",
|
||||
"failed_to_load_workspaces": "Не удалось загрузить рабочие пространства",
|
||||
"field_placeholder": "Заполнитель {{field}}",
|
||||
"filter": "Фильтр",
|
||||
"finish": "Завершить",
|
||||
"first_name": "Имя",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Сгенерировать",
|
||||
"go_back": "Назад",
|
||||
"go_to_dashboard": "Перейти к панели управления",
|
||||
"headline": "Заголовок",
|
||||
"hidden": "Скрыто",
|
||||
"hidden_field": "Скрытое поле",
|
||||
"hidden_fields": "Скрытые поля",
|
||||
"hide_column": "Скрыть столбец",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Изображение",
|
||||
"images": "Изображения",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "месяцы",
|
||||
"move_down": "Переместить вниз",
|
||||
"move_up": "Переместить вверх",
|
||||
"multiple_languages": "Несколько языков",
|
||||
"my_product": "мой продукт",
|
||||
"name": "Имя",
|
||||
"new": "Новый",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Результат не найден",
|
||||
"no_results": "Нет результатов",
|
||||
"no_surveys_found": "Опросы не найдены.",
|
||||
"no_text_found": "Текст не найден",
|
||||
"none_of_the_above": "Ничего из вышеперечисленного",
|
||||
"not_authenticated": "У вас нет прав для выполнения этого действия.",
|
||||
"not_authorized": "Нет доступа",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Настройки организации",
|
||||
"other": "Другое",
|
||||
"other_filters": "Другие фильтры",
|
||||
"others": "Другие",
|
||||
"other_placeholder": "Другой заполнитель",
|
||||
"overlay_color": "Цвет наложения",
|
||||
"overview": "Обзор",
|
||||
"password": "Пароль",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Пожалуйста, обновите ваш тарифный план",
|
||||
"powered_by_formbricks": "Работает на Formbricks",
|
||||
"preview": "Предпросмотр",
|
||||
"preview_survey": "Предпросмотр опроса",
|
||||
"privacy": "Политика конфиденциальности",
|
||||
"product_manager": "Менеджер продукта",
|
||||
"production": "Продакшн",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Ограничьте количество ответов, которые вы получаете от участников, соответствующих определённым критериям.",
|
||||
"read_docs": "Читать документацию",
|
||||
"recipients": "Получатели",
|
||||
"refresh": "Обновить",
|
||||
"remove": "Удалить",
|
||||
"remove_from_team": "Удалить из команды",
|
||||
"reorder_and_hide_columns": "Изменить порядок и скрыть столбцы",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Ответы",
|
||||
"restart": "Перезапустить",
|
||||
"role": "Роль",
|
||||
"row_n": "Строка {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Продажи",
|
||||
"save": "Сохранить",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Хранилище файлов не настроено, загрузка, скорее всего, не удастся",
|
||||
"string": "Текст",
|
||||
"styling": "Стилизация",
|
||||
"subheader": "Подзаголовок",
|
||||
"submit": "Отправить",
|
||||
"summary": "Сводка",
|
||||
"survey": "Опрос",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "например, Formbricks",
|
||||
"workspaces": "Рабочие пространства",
|
||||
"years": "годы",
|
||||
"you": "Вы",
|
||||
"you_are_downgraded_to_the_community_edition": "Ваша версия понижена до Community Edition.",
|
||||
"you_are_not_authorized_to_perform_this_action": "У вас нет прав для выполнения этого действия.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Вы достигли лимита в {projectLimit} рабочих пространств.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Создан новый атрибут «{key}» с типом «{dataType}»",
|
||||
"attributes_msg_userid_already_exists": "Этот user ID уже существует в данной среде и не был обновлён.",
|
||||
"contact_deleted_successfully": "Контакт успешно удалён",
|
||||
"contacts_table_refresh": "Обновить контакты",
|
||||
"contacts_table_refresh_success": "Контакты успешно обновлены",
|
||||
"create_attribute": "Создать атрибут",
|
||||
"create_new_attribute": "Создать новый атрибут",
|
||||
"create_new_attribute_description": "Создайте новый атрибут для целей сегментации.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Ссылка для приглашения в организацию готова!",
|
||||
"organization_name": "Название организации",
|
||||
"organization_name_description": "Дайте вашей организации понятное название.",
|
||||
"organization_name_placeholder": "например, Power Puff Girls",
|
||||
"organization_name_placeholder": "например, Acme Inc.",
|
||||
"organization_name_updated_successfully": "Название организации успешно обновлено",
|
||||
"organization_settings": "Настройки организации",
|
||||
"please_add_a_logo": "Пожалуйста, добавьте логотип",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Всё готово! Пора создать первый опрос",
|
||||
"alphabetical": "По алфавиту",
|
||||
"copy_survey": "Копировать опрос",
|
||||
"copy_survey_description": "Скопируйте этот опрос в другую среду",
|
||||
"copy_survey_error": "Не удалось скопировать опрос",
|
||||
"copy_survey_link_to_clipboard": "Скопировать ссылку на опрос в буфер обмена",
|
||||
"copy_survey_partially_success": "Успешно скопировано опросов: {success}, не удалось: {error}.",
|
||||
"copy_survey_success": "Опрос успешно скопирован!",
|
||||
"delete_survey_and_responses_warning": "Вы уверены, что хотите удалить этот опрос и все его ответы?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Выберите язык по умолчанию для этого опроса:",
|
||||
"2_activate_translation_for_specific_languages": "2. Активируйте перевод для выбранных языков:",
|
||||
"activate_translations": "Активировать переводы",
|
||||
"add": "Добавить +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Добавить задержку или автоматически закрыть опрос",
|
||||
"add_a_four_digit_pin": "Добавить четырёхзначный PIN-код",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Назначить =",
|
||||
"audience": "Аудитория",
|
||||
"auto_close_on_inactivity": "Автоматически закрывать при бездействии",
|
||||
"auto_progress_rating_and_nps": "Автоматический переход для вопросов с оценкой и NPS",
|
||||
"auto_progress_rating_and_nps_description": "Автоматический переход в блоках с одним вопросом. Обязательные вопросы скрывают кнопку «Далее», за исключением случаев, когда выбран вариант «Другое».",
|
||||
"auto_save_disabled": "Автосохранение отключено",
|
||||
"auto_save_disabled_tooltip": "Ваш опрос автоматически сохраняется только в режиме черновика. Это гарантирует, что публичные опросы не будут случайно обновлены.",
|
||||
"auto_save_on": "Автосохранение включено",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Изменения приведут к несоответствиям",
|
||||
"change_anyway": "Всё равно изменить",
|
||||
"change_background": "Изменить фон",
|
||||
"change_default": "Изменить по умолчанию",
|
||||
"change_question_type": "Изменить тип вопроса",
|
||||
"change_survey_type": "Смена типа опроса влияет на существующий доступ",
|
||||
"change_the_background_to_a_color_image_or_animation": "Изменить фон на цвет, изображение или анимацию.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Выберите, где запускать опрос.",
|
||||
"city": "Город",
|
||||
"close_survey_on_response_limit": "Закрыть опрос при достижении лимита ответов",
|
||||
"code": "Код",
|
||||
"color": "Цвет",
|
||||
"column_used_in_logic_error": "Этот столбец используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите его из логики.",
|
||||
"columns": "Столбцы",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Настроить логотип опроса",
|
||||
"darken_or_lighten_background_of_your_choice": "Затемните или осветлите выбранный фон.",
|
||||
"days_before_showing_this_survey_again": "или больше дней должно пройти между последним показом опроса и показом этого опроса.",
|
||||
"default_language": "Язык по умолчанию",
|
||||
"delete_anyways": "Удалить в любом случае",
|
||||
"delete_block": "Удалить блок",
|
||||
"delete_choice": "Удалить вариант",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Дублировать вопрос",
|
||||
"edit_link": "Редактировать ссылку",
|
||||
"edit_recall": "Редактировать напоминание",
|
||||
"edit_translations": "Редактировать переводы на {lang}",
|
||||
"element_not_found": "Вопрос не найден",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Разрешить респондентам менять язык опроса в любое время. Требуется минимум 2 активных языка.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Для защиты от спама используется reCAPTCHA v3, чтобы отфильтровывать спам-ответы.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Позволить респондентам писать более длинные, многострочные ответы.",
|
||||
"lower_label": "Нижняя метка",
|
||||
"manage_languages": "Управление языками",
|
||||
"manage_translations": "Управление переводами",
|
||||
"matrix_all_fields": "Все поля",
|
||||
"matrix_rows": "Строки",
|
||||
"max_file_size": "Максимальный размер файла",
|
||||
"max_file_size_limit_is": "Ограничение максимального размера файла",
|
||||
"missing_first": "Сначала отсутствующие",
|
||||
"move_question_to_block": "Переместить вопрос в блок",
|
||||
"multiply": "Умножить *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Требуется для самостоятельного размещения Cal.com",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "Метка кнопки «Далее»",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Скрытых полей пока нет. Добавьте первое ниже.",
|
||||
"no_images_found_for": "Изображения не найдены для «{query}»",
|
||||
"no_languages_found_add_first_one_to_get_started": "Языки не найдены. Добавьте первый, чтобы начать.",
|
||||
"no_languages_found_add_first_one_to_get_started": "В этом рабочем пространстве не найдено языков опроса. Пожалуйста, добавьте язык, чтобы начать работу.",
|
||||
"no_option_found": "Вариант не найден",
|
||||
"no_recall_items_found": "Не найдено ни одного элемента для напоминания",
|
||||
"no_variables_yet_add_first_one_below": "Пока нет переменных. Добавьте первую ниже.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Пожалуйста, введите корректный URL (например, https://example.com)",
|
||||
"please_set_a_survey_trigger": "Пожалуйста, установите триггер опроса",
|
||||
"please_specify": "Пожалуйста, уточните",
|
||||
"present_your_survey_in_multiple_languages": "Представьте свой опрос на нескольких языках",
|
||||
"prevent_double_submission": "Предотвратить повторную отправку",
|
||||
"prevent_double_submission_description": "Разрешить только 1 ответ на один адрес электронной почты",
|
||||
"progress_saved": "Прогресс сохранён",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 баллов",
|
||||
"show_block_settings": "Показать настройки блока",
|
||||
"show_button": "Показать кнопку",
|
||||
"show_in_order": "Показать по порядку",
|
||||
"show_language_switch": "Показать переключатель языка",
|
||||
"show_multiple_times": "Показать ограниченное количество раз",
|
||||
"show_only_once": "Показать только один раз",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Предпросмотр опроса 👀",
|
||||
"survey_styling": "Оформление формы",
|
||||
"survey_trigger": "Триггер опроса",
|
||||
"switch_multi_language_on_to_get_started": "Включите многоязычный режим, чтобы начать 👉",
|
||||
"target_block_not_found": "Целевой блок не найден",
|
||||
"targeted": "Нацелен",
|
||||
"ten_points": "10 баллов",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Показать один раз, даже если не будет ответа.",
|
||||
"then": "Затем",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Это действие удалит все переводы из этого опроса.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Это удалит данный язык и все его переводы из этого опроса. Это действие нельзя отменить.",
|
||||
"three_points": "3 балла",
|
||||
"times": "раз",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Чтобы сохранить единое расположение во всех опросах, вы можете",
|
||||
"translated": "Переведено",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Запустить опрос при выполнении одного из действий...",
|
||||
"try_lollipop_or_mountain": "Попробуйте «lollipop» или «mountain»...",
|
||||
"type_field_id": "Введите id поля",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Разрешить отвечать только пользователям с реальным email.",
|
||||
"visibility_and_recontact": "Видимость и повторный контакт",
|
||||
"visibility_and_recontact_description": "Управляйте, когда этот опрос может появляться и как часто он может повторяться.",
|
||||
"visible": "Видимый",
|
||||
"wait": "Ожидание",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Подождите несколько секунд после срабатывания триггера перед показом опроса",
|
||||
"waiting_time_across_surveys": "Период ожидания (между опросами)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Скачивание QR-кода",
|
||||
"drop_offs": "Прерывания",
|
||||
"drop_offs_tooltip": "Количество раз, когда опрос был начат, но не завершён.",
|
||||
"failed_to_copy_link": "Не удалось скопировать ссылку",
|
||||
"filter_added_successfully": "Фильтр успешно добавлен",
|
||||
"filter_updated_successfully": "Фильтр успешно обновлён",
|
||||
"filtered_responses_csv": "Отфильтрованные ответы (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Опрос успешно удалён!",
|
||||
"survey_duplicated_successfully": "Опрос успешно продублирован.",
|
||||
"survey_duplication_error": "Не удалось продублировать опрос.",
|
||||
"templates": {
|
||||
"all_channels": "Все каналы",
|
||||
"all_industries": "Все отрасли",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Дублирующийся язык или идентификатор языка",
|
||||
"edit_languages": "Редактировать языки",
|
||||
"identifier": "Идентификатор (ISO)",
|
||||
"incomplete_translations": "Неполные переводы",
|
||||
"language": "Язык",
|
||||
"language_deleted_successfully": "Язык успешно удалён",
|
||||
"languages_updated_successfully": "Языки успешно обновлены",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Пожалуйста, выберите язык",
|
||||
"remove_language": "Удалить язык",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Пожалуйста, удалите язык из этих опросов, чтобы удалить его из рабочей области.",
|
||||
"search_items": "Поиск элементов",
|
||||
"translate": "Перевести"
|
||||
"search_items": "Поиск элементов"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Добавить цвет фона",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Задаёт цвет незаполненной части полосы.",
|
||||
"advanced_styling_field_track_height": "Высота трека",
|
||||
"advanced_styling_field_track_height_description": "Управляет толщиной индикатора прогресса.",
|
||||
"advanced_styling_field_upper_label_color": "Цвет метки заголовка",
|
||||
"advanced_styling_field_upper_label_color_description": "Задаёт цвет маленькой метки над полями ввода.",
|
||||
"advanced_styling_field_upper_label_size": "Размер шрифта метки заголовка",
|
||||
"advanced_styling_field_upper_label_size_description": "Изменяет размер маленькой метки над полями ввода.",
|
||||
"advanced_styling_field_upper_label_weight": "Толщина шрифта метки заголовка",
|
||||
"advanced_styling_field_upper_label_weight_description": "Делает метку тоньше или жирнее.",
|
||||
"advanced_styling_field_upper_label_color": "Цвет подписи",
|
||||
"advanced_styling_field_upper_label_color_description": "Задаёт цвет маленьких подписей над полями ввода и подписей шкалы.",
|
||||
"advanced_styling_field_upper_label_size": "Размер шрифта подписи",
|
||||
"advanced_styling_field_upper_label_size_description": "Изменяет размер маленьких подписей над полями ввода и подписей шкалы.",
|
||||
"advanced_styling_field_upper_label_weight": "Насыщенность шрифта подписи",
|
||||
"advanced_styling_field_upper_label_weight_description": "Делает подписи светлее или жирнее.",
|
||||
"advanced_styling_section_buttons": "Кнопки",
|
||||
"advanced_styling_section_headlines": "Заголовки и описания",
|
||||
"advanced_styling_section_inputs": "Поля ввода",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Что мы могли бы сделать лучше?",
|
||||
"identify_customer_goals_description": "Лучше понять, создают ли ваши сообщения правильные ожидания относительно ценности вашего продукта.",
|
||||
"identify_customer_goals_name": "Определение целей клиента",
|
||||
"identify_customer_goals_question_1_choice_1": "Глубоко понять свою пользовательскую базу",
|
||||
"identify_customer_goals_question_1_choice_2": "Выявить возможности для допродаж",
|
||||
"identify_customer_goals_question_1_choice_3": "Создать наилучший продукт",
|
||||
"identify_customer_goals_question_1_choice_4": "Править миром, чтобы накормить всех брюссельской капустой на завтрак",
|
||||
"identify_customer_goals_question_1_headline": "Какова ваша основная цель использования $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Предложите скидку, чтобы узнать, что мешает регистрации.",
|
||||
"identify_sign_up_barriers_name": "Определение барьеров регистрации",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Получить скидку 10%",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Помогите нам лучше вас понять:",
|
||||
"improve_trial_conversion_question_2_button_label": "Далее",
|
||||
"improve_trial_conversion_question_2_headline": "Жаль это слышать. Какая была самая большая проблема при использовании $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Далее",
|
||||
"improve_trial_conversion_question_3_headline": "Что вы ожидали от $[projectName]?",
|
||||
"improve_trial_conversion_question_4_button_label": "Получить скидку 20%",
|
||||
"improve_trial_conversion_question_4_headline": "Жаль это слышать! Получите 20% скидку на первый год.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Мы рады предложить вам скидку 20% на годовой тариф.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Далее",
|
||||
"improve_trial_conversion_question_5_headline": "Чего бы вы хотели достичь?",
|
||||
"improve_trial_conversion_question_5_subheader": "Пожалуйста, выберите один из следующих вариантов:",
|
||||
"improve_trial_conversion_question_5_subheader": "Пожалуйста, опишите ниже:",
|
||||
"improve_trial_conversion_question_6_headline": "Как вы сейчас решаете свою проблему?",
|
||||
"improve_trial_conversion_question_6_subheader": "Пожалуйста, укажите альтернативные решения:",
|
||||
"integration_setup_survey_description": "Оцените, насколько легко пользователи могут добавлять интеграции в ваш продукт. Найдите слабые места.",
|
||||
|
||||
+41
-29
@@ -152,6 +152,7 @@
|
||||
"centered_modal": "Centrerad modal",
|
||||
"change_organization": "Byt organisation",
|
||||
"change_workspace": "Byt arbetsyta",
|
||||
"choice_n": "Val {{n}}",
|
||||
"choices": "Val",
|
||||
"choose_environment": "Välj miljö",
|
||||
"choose_organization": "Välj organisation",
|
||||
@@ -165,6 +166,7 @@
|
||||
"close": "Stäng",
|
||||
"code": "Kod",
|
||||
"collapse_rows": "Dölj rader",
|
||||
"column_n": "Kolumn {{n}}",
|
||||
"completed": "Slutförd",
|
||||
"configuration": "Konfiguration",
|
||||
"confirm": "Bekräfta",
|
||||
@@ -195,6 +197,7 @@
|
||||
"created_by": "Skapad av",
|
||||
"customer_success": "Kundframgång",
|
||||
"dark_overlay": "Mörkt överlägg",
|
||||
"data_refreshed_successfully": "Data uppdaterades",
|
||||
"date": "Datum",
|
||||
"days": "dagar",
|
||||
"default": "Standard",
|
||||
@@ -236,6 +239,7 @@
|
||||
"failed_to_copy_to_clipboard": "Misslyckades att kopiera till urklipp",
|
||||
"failed_to_load_organizations": "Misslyckades att ladda organisationer",
|
||||
"failed_to_load_workspaces": "Det gick inte att ladda arbetsytor",
|
||||
"field_placeholder": "Platshållare för {{field}}",
|
||||
"filter": "Filter",
|
||||
"finish": "Slutför",
|
||||
"first_name": "Förnamn",
|
||||
@@ -247,10 +251,12 @@
|
||||
"generate": "Generera",
|
||||
"go_back": "Gå tillbaka",
|
||||
"go_to_dashboard": "Gå till instrumentpanelen",
|
||||
"headline": "Rubrik",
|
||||
"hidden": "Dold",
|
||||
"hidden_field": "Dolt fält",
|
||||
"hidden_fields": "Dolda fält",
|
||||
"hide_column": "Dölj kolumn",
|
||||
"html": "HTML",
|
||||
"id": "ID",
|
||||
"image": "Bild",
|
||||
"images": "Bilder",
|
||||
@@ -298,7 +304,6 @@
|
||||
"months": "månader",
|
||||
"move_down": "Flytta ner",
|
||||
"move_up": "Flytta upp",
|
||||
"multiple_languages": "Flera språk",
|
||||
"my_product": "min produkt",
|
||||
"name": "Namn",
|
||||
"new": "Ny",
|
||||
@@ -313,6 +318,7 @@
|
||||
"no_result_found": "Inget resultat hittades",
|
||||
"no_results": "Inga resultat",
|
||||
"no_surveys_found": "Inga enkäter hittades.",
|
||||
"no_text_found": "Ingen text hittades",
|
||||
"none_of_the_above": "Inget av ovanstående",
|
||||
"not_authenticated": "Du är inte autentiserad för att utföra denna åtgärd.",
|
||||
"not_authorized": "Ej behörig",
|
||||
@@ -336,7 +342,7 @@
|
||||
"organization_settings": "Organisationsinställningar",
|
||||
"other": "Annat",
|
||||
"other_filters": "Andra filter",
|
||||
"others": "Andra",
|
||||
"other_placeholder": "Annan platshållare",
|
||||
"overlay_color": "Overlay-färg",
|
||||
"overview": "Översikt",
|
||||
"password": "Lösenord",
|
||||
@@ -354,7 +360,6 @@
|
||||
"please_upgrade_your_plan": "Vänligen uppgradera din plan",
|
||||
"powered_by_formbricks": "Drivs av Formbricks",
|
||||
"preview": "Förhandsgranska",
|
||||
"preview_survey": "Förhandsgranska enkät",
|
||||
"privacy": "Integritetspolicy",
|
||||
"product_manager": "Produktchef",
|
||||
"production": "Produktion",
|
||||
@@ -369,6 +374,7 @@
|
||||
"quotas_description": "Begränsa antalet svar du får från deltagare som uppfyller vissa kriterier.",
|
||||
"read_docs": "Läs dokumentation",
|
||||
"recipients": "Mottagare",
|
||||
"refresh": "Uppdatera",
|
||||
"remove": "Ta bort",
|
||||
"remove_from_team": "Ta bort från teamet",
|
||||
"reorder_and_hide_columns": "Ordna om och dölj kolumner",
|
||||
@@ -381,6 +387,7 @@
|
||||
"responses": "Svar",
|
||||
"restart": "Starta om",
|
||||
"role": "Roll",
|
||||
"row_n": "Rad {{n}}",
|
||||
"saas": "SaaS",
|
||||
"sales": "Försäljning",
|
||||
"save": "Spara",
|
||||
@@ -419,6 +426,7 @@
|
||||
"storage_not_configured": "Fillagring är inte konfigurerad, uppladdningar kommer sannolikt att misslyckas",
|
||||
"string": "Text",
|
||||
"styling": "Styling",
|
||||
"subheader": "Underrubrik",
|
||||
"submit": "Skicka",
|
||||
"summary": "Sammanfattning",
|
||||
"survey": "Enkät",
|
||||
@@ -487,7 +495,6 @@
|
||||
"workspace_name_placeholder": "t.ex. Formbricks",
|
||||
"workspaces": "Arbetsytor",
|
||||
"years": "år",
|
||||
"you": "Du",
|
||||
"you_are_downgraded_to_the_community_edition": "Du har nedgraderats till Community Edition.",
|
||||
"you_are_not_authorized_to_perform_this_action": "Du har inte behörighet att utföra denna åtgärd.",
|
||||
"you_have_reached_your_limit_of_workspace_limit": "Du har nått din gräns på {projectLimit} arbetsytor.",
|
||||
@@ -669,8 +676,6 @@
|
||||
"attributes_msg_new_attribute_created": "Nytt attribut ”{key}” med typen ”{dataType}” har skapats",
|
||||
"attributes_msg_userid_already_exists": "Användar-ID finns redan för denna miljö och uppdaterades inte.",
|
||||
"contact_deleted_successfully": "Kontakt borttagen",
|
||||
"contacts_table_refresh": "Uppdatera kontakter",
|
||||
"contacts_table_refresh_success": "Kontakter uppdaterade",
|
||||
"create_attribute": "Skapa attribut",
|
||||
"create_new_attribute": "Skapa nytt attribut",
|
||||
"create_new_attribute_description": "Skapa ett nytt attribut för segmenteringsändamål.",
|
||||
@@ -1193,7 +1198,7 @@
|
||||
"organization_invite_link_ready": "Din organisationsinbjudningslänk är redo!",
|
||||
"organization_name": "Organisationsnamn",
|
||||
"organization_name_description": "Ge din organisation ett beskrivande namn.",
|
||||
"organization_name_placeholder": "t.ex. Power Puff Girls",
|
||||
"organization_name_placeholder": "t.ex. Acme Inc.",
|
||||
"organization_name_updated_successfully": "Organisationsnamn uppdaterat",
|
||||
"organization_settings": "Organisationsinställningar",
|
||||
"please_add_a_logo": "Vänligen lägg till en logotyp",
|
||||
@@ -1305,16 +1310,10 @@
|
||||
"surveys": {
|
||||
"all_set_time_to_create_first_survey": "Allt klart! Dags att skapa din första enkät",
|
||||
"alphabetical": "Alfabetisk",
|
||||
"copy_survey": "Kopiera enkät",
|
||||
"copy_survey_description": "Kopiera denna enkät till en annan miljö",
|
||||
"copy_survey_error": "Misslyckades med att kopiera enkät",
|
||||
"copy_survey_link_to_clipboard": "Kopiera enkätlänk till urklipp",
|
||||
"copy_survey_partially_success": "{success} enkäter kopierade, {error} misslyckades.",
|
||||
"copy_survey_success": "Enkät kopierad!",
|
||||
"delete_survey_and_responses_warning": "Är du säker på att du vill ta bort denna enkät och alla dess svar?",
|
||||
"edit": {
|
||||
"1_choose_the_default_language_for_this_survey": "1. Välj standardspråk för denna enkät:",
|
||||
"2_activate_translation_for_specific_languages": "2. Aktivera översättning för specifika språk:",
|
||||
"activate_translations": "Aktivera översättningar",
|
||||
"add": "Lägg till +",
|
||||
"add_a_delay_or_auto_close_the_survey": "Lägg till fördröjning eller stäng enkäten automatiskt",
|
||||
"add_a_four_digit_pin": "Lägg till en fyrsiffrig PIN",
|
||||
@@ -1363,6 +1362,8 @@
|
||||
"assign": "Tilldela =",
|
||||
"audience": "Målgrupp",
|
||||
"auto_close_on_inactivity": "Stäng automatiskt vid inaktivitet",
|
||||
"auto_progress_rating_and_nps": "Gå vidare automatiskt vid betygs- och NPS-frågor",
|
||||
"auto_progress_rating_and_nps_description": "Gå automatiskt vidare i block med en enda fråga. Obligatoriska frågor döljer Nästa, utom när \"Annat\" är valt.",
|
||||
"auto_save_disabled": "Automatisk sparning inaktiverad",
|
||||
"auto_save_disabled_tooltip": "Din enkät sparas endast automatiskt när den är ett utkast. Detta säkerställer att publika enkäter inte uppdateras oavsiktligt.",
|
||||
"auto_save_on": "Automatisk sparning på",
|
||||
@@ -1408,6 +1409,7 @@
|
||||
"caution_text": "Ändringar kommer att leda till inkonsekvenser",
|
||||
"change_anyway": "Ändra ändå",
|
||||
"change_background": "Ändra bakgrund",
|
||||
"change_default": "Ändra standard",
|
||||
"change_question_type": "Ändra frågetyp",
|
||||
"change_survey_type": "Byte av enkättyp påverkar befintlig åtkomst",
|
||||
"change_the_background_to_a_color_image_or_animation": "Ändra bakgrunden till en färg, bild eller animering.",
|
||||
@@ -1420,6 +1422,7 @@
|
||||
"choose_where_to_run_the_survey": "Välj var enkäten ska köras.",
|
||||
"city": "Stad",
|
||||
"close_survey_on_response_limit": "Stäng enkät vid svarsgräns",
|
||||
"code": "Kod",
|
||||
"color": "Färg",
|
||||
"column_used_in_logic_error": "Denna kolumn används i logiken för fråga {questionIndex}. Vänligen ta bort den från logiken först.",
|
||||
"columns": "Kolumner",
|
||||
@@ -1444,6 +1447,7 @@
|
||||
"customize_survey_logo": "Anpassa undersökningens logotyp",
|
||||
"darken_or_lighten_background_of_your_choice": "Gör bakgrunden mörkare eller ljusare efter eget val.",
|
||||
"days_before_showing_this_survey_again": "eller fler dagar måste gå mellan den senaste visade enkäten och att visa denna enkät.",
|
||||
"default_language": "Standardspråk",
|
||||
"delete_anyways": "Ta bort ändå",
|
||||
"delete_block": "Ta bort block",
|
||||
"delete_choice": "Ta bort val",
|
||||
@@ -1463,7 +1467,6 @@
|
||||
"duplicate_question": "Duplicera fråga",
|
||||
"edit_link": "Redigera länk",
|
||||
"edit_recall": "Redigera återkallning",
|
||||
"edit_translations": "Redigera {lang} översättningar",
|
||||
"element_not_found": "Fråga hittades inte",
|
||||
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Tillåt respondenter att byta språk när som helst. Kräver minst 2 aktiva språk.",
|
||||
"enable_recaptcha_to_protect_your_survey_from_spam": "Spamskydd använder reCAPTCHA v3 för att filtrera bort spam-svar.",
|
||||
@@ -1600,10 +1603,12 @@
|
||||
"long_answer_toggle_description": "Tillåt respondenter att skriva längre svar på flera rader.",
|
||||
"lower_label": "Lägre etikett",
|
||||
"manage_languages": "Hantera språk",
|
||||
"manage_translations": "Hantera översättningar",
|
||||
"matrix_all_fields": "Alla fält",
|
||||
"matrix_rows": "Rader",
|
||||
"max_file_size": "Max filstorlek",
|
||||
"max_file_size_limit_is": "Maximal filstorleksgräns är",
|
||||
"missing_first": "Saknade först",
|
||||
"move_question_to_block": "Flytta fråga till block",
|
||||
"multiply": "Multiplicera *",
|
||||
"needed_for_self_hosted_cal_com_instance": "Behövs för en självhostad Cal.com-instans",
|
||||
@@ -1611,7 +1616,7 @@
|
||||
"next_button_label": "\"Nästa\"-knappetikett",
|
||||
"no_hidden_fields_yet_add_first_one_below": "Inga dolda fält ännu. Lägg till det första nedan.",
|
||||
"no_images_found_for": "Inga bilder hittades för ''{query}\"",
|
||||
"no_languages_found_add_first_one_to_get_started": "Inga språk hittades. Lägg till det första för att komma igång.",
|
||||
"no_languages_found_add_first_one_to_get_started": "Inga undersökningsspråk hittades i denna arbetsyta. Lägg till ett för att komma igång.",
|
||||
"no_option_found": "Inget alternativ hittat",
|
||||
"no_recall_items_found": "Inga återkallningsobjekt hittades",
|
||||
"no_variables_yet_add_first_one_below": "Inga variabler ännu. Lägg till den första nedan.",
|
||||
@@ -1638,6 +1643,7 @@
|
||||
"please_enter_a_valid_url": "Vänligen ange en giltig URL (t.ex. https://example.com)",
|
||||
"please_set_a_survey_trigger": "Vänligen ställ in en enkätutlösare",
|
||||
"please_specify": "Vänligen specificera",
|
||||
"present_your_survey_in_multiple_languages": "Presentera din enkät på flera språk",
|
||||
"prevent_double_submission": "Förhindra dubbelinskickning",
|
||||
"prevent_double_submission_description": "Tillåt endast 1 svar per e-postadress",
|
||||
"progress_saved": "Framsteg sparade",
|
||||
@@ -1729,6 +1735,7 @@
|
||||
"seven_points": "7 poäng",
|
||||
"show_block_settings": "Visa blockinställningar",
|
||||
"show_button": "Visa knapp",
|
||||
"show_in_order": "Visa i ordning",
|
||||
"show_language_switch": "Visa språkväxlare",
|
||||
"show_multiple_times": "Visa ett begränsat antal gånger",
|
||||
"show_only_once": "Visa endast en gång",
|
||||
@@ -1760,7 +1767,6 @@
|
||||
"survey_preview": "Enkätförhandsgranskning 👀",
|
||||
"survey_styling": "Formulärstil",
|
||||
"survey_trigger": "Enkätutlösare",
|
||||
"switch_multi_language_on_to_get_started": "Slå på flerspråkighet för att komma igång 👉",
|
||||
"target_block_not_found": "Målblock hittades inte",
|
||||
"targeted": "Riktad",
|
||||
"ten_points": "10 poäng",
|
||||
@@ -1768,9 +1774,11 @@
|
||||
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Visa en enda gång, även om de inte svarar.",
|
||||
"then": "Sedan",
|
||||
"this_action_will_remove_all_the_translations_from_this_survey": "Denna åtgärd kommer att ta bort alla översättningar från denna enkät.",
|
||||
"this_will_remove_the_language_and_all_its_translations": "Detta tar bort språket och alla dess översättningar från denna enkät. Denna åtgärd kan inte ångras.",
|
||||
"three_points": "3 poäng",
|
||||
"times": "gånger",
|
||||
"to_keep_the_placement_over_all_surveys_consistent_you_can": "För att hålla placeringen konsekvent över alla enkäter kan du",
|
||||
"translated": "Översatt",
|
||||
"trigger_survey_when_one_of_the_actions_is_fired": "Utlös enkät när en av åtgärderna aktiveras...",
|
||||
"try_lollipop_or_mountain": "Prova 'lollipop' eller 'mountain'...",
|
||||
"type_field_id": "Skriv fält-ID",
|
||||
@@ -1845,6 +1853,7 @@
|
||||
"verify_email_before_submission_description": "Låt endast personer med en riktig e-post svara.",
|
||||
"visibility_and_recontact": "Synlighet och återkontakt",
|
||||
"visibility_and_recontact_description": "Kontrollera när denna enkät kan visas och hur ofta den kan visas igen.",
|
||||
"visible": "Synlig",
|
||||
"wait": "Vänta",
|
||||
"wait_a_few_seconds_after_the_trigger_before_showing_the_survey": "Vänta några sekunder efter utlösningen innan enkäten visas",
|
||||
"waiting_time_across_surveys": "Väntetid (mellan enkäter)",
|
||||
@@ -2053,7 +2062,6 @@
|
||||
"downloading_qr_code": "Laddar ner QR-kod",
|
||||
"drop_offs": "Avhopp",
|
||||
"drop_offs_tooltip": "Antal gånger enkäten har startats men inte slutförts.",
|
||||
"failed_to_copy_link": "Misslyckades med att kopiera länk",
|
||||
"filter_added_successfully": "Filter tillagt",
|
||||
"filter_updated_successfully": "Filter uppdaterat",
|
||||
"filtered_responses_csv": "Filtrerade svar (CSV)",
|
||||
@@ -2141,7 +2149,6 @@
|
||||
},
|
||||
"survey_deleted_successfully": "Enkät borttagen!",
|
||||
"survey_duplicated_successfully": "Enkät duplicerad.",
|
||||
"survey_duplication_error": "Misslyckades med att duplicera enkäten.",
|
||||
"templates": {
|
||||
"all_channels": "Alla kanaler",
|
||||
"all_industries": "Alla branscher",
|
||||
@@ -2229,7 +2236,6 @@
|
||||
"duplicate_language_or_language_id": "Duplicerat språk eller språk-ID",
|
||||
"edit_languages": "Redigera språk",
|
||||
"identifier": "Identifierare (ISO)",
|
||||
"incomplete_translations": "Ofullständiga översättningar",
|
||||
"language": "Språk",
|
||||
"language_deleted_successfully": "Språket har tagits bort",
|
||||
"languages_updated_successfully": "Språken har uppdaterats",
|
||||
@@ -2239,8 +2245,7 @@
|
||||
"please_select_a_language": "Vänligen välj ett språk",
|
||||
"remove_language": "Ta bort språk",
|
||||
"remove_language_from_surveys_to_remove_it_from_workspace": "Ta bort språket från dessa enkäter för att kunna ta bort det från arbetsytan.",
|
||||
"search_items": "Sök objekt",
|
||||
"translate": "Översätt"
|
||||
"search_items": "Sök objekt"
|
||||
},
|
||||
"look": {
|
||||
"add_background_color": "Lägg till bakgrundsfärg",
|
||||
@@ -2300,12 +2305,12 @@
|
||||
"advanced_styling_field_track_bg_description": "Färgar den ofyllda delen av stapeln.",
|
||||
"advanced_styling_field_track_height": "Spårets höjd",
|
||||
"advanced_styling_field_track_height_description": "Styr tjockleken på förloppsstapeln.",
|
||||
"advanced_styling_field_upper_label_color": "Rubriketikettens färg",
|
||||
"advanced_styling_field_upper_label_color_description": "Färgar den lilla etiketten ovanför fälten.",
|
||||
"advanced_styling_field_upper_label_size": "Rubriketikettens teckenstorlek",
|
||||
"advanced_styling_field_upper_label_size_description": "Skalar storleken på den lilla etiketten ovanför fälten.",
|
||||
"advanced_styling_field_upper_label_weight": "Rubriketikettens teckentjocklek",
|
||||
"advanced_styling_field_upper_label_weight_description": "Gör etiketten tunnare eller fetare.",
|
||||
"advanced_styling_field_upper_label_color": "Etiketfärg",
|
||||
"advanced_styling_field_upper_label_color_description": "Färglägger de små etiketterna ovanför inmatningsfält och skalans etiketter.",
|
||||
"advanced_styling_field_upper_label_size": "Etiketttextstorlek",
|
||||
"advanced_styling_field_upper_label_size_description": "Skalar storleken på de små etiketterna ovanför inmatningsfält och skalans etiketter.",
|
||||
"advanced_styling_field_upper_label_weight": "Etiketttextvikt",
|
||||
"advanced_styling_field_upper_label_weight_description": "Gör etiketterna ljusare eller fetare.",
|
||||
"advanced_styling_section_buttons": "Knappar",
|
||||
"advanced_styling_section_headlines": "Rubriker & beskrivningar",
|
||||
"advanced_styling_section_inputs": "Inmatningar",
|
||||
@@ -2876,6 +2881,11 @@
|
||||
"gauge_feature_satisfaction_question_2_headline": "Vad är en sak vi kunde göra bättre?",
|
||||
"identify_customer_goals_description": "Förstå bättre om din kommunikation skapar rätt förväntningar på värdet din produkt ger.",
|
||||
"identify_customer_goals_name": "Identifiera kundmål",
|
||||
"identify_customer_goals_question_1_choice_1": "Förstå min användarbas på djupet",
|
||||
"identify_customer_goals_question_1_choice_2": "Identifiera merförsäljningsmöjligheter",
|
||||
"identify_customer_goals_question_1_choice_3": "Bygga bästa möjliga produkt",
|
||||
"identify_customer_goals_question_1_choice_4": "Härska över världen för att få alla att äta brysselkål till frukost",
|
||||
"identify_customer_goals_question_1_headline": "Vad är ditt primära mål med att använda $[projectName]?",
|
||||
"identify_sign_up_barriers_description": "Erbjud en rabatt för att samla insikter om registreringshinder.",
|
||||
"identify_sign_up_barriers_name": "Identifiera registreringshinder",
|
||||
"identify_sign_up_barriers_question_1_button_label": "Få 10% rabatt",
|
||||
@@ -2950,12 +2960,14 @@
|
||||
"improve_trial_conversion_question_1_subheader": "Hjälp oss förstå dig bättre:",
|
||||
"improve_trial_conversion_question_2_button_label": "Nästa",
|
||||
"improve_trial_conversion_question_2_headline": "Tråkigt att höra. Vad var det största problemet med att använda $[projectName]?",
|
||||
"improve_trial_conversion_question_3_button_label": "Nästa",
|
||||
"improve_trial_conversion_question_3_headline": "Vad förväntade du dig att $[projectName] skulle göra?",
|
||||
"improve_trial_conversion_question_4_button_label": "Få 20% rabatt",
|
||||
"improve_trial_conversion_question_4_headline": "Tråkigt att höra! Få 20% rabatt första året.",
|
||||
"improve_trial_conversion_question_4_html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>Vi erbjuder dig gärna 20% rabatt på en årsplan.</span></p>",
|
||||
"improve_trial_conversion_question_5_button_label": "Nästa",
|
||||
"improve_trial_conversion_question_5_headline": "Vad vill du uppnå?",
|
||||
"improve_trial_conversion_question_5_subheader": "Vänligen välj ett av följande alternativ:",
|
||||
"improve_trial_conversion_question_5_subheader": "Beskriv gärna nedan:",
|
||||
"improve_trial_conversion_question_6_headline": "Hur löser du ditt problem nu?",
|
||||
"improve_trial_conversion_question_6_subheader": "Vänligen nämn alternativa lösningar:",
|
||||
"integration_setup_survey_description": "Utvärdera hur enkelt användare kan lägga till integrationer i din produkt. Hitta blinda fläckar.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user