diff --git a/.env.example b/.env.example index fbe29f4356..72dd26c7f1 100644 --- a/.env.example +++ b/.env.example @@ -184,8 +184,13 @@ ENTERPRISE_LICENSE_KEY= # Ignore Rate Limiting across the Formbricks app # RATE_LIMITING_DISABLED=1 -# OpenTelemetry URL for tracing -# OPENTELEMETRY_LISTENER_URL=http://localhost:4318/v1/traces +# OpenTelemetry OTLP endpoint (base URL, exporters append /v1/traces and /v1/metrics) +# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 +# OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf +# OTEL_SERVICE_NAME=formbricks +# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=development +# OTEL_TRACES_SAMPLER=parentbased_traceidratio +# OTEL_TRACES_SAMPLER_ARG=1 # Unsplash API Key UNSPLASH_ACCESS_KEY= diff --git a/.github/workflows/release-helm-chart.yml b/.github/workflows/release-helm-chart.yml index eca5a3d714..9464921020 100644 --- a/.github/workflows/release-helm-chart.yml +++ b/.github/workflows/release-helm-chart.yml @@ -65,8 +65,8 @@ jobs: set -euo pipefail echo "Updating Chart.yaml with version: ${VERSION}" - yq -i ".version = \"${VERSION}\"" helm-chart/Chart.yaml - yq -i ".appVersion = \"${VERSION}\"" helm-chart/Chart.yaml + yq -i ".version = \"${VERSION}\"" charts/formbricks/Chart.yaml + yq -i ".appVersion = \"${VERSION}\"" charts/formbricks/Chart.yaml echo "✅ Successfully updated Chart.yaml" @@ -77,7 +77,7 @@ jobs: set -euo pipefail echo "Packaging Helm chart version: ${VERSION}" - helm package ./helm-chart + helm package ./charts/formbricks echo "✅ Successfully packaged formbricks-${VERSION}.tgz" diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 4c5ba986f3..9f0a1fc2fa 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -9,6 +9,7 @@ on: merge_group: permissions: contents: read + pull-requests: read jobs: sonarqube: name: SonarQube @@ -50,6 +51,9 @@ jobs: pnpm test:coverage - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf + with: + args: > + -Dsonar.verbose=true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/translation-check.yml b/.github/workflows/translation-check.yml index c901eb5832..30e90872ec 100644 --- a/.github/workflows/translation-check.yml +++ b/.github/workflows/translation-check.yml @@ -32,21 +32,20 @@ jobs: with: egress-policy: audit - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Setup Node.js - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - node-version: 18 + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Setup pnpm - uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + - name: Setup Node.js 22.x + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af with: - version: 9.15.9 + node-version: 22.x + + - name: Install pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --config.platform=linux --config.architecture=x64 - name: Validate translation keys run: | diff --git a/.gitignore b/.gitignore index c5a11230c6..19b187e231 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ **/.next/ **/out/ **/build +**/next-env.d.ts # node **/dist/ @@ -63,3 +64,5 @@ packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.xcworkspace/xcuserdat .cursorrules i18n.cache stats.html +# next-agents-md +.next-docs/ diff --git a/AGENTS.md b/AGENTS.md index d84dc5c6a5..cd74ba8c4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,19 @@ Formbricks runs as a pnpm/turbo monorepo. `apps/web` is the Next.js product surf - `pnpm test:e2e` — launch the Playwright browser regression suite. - `pnpm db:migrate:dev` — apply Prisma migrations against the dev database. +### Survey Packages Build & Cache + +The `@formbricks/surveys` package is pre-compiled (Vite → UMD + ESM) and the built bundle is copied to `apps/web/public/js/`. The Next.js app imports from `dist/`, **not** the source files. This means: + +- After any change to `packages/surveys` or its dependencies (`packages/survey-ui`, `packages/types`, etc.), you **must rebuild** for changes to take effect in the running app. +- Turborepo caches build outputs aggressively. Always use `--force` to bypass the cache when iterating on survey packages: + ``` + rm -rf packages/surveys/dist apps/web/public/js/surveys.* node_modules/.cache/turbo + pnpm build --filter=@formbricks/surveys... --force + ``` +- The browser also caches the UMD bundle (`surveys.umd.cjs`) served from `public/js/`. After rebuilding, do a **hard refresh** (Cmd+Shift+R / Ctrl+Shift+R) or disable the browser cache via DevTools to pick up the new bundle. +- If changes still don't appear, restart the Next.js dev server (`pnpm dev`). + ## Coding Style & Naming Conventions TypeScript, React, and Prisma are the primary languages. Use the shared ESLint presets (`@formbricks/eslint-config`) and Prettier preset (110-char width, semicolons, double quotes, sorted import groups). Two-space indentation is standard; prefer `PascalCase` for React components and folders under `modules/`, `camelCase` for functions/variables, and `SCREAMING_SNAKE_CASE` only for constants. When adding mocks, place them inside `__mocks__` so import ordering stays stable. @@ -80,3 +93,5 @@ Prefer Vitest with Testing Library for logic in `.ts` files, keeping specs coloc ## Commit & Pull Request Guidelines Commits follow a lightweight Conventional Commit format (`fix:`, `chore:`, `feat:`) and usually append the PR number, e.g. `fix: update OpenAPI schema (#6617)`. Keep commits scoped and lint-clean. Pull requests should outline the problem, summarize the solution, and link to issues or product specs. Attach screenshots or gifs for UI-facing work, list any migrations or env changes, and paste the output of relevant commands (`pnpm test`, `pnpm lint`, `pnpm db:migrate:dev`) so reviewers can verify readiness. + +[Next.js Docs Index]|root: ./.next-docs|STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.|If docs missing, run this command first: npx @next/codemod agents-md --output AGENTS.md|01-app:{04-glossary.mdx}|01-app/01-getting-started:{01-installation.mdx,02-project-structure.mdx,03-layouts-and-pages.mdx,04-linking-and-navigating.mdx,05-server-and-client-components.mdx,06-cache-components.mdx,07-fetching-data.mdx,08-updating-data.mdx,09-caching-and-revalidating.mdx,10-error-handling.mdx,11-css.mdx,12-images.mdx,13-fonts.mdx,14-metadata-and-og-images.mdx,15-route-handlers.mdx,16-proxy.mdx,17-deploying.mdx,18-upgrading.mdx}|01-app/02-guides:{analytics.mdx,authentication.mdx,backend-for-frontend.mdx,caching.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,data-security.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,json-ld.mdx,lazy-loading.mdx,local-development.mdx,mcp.mdx,mdx.mdx,memory-usage.mdx,multi-tenant.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,prefetching.mdx,production-checklist.mdx,progressive-web-apps.mdx,public-static-pages.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,single-page-applications.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx,videos.mdx}|01-app/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|01-app/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|01-app/02-guides/upgrading:{codemods.mdx,version-14.mdx,version-15.mdx,version-16.mdx}|01-app/03-api-reference:{07-edge.mdx,08-turbopack.mdx}|01-app/03-api-reference/01-directives:{use-cache-private.mdx,use-cache-remote.mdx,use-cache.mdx,use-client.mdx,use-server.mdx}|01-app/03-api-reference/02-components:{font.mdx,form.mdx,image.mdx,link.mdx,script.mdx}|01-app/03-api-reference/03-file-conventions/01-metadata:{app-icons.mdx,manifest.mdx,opengraph-image.mdx,robots.mdx,sitemap.mdx}|01-app/03-api-reference/03-file-conventions:{default.mdx,dynamic-routes.mdx,error.mdx,forbidden.mdx,instrumentation-client.mdx,instrumentation.mdx,intercepting-routes.mdx,layout.mdx,loading.mdx,mdx-components.mdx,not-found.mdx,page.mdx,parallel-routes.mdx,proxy.mdx,public-folder.mdx,route-groups.mdx,route-segment-config.mdx,route.mdx,src-folder.mdx,template.mdx,unauthorized.mdx}|01-app/03-api-reference/04-functions:{after.mdx,cacheLife.mdx,cacheTag.mdx,connection.mdx,cookies.mdx,draft-mode.mdx,fetch.mdx,forbidden.mdx,generate-image-metadata.mdx,generate-metadata.mdx,generate-sitemaps.mdx,generate-static-params.mdx,generate-viewport.mdx,headers.mdx,image-response.mdx,next-request.mdx,next-response.mdx,not-found.mdx,permanentRedirect.mdx,redirect.mdx,refresh.mdx,revalidatePath.mdx,revalidateTag.mdx,unauthorized.mdx,unstable_cache.mdx,unstable_noStore.mdx,unstable_rethrow.mdx,updateTag.mdx,use-link-status.mdx,use-params.mdx,use-pathname.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,use-selected-layout-segment.mdx,use-selected-layout-segments.mdx,userAgent.mdx}|01-app/03-api-reference/05-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,appDir.mdx,assetPrefix.mdx,authInterrupts.mdx,basePath.mdx,browserDebugInfoInTerminal.mdx,cacheComponents.mdx,cacheHandlers.mdx,cacheLife.mdx,compress.mdx,crossOrigin.mdx,cssChunking.mdx,devIndicators.mdx,distDir.mdx,env.mdx,expireTime.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,htmlLimitedBots.mdx,httpAgentOptions.mdx,images.mdx,incrementalCacheHandlerPath.mdx,inlineCss.mdx,isolatedDevBuild.mdx,logging.mdx,mdxRs.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactCompiler.mdx,reactMaxHeadersLength.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,sassOptions.mdx,serverActions.mdx,serverComponentsHmrCache.mdx,serverExternalPackages.mdx,staleTimes.mdx,staticGeneration.mdx,taint.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,turbopackFileSystemCache.mdx,typedRoutes.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,viewTransition.mdx,webVitalsAttribution.mdx,webpack.mdx}|01-app/03-api-reference/05-config:{02-typescript.mdx,03-eslint.mdx}|01-app/03-api-reference/06-cli:{create-next-app.mdx,next.mdx}|02-pages/01-getting-started:{01-installation.mdx,02-project-structure.mdx,04-images.mdx,05-fonts.mdx,06-css.mdx,11-deploying.mdx}|02-pages/02-guides:{analytics.mdx,authentication.mdx,babel.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,lazy-loading.mdx,mdx.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,post-css.mdx,preview-mode.mdx,production-checklist.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx}|02-pages/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|02-pages/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|02-pages/02-guides/upgrading:{codemods.mdx,version-10.mdx,version-11.mdx,version-12.mdx,version-13.mdx,version-14.mdx,version-9.mdx}|02-pages/03-building-your-application/01-routing:{01-pages-and-layouts.mdx,02-dynamic-routes.mdx,03-linking-and-navigating.mdx,05-custom-app.mdx,06-custom-document.mdx,07-api-routes.mdx,08-custom-error.mdx}|02-pages/03-building-your-application/02-rendering:{01-server-side-rendering.mdx,02-static-site-generation.mdx,04-automatic-static-optimization.mdx,05-client-side-rendering.mdx}|02-pages/03-building-your-application/03-data-fetching:{01-get-static-props.mdx,02-get-static-paths.mdx,03-forms-and-mutations.mdx,03-get-server-side-props.mdx,05-client-side.mdx}|02-pages/03-building-your-application/06-configuring:{12-error-handling.mdx}|02-pages/04-api-reference:{06-edge.mdx,08-turbopack.mdx}|02-pages/04-api-reference/01-components:{font.mdx,form.mdx,head.mdx,image-legacy.mdx,image.mdx,link.mdx,script.mdx}|02-pages/04-api-reference/02-file-conventions:{instrumentation.mdx,proxy.mdx,public-folder.mdx,src-folder.mdx}|02-pages/04-api-reference/03-functions:{get-initial-props.mdx,get-server-side-props.mdx,get-static-paths.mdx,get-static-props.mdx,next-request.mdx,next-response.mdx,use-params.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,userAgent.mdx}|02-pages/04-api-reference/04-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,assetPrefix.mdx,basePath.mdx,bundlePagesRouterDependencies.mdx,compress.mdx,crossOrigin.mdx,devIndicators.mdx,distDir.mdx,env.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,httpAgentOptions.mdx,images.mdx,isolatedDevBuild.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,serverExternalPackages.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,webVitalsAttribution.mdx,webpack.mdx}|02-pages/04-api-reference/04-config:{01-typescript.mdx,02-eslint.mdx}|02-pages/04-api-reference/05-cli:{create-next-app.mdx,next.mdx}|03-architecture:{accessibility.mdx,fast-refresh.mdx,nextjs-compiler.mdx,supported-browsers.mdx}|04-community:{01-contribution-guide.mdx,02-rspack.mdx} diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js index 8b5dc0e00e..a114584fc8 100644 --- a/apps/web/.eslintrc.js +++ b/apps/web/.eslintrc.js @@ -1,20 +1,4 @@ module.exports = { extends: ["@formbricks/eslint-config/legacy-next.js"], ignorePatterns: ["**/package.json", "**/tsconfig.json"], - overrides: [ - { - files: ["locales/*.json"], - plugins: ["i18n-json"], - rules: { - "i18n-json/identical-keys": [ - "error", - { - filePath: require("path").join(__dirname, "locales", "en-US.json"), - checkExtraKeys: false, - checkMissingKeys: true, - }, - ], - }, - }, - ], }; diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index b9381e45e3..a54c504043 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine3.22 AS base +FROM node:24-alpine3.23 AS base # ## step 1: Prune monorepo @@ -20,7 +20,7 @@ FROM base AS installer # Enable corepack and prepare pnpm RUN npm install --ignore-scripts -g corepack@latest RUN corepack enable -RUN corepack prepare pnpm@9.15.9 --activate +RUN corepack prepare pnpm@10.28.2 --activate # Install necessary build tools and compilers RUN apk update && apk add --no-cache cmake g++ gcc jq make openssl-dev python3 @@ -69,20 +69,14 @@ RUN --mount=type=secret,id=database_url \ --mount=type=secret,id=sentry_auth_token \ /tmp/read-secrets.sh pnpm build --filter=@formbricks/web... -# Extract Prisma version -RUN jq -r '.devDependencies.prisma' packages/database/package.json > /prisma_version.txt - # ## step 3: setup production runner # FROM base AS runner -RUN npm install --ignore-scripts -g corepack@latest && \ - corepack enable - -RUN apk add --no-cache curl \ - && apk add --no-cache supercronic \ - # && addgroup --system --gid 1001 nodejs \ +# Update npm to latest, then create user +# Note: npm's bundled tar has a known vulnerability but npm is only used during build, not at runtime +RUN npm install --ignore-scripts -g npm@latest \ && addgroup -S nextjs \ && adduser -S -u 1001 -G nextjs nextjs @@ -113,15 +107,13 @@ RUN chown nextjs:nextjs ./packages/database/schema.prisma && chmod 644 ./package COPY --from=installer /app/packages/database/dist ./packages/database/dist RUN chown -R nextjs:nextjs ./packages/database/dist && chmod -R 755 ./packages/database/dist +# Copy prisma client packages COPY --from=installer /app/node_modules/@prisma/client ./node_modules/@prisma/client RUN chown -R nextjs:nextjs ./node_modules/@prisma/client && chmod -R 755 ./node_modules/@prisma/client COPY --from=installer /app/node_modules/.prisma ./node_modules/.prisma RUN chown -R nextjs:nextjs ./node_modules/.prisma && chmod -R 755 ./node_modules/.prisma -COPY --from=installer /prisma_version.txt . -RUN chown nextjs:nextjs ./prisma_version.txt && chmod 644 ./prisma_version.txt - COPY --from=installer /app/node_modules/@paralleldrive/cuid2 ./node_modules/@paralleldrive/cuid2 RUN chmod -R 755 ./node_modules/@paralleldrive/cuid2 @@ -134,7 +126,25 @@ RUN chmod -R 755 ./node_modules/@noble/hashes COPY --from=installer /app/node_modules/zod ./node_modules/zod RUN chmod -R 755 ./node_modules/zod -RUN npm install -g prisma@6 +# Pino loads transport code in worker threads via dynamic require(). +# Next.js file tracing only traces static imports, missing runtime-loaded files +# (e.g. pino/lib/transport-stream.js, transport targets). +# Copy the full packages to ensure all runtime files are available. +COPY --from=installer /app/node_modules/pino ./node_modules/pino +RUN chmod -R 755 ./node_modules/pino + +COPY --from=installer /app/node_modules/pino-opentelemetry-transport ./node_modules/pino-opentelemetry-transport +RUN chmod -R 755 ./node_modules/pino-opentelemetry-transport + +COPY --from=installer /app/node_modules/pino-abstract-transport ./node_modules/pino-abstract-transport +RUN chmod -R 755 ./node_modules/pino-abstract-transport + +COPY --from=installer /app/node_modules/otlp-logger ./node_modules/otlp-logger +RUN chmod -R 755 ./node_modules/otlp-logger + +# Install prisma CLI globally for database migrations and fix permissions for nextjs user +RUN npm install --ignore-scripts -g prisma@6 \ + && chown -R nextjs:nextjs /usr/local/lib/node_modules/prisma # Create a startup script to handle the conditional logic COPY --from=installer /app/apps/web/scripts/docker/next-start.sh /home/nextjs/start.sh @@ -144,10 +154,8 @@ EXPOSE 3000 ENV HOSTNAME="0.0.0.0" USER nextjs -# Prepare pnpm as the nextjs user to ensure it's available at runtime # Prepare volumes for uploads and SAML connections -RUN corepack prepare pnpm@9.15.9 --activate && \ - mkdir -p /home/nextjs/apps/web/uploads/ && \ +RUN mkdir -p /home/nextjs/apps/web/uploads/ && \ mkdir -p /home/nextjs/apps/web/saml-connection VOLUME /home/nextjs/apps/web/uploads/ diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts index 216b19427e..0201e02a60 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts @@ -25,7 +25,7 @@ const mockProject: TProject = { }, placement: "bottomRight", clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], languages: [], logo: null, diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/settings/components/ProjectSettings.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/settings/components/ProjectSettings.tsx index 69c15869ef..ac94995ece 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/settings/components/ProjectSettings.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/settings/components/ProjectSettings.tsx @@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import Image from "next/image"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -17,6 +17,7 @@ import { import { createProjectAction } from "@/app/(app)/environments/[environmentId]/actions"; import { previewSurvey } from "@/app/lib/templates"; import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@/lib/localStorage"; +import { buildStylingFromBrandColor } from "@/lib/styling/constants"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { TOrganizationTeam } from "@/modules/ee/teams/project-teams/types/team"; import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal"; @@ -64,10 +65,17 @@ export const ProjectSettings = ({ const { t } = useTranslation(); const addProject = async (data: TProjectUpdateInput) => { try { + // Build the full styling from the chosen brand color so all derived + // colours (question, button, input, option, progress, etc.) are persisted. + // Without this, only brandColor is saved and the look-and-feel page falls + // back to STYLE_DEFAULTS computed from the default brand (#64748b). + const fullStyling = buildStylingFromBrandColor(data.styling?.brandColor?.light); + const createProjectResponse = await createProjectAction({ organizationId, data: { ...data, + styling: fullStyling, config: { channel, industry }, teamIds: data.teamIds, }, @@ -112,6 +120,7 @@ export const ProjectSettings = ({ const projectName = form.watch("name"); const logoUrl = form.watch("logo.url"); const brandColor = form.watch("styling.brandColor.light") ?? defaultBrandColor; + const previewStyling = useMemo(() => buildStylingFromBrandColor(brandColor), [brandColor]); const { isSubmitting } = form.formState; const organizationTeamsOptions = organizationTeams.map((team) => ({ @@ -226,7 +235,7 @@ export const ProjectSettings = ({ alt="Logo" width={256} height={56} - className="absolute top-2 left-2 -mb-6 h-20 w-auto max-w-64 rounded-lg border object-contain p-1" + className="absolute left-2 top-2 -mb-6 h-20 w-auto max-w-64 rounded-lg border object-contain p-1" /> )}

{t("common.preview")}

@@ -235,7 +244,7 @@ export const ProjectSettings = ({ appUrl={publicDomain} isPreviewMode={true} survey={previewSurvey(projectName || "my Product", t)} - styling={{ brandColor: { light: brandColor } }} + styling={previewStyling} isBrandingEnabled={false} languageCode="default" onFileUpload={async (file) => file.name} diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts index 618c2512e7..59459dada8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { ZId } from "@formbricks/types/common"; -import { OperationNotAllowedError } from "@formbricks/types/errors"; +import { AuthorizationError, OperationNotAllowedError } from "@formbricks/types/errors"; import { ZProjectUpdateInput } from "@formbricks/types/project"; import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service"; import { getOrganization } from "@/lib/organization/service"; @@ -138,7 +138,7 @@ export const getProjectsForSwitcherAction = authenticatedActionClient // Need membership for getProjectsByUserId (1 DB query) const membership = await getMembershipByUserIdOrganizationId(ctx.user.id, parsedInput.organizationId); if (!membership) { - throw new Error("Membership not found"); + throw new AuthorizationError("Membership not found"); } return await getProjectsByUserId(ctx.user.id, membership); diff --git a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx index 546bfef45f..cd136f31b7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx @@ -36,7 +36,7 @@ export const EnvironmentLayout = async ({ layoutData, children }: EnvironmentLay // Calculate derived values (no queries) const { isMember, isOwner, isManager } = getAccessFlags(membership.role); - const { features, lastChecked, isPendingDowngrade, active } = license; + const { features, lastChecked, isPendingDowngrade, active, status } = license; const isMultiOrgEnabled = features?.isMultiOrgEnabled ?? false; const organizationProjectsLimit = await getOrganizationProjectsLimit(organization.billing.limits); const isOwnerOrManager = isOwner || isManager; @@ -63,6 +63,7 @@ export const EnvironmentLayout = async ({ layoutData, children }: EnvironmentLay active={active} environmentId={environment.id} locale={user.locale} + status={status} />
diff --git a/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.tsx b/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.tsx index d663e84896..7a2618ee8a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.tsx @@ -110,7 +110,10 @@ export const MainNavigation = ({ href: `/environments/${environment.id}/contacts`, name: "Distribute", icon: UserIcon, - isActive: pathname?.includes("/contacts") || pathname?.includes("/segments"), + isActive: + pathname?.includes("/contacts") || + pathname?.includes("/segments") || + pathname?.includes("/attributes"), }, { name: "Unify", diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx new file mode 100644 index 0000000000..8aa0355817 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx @@ -0,0 +1,142 @@ +"use client"; + +import { TFunction } from "i18next"; +import { RotateCcwIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; +import { recheckLicenseAction } from "@/modules/ee/license-check/actions"; +import { Alert, AlertDescription } from "@/modules/ui/components/alert"; +import { Badge } from "@/modules/ui/components/badge"; +import { Button } from "@/modules/ui/components/button"; +import { SettingsCard } from "../../../components/SettingsCard"; + +type LicenseStatus = "active" | "expired" | "unreachable" | "invalid_license"; + +interface EnterpriseLicenseStatusProps { + status: LicenseStatus; + gracePeriodEnd?: Date; + environmentId: string; +} + +const getBadgeConfig = ( + status: LicenseStatus, + t: TFunction +): { type: "success" | "error" | "warning" | "gray"; label: string } => { + switch (status) { + case "active": + return { type: "success", label: t("environments.settings.enterprise.license_status_active") }; + case "expired": + return { type: "error", label: t("environments.settings.enterprise.license_status_expired") }; + case "unreachable": + return { type: "warning", label: t("environments.settings.enterprise.license_status_unreachable") }; + case "invalid_license": + return { type: "error", label: t("environments.settings.enterprise.license_status_invalid") }; + default: + return { type: "gray", label: t("environments.settings.enterprise.license_status") }; + } +}; + +export const EnterpriseLicenseStatus = ({ + status, + gracePeriodEnd, + environmentId, +}: EnterpriseLicenseStatusProps) => { + const { t } = useTranslation(); + const router = useRouter(); + const [isRechecking, setIsRechecking] = useState(false); + + const handleRecheck = async () => { + setIsRechecking(true); + try { + const result = await recheckLicenseAction({ environmentId }); + if (result?.serverError) { + toast.error(result.serverError || t("environments.settings.enterprise.recheck_license_failed")); + return; + } + + if (result?.data) { + if (result.data.status === "unreachable") { + toast.error(t("environments.settings.enterprise.recheck_license_unreachable")); + } else if (result.data.status === "invalid_license") { + toast.error(t("environments.settings.enterprise.recheck_license_invalid")); + } else { + toast.success(t("environments.settings.enterprise.recheck_license_success")); + } + router.refresh(); + } else { + toast.error(t("environments.settings.enterprise.recheck_license_failed")); + } + } catch (error) { + toast.error( + error instanceof Error ? error.message : t("environments.settings.enterprise.recheck_license_failed") + ); + } finally { + setIsRechecking(false); + } + }; + + const badgeConfig = getBadgeConfig(status, t); + + return ( + +
+
+
+ +
+ +
+ {status === "unreachable" && gracePeriodEnd && ( + + + {t("environments.settings.enterprise.license_unreachable_grace_period", { + gracePeriodEnd: new Date(gracePeriodEnd).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }), + })} + + + )} + {status === "invalid_license" && ( + + + {t("environments.settings.enterprise.license_invalid_description")} + + + )} +

+ {t("environments.settings.enterprise.questions_please_reach_out_to")}{" "} + + hola@formbricks.com + +

+
+
+ ); +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx index a515bcd75f..9252c58e3b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx @@ -2,9 +2,10 @@ import { CheckIcon } from "lucide-react"; import Link from "next/link"; import { notFound } from "next/navigation"; import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; +import { EnterpriseLicenseStatus } from "@/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus"; import { IS_FORMBRICKS_CLOUD } from "@/lib/constants"; import { getTranslate } from "@/lingodotdev/server"; -import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license"; +import { GRACE_PERIOD_MS, getEnterpriseLicense } from "@/modules/ee/license-check/lib/license"; import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { Button } from "@/modules/ui/components/button"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; @@ -25,7 +26,8 @@ const Page = async (props) => { return notFound(); } - const { active: isEnterpriseEdition } = await getEnterpriseLicense(); + const licenseState = await getEnterpriseLicense(); + const hasLicense = licenseState.status !== "no-license"; const paidFeatures = [ { @@ -90,35 +92,22 @@ const Page = async (props) => { activeId="enterprise" /> - {isEnterpriseEdition ? ( -
-
-
-
-
- -
-

- {t( - "environments.settings.enterprise.your_enterprise_license_is_active_all_features_unlocked" - )} -

-
-

- {t("environments.settings.enterprise.questions_please_reach_out_to")}{" "} - - hola@formbricks.com - -

-
-
-
+ {hasLicense ? ( + ) : (
    - {paidFeatures.map((feature, index) => ( -
  • + {paidFeatures.map((feature) => ( +
  • diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx index dc3cc2e4bb..14df58d4a9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import { TOrganization } from "@formbricks/types/organizations"; import { deleteOrganizationAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions"; import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage"; +import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { Alert, AlertDescription } from "@/modules/ui/components/alert"; import { Button } from "@/modules/ui/components/button"; import { DeleteDialog } from "@/modules/ui/components/delete-dialog"; @@ -32,7 +33,12 @@ export const DeleteOrganization = ({ setIsDeleting(true); try { - await deleteOrganizationAction({ organizationId: organization.id }); + const result = await deleteOrganizationAction({ organizationId: organization.id }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + setIsDeleting(false); + return; + } toast.success(t("environments.settings.general.organization_deleted_successfully")); if (typeof localStorage !== "undefined") { localStorage.removeItem(FORMBRICKS_ENVIRONMENT_ID_LS); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx index e763dbfa40..9d22a6088b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx @@ -316,6 +316,14 @@ export const generateResponseTableColumns = ( }, }; + const responseIdColumn: ColumnDef = { + accessorKey: "responseId", + header: () =>
    {t("common.response_id")}
    , + cell: ({ row }) => { + return ; + }, + }; + const quotasColumn: ColumnDef = { accessorKey: "quota", header: t("common.quota"), @@ -414,6 +422,7 @@ export const generateResponseTableColumns = ( const baseColumns = [ personColumn, singleUseIdColumn, + responseIdColumn, dateColumn, ...(showQuotasColumn ? [quotasColumn] : []), statusColumn, diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/FileUploadSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/FileUploadSummary.tsx index 93eba1af0b..c631558eb9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/FileUploadSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/FileUploadSummary.tsx @@ -8,7 +8,7 @@ import { TSurvey, TSurveyElementSummaryFileUpload } from "@formbricks/types/surv import { TUserLocale } from "@formbricks/types/user"; import { timeSince } from "@/lib/time"; import { getContactIdentifier } from "@/lib/utils/contact"; -import { getOriginalFileNameFromUrl } from "@/modules/storage/utils"; +import { getOriginalFileNameFromUrl } from "@/modules/storage/url-helpers"; import { PersonAvatar } from "@/modules/ui/components/avatars"; import { Button } from "@/modules/ui/components/button"; import { EmptyState } from "@/modules/ui/components/empty-state"; diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/AddIntegrationModal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/AddIntegrationModal.tsx index ef75bfd445..a26091bb1b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/AddIntegrationModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/AddIntegrationModal.tsx @@ -21,6 +21,7 @@ import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[envir import { BaseSelectDropdown } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/BaseSelectDropdown"; import { fetchTables } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/lib/airtable"; import AirtableLogo from "@/images/airtableLogo.svg"; +import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { recallToHeadline } from "@/lib/utils/recall"; import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils"; import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings"; @@ -268,7 +269,14 @@ export const AddIntegrationModal = ({ airtableIntegrationData.config?.data.push(integrationData); } - await createOrUpdateIntegrationAction({ environmentId, integrationData: airtableIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: airtableIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } if (isEditMode) { toast.success(t("environments.integrations.integration_updated_successfully")); } else { @@ -304,7 +312,11 @@ export const AddIntegrationModal = ({ const integrationData = structuredClone(airtableIntegrationData); integrationData.config.data.splice(index, 1); - await createOrUpdateIntegrationAction({ environmentId, integrationData }); + const result = await createOrUpdateIntegrationAction({ environmentId, integrationData }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } handleClose(); router.refresh(); diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/AddIntegrationModal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/AddIntegrationModal.tsx index 1cdb9b19d0..0b16be342f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/AddIntegrationModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/AddIntegrationModal.tsx @@ -165,7 +165,14 @@ export const AddIntegrationModal = ({ // create action googleSheetIntegrationData.config.data.push(integrationData); } - await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: googleSheetIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } if (selectedIntegration) { toast.success(t("environments.integrations.integration_updated_successfully")); } else { @@ -205,7 +212,14 @@ export const AddIntegrationModal = ({ googleSheetIntegrationData.config.data.splice(selectedIntegration!.index, 1); try { setIsDeleting(true); - await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: googleSheetIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } toast.success(t("environments.integrations.integration_removed_successfully")); setOpen(false); } catch (error) { @@ -266,7 +280,7 @@ export const AddIntegrationModal = ({
    -
    +
    {surveyElements.map((question) => (
    diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/AddIntegrationModal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/AddIntegrationModal.tsx index 55003f64b0..c3409375e5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/AddIntegrationModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/AddIntegrationModal.tsx @@ -22,6 +22,7 @@ import { createEmptyMapping, } from "@/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/MappingRow"; import NotionLogo from "@/images/notion.png"; +import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { recallToHeadline } from "@/lib/utils/recall"; import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils"; import { Button } from "@/modules/ui/components/button"; @@ -217,7 +218,14 @@ export const AddIntegrationModal = ({ notionIntegrationData.config.data.push(integrationData); } - await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: notionIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } if (selectedIntegration) { toast.success(t("environments.integrations.integration_updated_successfully")); } else { @@ -236,7 +244,14 @@ export const AddIntegrationModal = ({ notionIntegrationData.config.data.splice(selectedIntegration!.index, 1); try { setIsDeleting(true); - await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: notionIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } toast.success(t("environments.integrations.integration_removed_successfully")); setOpen(false); } catch (error) { diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/slack/components/AddChannelMappingModal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/slack/components/AddChannelMappingModal.tsx index 88e501bbae..13bc7af4d5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/slack/components/AddChannelMappingModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/integrations/slack/components/AddChannelMappingModal.tsx @@ -17,6 +17,7 @@ import { TSurvey } from "@formbricks/types/surveys/types"; import { getTextContent } from "@formbricks/types/surveys/validation"; import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/actions"; import SlackLogo from "@/images/slacklogo.png"; +import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { recallToHeadline } from "@/lib/utils/recall"; import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils"; import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings"; @@ -144,7 +145,14 @@ export const AddChannelMappingModal = ({ // create action slackIntegrationData.config.data.push(integrationData); } - await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: slackIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } if (selectedIntegration) { toast.success(t("environments.integrations.integration_updated_successfully")); } else { @@ -181,7 +189,14 @@ export const AddChannelMappingModal = ({ slackIntegrationData.config.data.splice(selectedIntegration!.index, 1); try { setIsDeleting(true); - await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData }); + const result = await createOrUpdateIntegrationAction({ + environmentId, + integrationData: slackIntegrationData, + }); + if (result?.serverError) { + toast.error(getFormattedErrorMessage(result)); + return; + } toast.success(t("environments.integrations.integration_removed_successfully")); setOpen(false); } catch (error) { diff --git a/apps/web/app/api/(internal)/pipeline/route.ts b/apps/web/app/api/(internal)/pipeline/route.ts index a897d1a8c8..4b65fe4b2a 100644 --- a/apps/web/app/api/(internal)/pipeline/route.ts +++ b/apps/web/app/api/(internal)/pipeline/route.ts @@ -31,7 +31,10 @@ export const POST = async (request: Request) => { } const jsonInput = await request.json(); - const convertedJsonInput = convertDatesInObject(jsonInput); + const convertedJsonInput = convertDatesInObject( + jsonInput, + new Set(["contactAttributes", "variables", "data", "meta"]) + ); const inputValidation = ZPipelineInput.safeParse(convertedJsonInput); diff --git a/apps/web/app/api/google-sheet/callback/route.ts b/apps/web/app/api/google-sheet/callback/route.ts index 3744ec6be3..676a0a509c 100644 --- a/apps/web/app/api/google-sheet/callback/route.ts +++ b/apps/web/app/api/google-sheet/callback/route.ts @@ -1,4 +1,5 @@ import { google } from "googleapis"; +import { getServerSession } from "next-auth"; import { responses } from "@/app/lib/api/response"; import { GOOGLE_SHEETS_CLIENT_ID, @@ -6,18 +7,29 @@ import { GOOGLE_SHEETS_REDIRECT_URL, WEBAPP_URL, } from "@/lib/constants"; +import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { createOrUpdateIntegration } from "@/lib/integration/service"; +import { authOptions } from "@/modules/auth/lib/authOptions"; export const GET = async (req: Request) => { - const url = req.url; - const queryParams = new URLSearchParams(url.split("?")[1]); // Split the URL and get the query parameters - const environmentId = queryParams.get("state"); // Get the value of the 'state' parameter - const code = queryParams.get("code"); + const url = new URL(req.url); + const environmentId = url.searchParams.get("state"); + const code = url.searchParams.get("code"); if (!environmentId) { return responses.badRequestResponse("Invalid environmentId"); } + const session = await getServerSession(authOptions); + if (!session) { + return responses.notAuthenticatedResponse(); + } + + const canUserAccessEnvironment = await hasUserEnvironmentAccess(session.user.id, environmentId); + if (!canUserAccessEnvironment) { + return responses.unauthorizedResponse(); + } + if (code && typeof code !== "string") { return responses.badRequestResponse("`code` must be a string"); } diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts deleted file mode 100644 index 0df10918d8..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts +++ /dev/null @@ -1,180 +0,0 @@ -// Deprecated: This api route is deprecated now and will be removed in the future. -// Deprecated: This is currently only being used for the older react native SDKs. Please upgrade to the latest SDKs. -import { NextRequest, userAgent } from "next/server"; -import { prisma } from "@formbricks/database"; -import { logger } from "@formbricks/logger"; -import { TJsPeopleUserIdInput, ZJsPeopleUserIdInput } from "@formbricks/types/js"; -import { TSurvey } from "@formbricks/types/surveys/types"; -import { getContactByUserId } from "@/app/api/v1/client/[environmentId]/app/sync/lib/contact"; -import { getSyncSurveys } from "@/app/api/v1/client/[environmentId]/app/sync/lib/survey"; -import { replaceAttributeRecall } from "@/app/api/v1/client/[environmentId]/app/sync/lib/utils"; -import { responses } from "@/app/lib/api/response"; -import { transformErrorToDetails } from "@/app/lib/api/validator"; -import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; -import { getActionClasses } from "@/lib/actionClass/service"; -import { IS_FORMBRICKS_CLOUD } from "@/lib/constants"; -import { getEnvironment, updateEnvironment } from "@/lib/environment/service"; -import { - getMonthlyOrganizationResponseCount, - getOrganizationByEnvironmentId, -} from "@/lib/organization/service"; -import { getProjectByEnvironmentId } from "@/lib/project/service"; -import { COLOR_DEFAULTS } from "@/lib/styling/constants"; - -const validateInput = ( - environmentId: string, - userId: string -): { isValid: true; data: TJsPeopleUserIdInput } | { isValid: false; error: Response } => { - const inputValidation = ZJsPeopleUserIdInput.safeParse({ environmentId, userId }); - if (!inputValidation.success) { - return { - isValid: false, - error: responses.badRequestResponse( - "Fields are missing or incorrectly formatted", - transformErrorToDetails(inputValidation.error), - true - ), - }; - } - return { isValid: true, data: inputValidation.data }; -}; - -const checkResponseLimit = async (environmentId: string): Promise => { - if (!IS_FORMBRICKS_CLOUD) return false; - - const organization = await getOrganizationByEnvironmentId(environmentId); - if (!organization) { - logger.error({ environmentId }, "Organization does not exist"); - - // fail closed if the organization does not exist - return true; - } - - const currentResponseCount = await getMonthlyOrganizationResponseCount(organization.id); - const monthlyResponseLimit = organization.billing.limits.monthly.responses; - const isLimitReached = monthlyResponseLimit !== null && currentResponseCount >= monthlyResponseLimit; - - return isLimitReached; -}; - -export const OPTIONS = async (): Promise => { - return responses.successResponse({}, true); -}; - -export const GET = withV1ApiWrapper({ - handler: async ({ - req, - props, - }: { - req: NextRequest; - props: { params: Promise<{ environmentId: string; userId: string }> }; - }) => { - const params = await props.params; - try { - const { device } = userAgent(req); - - // validate using zod - const validation = validateInput(params.environmentId, params.userId); - if (!validation.isValid) { - return { response: validation.error }; - } - - const { environmentId, userId } = validation.data; - - const environment = await getEnvironment(environmentId); - if (!environment) { - throw new Error("Environment does not exist"); - } - - const project = await getProjectByEnvironmentId(environmentId); - - if (!project) { - throw new Error("Project not found"); - } - - if (!environment.appSetupCompleted) { - await updateEnvironment(environment.id, { appSetupCompleted: true }); - } - - // check organization subscriptions and response limits - const isAppSurveyResponseLimitReached = await checkResponseLimit(environmentId); - - let contact = await getContactByUserId(environmentId, userId); - if (!contact) { - contact = await prisma.contact.create({ - data: { - attributes: { - create: { - attributeKey: { - connect: { - key_environmentId: { - key: "userId", - environmentId, - }, - }, - }, - value: userId, - }, - }, - environment: { connect: { id: environmentId } }, - }, - select: { - id: true, - attributes: { select: { attributeKey: { select: { key: true } }, value: true } }, - }, - }); - } - - const contactAttributes = contact.attributes.reduce((acc, attribute) => { - acc[attribute.attributeKey.key] = attribute.value; - return acc; - }, {}) as Record; - - const [surveys, actionClasses] = await Promise.all([ - getSyncSurveys( - environmentId, - contact.id, - contactAttributes, - device.type === "mobile" ? "phone" : "desktop" - ), - getActionClasses(environmentId), - ]); - - const updatedProject: any = { - ...project, - brandColor: project.styling.brandColor?.light ?? COLOR_DEFAULTS.brandColor, - ...(project.styling.highlightBorderColor?.light && { - highlightBorderColor: project.styling.highlightBorderColor.light, - }), - }; - - const language = contactAttributes["language"]; - - // Scenario 1: Multi language and updated trigger action classes supported. - // Use the surveys as they are. - let transformedSurveys: TSurvey[] = surveys; - - // creating state object - let state = { - surveys: !isAppSurveyResponseLimitReached - ? transformedSurveys.map((survey) => replaceAttributeRecall(survey, contactAttributes)) - : [], - actionClasses, - language, - project: updatedProject, - }; - - return { - response: responses.successResponse({ ...state }, true), - }; - } catch (error) { - logger.error({ error, url: req.url }, "Error in GET /api/v1/client/[environmentId]/app/sync/[userId]"); - return { - response: responses.internalServerErrorResponse( - "Unable to handle the request: " + error.message, - true - ), - }; - } - }, -}); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.test.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.test.ts deleted file mode 100644 index 4c2a826cef..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { afterEach, describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { TContact } from "@/modules/ee/contacts/types/contact"; -import { getContactByUserId } from "./contact"; - -// Mock prisma -vi.mock("@formbricks/database", () => ({ - prisma: { - contact: { - findFirst: vi.fn(), - }, - }, -})); - -const environmentId = "test-environment-id"; -const userId = "test-user-id"; -const contactId = "test-contact-id"; - -const contactMock: Partial & { - attributes: { value: string; attributeKey: { key: string } }[]; -} = { - id: contactId, - attributes: [ - { attributeKey: { key: "userId" }, value: userId }, - { attributeKey: { key: "email" }, value: "test@example.com" }, - ], -}; - -describe("getContactByUserId", () => { - afterEach(() => { - vi.resetAllMocks(); - }); - - test("should return contact if found", async () => { - vi.mocked(prisma.contact.findFirst).mockResolvedValue(contactMock as any); - - const contact = await getContactByUserId(environmentId, userId); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId, - }, - value: userId, - }, - }, - }, - select: { - id: true, - attributes: { select: { attributeKey: { select: { key: true } }, value: true } }, - }, - }); - expect(contact).toEqual(contactMock); - }); - - test("should return null if contact not found", async () => { - vi.mocked(prisma.contact.findFirst).mockResolvedValue(null); - - const contact = await getContactByUserId(environmentId, userId); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId, - }, - value: userId, - }, - }, - }, - select: { - id: true, - attributes: { select: { attributeKey: { select: { key: true } }, value: true } }, - }, - }); - expect(contact).toBeNull(); - }); -}); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.ts deleted file mode 100644 index 83aaf41a53..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/contact.ts +++ /dev/null @@ -1,42 +0,0 @@ -import "server-only"; -import { cache as reactCache } from "react"; -import { prisma } from "@formbricks/database"; - -export const getContactByUserId = reactCache( - async ( - environmentId: string, - userId: string - ): Promise<{ - attributes: { - value: string; - attributeKey: { - key: string; - }; - }[]; - id: string; - } | null> => { - const contact = await prisma.contact.findFirst({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId, - }, - value: userId, - }, - }, - }, - select: { - id: true, - attributes: { select: { attributeKey: { select: { key: true } }, value: true } }, - }, - }); - - if (!contact) { - return null; - } - - return contact; - } -); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.test.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.test.ts deleted file mode 100644 index 62cbe1259c..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.test.ts +++ /dev/null @@ -1,323 +0,0 @@ -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 { TProject } from "@formbricks/types/project"; -import { TSegment } from "@formbricks/types/segment"; -import { TSurvey } from "@formbricks/types/surveys/types"; -import { getProjectByEnvironmentId } from "@/lib/project/service"; -import { getSurveys } from "@/lib/survey/service"; -import { anySurveyHasFilters } from "@/lib/survey/utils"; -import { diffInDays } from "@/lib/utils/datetime"; -import { evaluateSegment } from "@/modules/ee/contacts/segments/lib/segments"; -import { getSyncSurveys } from "./survey"; - -vi.mock("@/lib/project/service", () => ({ - getProjectByEnvironmentId: vi.fn(), -})); -vi.mock("@/lib/survey/service", () => ({ - getSurveys: vi.fn(), -})); -vi.mock("@/lib/survey/utils", () => ({ - anySurveyHasFilters: vi.fn(), -})); -vi.mock("@/lib/utils/datetime", () => ({ - diffInDays: vi.fn(), -})); -vi.mock("@/lib/utils/validate", () => ({ - validateInputs: vi.fn(), -})); -vi.mock("@/modules/ee/contacts/segments/lib/segments", () => ({ - evaluateSegment: vi.fn(), -})); -vi.mock("@formbricks/database", () => ({ - prisma: { - display: { - findMany: vi.fn(), - }, - response: { - findMany: vi.fn(), - }, - }, -})); -vi.mock("@formbricks/logger", () => ({ - logger: { - error: vi.fn(), - }, -})); - -const environmentId = "test-env-id"; -const contactId = "test-contact-id"; -const contactAttributes = { userId: "user1", email: "test@example.com" }; -const deviceType = "desktop"; - -const mockProject = { - id: "proj1", - name: "Test Project", - createdAt: new Date(), - updatedAt: new Date(), - organizationId: "org1", - environments: [], - recontactDays: 10, - inAppSurveyBranding: true, - linkSurveyBranding: true, - placement: "bottomRight", - clickOutsideClose: true, - darkOverlay: false, - languages: [], -} as unknown as TProject; - -const baseSurvey: TSurvey = { - id: "survey1", - createdAt: new Date(), - updatedAt: new Date(), - name: "Test Survey 1", - environmentId: environmentId, - type: "app", - status: "inProgress", - questions: [], - displayOption: "displayOnce", - recontactDays: null, - autoClose: null, - delay: 0, - displayPercentage: null, - autoComplete: null, - segment: null, - surveyClosedMessage: null, - singleUse: null, - styling: null, - pin: null, - displayLimit: null, - welcomeCard: { enabled: false } as TSurvey["welcomeCard"], - endings: [], - triggers: [], - languages: [], - variables: [], - hiddenFields: { enabled: false }, - createdBy: null, - isSingleResponsePerEmailEnabled: false, - isVerifyEmailEnabled: false, - projectOverwrites: null, - showLanguageSwitch: false, - isBackButtonHidden: false, - followUps: [], - recaptcha: { enabled: false, threshold: 0.5 }, -}; - -// Helper function to create mock display objects -const createMockDisplay = (id: string, surveyId: string, contactId: string, createdAt?: Date) => ({ - id, - createdAt: createdAt || new Date(), - updatedAt: new Date(), - surveyId, - contactId, - responseId: null, - status: null, -}); - -// Helper function to create mock response objects -const createMockResponse = (id: string, surveyId: string, contactId: string, createdAt?: Date) => ({ - id, - createdAt: createdAt || new Date(), - updatedAt: new Date(), - finished: false, - surveyId, - contactId, - endingId: null, - data: {}, - variables: {}, - ttc: {}, - meta: {}, - contactAttributes: null, - singleUseId: null, - language: null, - displayId: null, -}); - -describe("getSyncSurveys", () => { - beforeEach(() => { - vi.mocked(getProjectByEnvironmentId).mockResolvedValue(mockProject); - vi.mocked(prisma.display.findMany).mockResolvedValue([]); - vi.mocked(prisma.response.findMany).mockResolvedValue([]); - vi.mocked(anySurveyHasFilters).mockReturnValue(false); - vi.mocked(evaluateSegment).mockResolvedValue(true); - vi.mocked(diffInDays).mockReturnValue(100); // Assume enough days passed - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - test("should throw error if product not found", async () => { - vi.mocked(getProjectByEnvironmentId).mockResolvedValue(null); - await expect(getSyncSurveys(environmentId, contactId, contactAttributes, deviceType)).rejects.toThrow( - "Project not found" - ); - }); - - test("should return empty array if no surveys found", async () => { - vi.mocked(getSurveys).mockResolvedValue([]); - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - }); - - test("should return empty array if no 'app' type surveys in progress", async () => { - const surveys: TSurvey[] = [ - { ...baseSurvey, id: "s1", type: "link", status: "inProgress" }, - { ...baseSurvey, id: "s2", type: "app", status: "paused" }, - ]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - }); - - test("should filter by displayOption 'displayOnce'", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", displayOption: "displayOnce" }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(prisma.display.findMany).mockResolvedValue([createMockDisplay("d1", "s1", contactId)]); // Already displayed - - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - - vi.mocked(prisma.display.findMany).mockResolvedValue([]); // Not displayed yet - const result2 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result2).toEqual(surveys); - }); - - test("should filter by displayOption 'displayMultiple'", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", displayOption: "displayMultiple" }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(prisma.response.findMany).mockResolvedValue([createMockResponse("r1", "s1", contactId)]); // Already responded - - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - - vi.mocked(prisma.response.findMany).mockResolvedValue([]); // Not responded yet - const result2 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result2).toEqual(surveys); - }); - - test("should filter by displayOption 'displaySome'", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", displayOption: "displaySome", displayLimit: 2 }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(prisma.display.findMany).mockResolvedValue([ - createMockDisplay("d1", "s1", contactId), - createMockDisplay("d2", "s1", contactId), - ]); // Display limit reached - - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - - vi.mocked(prisma.display.findMany).mockResolvedValue([createMockDisplay("d1", "s1", contactId)]); // Within limit - const result2 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result2).toEqual(surveys); - - // Test with response already submitted - vi.mocked(prisma.response.findMany).mockResolvedValue([createMockResponse("r1", "s1", contactId)]); - const result3 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result3).toEqual([]); - }); - - test("should not filter by displayOption 'respondMultiple'", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", displayOption: "respondMultiple" }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(prisma.display.findMany).mockResolvedValue([createMockDisplay("d1", "s1", contactId)]); - vi.mocked(prisma.response.findMany).mockResolvedValue([createMockResponse("r1", "s1", contactId)]); - - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual(surveys); - }); - - test("should filter by product recontactDays if survey recontactDays is null", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", recontactDays: null }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - const displayDate = new Date(); - vi.mocked(prisma.display.findMany).mockResolvedValue([ - createMockDisplay("d1", "s2", contactId, displayDate), // Display for another survey - ]); - - vi.mocked(diffInDays).mockReturnValue(5); // Not enough days passed (product.recontactDays = 10) - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); - expect(diffInDays).toHaveBeenCalledWith(expect.any(Date), displayDate); - - vi.mocked(diffInDays).mockReturnValue(15); // Enough days passed - const result2 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result2).toEqual(surveys); - }); - - test("should return surveys if no segment filters exist", async () => { - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1" }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(anySurveyHasFilters).mockReturnValue(false); - - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual(surveys); - expect(evaluateSegment).not.toHaveBeenCalled(); - }); - - test("should evaluate segment filters if they exist", async () => { - const segment = { id: "seg1", filters: [{}] } as TSegment; // Mock filter structure - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", segment }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(anySurveyHasFilters).mockReturnValue(true); - - // Case 1: Segment evaluation matches - vi.mocked(evaluateSegment).mockResolvedValue(true); - const result1 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result1).toEqual(surveys); - expect(evaluateSegment).toHaveBeenCalledWith( - { - attributes: contactAttributes, - deviceType, - environmentId, - contactId, - userId: contactAttributes.userId, - }, - segment.filters - ); - - // Case 2: Segment evaluation does not match - vi.mocked(evaluateSegment).mockResolvedValue(false); - const result2 = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result2).toEqual([]); - }); - - test("should handle Prisma errors", async () => { - const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", { - code: "P2025", - clientVersion: "test", - }); - vi.mocked(getSurveys).mockRejectedValue(prismaError); - - await expect(getSyncSurveys(environmentId, contactId, contactAttributes, deviceType)).rejects.toThrow( - DatabaseError - ); - expect(logger.error).toHaveBeenCalledWith(prismaError); - }); - - test("should handle general errors", async () => { - const generalError = new Error("Something went wrong"); - vi.mocked(getSurveys).mockRejectedValue(generalError); - - await expect(getSyncSurveys(environmentId, contactId, contactAttributes, deviceType)).rejects.toThrow( - generalError - ); - }); - - test("should throw ResourceNotFoundError if resolved surveys are null after filtering", async () => { - const segment = { id: "seg1", filters: [{}] } as TSegment; // Mock filter structure - const surveys: TSurvey[] = [{ ...baseSurvey, id: "s1", segment }]; - vi.mocked(getSurveys).mockResolvedValue(surveys); - vi.mocked(anySurveyHasFilters).mockReturnValue(true); - vi.mocked(evaluateSegment).mockResolvedValue(false); // Ensure all surveys are filtered out - - // This scenario is tricky to force directly as the code checks `if (!surveys)` before returning. - // However, if `Promise.all` somehow resolved to null/undefined (highly unlikely), it should throw. - // We can simulate this by mocking `Promise.all` if needed, but the current code structure makes this hard to test. - // Let's assume the filter logic works correctly and test the intended path. - const result = await getSyncSurveys(environmentId, contactId, contactAttributes, deviceType); - expect(result).toEqual([]); // Expect empty array, not an error in this case. - }); -}); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.ts deleted file mode 100644 index 307b04d493..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/survey.ts +++ /dev/null @@ -1,148 +0,0 @@ -import "server-only"; -import { Prisma } from "@prisma/client"; -import { cache as reactCache } from "react"; -import { prisma } from "@formbricks/database"; -import { logger } from "@formbricks/logger"; -import { ZId } from "@formbricks/types/common"; -import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; -import { TSurvey } from "@formbricks/types/surveys/types"; -import { getProjectByEnvironmentId } from "@/lib/project/service"; -import { getSurveys } from "@/lib/survey/service"; -import { anySurveyHasFilters } from "@/lib/survey/utils"; -import { diffInDays } from "@/lib/utils/datetime"; -import { validateInputs } from "@/lib/utils/validate"; -import { evaluateSegment } from "@/modules/ee/contacts/segments/lib/segments"; - -export const getSyncSurveys = reactCache( - async ( - environmentId: string, - contactId: string, - contactAttributes: Record, - deviceType: "phone" | "desktop" = "desktop" - ): Promise => { - validateInputs([environmentId, ZId]); - try { - const project = await getProjectByEnvironmentId(environmentId); - - if (!project) { - throw new Error("Project not found"); - } - - let surveys = await getSurveys(environmentId); - - // filtered surveys for running and web - surveys = surveys.filter((survey) => survey.status === "inProgress" && survey.type === "app"); - - // if no surveys are left, return an empty array - if (surveys.length === 0) { - return []; - } - - const displays = await prisma.display.findMany({ - where: { - contactId, - }, - }); - - const responses = await prisma.response.findMany({ - where: { - contactId, - }, - }); - - // filter surveys that meet the displayOption criteria - surveys = surveys.filter((survey) => { - switch (survey.displayOption) { - case "respondMultiple": - return true; - case "displayOnce": - return displays.filter((display) => display.surveyId === survey.id).length === 0; - case "displayMultiple": - if (!responses) return true; - else { - return responses.filter((response) => response.surveyId === survey.id).length === 0; - } - case "displaySome": - if (survey.displayLimit === null) { - return true; - } - - if (responses && responses.filter((response) => response.surveyId === survey.id).length !== 0) { - return false; - } - - return displays.filter((display) => display.surveyId === survey.id).length < survey.displayLimit; - default: - throw Error("Invalid displayOption"); - } - }); - - const latestDisplay = displays[0]; - - // filter surveys that meet the recontactDays criteria - surveys = surveys.filter((survey) => { - if (!latestDisplay) { - return true; - } else if (survey.recontactDays !== null) { - const lastDisplaySurvey = displays.filter((display) => display.surveyId === survey.id)[0]; - if (!lastDisplaySurvey) { - return true; - } - return diffInDays(new Date(), new Date(lastDisplaySurvey.createdAt)) >= survey.recontactDays; - } else if (project.recontactDays !== null) { - return diffInDays(new Date(), new Date(latestDisplay.createdAt)) >= project.recontactDays; - } else { - return true; - } - }); - - // if no surveys are left, return an empty array - if (surveys.length === 0) { - return []; - } - - // if no surveys have segment filters, return the surveys - if (!anySurveyHasFilters(surveys)) { - return surveys; - } - - // the surveys now have segment filters, so we need to evaluate them - const surveyPromises = surveys.map(async (survey) => { - const { segment } = survey; - // if the survey has no segment, or the segment has no filters, we return the survey - if (!segment || !segment.filters?.length) { - return survey; - } - - // Evaluate the segment filters - const result = await evaluateSegment( - { - attributes: contactAttributes ?? {}, - deviceType, - environmentId, - contactId, - userId: String(contactAttributes.userId), - }, - segment.filters - ); - - return result ? survey : null; - }); - - const resolvedSurveys = await Promise.all(surveyPromises); - surveys = resolvedSurveys.filter((survey) => !!survey) as TSurvey[]; - - if (!surveys) { - throw new ResourceNotFoundError("Survey", environmentId); - } - return surveys; - } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError) { - logger.error(error); - throw new DatabaseError(error.message); - } - - throw error; - } - } -); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.test.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.test.ts deleted file mode 100644 index 1b8cc1ba6d..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.test.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { TAttributes } from "@formbricks/types/attributes"; -import { TLanguage } from "@formbricks/types/project"; -import { - TSurvey, - TSurveyEnding, - TSurveyQuestion, - TSurveyQuestionTypeEnum, -} from "@formbricks/types/surveys/types"; -import { parseRecallInfo } from "@/lib/utils/recall"; -import { replaceAttributeRecall } from "./utils"; - -vi.mock("@/lib/utils/recall", () => ({ - parseRecallInfo: vi.fn((text, attributes) => { - const recallPattern = /recall:([a-zA-Z0-9_-]+)/; - const match = text.match(recallPattern); - if (match && match[1]) { - const recallKey = match[1]; - const attributeValue = attributes[recallKey]; - if (attributeValue !== undefined) { - return text.replace(recallPattern, `parsed-${attributeValue}`); - } - } - return text; // Return original text if no match or attribute not found - }), -})); - -const baseSurvey: TSurvey = { - id: "survey1", - createdAt: new Date(), - updatedAt: new Date(), - name: "Test Survey", - environmentId: "env1", - type: "app", - status: "inProgress", - questions: [], - endings: [], - welcomeCard: { enabled: false } as TSurvey["welcomeCard"], - languages: [ - { language: { id: "lang1", code: "en" } as unknown as TLanguage, default: true, enabled: true }, - ], - triggers: [], - recontactDays: null, - displayLimit: null, - singleUse: null, - styling: null, - surveyClosedMessage: null, - hiddenFields: { enabled: false }, - variables: [], - createdBy: null, - isSingleResponsePerEmailEnabled: false, - isVerifyEmailEnabled: false, - projectOverwrites: null, - showLanguageSwitch: false, - isBackButtonHidden: false, - followUps: [], - recaptcha: { enabled: false, threshold: 0.5 }, - displayOption: "displayOnce", - autoClose: null, - delay: 0, - displayPercentage: null, - autoComplete: null, - segment: null, - pin: null, - metadata: {}, -}; - -const attributes: TAttributes = { - name: "John Doe", - email: "john.doe@example.com", - plan: "premium", -}; - -describe("replaceAttributeRecall", () => { - test("should replace recall info in question headlines and subheaders", () => { - const surveyWithRecall: TSurvey = { - ...baseSurvey, - questions: [ - { - id: "q1", - type: TSurveyQuestionTypeEnum.OpenText, - headline: { default: "Hello recall:name!" }, - subheader: { default: "Your email is recall:email" }, - required: true, - buttonLabel: { default: "Next" }, - placeholder: { default: "Type here..." }, - longAnswer: false, - logic: [], - } as unknown as TSurveyQuestion, - ], - }; - - const result = replaceAttributeRecall(surveyWithRecall, attributes); - expect(result.questions[0].headline.default).toBe("Hello parsed-John Doe!"); - expect(result.questions[0].subheader?.default).toBe("Your email is parsed-john.doe@example.com"); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Hello recall:name!", attributes); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Your email is recall:email", attributes); - }); - - test("should replace recall info in welcome card headline", () => { - const surveyWithRecall: TSurvey = { - ...baseSurvey, - welcomeCard: { - enabled: true, - headline: { default: "Welcome, recall:name!" }, - subheader: { default: "

    Some content

    " }, - buttonLabel: { default: "Start" }, - timeToFinish: false, - showResponseCount: false, - }, - }; - - const result = replaceAttributeRecall(surveyWithRecall, attributes); - expect(result.welcomeCard.headline?.default).toBe("Welcome, parsed-John Doe!"); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Welcome, recall:name!", attributes); - }); - - test("should replace recall info in end screen headlines and subheaders", () => { - const surveyWithRecall: TSurvey = { - ...baseSurvey, - endings: [ - { - type: "endScreen", - headline: { default: "Thank you, recall:name!" }, - subheader: { default: "Your plan: recall:plan" }, - buttonLabel: { default: "Finish" }, - buttonLink: "https://example.com", - } as unknown as TSurveyEnding, - ], - }; - - const result = replaceAttributeRecall(surveyWithRecall, attributes); - expect(result.endings[0].type).toBe("endScreen"); - if (result.endings[0].type === "endScreen") { - expect(result.endings[0].headline?.default).toBe("Thank you, parsed-John Doe!"); - expect(result.endings[0].subheader?.default).toBe("Your plan: parsed-premium"); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Thank you, recall:name!", attributes); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Your plan: recall:plan", attributes); - } - }); - - test("should handle multiple languages", () => { - const surveyMultiLang: TSurvey = { - ...baseSurvey, - languages: [ - { language: { id: "lang1", code: "en" } as unknown as TLanguage, default: true, enabled: true }, - { language: { id: "lang2", code: "es" } as unknown as TLanguage, default: false, enabled: true }, - ], - questions: [ - { - id: "q1", - type: TSurveyQuestionTypeEnum.OpenText, - headline: { default: "Hello recall:name!", es: "Hola recall:name!" }, - required: true, - buttonLabel: { default: "Next", es: "Siguiente" }, - placeholder: { default: "Type here...", es: "Escribe aquí..." }, - longAnswer: false, - logic: [], - } as unknown as TSurveyQuestion, - ], - }; - - const result = replaceAttributeRecall(surveyMultiLang, attributes); - expect(result.questions[0].headline.default).toBe("Hello parsed-John Doe!"); - expect(result.questions[0].headline.es).toBe("Hola parsed-John Doe!"); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Hello recall:name!", attributes); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Hola recall:name!", attributes); - }); - - test("should not replace if recall key is not in attributes", () => { - const surveyWithRecall: TSurvey = { - ...baseSurvey, - questions: [ - { - id: "q1", - type: TSurveyQuestionTypeEnum.OpenText, - headline: { default: "Your company: recall:company" }, - required: true, - buttonLabel: { default: "Next" }, - placeholder: { default: "Type here..." }, - longAnswer: false, - logic: [], - } as unknown as TSurveyQuestion, - ], - }; - - const result = replaceAttributeRecall(surveyWithRecall, attributes); - expect(result.questions[0].headline.default).toBe("Your company: recall:company"); - expect(vi.mocked(parseRecallInfo)).toHaveBeenCalledWith("Your company: recall:company", attributes); - }); - - test("should handle surveys with no recall information", async () => { - const surveyNoRecall: TSurvey = { - ...baseSurvey, - questions: [ - { - id: "q1", - type: TSurveyQuestionTypeEnum.OpenText, - headline: { default: "Just a regular question" }, - required: true, - buttonLabel: { default: "Next" }, - placeholder: { default: "Type here..." }, - longAnswer: false, - logic: [], - } as unknown as TSurveyQuestion, - ], - welcomeCard: { - enabled: true, - headline: { default: "Welcome!" }, - subheader: { default: "

    Some content

    " }, - buttonLabel: { default: "Start" }, - timeToFinish: false, - showResponseCount: false, - }, - endings: [ - { - type: "endScreen", - headline: { default: "Thank you!" }, - buttonLabel: { default: "Finish" }, - } as unknown as TSurveyEnding, - ], - }; - const parseRecallInfoSpy = vi.spyOn(await import("@/lib/utils/recall"), "parseRecallInfo"); - - const result = replaceAttributeRecall(surveyNoRecall, attributes); - expect(result).toEqual(surveyNoRecall); // Should be unchanged - expect(parseRecallInfoSpy).not.toHaveBeenCalled(); - parseRecallInfoSpy.mockRestore(); - }); - - test("should handle surveys with empty questions, endings, or disabled welcome card", async () => { - const surveyEmpty: TSurvey = { - ...baseSurvey, - questions: [], - endings: [], - welcomeCard: { enabled: false } as TSurvey["welcomeCard"], - }; - const parseRecallInfoSpy = vi.spyOn(await import("@/lib/utils/recall"), "parseRecallInfo"); - - const result = replaceAttributeRecall(surveyEmpty, attributes); - expect(result).toEqual(surveyEmpty); - expect(parseRecallInfoSpy).not.toHaveBeenCalled(); - parseRecallInfoSpy.mockRestore(); - }); -}); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.ts deleted file mode 100644 index 4ee1d7967b..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/lib/utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { TAttributes } from "@formbricks/types/attributes"; -import { TSurvey } from "@formbricks/types/surveys/types"; -import { parseRecallInfo } from "@/lib/utils/recall"; - -export const replaceAttributeRecall = (survey: TSurvey, attributes: TAttributes): TSurvey => { - const surveyTemp = structuredClone(survey); - const languages = surveyTemp.languages - .map((surveyLanguage) => { - if (surveyLanguage.default) { - return "default"; - } - - if (surveyLanguage.enabled) { - return surveyLanguage.language.code; - } - - return null; - }) - .filter((language): language is string => language !== null); - - surveyTemp.questions.forEach((question) => { - languages.forEach((language) => { - if (question.headline[language]?.includes("recall:")) { - question.headline[language] = parseRecallInfo(question.headline[language], attributes); - } - if (question.subheader && question.subheader[language]?.includes("recall:")) { - question.subheader[language] = parseRecallInfo(question.subheader[language], attributes); - } - }); - }); - if (surveyTemp.welcomeCard.enabled && surveyTemp.welcomeCard.headline) { - languages.forEach((language) => { - if (surveyTemp.welcomeCard.headline && surveyTemp.welcomeCard.headline[language]?.includes("recall:")) { - surveyTemp.welcomeCard.headline[language] = parseRecallInfo( - surveyTemp.welcomeCard.headline[language], - attributes - ); - } - }); - } - surveyTemp.endings.forEach((ending) => { - if (ending.type === "endScreen") { - languages.forEach((language) => { - if (ending.headline && ending.headline[language]?.includes("recall:")) { - ending.headline[language] = parseRecallInfo(ending.headline[language], attributes); - if (ending.subheader && ending.subheader[language]?.includes("recall:")) { - ending.subheader[language] = parseRecallInfo(ending.subheader[language], attributes); - } - } - }); - } - }); - - return surveyTemp; -}; diff --git a/apps/web/app/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts b/apps/web/app/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts deleted file mode 100644 index 0d30268d09..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { - OPTIONS, - PUT, -} from "@/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/route"; - -export { OPTIONS, PUT }; diff --git a/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.test.ts b/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.test.ts new file mode 100644 index 0000000000..0e786a4429 --- /dev/null +++ b/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.test.ts @@ -0,0 +1,314 @@ +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, ResourceNotFoundError } from "@formbricks/types/errors"; +import { getEnvironmentStateData } from "./data"; + +// Mock dependencies +vi.mock("@formbricks/database", () => ({ + prisma: { + environment: { + findUnique: vi.fn(), + }, + }, +})); + +vi.mock("@formbricks/logger", () => ({ + logger: { + error: vi.fn(), + }, +})); + +vi.mock("@/modules/survey/lib/utils", () => ({ + transformPrismaSurvey: vi.fn((survey) => survey), +})); + +const environmentId = "cjld2cjxh0000qzrmn831i7rn"; + +const mockEnvironmentData = { + id: environmentId, + type: "production", + appSetupCompleted: true, + project: { + id: "project-123", + recontactDays: 30, + clickOutsideClose: true, + overlay: "none", + placement: "bottomRight", + inAppSurveyBranding: true, + styling: { allowStyleOverwrite: false }, + organization: { + id: "org-123", + billing: { + plan: "free", + limits: { monthly: { responses: 100 } }, + }, + }, + }, + actionClasses: [ + { + id: "action-1", + type: "code", + name: "Test Action", + key: "test-action", + noCodeConfig: null, + }, + ], + surveys: [ + { + id: "survey-1", + name: "Test Survey", + type: "app", + status: "inProgress", + welcomeCard: { enabled: false }, + questions: [], + blocks: null, + variables: [], + showLanguageSwitch: false, + languages: [], + endings: [], + autoClose: null, + styling: null, + recaptcha: { enabled: false }, + segment: null, + recontactDays: null, + displayLimit: null, + displayOption: "displayOnce", + hiddenFields: { enabled: false }, + isBackButtonHidden: false, + triggers: [], + displayPercentage: null, + delay: 0, + projectOverwrites: null, + }, + ], +}; + +describe("getEnvironmentStateData", () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + test("should return environment state data when environment exists", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue(mockEnvironmentData as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result).toEqual({ + environment: { + id: environmentId, + type: "production", + appSetupCompleted: true, + project: { + id: "project-123", + recontactDays: 30, + clickOutsideClose: true, + overlay: "none", + placement: "bottomRight", + inAppSurveyBranding: true, + styling: { allowStyleOverwrite: false }, + }, + }, + organization: { + id: "org-123", + billing: { + plan: "free", + limits: { monthly: { responses: 100 } }, + }, + }, + surveys: mockEnvironmentData.surveys, + actionClasses: mockEnvironmentData.actionClasses, + }); + + expect(prisma.environment.findUnique).toHaveBeenCalledWith({ + where: { id: environmentId }, + select: expect.objectContaining({ + id: true, + type: true, + appSetupCompleted: true, + project: expect.any(Object), + actionClasses: expect.any(Object), + surveys: expect.any(Object), + }), + }); + }); + + test("should throw ResourceNotFoundError when environment is not found", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue(null); + + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError); + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow("environment"); + }); + + test("should throw ResourceNotFoundError when project is not found", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + project: null, + } as never); + + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError); + }); + + test("should throw ResourceNotFoundError when organization is not found", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + project: { + ...mockEnvironmentData.project, + organization: null, + }, + } as never); + + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError); + }); + + test("should throw DatabaseError on Prisma database errors", async () => { + const prismaError = new Prisma.PrismaClientKnownRequestError("Connection failed", { + code: "P2024", + clientVersion: "5.0.0", + }); + vi.mocked(prisma.environment.findUnique).mockRejectedValue(prismaError); + + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(DatabaseError); + expect(logger.error).toHaveBeenCalled(); + }); + + test("should rethrow unexpected errors", async () => { + const unexpectedError = new Error("Unexpected error"); + vi.mocked(prisma.environment.findUnique).mockRejectedValue(unexpectedError); + + await expect(getEnvironmentStateData(environmentId)).rejects.toThrow("Unexpected error"); + expect(logger.error).toHaveBeenCalled(); + }); + + test("should handle empty surveys array", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + surveys: [], + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.surveys).toEqual([]); + }); + + test("should handle empty actionClasses array", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + actionClasses: [], + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.actionClasses).toEqual([]); + }); + + test("should transform surveys using transformPrismaSurvey", async () => { + const multipleSurveys = [ + ...mockEnvironmentData.surveys, + { + ...mockEnvironmentData.surveys[0], + id: "survey-2", + name: "Second Survey", + }, + ]; + + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + surveys: multipleSurveys, + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.surveys).toHaveLength(2); + }); + + test("should correctly map project properties to environment.project", async () => { + const customProject = { + ...mockEnvironmentData.project, + recontactDays: 14, + clickOutsideClose: false, + overlay: "dark", + placement: "center", + inAppSurveyBranding: false, + styling: { allowStyleOverwrite: true, brandColor: "#ff0000" }, + }; + + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + project: customProject, + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.environment.project).toEqual({ + id: "project-123", + recontactDays: 14, + clickOutsideClose: false, + overlay: "dark", + placement: "center", + inAppSurveyBranding: false, + styling: { allowStyleOverwrite: true, brandColor: "#ff0000" }, + }); + }); + + test("should validate environmentId input", async () => { + // Invalid CUID should throw validation error + await expect(getEnvironmentStateData("invalid-id")).rejects.toThrow(); + }); + + test("should handle different environment types", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + type: "development", + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.environment.type).toBe("development"); + }); + + test("should handle appSetupCompleted false", async () => { + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + appSetupCompleted: false, + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.environment.appSetupCompleted).toBe(false); + }); + + test("should correctly extract organization billing data", async () => { + const customBilling = { + plan: "enterprise", + stripeCustomerId: "cus_123", + limits: { + monthly: { responses: 10000, miu: 50000 }, + projects: 100, + }, + }; + + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ + ...mockEnvironmentData, + project: { + ...mockEnvironmentData.project, + organization: { + id: "org-enterprise", + billing: customBilling, + }, + }, + } as never); + + const result = await getEnvironmentStateData(environmentId); + + expect(result.organization).toEqual({ + id: "org-enterprise", + billing: customBilling, + }); + }); +}); diff --git a/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts b/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts index 32fdb52884..e88afe9141 100644 --- a/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts +++ b/apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts @@ -54,7 +54,7 @@ export const getEnvironmentStateData = async (environmentId: string): Promise { + // Validate response data against validation rules + const mergedData = { + ...response.data, + ...responseUpdateInput.data, + }; + + const isFinished = responseUpdateInput.finished ?? false; + + const validationErrors = validateResponseData( + survey.blocks, + mergedData, + responseUpdateInput.language ?? response.language ?? "en", + isFinished, + survey.questions + ); + + if (validationErrors) { + return { + response: responses.badRequestResponse( + "Validation failed", + formatValidationErrorsForV1Api(validationErrors), + true + ), + }; + } +}; + export const PUT = withV1ApiWrapper({ handler: async ({ req, @@ -113,6 +147,11 @@ export const PUT = withV1ApiWrapper({ }; } + const validationResult = validateResponse(response, survey, inputValidation.data); + if (validationResult) { + return validationResult; + } + // update response with quota evaluation let updatedResponse; try { diff --git a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts index 4ac3f35edc..af4aaff8e6 100644 --- a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts +++ b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts @@ -6,12 +6,14 @@ import { ZEnvironmentId } from "@formbricks/types/environment"; import { InvalidInputError } from "@formbricks/types/errors"; import { TResponseWithQuotaFull } from "@formbricks/types/quota"; import { TResponseInput, ZResponseInput } from "@formbricks/types/responses"; +import { TSurvey } from "@formbricks/types/surveys/types"; import { responses } from "@/app/lib/api/response"; import { transformErrorToDetails } from "@/app/lib/api/validator"; import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; import { sendToPipeline } from "@/app/lib/pipelines"; import { getSurvey } from "@/lib/survey/service"; import { getClientIpFromHeaders } from "@/lib/utils/client-ip"; +import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation"; import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils"; import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers"; import { validateFileUploads } from "@/modules/storage/utils"; @@ -33,6 +35,27 @@ export const OPTIONS = async (): Promise => { ); }; +const validateResponse = (responseInputData: TResponseInput, survey: TSurvey) => { + // Validate response data against validation rules + const validationErrors = validateResponseData( + survey.blocks, + responseInputData.data, + responseInputData.language ?? "en", + responseInputData.finished, + survey.questions + ); + + if (validationErrors) { + return { + response: responses.badRequestResponse( + "Validation failed", + formatValidationErrorsForV1Api(validationErrors), + true + ), + }; + } +}; + export const POST = withV1ApiWrapper({ handler: async ({ req, props }: { req: NextRequest; props: Context }) => { const params = await props.params; @@ -123,6 +146,11 @@ export const POST = withV1ApiWrapper({ }; } + const validationResult = validateResponse(responseInputData, survey); + if (validationResult) { + return validationResult; + } + let response: TResponseWithQuotaFull; try { const meta: TResponseInput["meta"] = { diff --git a/apps/web/app/api/v1/client/og/route.tsx b/apps/web/app/api/v1/client/og/route.tsx index 092a016634..ab585f2e34 100644 --- a/apps/web/app/api/v1/client/og/route.tsx +++ b/apps/web/app/api/v1/client/og/route.tsx @@ -1,4 +1,4 @@ -import { ImageResponse } from "@vercel/og"; +import { ImageResponse } from "next/og"; import { NextRequest } from "next/server"; export const GET = async (req: NextRequest) => { diff --git a/apps/web/app/api/v1/integrations/notion/callback/route.ts b/apps/web/app/api/v1/integrations/notion/callback/route.ts index 06e56c77de..ea5c69642d 100644 --- a/apps/web/app/api/v1/integrations/notion/callback/route.ts +++ b/apps/web/app/api/v1/integrations/notion/callback/route.ts @@ -1,7 +1,7 @@ import { NextRequest } from "next/server"; import { TIntegrationNotionConfigData, TIntegrationNotionInput } from "@formbricks/types/integration/notion"; import { responses } from "@/app/lib/api/response"; -import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; +import { TSessionAuthentication, withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; import { ENCRYPTION_KEY, NOTION_OAUTH_CLIENT_ID, @@ -10,10 +10,17 @@ import { WEBAPP_URL, } from "@/lib/constants"; import { symmetricEncrypt } from "@/lib/crypto"; +import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service"; export const GET = withV1ApiWrapper({ - handler: async ({ req }: { req: NextRequest }) => { + handler: async ({ + req, + authentication, + }: { + req: NextRequest; + authentication: NonNullable; + }) => { const url = req.url; const queryParams = new URLSearchParams(url.split("?")[1]); // Split the URL and get the query parameters const environmentId = queryParams.get("state"); // Get the value of the 'state' parameter @@ -26,6 +33,13 @@ export const GET = withV1ApiWrapper({ }; } + const canUserAccessEnvironment = await hasUserEnvironmentAccess(authentication.user.id, environmentId); + if (!canUserAccessEnvironment) { + return { + response: responses.unauthorizedResponse(), + }; + } + if (code && typeof code !== "string") { return { response: responses.badRequestResponse("`code` must be a string"), diff --git a/apps/web/app/api/v1/integrations/slack/callback/route.ts b/apps/web/app/api/v1/integrations/slack/callback/route.ts index d2b58db002..f254e774d7 100644 --- a/apps/web/app/api/v1/integrations/slack/callback/route.ts +++ b/apps/web/app/api/v1/integrations/slack/callback/route.ts @@ -5,12 +5,19 @@ import { TIntegrationSlackCredential, } from "@formbricks/types/integration/slack"; import { responses } from "@/app/lib/api/response"; -import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; +import { TSessionAuthentication, withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@/lib/constants"; +import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service"; export const GET = withV1ApiWrapper({ - handler: async ({ req }: { req: NextRequest }) => { + handler: async ({ + req, + authentication, + }: { + req: NextRequest; + authentication: NonNullable; + }) => { const url = req.url; const queryParams = new URLSearchParams(url.split("?")[1]); // Split the URL and get the query parameters const environmentId = queryParams.get("state"); // Get the value of the 'state' parameter @@ -23,6 +30,13 @@ export const GET = withV1ApiWrapper({ }; } + const canUserAccessEnvironment = await hasUserEnvironmentAccess(authentication.user.id, environmentId); + if (!canUserAccessEnvironment) { + return { + response: responses.unauthorizedResponse(), + }; + } + if (code && typeof code !== "string") { return { response: responses.badRequestResponse("`code` must be a string"), diff --git a/apps/web/app/api/v1/management/responses/[responseId]/route.ts b/apps/web/app/api/v1/management/responses/[responseId]/route.ts index 66333e3ce0..189fdfee63 100644 --- a/apps/web/app/api/v1/management/responses/[responseId]/route.ts +++ b/apps/web/app/api/v1/management/responses/[responseId]/route.ts @@ -8,10 +8,7 @@ import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib import { sendToPipeline } from "@/app/lib/pipelines"; import { deleteResponse, getResponse } from "@/lib/response/service"; import { getSurvey } from "@/lib/survey/service"; -import { - formatValidationErrorsForV1Api, - validateResponseData, -} from "@/modules/api/v2/management/responses/lib/validation"; +import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation"; import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils"; import { validateFileUploads } from "@/modules/storage/utils"; import { updateResponseWithQuotaEvaluation } from "./lib/response"; @@ -149,6 +146,7 @@ export const PUT = withV1ApiWrapper({ result.survey.blocks, responseUpdate.data, responseUpdate.language ?? "en", + responseUpdate.finished, result.survey.questions ); diff --git a/apps/web/app/api/v1/management/responses/route.ts b/apps/web/app/api/v1/management/responses/route.ts index 49090e33fd..8d92197543 100644 --- a/apps/web/app/api/v1/management/responses/route.ts +++ b/apps/web/app/api/v1/management/responses/route.ts @@ -7,10 +7,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator"; import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; import { sendToPipeline } from "@/app/lib/pipelines"; import { getSurvey } from "@/lib/survey/service"; -import { - formatValidationErrorsForV1Api, - validateResponseData, -} from "@/modules/api/v2/management/responses/lib/validation"; +import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation"; import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils"; import { validateFileUploads } from "@/modules/storage/utils"; import { @@ -158,6 +155,7 @@ export const POST = withV1ApiWrapper({ surveyResult.survey.blocks, responseInput.data, responseInput.language ?? "en", + responseInput.finished, surveyResult.survey.questions ); diff --git a/apps/web/app/api/v2/client/[environmentId]/contacts/[userId]/attributes/route.ts b/apps/web/app/api/v2/client/[environmentId]/contacts/[userId]/attributes/route.ts deleted file mode 100644 index 0d30268d09..0000000000 --- a/apps/web/app/api/v2/client/[environmentId]/contacts/[userId]/attributes/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { - OPTIONS, - PUT, -} from "@/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/route"; - -export { OPTIONS, PUT }; diff --git a/apps/web/app/api/v2/client/[environmentId]/identify/contacts/[userId]/route.ts b/apps/web/app/api/v2/client/[environmentId]/identify/contacts/[userId]/route.ts deleted file mode 100644 index 811f041294..0000000000 --- a/apps/web/app/api/v2/client/[environmentId]/identify/contacts/[userId]/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { - GET, - OPTIONS, -} from "@/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/route"; - -export { GET, OPTIONS }; diff --git a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts index b366e0417b..d4427739e0 100644 --- a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts +++ b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts @@ -11,6 +11,7 @@ import { sendToPipeline } from "@/app/lib/pipelines"; import { getSurvey } from "@/lib/survey/service"; import { getElementsFromBlocks } from "@/lib/survey/utils"; import { getClientIpFromHeaders } from "@/lib/utils/client-ip"; +import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation"; import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element"; import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils"; import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers"; @@ -106,6 +107,23 @@ export const POST = async (request: Request, context: Context): Promise void }) => const errorData = getClientErrorData(error); const { title, description } = getErrorMessages(errorData.type, t); - if (process.env.NODE_ENV === "development") { - console.error(error.message); - } else { - Sentry.captureException(error); - } + useEffect(() => { + if (process.env.NODE_ENV === "development") { + console.error(error.message); + } else if (!isExpectedError(error)) { + Sentry.captureException(error); + } + }, [error]); return (
    diff --git a/apps/web/app/lib/api/with-api-logging.test.ts b/apps/web/app/lib/api/with-api-logging.test.ts index 9b9705c9f3..6aa388f9b7 100644 --- a/apps/web/app/lib/api/with-api-logging.test.ts +++ b/apps/web/app/lib/api/with-api-logging.test.ts @@ -68,7 +68,6 @@ vi.mock("@/app/middleware/endpoint-validator", async () => { isClientSideApiRoute: vi.fn().mockReturnValue({ isClientSideApi: false, isRateLimited: true }), isManagementApiRoute: vi.fn().mockReturnValue({ isManagementApi: false, authenticationMethod: "apiKey" }), isIntegrationRoute: vi.fn().mockReturnValue(false), - isSyncWithUserIdentificationEndpoint: vi.fn().mockReturnValue(null), }; }); @@ -82,7 +81,6 @@ vi.mock("@/modules/core/rate-limit/rate-limit-configs", () => ({ api: { client: { windowMs: 60000, max: 100 }, v1: { windowMs: 60000, max: 1000 }, - syncUserIdentification: { windowMs: 60000, max: 50 }, }, }, })); @@ -463,45 +461,6 @@ describe("withV1ApiWrapper", () => { expect(handler).not.toHaveBeenCalled(); }); - test("handles sync user identification rate limiting", async () => { - const { applyRateLimit, applyIPRateLimit } = await import("@/modules/core/rate-limit/helpers"); - const { - isClientSideApiRoute, - isManagementApiRoute, - isIntegrationRoute, - isSyncWithUserIdentificationEndpoint, - } = await import("@/app/middleware/endpoint-validator"); - const { authenticateRequest } = await import("@/app/api/v1/auth"); - - vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: true, isRateLimited: true }); - vi.mocked(isManagementApiRoute).mockReturnValue({ - isManagementApi: false, - authenticationMethod: AuthenticationMethod.None, - }); - vi.mocked(isIntegrationRoute).mockReturnValue(false); - vi.mocked(isSyncWithUserIdentificationEndpoint).mockReturnValue({ - userId: "user-123", - environmentId: "env-123", - }); - vi.mocked(authenticateRequest).mockResolvedValue(null); - vi.mocked(applyIPRateLimit).mockResolvedValue(undefined); - const rateLimitError = new Error("Sync rate limit exceeded"); - rateLimitError.message = "Sync rate limit exceeded"; - vi.mocked(applyRateLimit).mockRejectedValue(rateLimitError); - - const handler = vi.fn(); - const req = createMockRequest({ url: "/api/v1/client/env-123/app/sync/user-123" }); - const { withV1ApiWrapper } = await import("./with-api-logging"); - const wrapped = withV1ApiWrapper({ handler }); - const res = await wrapped(req, undefined); - - expect(res.status).toBe(429); - expect(applyRateLimit).toHaveBeenCalledWith( - expect.objectContaining({ windowMs: 60000, max: 50 }), - "user-123" - ); - }); - test("skips audit log creation when no action/targetType provided", async () => { const { queueAuditEvent: mockedQueueAuditEvent } = (await import( "@/modules/ee/audit-logs/lib/handler" diff --git a/apps/web/app/lib/api/with-api-logging.ts b/apps/web/app/lib/api/with-api-logging.ts index 2c05a8bfef..fe61621a1d 100644 --- a/apps/web/app/lib/api/with-api-logging.ts +++ b/apps/web/app/lib/api/with-api-logging.ts @@ -10,7 +10,6 @@ import { isClientSideApiRoute, isIntegrationRoute, isManagementApiRoute, - isSyncWithUserIdentificationEndpoint, } from "@/app/middleware/endpoint-validator"; import { AUDIT_LOG_ENABLED, IS_PRODUCTION, SENTRY_DSN } from "@/lib/constants"; import { authOptions } from "@/modules/auth/lib/authOptions"; @@ -48,23 +47,16 @@ enum ApiV1RouteTypeEnum { } /** - * Apply client-side API rate limiting (IP-based or sync-specific) + * Apply client-side API rate limiting (IP-based) */ -const applyClientRateLimit = async (url: string, customRateLimitConfig?: TRateLimitConfig): Promise => { - const syncEndpoint = isSyncWithUserIdentificationEndpoint(url); - if (syncEndpoint) { - const syncRateLimitConfig = rateLimitConfigs.api.syncUserIdentification; - await applyRateLimit(syncRateLimitConfig, syncEndpoint.userId); - } else { - await applyIPRateLimit(customRateLimitConfig ?? rateLimitConfigs.api.client); - } +const applyClientRateLimit = async (customRateLimitConfig?: TRateLimitConfig): Promise => { + await applyIPRateLimit(customRateLimitConfig ?? rateLimitConfigs.api.client); }; /** * Handle rate limiting based on authentication and API type */ const handleRateLimiting = async ( - url: string, authentication: TApiV1Authentication, routeType: ApiV1RouteTypeEnum, customRateLimitConfig?: TRateLimitConfig @@ -84,7 +76,7 @@ const handleRateLimiting = async ( } if (routeType === ApiV1RouteTypeEnum.Client) { - await applyClientRateLimit(url, customRateLimitConfig); + await applyClientRateLimit(customRateLimitConfig); } } catch (error) { return responses.tooManyRequestsResponse(error.message); @@ -255,7 +247,6 @@ const getRouteType = ( * Features: * - Performs authentication once and passes result to handler * - Applies API key-based rate limiting with differentiated limits for client vs management APIs - * - Includes additional sync user identification rate limiting for client-side sync endpoints * - Sets userId and organizationId in audit log automatically when audit logging is enabled * - System and Sentry logs are always called for non-success responses * - Uses function overloads to provide type safety without requiring type guards @@ -328,12 +319,7 @@ export const withV1ApiWrapper: { // === Rate Limiting === if (isRateLimited) { - const rateLimitResponse = await handleRateLimiting( - req.nextUrl.pathname, - authentication, - routeType, - customRateLimitConfig - ); + const rateLimitResponse = await handleRateLimiting(authentication, routeType, customRateLimitConfig); if (rateLimitResponse) return rateLimitResponse; } diff --git a/apps/web/app/lib/templates.ts b/apps/web/app/lib/templates.ts index 9e0567c9e4..4e70295f74 100644 --- a/apps/web/app/lib/templates.ts +++ b/apps/web/app/lib/templates.ts @@ -4848,12 +4848,14 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => { t("templates.preview_survey_question_2_choice_2_label"), ], headline: t("templates.preview_survey_question_2_headline"), + subheader: t("templates.preview_survey_question_2_subheader"), required: true, shuffleOption: "none", }), isDraft: true, }, ], + buttonLabel: createI18nString(t("templates.next"), []), backButtonLabel: createI18nString(t("templates.preview_survey_question_2_back_button_label"), []), }, { diff --git a/apps/web/app/middleware/endpoint-validator.test.ts b/apps/web/app/middleware/endpoint-validator.test.ts index 770b10337b..2e349e977a 100644 --- a/apps/web/app/middleware/endpoint-validator.test.ts +++ b/apps/web/app/middleware/endpoint-validator.test.ts @@ -8,7 +8,6 @@ import { isManagementApiRoute, isPublicDomainRoute, isRouteAllowedForDomain, - isSyncWithUserIdentificationEndpoint, } from "./endpoint-validator"; describe("endpoint-validator", () => { @@ -270,58 +269,6 @@ describe("endpoint-validator", () => { }); }); - describe("isSyncWithUserIdentificationEndpoint", () => { - test("should return environmentId and userId for valid sync URLs", () => { - const result1 = isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/sync/user456"); - expect(result1).toEqual({ - environmentId: "env123", - userId: "user456", - }); - - const result2 = isSyncWithUserIdentificationEndpoint("/api/v1/client/abc-123/app/sync/xyz-789"); - expect(result2).toEqual({ - environmentId: "abc-123", - userId: "xyz-789", - }); - - const result3 = isSyncWithUserIdentificationEndpoint( - "/api/v1/client/env_123_test/app/sync/user_456_test" - ); - expect(result3).toEqual({ - environmentId: "env_123_test", - userId: "user_456_test", - }); - }); - - test("should handle optional trailing slash", () => { - // Test both with and without trailing slash - const result1 = isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/sync/user456"); - expect(result1).toEqual({ - environmentId: "env123", - userId: "user456", - }); - - const result2 = isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/sync/user456/"); - expect(result2).toEqual({ - environmentId: "env123", - userId: "user456", - }); - }); - - test("should return false for invalid sync URLs", () => { - expect(isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/sync")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/something")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("/api/something")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/other/user456")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("/api/v2/client/env123/app/sync/user456")).toBe(false); // only v1 supported - }); - - test("should handle empty or malformed IDs", () => { - expect(isSyncWithUserIdentificationEndpoint("/api/v1/client//app/sync/user456")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("/api/v1/client/env123/app/sync/")).toBe(false); - }); - }); - describe("isPublicDomainRoute", () => { test("should return true for health endpoint", () => { expect(isPublicDomainRoute("/health")).toBe(true); @@ -582,12 +529,6 @@ describe("endpoint-validator", () => { test("should handle special characters in survey IDs", () => { expect(isPublicDomainRoute("/s/survey-123_test.v2")).toBe(true); expect(isPublicDomainRoute("/c/jwt.token.with.dots")).toBe(true); - expect( - isSyncWithUserIdentificationEndpoint("/api/v1/client/env-123_test/app/sync/user-456_test") - ).toEqual({ - environmentId: "env-123_test", - userId: "user-456_test", - }); }); }); @@ -628,15 +569,6 @@ describe("endpoint-validator", () => { const longSurveyId = "a".repeat(1000); const longPath = `s/${longSurveyId}`; expect(isPublicDomainRoute(`/${longPath}`)).toBe(true); - - const longEnvironmentId = "env" + "a".repeat(1000); - const longUserId = "user" + "b".repeat(1000); - expect( - isSyncWithUserIdentificationEndpoint(`/api/v1/client/${longEnvironmentId}/app/sync/${longUserId}`) - ).toEqual({ - environmentId: longEnvironmentId, - userId: longUserId, - }); }); test("should handle empty and minimal inputs", () => { @@ -651,7 +583,6 @@ describe("endpoint-validator", () => { }); expect(isIntegrationRoute("")).toBe(false); expect(isAuthProtectedRoute("")).toBe(false); - expect(isSyncWithUserIdentificationEndpoint("")).toBe(false); }); }); diff --git a/apps/web/app/middleware/endpoint-validator.ts b/apps/web/app/middleware/endpoint-validator.ts index f8aa5001d8..82c2469349 100644 --- a/apps/web/app/middleware/endpoint-validator.ts +++ b/apps/web/app/middleware/endpoint-validator.ts @@ -43,14 +43,6 @@ export const isAuthProtectedRoute = (url: string): boolean => { return protectedRoutes.some((route) => url.startsWith(route)); }; -export const isSyncWithUserIdentificationEndpoint = ( - url: string -): { environmentId: string; userId: string } | false => { - const regex = /\/api\/v1\/client\/(?[^/]+)\/app\/sync\/(?[^/]+)/; - const match = url.match(regex); - return match ? { environmentId: match.groups!.environmentId, userId: match.groups!.userId } : false; -}; - /** * Check if the route should be accessible on the public domain (PUBLIC_URL) * Uses whitelist approach - only explicitly allowed routes are accessible diff --git a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts index 749e3fe540..140990e1e8 100644 --- a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts +++ b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts @@ -8,7 +8,7 @@ import { authorizePrivateDownload } from "@/app/storage/[environmentId]/[accessT 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 { deleteFile, getSignedUrlForDownload } from "@/modules/storage/service"; +import { deleteFile, getFileStreamForDownload } from "@/modules/storage/service"; import { getErrorResponseFromStorageError } from "@/modules/storage/utils"; import { logFileDeletion } from "./lib/audit-logs"; @@ -39,21 +39,25 @@ export const GET = async ( } } - const signedUrlResult = await getSignedUrlForDownload(fileName, environmentId, accessType); + // Stream the file directly + const streamResult = await getFileStreamForDownload(fileName, environmentId, accessType); - if (!signedUrlResult.ok) { - const errorResponse = getErrorResponseFromStorageError(signedUrlResult.error, { fileName }); + if (!streamResult.ok) { + const errorResponse = getErrorResponseFromStorageError(streamResult.error, { fileName }); return errorResponse; } - return new Response(null, { - status: 302, + const { body, contentType, contentLength } = streamResult.data; + + return new Response(body, { + status: 200, headers: { - Location: signedUrlResult.data, + "Content-Type": contentType, + ...(contentLength > 0 && { "Content-Length": String(contentLength) }), "Cache-Control": accessType === "private" ? "no-store, no-cache, must-revalidate" - : "public, max-age=300, s-maxage=300, stale-while-revalidate=300", + : "public, max-age=31536000, immutable", }, }); }; diff --git a/apps/web/i18n.json b/apps/web/i18n.json index 2a60f88d47..d4ab723f61 100644 --- a/apps/web/i18n.json +++ b/apps/web/i18n.json @@ -9,17 +9,18 @@ "source": "en-US", "targets": [ "de-DE", + "es-ES", "fr-FR", + "hu-HU", "ja-JP", + "nl-NL", "pt-BR", "pt-PT", "ro-RO", - "zh-Hans-CN", - "zh-Hant-TW", - "nl-NL", - "es-ES", + "ru-RU", "sv-SE", - "ru-RU" + "zh-Hans-CN", + "zh-Hant-TW" ] }, "version": 1.8 diff --git a/apps/web/i18n.lock b/apps/web/i18n.lock index 6fd2ceb8fa..420e5ad7d1 100644 --- a/apps/web/i18n.lock +++ b/apps/web/i18n.lock @@ -12,7 +12,7 @@ checksums: auth/email-change/email_change_success: fdd29f7865a962bcf731b02825c18df3 auth/email-change/email_change_success_description: 0229631eca60cf026a644059085e1342 auth/email-change/email_verification_failed: e3301deb26bbca3f11149489e527ef46 - auth/email-change/email_verification_loading: f5776e1cf74eb1e652ba75f8d8e41465 + auth/email-change/email_verification_loading: 9f3517439fa1f2ef2a46ac6859c182da auth/email-change/email_verification_loading_description: 37be603a1bcba6b78b17cf09e1a01bc9 auth/email-change/invalid_or_expired_token: a7fae3e7ed092d89731430f39e255670 auth/email-change/new_email: 45b7a175e9f4aaf32558ae59dbfa9541 @@ -24,7 +24,7 @@ checksums: auth/forgot-password/reset/new_password: c3e35b0671004c7bbd9fa9b9654744d7 auth/forgot-password/reset/no_token_provided: c28440cd9c66ba90886caf8415b31e33 auth/forgot-password/reset/passwords_do_not_match: 37ca1f4e0afc9a0b8e9617f767103c92 - auth/forgot-password/reset/success/heading: c03d20ea0af0fa9570e53c4c8161de57 + auth/forgot-password/reset/success/heading: b2cd07687529c9efa37d11875ab957b7 auth/forgot-password/reset/success/text: 27380a18e2c15751a541bed797895713 auth/forgot-password/reset_password: c21b33e73f096fd7b02171d399b36adf auth/forgot-password/reset_password_description: d5b9cfe064b5e1adb7c9af91f535e3be @@ -39,7 +39,7 @@ checksums: auth/invite/invite_not_found: fe5acb3772bf4fe03ac3171ecb959e08 auth/invite/invite_not_found_description: 128b5aed01d944ad692e2c54e3dd9f70 auth/invite/login: f4f219abeb5a465ecb1c7efaf50246de - auth/invite/welcome_to_organization: 6dacef76e12db3a13696243c8c76331a + auth/invite/welcome_to_organization: 5dedd03b80d2a8d60fa906318dba2149 auth/invite/welcome_to_organization_description: d3c912d4056cc4af9c08fb2a0919823a auth/last_used: 9049bf77c57c92b7a5a760ba2a40128c auth/login/backup_code: 870711a6df11ae55366d018255b466cf @@ -67,8 +67,8 @@ checksums: auth/signup/security_updates_title: de5127f5847cdd412906607e1402f48d auth/signup/terms_of_service: 5add91f519e39025708e54a7eb7a9fc5 auth/signup/title: 96addc349f834eaa5d14c786d5478b1c - auth/signup_without_verification_success/user_successfully_created: ff849ebedc5dacb36493d7894f16edc7 - auth/signup_without_verification_success/user_successfully_created_info: 60687ad06a83c96dfd7dbb5521768166 + auth/signup_without_verification_success/user_successfully_created: e7ee262de26fdbd500023d9fb1982cf9 + auth/signup_without_verification_success/user_successfully_created_info: 0502de5e7ef8594cc65cf890f13f9fcc auth/verification-requested/invalid_email_address: b2d9f25626f2d15c7c63e0281bccc247 auth/verification-requested/invalid_token: 9008919416c117067e95efda0024655d auth/verification-requested/new_email_verification_success: d76f50ad638163a1cb964a191bcf7176 @@ -76,10 +76,10 @@ checksums: auth/verification-requested/please_confirm_your_email_address: 73aa4a257a4ccbfed94d888a9e265533 auth/verification-requested/resend_verification_email: bd0e6492ef0f3dd61eb1e2ba20f3e147 auth/verification-requested/verification_email_resent_successfully: ef61258c14559afcd00252f90e6876fb - auth/verification-requested/verification_email_successfully_sent_info: faa9b86dfaea7b7ecb391a907404f9c5 - auth/verification-requested/you_didnt_receive_an_email_or_your_link_expired: d77d6fe3e0335bd7a84999ca08c49ca8 + auth/verification-requested/verification_email_successfully_sent_info: 2a98caf0d12239699cd3f6bbc987b941 + auth/verification-requested/you_didnt_receive_an_email_or_your_link_expired: cab28d1246e22c11d0c3ce907d55c983 auth/verify/no_token_provided: 9eaa5fc257cf6d9fef6c204ee3761f72 - auth/verify/verifying: 421ed451e3e4b14dc2dcf25956fca011 + auth/verify/verifying: f8ec791a2abb6d2e7c332d94efa7b478 billing_confirmation/back_to_billing_overview: b6be1b9c1d25fdafe37a27fcc3c5fa56 billing_confirmation/thanks_for_upgrading: d59d5605297e52364b444d335a940878 billing_confirmation/upgrade_successful: 57281b5f15480a026a2f67b195d69247 @@ -162,14 +162,15 @@ checksums: common/customer_success: 2b0c99a5f57e1d16cf0a998f9bb116c4 common/dark_overlay: 173e84b526414dbc70dbf9737e443b60 common/date: 56f41c5d30a76295bb087b20b7bee4c3 + common/days: c95fe8aedde21a0b5653dbd0b3c58b48 common/default: d9c6dc5c412fe94143dfd1d332ec81d4 common/delete: 8bcf303dd10a645b5baacb02b47d72c9 common/description: e17686a22ffad04cc7bb70524ed4478b common/dev_env: e650911d5e19ba256358e0cda154c005 common/development: 85211dbb918bda7a6e87649dcfc1b17a - common/development_environment_banner: 90aa859c1f43a9c2d9fbc758853d0c20 + common/development_environment_banner: c5ee195b0d4b88e43dad7b52097de506 common/disable: 81b754fd7962e0bd9b6ba87f3972e7fc - common/disallow: 144b91c92ed07f29ff362fa9edc2e4d0 + common/disallow: 01c8ed3ce545ed836d3ccffc562c8a0c common/discard: de83a114a79d086e372c43dbfe9f47b4 common/dismissed: f0e21b3fe28726c577a7238a63cc29c2 common/docs: 1563fcb5ddb5037b0709ccd3dd384a92 @@ -187,9 +188,9 @@ checksums: common/enterprise_license: e81bf506f47968870c7bd07245648a0d common/environment: 0844e8dc1485339c8de066dc0a9bb6a1 common/environment_not_found: 4d7610bdb55a8b5e6131bb5b08ce04c5 - common/environment_notice: 0a860e3fa89407726dd8a2083a6b7fd5 + common/environment_notice: 228a8668be1812e031f438d166861729 common/error: 3c95bcb32c2104b99a46f5b3dd015248 - common/error_component_description: ef56acbb125f59ffae500a4bc8500e48 + common/error_component_description: fa9eee04f864c3fe6e6681f716caa015 common/error_component_title: ae68fa341a143aaa13a5ea30dd57a63e common/error_rate_limit_description: 37791a33a947204662ee9c6544e90f51 common/error_rate_limit_title: 23ac9419e267e610e1bfd38e1dc35dc0 @@ -228,6 +229,7 @@ checksums: common/label: a5c71bf158481233f8215dbd38cc196b common/language: 277fd1a41cc237a437cd1d5e4a80463b common/learn_more: e598091d132f890c37a6d4ed94f6d794 + common/license_expired: 7af13535e320e4197989472c01387d2c common/light_overlay: 0499907ea7b8405f4267b117998b5a78 common/limits_reached: 98879ae8c2be281ad6d283d7029d0148 common/link: 60f0aa33e70ca6df3b6659802b16f433 @@ -246,8 +248,9 @@ checksums: common/membership_not_found: 7ac63584af23396aace9992ad919ffd4 common/metadata: 695d4f7da261ba76e3be4de495491028 common/mobile_overlay_app_works_best_on_desktop: 4509f7bfbb4edbd42e534042d6cb7e72 - common/mobile_overlay_surveys_look_good: 6d73b635018b4a5a89cce58e1d2497f5 + common/mobile_overlay_surveys_look_good: d85169e86077738b9837647bf6d1c7d2 common/mobile_overlay_title: 42f52259b7527989fb3a3240f5352a8b + common/months: da74749fbe80394fa0f72973d7b0964a common/move_down: 4f4de55743043355ad4a839aff2c48ff common/move_up: 69f25b205c677abdb26cbb69d97cd10b common/multiple_languages: 7d8ddd4b40d32fcd7bd6f7bac6485b1f @@ -258,6 +261,7 @@ checksums: common/no_background_image_found: 4108a781a9022c65671a826d4e299d5b common/no_code: f602144ab7d28a5b19a446bf74b4dcc4 common/no_files_uploaded: c97be829e195a41b2f6b6717b87a232b + common/no_overlay: 03cde9e91f08e4dd539d788e1e01407f common/no_quotas_found: 19dea6bcc39b579351073b3974990cb6 common/no_result_found: fedddbc0149972ea072a9e063198a16d common/no_results: 0e9b73265c6542240f5a3bf6b43e9280 @@ -284,6 +288,7 @@ checksums: common/organization_teams_not_found: ce29fcb7a4e8b4582f92b65dea9b7d4e common/other: 79acaa6cd481262bea4e743a422529d2 common/others: 39160224ce0e35eb4eb252c997edf4d8 + common/overlay_color: 4b72073285d13fff93d094aabffe05ac common/overview: 30c54e4dc4ce599b87d94be34a8617f5 common/password: 223a61cf906ab9c40d22612c588dff48 common/paused: edb1f7b7219e1c9b7aa67159090d6991 @@ -323,6 +328,7 @@ checksums: common/request_trial_license: 560df1240ef621f7c60d3f7d65422ccd common/reset_to_default: 68ee98b46677392f44b505b268053b26 common/response: c7a9d88269d8ff117abcbc0d97f88b2c + common/response_id: 73375099cc976dc7203b8e27f5f709e0 common/responses: 14bb6c69f906d7bbd1359f7ef1bb3c28 common/restart: bab6232e89f24e3129f8e48268739d5b common/role: 53743bbb6ca938f5b893552e839d067f @@ -363,6 +369,7 @@ checksums: common/status: 4e1fcce15854d824919b4a582c697c90 common/step_by_step_manual: 2894a07952a4fd11d98d5d8f1088690c common/storage_not_configured: b0c3e339f6d71f23fdd189e7bcb076f6 + common/string: 4ddccc1974775ed7357f9beaf9361cec common/styling: 240fc91eb03c52d46b137f82e7aec2a1 common/submit: 7c91ef5f747eea9f77a9c4f23e19fb2e common/summary: 13eb7b8a239fb4702dfdaee69100a220 @@ -418,6 +425,7 @@ checksums: common/website_and_app_connection: 60fea5cff5bddb4db3c8a1b0a2f9ec63 common/website_app_survey: 258579927ed3955dcc8e1cbd7f0df17f common/website_survey: 17513d25a07b6361768a15ec622b021b + common/weeks: 545de30df4f44d3f6d1d344af6a10815 common/welcome_card: 76081ebd5b2e35da9b0f080323704ae7 common/workspace_configuration: d0a5812d6a97d7724d565b1017c34387 common/workspace_created_successfully: bf401ae83da954f1db48724e2a8e40f1 @@ -428,6 +436,7 @@ checksums: common/workspace_not_found: 038fb0aaf3570610f4377b9eaed13752 common/workspace_permission_not_found: e94bdff8af51175c5767714f82bb4833 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 @@ -435,6 +444,7 @@ checksums: common/you_have_reached_your_monthly_miu_limit_of: ded62fc6842c707f62622386ca34f71a common/you_have_reached_your_monthly_response_limit_of: 3824db23ecc3dcd2b1787b98ccfdd5f9 common/you_will_be_downgraded_to_the_community_edition_on_date: bff35b54c13e2c205dc4c19056261cc0 + common/your_license_has_expired_please_renew: 3f21ae4a7deab351b143b407ece58254 emails/accept: f8cc1de4f5e3c850cfdbbc0ec831ade7 emails/click_or_drag_to_upload_files: 64f59bc339568d52b8464b82546b70ea emails/email_customization_preview_email_heading: 8b798cb8438b3dd356c02dab33b4c897 @@ -443,14 +453,14 @@ checksums: emails/email_footer_text_1: e81916a600010a6b6336aa747c153a76 emails/email_footer_text_2: 6fd719fe916a7155e1b0b88a72420717 emails/email_template_text_1: 86b06d249ef069e0fe2457fe3889c416 - emails/embed_survey_preview_email_didnt_request: 90f4e11311cbeebf20614d306fe71296 + emails/embed_survey_preview_email_didnt_request: 895b4463965beac0df080ccdef847824 emails/embed_survey_preview_email_environment_id: 4dc6803f9b79c8a4be35a4126a582331 emails/embed_survey_preview_email_fight_spam: 71f345df8d483d5b6c2c6dbab458d0b2 emails/embed_survey_preview_email_heading: 2a1982b3aeb91476cefb62554031394a emails/embed_survey_preview_email_subject: 3240ad34aac02860909e10187745070e emails/embed_survey_preview_email_text: 2eceb6fdedef104db8cfad4de7fdb9aa emails/forgot_password_email_change_password: fe6d4ba303b82f4833b3293f0c4e88c0 - emails/forgot_password_email_did_not_request: 80c88d18a12cdab0c1cda017a28e2d11 + emails/forgot_password_email_did_not_request: 79d35c3800e23e9d4c95bf33f250104f emails/forgot_password_email_heading: fe6d4ba303b82f4833b3293f0c4e88c0 emails/forgot_password_email_link_valid_for_24_hours: 1616714e6bf36e4379b9868e98e82957 emails/forgot_password_email_subject: bd7a2b22e7b480c29f512532fd2b7e2b @@ -458,12 +468,12 @@ checksums: emails/hidden_field: 3ed5c58d0ed359e558cdf7bd33606d2d emails/imprint: c4e5f2a1994d3cc5896b200709cc499c emails/invite_accepted_email_heading: 80763c6e4585cd57fa58e4d2d82e6500 - emails/invite_accepted_email_subject: 4f5f2a68c98dd1dd01143fcae3be5562 + emails/invite_accepted_email_subject: 3c54e63b2336c60fcf6ee333ba4b1208 emails/invite_accepted_email_text: 48d792826ab9a97eed27599c17ec70d5 emails/invite_email_button_label: 02099d40cd11e717c0431fa43e68272c emails/invite_email_heading: d9f9b18e4de575980de3cde3e4ed08bf emails/invite_email_text: 1499fa615105121a133440929b039a64 - emails/invite_member_email_subject: 295e329b1642339dc7cc2b49a687e1f8 + emails/invite_member_email_subject: 7f670bc261a9505953ec04ea7aa53ed1 emails/new_email_verification_text: b7f00f47d04afa9e872176d9933f2d93 emails/number_variable: d4f2bbb1965c791cf9921a5112914f3f emails/password_changed_email_heading: 601f68fc8bef9c5ecf79f4ec4de5ad06 @@ -478,7 +488,7 @@ checksums: emails/schedule_your_meeting: 01683323bd7373560cd2cb2737dbaf06 emails/select_a_date: 521e4a705800da06d091fde3e801ce02 emails/survey_response_finished_email_congrats: 4cc39698d6a16f68cf7d9db902c5978e - emails/survey_response_finished_email_dont_want_notifications: 5c6fbb5adc79ed133911918984616609 + emails/survey_response_finished_email_dont_want_notifications: 749171800180682839896dc04a53ca10 emails/survey_response_finished_email_hey: 20c5157a424f7d49ceeb27e6fb13d194 emails/survey_response_finished_email_turn_off_notifications_for_all_new_forms: baf2a086dbc58396dbe618d3bd20258e emails/survey_response_finished_email_turn_off_notifications_for_this_form: 7b6a7074490ceaf3d1903a37169364d6 @@ -566,14 +576,14 @@ checksums: environments/actions/your_survey_would_be_shown_on_this_url: 766fdeeb52d170c156af5d035a1f8c37 environments/actions/your_survey_would_not_be_shown: af44fe160f449ff9557ebe5d3686832d environments/connect/congrats: c2f5b597aabdf298cf9f0452863e2dc6 - environments/connect/connection_successful_message: fede93d74a65f468411c3cbdce6f7b08 - environments/connect/do_it_later: e372c307fb89ccd47859a7c0cdfa0215 + environments/connect/connection_successful_message: fa1f29883e15e8697c6c477bdf5cb645 + environments/connect/do_it_later: ab4accfbe53d924ab3ffaf9ea78a75f3 environments/connect/finish_onboarding: 9920084ce60cffa92ed09fdf8d5bb6bc environments/connect/headline: 9c75985f941e19f94ac5141207409773 environments/connect/import_formbricks_and_initialize_the_widget_in_your_component: acd129a32107247c4b2f76a41e05c5db environments/connect/insert_this_code_into_the_head_tag_of_your_website: c4bec1089168efe0fa565397bd7cb115 environments/connect/subtitle: d913df50a1c6b7acc283484195f3732a - environments/connect/waiting_for_your_signal: 9b0a62d5b61c04674596643d2f9f5c09 + environments/connect/waiting_for_your_signal: 8dc94c219cff462c521190a1f3bd5d5d environments/contacts/add_attribute: 70f153afc5c400527ffccfb2eec58ae9 environments/contacts/attribute_created_successfully: e9f90d366d817f2f1c81fb819c0e2f05 environments/contacts/attribute_description: e17686a22ffad04cc7bb70524ed4478b @@ -589,6 +599,12 @@ checksums: environments/contacts/attribute_updated_successfully: 0e64422156c29940cd4dab2f9d1f40b2 environments/contacts/attribute_value: 34b0eaa85808b15cbc4be94c64d0146b environments/contacts/attribute_value_placeholder: 90fb17015de807031304d7a650a6cb8c + environments/contacts/attributes_msg_attribute_limit_exceeded: a6c430860f307f9cc90c449f96a1284f + environments/contacts/attributes_msg_attribute_type_validation_error: ed177ce83bd174ed6be7e889664f93a1 + environments/contacts/attributes_msg_email_already_exists: a3ea1265e3db885f53d0e589aecf6260 + environments/contacts/attributes_msg_email_or_userid_required: 3be0e745cd3500c9a23bad2e25ad3147 + environments/contacts/attributes_msg_new_attribute_created: c4c7b27523058f43b70411d7aa6510e5 + environments/contacts/attributes_msg_userid_already_exists: d2d95ece4b06507be18c9ba240b0a26b environments/contacts/contact_deleted_successfully: c5b64a42a50e055f9e27ec49e20e03fa environments/contacts/contact_not_found: 045396f0b13fafd43612a286263737c0 environments/contacts/contacts_table_refresh: 6a959475991dd4ab28ad881bae569a09 @@ -597,35 +613,54 @@ checksums: environments/contacts/create_key: 0d385c354af8963acbe35cd646710f86 environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26 + environments/contacts/custom_attributes: fffc7722742d1291b102dc737cf2fc9e + environments/contacts/data_type: 1ea127ba2c18d0d91fb0361cc6747e2b + environments/contacts/data_type_cannot_be_changed: 22603f6193fdac3784eeef8315df70de + environments/contacts/data_type_description: 800bf4935df15e6cb14269e1b60c506e + environments/contacts/date_value_required: e0e75b75ae4e8c02f03284954756adc9 environments/contacts/delete_attribute_confirmation: 01d99b89eb3d27ff468d0db1b4aeb394 - environments/contacts/delete_contact_confirmation: 4304d36277daa205b4aa09f5e0d494ab - environments/contacts/delete_contact_confirmation_with_quotas: 7c0e2e223ca55101270ac2988c53e616 + environments/contacts/delete_contact_confirmation: 2d45579e0bb4bc40fb1ee75b43c0e7a4 + environments/contacts/delete_contact_confirmation_with_quotas: d3d17f13ae46ce04c126c82bf01299ac environments/contacts/edit_attribute: 92a83c96a5d850e7d39002e8fd5898f4 environments/contacts/edit_attribute_description: 073a3084bb2f3b34ed1320ed1cd6db3c environments/contacts/edit_attribute_values: 44e4e7a661cc1b59200bb07c710072a7 environments/contacts/edit_attribute_values_description: 21593dfaf4cad965ffc17685bc005509 + environments/contacts/edit_attributes: a5c3b540441d34b4c0b7faab8f0f0c89 environments/contacts/edit_attributes_success: 39f93b1a6f1605bc5951f4da5847bb22 environments/contacts/generate_personal_link: 9ac0865f6876d40fe858f94eae781eb8 environments/contacts/generate_personal_link_description: b9dbaf9e2d8362505b7e3cfa40f415a6 + environments/contacts/invalid_csv_column_names: dcb8534e7d4c00b9ea7bdaf389f72328 + environments/contacts/invalid_date_format: 5bad9730ac5a5bacd0792098f712b1c4 + environments/contacts/invalid_number_format: bd0422507385f671c3046730a6febc64 environments/contacts/no_published_link_surveys_available: 9c1abc5b21aba827443cdf87dd6c8bfe environments/contacts/no_published_surveys: bd945b0e2e2328c17615c94143bdd62b environments/contacts/no_responses_found: f10190cffdda4ca1bed479acbb89b13f environments/contacts/not_provided: a09e4d61bbeb04b927406a50116445e2 + environments/contacts/number_value_required: d82a198a378eb120f3329e4d3fd4d3f7 environments/contacts/personal_link_generated: efb7a0420bd459847eb57bca41a4ab0d environments/contacts/personal_link_generated_but_clipboard_failed: 4eb1e208e729bd5ac00c33f72fc38d53 environments/contacts/personal_survey_link: 5b3f1afc53733718c4ed5b1443b6a604 environments/contacts/please_select_a_survey: 465aa7048773079c8ffdde8b333b78eb - environments/contacts/search_attribute_keys: 2811dd44b98a6a74e55246bfff6100d4 + environments/contacts/search_attribute_keys: af44ef2eb5208c4c4ce423dc7707de13 environments/contacts/search_contact: 020205a93846ab3e12c203ac4fa97c12 environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514 environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79 + environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38 + environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6 environments/contacts/unlock_contacts_description: c5572047f02b4c39e5109f9de715499d environments/contacts/unlock_contacts_title: a8b3d7db03eb404d9267fd5cdd6d5ddb + environments/contacts/upload_contacts_error_attribute_type_mismatch: 70a60f0886ce767c00defa7d4aad0f93 + environments/contacts/upload_contacts_error_duplicate_mappings: 9c1e1f07e476226bad98ccfa07979fec + environments/contacts/upload_contacts_error_file_too_large: 0c1837286c55d18049277465bc2444c1 + environments/contacts/upload_contacts_error_generic: 3a8d35a421b377198361af9972392693 + environments/contacts/upload_contacts_error_invalid_file_type: 15ef4fa7c2d5273b05a042f398655e81 + environments/contacts/upload_contacts_error_no_valid_contacts: 27fbd24ed2d2fa3b6ed7b3a8c1dad343 + environments/contacts/upload_contacts_modal_attribute_header: 263246ad2a76f8e2f80f0ed175d7629a environments/contacts/upload_contacts_modal_attributes_description: e2cedbd4a043423002cbb2048e2145ac environments/contacts/upload_contacts_modal_attributes_new: 9829382598c681de74130440a37b560f environments/contacts/upload_contacts_modal_attributes_search_or_add: 1874839e465650d353282b43b00247a9 - environments/contacts/upload_contacts_modal_attributes_should_be_mapped_to: 693dfe5836e90b1c4c7c65b015418174 environments/contacts/upload_contacts_modal_attributes_title: 86d0ae6fea0fbb119722ed3841f8385a + environments/contacts/upload_contacts_modal_csv_column_header: f181add48fb8325efaa40579fe8c343e environments/contacts/upload_contacts_modal_description: 41566d40d25cc882aa9f82d87b4e2f03 environments/contacts/upload_contacts_modal_download_example_csv: 7a186fc4941b264452ee6c9e785769de environments/contacts/upload_contacts_modal_duplicates_description: 112ce4641088520469a83a0bd740b073 @@ -637,7 +672,7 @@ checksums: environments/contacts/upload_contacts_modal_duplicates_update_description: 42de2e1a0c67b782a4c713df221e55f9 environments/contacts/upload_contacts_modal_duplicates_update_title: 079fc039262fd31b10532929685c2d1b environments/contacts/upload_contacts_modal_pick_different_file: e748a6e81a425ef9aa33f96ca4edc157 - environments/contacts/upload_contacts_modal_preview: c4406f8d9a54f131abfff4e9928228bb + environments/contacts/upload_contacts_modal_preview: 2ea999d47068d8d626cde456ae0ac44a environments/contacts/upload_contacts_modal_upload_btn: 47b7f3bcf478a7d8dc258d2efc80af37 environments/contacts/upload_contacts_success: cd5d6b6d587586dd4f944868c92835bc environments/formbricks_logo: b7ee57de32c8b13463cc8ca8643eddd4 @@ -688,7 +723,7 @@ checksums: environments/integrations/make_integration_description: b34803ccc66519fa7b3732c5222a030b environments/integrations/manage_webhooks: eaff72d6b087dcaabf785c79e1e315b4 environments/integrations/n8n_integration_description: 1c8ff9f7d49251899cd2b2c427b9499c - environments/integrations/notion/col_name_of_type_is_not_supported: 79facd1921a35addc656d88335431cdf + environments/integrations/notion/col_name_of_type_is_not_supported: 2dcdbba79c40d25abf9a8da3095fd6fa environments/integrations/notion/connect_with_notion: 9fe47c7180ef4083cdeb6cf39c8c6ff6 environments/integrations/notion/connected_with_workspace: c63bd5c91245b85ba433cea58a8accd1 environments/integrations/notion/create_at_least_one_database_to_setup_this_integration: 5af87e3fc22d2a8a87b85e093442aae7 @@ -707,7 +742,7 @@ checksums: environments/integrations/notion/please_resolve_mapping_errors: edac1f44854cf8b2c084556ba16acb89 environments/integrations/notion/please_select_a_database: bac31a549324331f7f5e06bbb4b3a4a9 environments/integrations/notion/please_select_at_least_one_mapping: 021007066638f67308b1d2669cde6237 - environments/integrations/notion/que_name_of_type_cant_be_mapped_to: 76fd17850e9304c4a4d4abb82dea5234 + environments/integrations/notion/que_name_of_type_cant_be_mapped_to: dc1be992916dd8abb3710a5e3c24e0d4 environments/integrations/notion/select_a_database: c5edca60c3c4bc3c72d2e2743279d356 environments/integrations/notion/select_a_field_to_map: be4534891877b77993bea1cd3d370814 environments/integrations/notion/select_a_survey_question: ba8815a54d41937ba57c628294f978bd @@ -722,7 +757,7 @@ checksums: environments/integrations/slack/connect_your_first_slack_channel: 9f2688d31c725a53f70b9e2a1e6e3cc0 environments/integrations/slack/connected_with_team: a38dfe3a3c041c91052c998eed2b51a9 environments/integrations/slack/create_at_least_one_channel_error: 14fbf87a2fff12688637292ac38cbf18 - environments/integrations/slack/dont_see_your_channel: 134d25e7e7448f2c3d31e6ebbd4ef995 + environments/integrations/slack/dont_see_your_channel: d86e95b25daebbf2d2f5d3ba34d48198 environments/integrations/slack/link_channel: 82fd1c6079dfd7e4b6a1d1db4ccfc159 environments/integrations/slack/link_slack_channel: 6fb94faeba5d031846f62837a3cca23e environments/integrations/slack/please_select_a_channel: 7d2e5b9e8d5cd804f1e4d6780d3dff0f @@ -763,13 +798,13 @@ checksums: environments/integrations/webhooks/webhook_deleted_successfully: fcefd247ec76a372002d2cffac3c5b0f environments/integrations/webhooks/webhook_name_placeholder: ffa3274cf83d8dc05c882fbf61c48f8f environments/integrations/webhooks/webhook_test_failed_due_to: 01c4414e740d927b4082a089bf16d9aa - environments/integrations/webhooks/webhook_updated_successfully: 207591a466b63f57825c4bf28374ab99 + environments/integrations/webhooks/webhook_updated_successfully: d67dc42810694fd1344a9a9545aca1e7 environments/integrations/webhooks/webhook_url_placeholder: 5f2f7c074f6797e1506e21ce865219c1 environments/integrations/website_or_app_integration_description: d49af190ebc89de0d281bd1434bd4008 environments/integrations/zapier_integration_description: b8304da62a2490eced5ae50038d34e39 environments/segments/add_filter_below: be9b9c51d4d61903e782fb37931d8905 environments/segments/add_your_first_filter_to_get_started: 365f9fc1600e2e44e2502e9ad9fde46a - environments/segments/cannot_delete_segment_used_in_surveys: 134200217852566d6743245006737093 + environments/segments/cannot_delete_segment_used_in_surveys: c103d594711c8a08970dc01787bcc93e environments/segments/clone_and_edit_segment: dce49d8027b5efda6daebaf86533cf7f environments/segments/create_group: 4566e056e5217dc02a383105892fe18c environments/segments/create_your_first_segment: f5f6f2ebe4ab09cdddb8704178f14736 @@ -792,6 +827,40 @@ checksums: environments/segments/no_attributes_yet: 57beecc917dcd598ccdd0ccfb364a960 environments/segments/no_filters_yet: d885a68516840e15dd27f1c17d9a8975 environments/segments/no_segments_yet: 6307a4163a5bd553bb2aba074d24be9c + environments/segments/operator_contains: 06dd606c0a8f81f9a03b414e9ae89440 + environments/segments/operator_does_not_contain: 854da2bdf10613ce62fb454bab16c58b + environments/segments/operator_ends_with: 2bd866369766c6a2ef74bb9fa74b1d7e + environments/segments/operator_is_after: f9d9296eb9a5a7d168cc4e65a4095a87 + environments/segments/operator_is_before: 2462480cf4e8d2832b64004fbd463e55 + environments/segments/operator_is_between: 41ff45044d8a017a8a74f72be57916b8 + environments/segments/operator_is_newer_than: c41e03366623caed6b2c224e50387614 + environments/segments/operator_is_not_set: 906801489132487ef457652af4835142 + environments/segments/operator_is_older_than: acca6b309da507bbc5973c4b56b698b0 + environments/segments/operator_is_same_day: c06506b6eb9f6491f15685baccd68897 + environments/segments/operator_is_set: 9850468156356f95884bbaf56b6687aa + environments/segments/operator_starts_with: 37e55e9080c84a1855956161e7885c21 + environments/segments/operator_title_contains: 41c8c25407527a5336404313f4c8d650 + environments/segments/operator_title_does_not_contain: d618eb0f854f7efa0d7c644e6628fa42 + environments/segments/operator_title_ends_with: c8a5f60f1bd1d8fa018dbbf49806fb5b + environments/segments/operator_title_equals: 73439e2839b8049e68079b1b6f2e3c41 + environments/segments/operator_title_greater_equal: 556b342cee0ac7055171e41be80f49e4 + environments/segments/operator_title_greater_than: e06dabbbf3a9c527502c997101edab40 + environments/segments/operator_title_is_after: bd4cf644e442fca330cb483528485e5f + environments/segments/operator_title_is_before: a47ce3825c5c7cea7ed7eb8d5505a2d5 + environments/segments/operator_title_is_between: 5721c877c60f0005dc4ce78d4c0d3fdc + environments/segments/operator_title_is_newer_than: 133731671413c702a55cdfb9134d63f8 + environments/segments/operator_title_is_not_set: c1a6fd89387686d3a5426a768bb286e9 + environments/segments/operator_title_is_older_than: 9064cd482f2312c8b10aee4937d0278d + environments/segments/operator_title_is_same_day: 9340bf7bd6ab504d71b0e957ca9fcf4c + environments/segments/operator_title_is_set: 1c66019bd162201db83aef305ab2a161 + environments/segments/operator_title_less_equal: 235dbef60cd0af5ff1d319aab24a1109 + environments/segments/operator_title_less_than: e9f3c9742143760b28bf4e326f63a97b + environments/segments/operator_title_not_equals: a186482f46739c9fe8683826a1cab723 + environments/segments/operator_title_starts_with: f6673c17475708313c6a0f245b561781 + environments/segments/operator_title_user_is_in: 33ecd1bc30f56d97133368f1b244ee4b + environments/segments/operator_title_user_is_not_in: 99d576a3611d171947fd88c317aaf5f3 + environments/segments/operator_user_is_in: 33ecd1bc30f56d97133368f1b244ee4b + environments/segments/operator_user_is_not_in: 99d576a3611d171947fd88c317aaf5f3 environments/segments/person_and_attributes: 507023d577326a6326dd9603dcdc589d environments/segments/phone: b9537ee90fc5b0116942e0af29d926cc environments/segments/please_remove_the_segment_from_these_surveys_in_order_to_delete_it: 1858a8ae40bed3a8c06c3bb518e0b8aa @@ -800,11 +869,11 @@ checksums: environments/segments/reset_all_filters: 0de0d7962a1179fac358cb1cf9546df3 environments/segments/save_as_new_segment: d9d42bdeabf936d4ec97325dbdca0012 environments/segments/save_your_filters_as_a_segment_to_use_it_in_other_surveys: cc40e9b2a5ab59684f075bcb2f55f18d - environments/segments/segment_created_successfully: 1fc43c11c7cfb37a4dffaaa0e2152ab4 - environments/segments/segment_deleted_successfully: 46277ae2efb24b5f5cbb13307bca7b0d + environments/segments/segment_created_successfully: 7dfaf11a929000b387c0d5f6dc18e81a + environments/segments/segment_deleted_successfully: 931c41216210ffaa5c08c874a0751da3 environments/segments/segment_id: 875b44f7289197810baf2e1c03de5c77 environments/segments/segment_saved_successfully: ce116c8dc576cac49753082eb2ecb413 - environments/segments/segment_updated_successfully: 925d7937004d9f00afb8a856471d364f + environments/segments/segment_updated_successfully: 2eca3ff5c645bdccd16c306d663114a1 environments/segments/segments_help_you_target_users_with_same_characteristics_easily: b18b75bda13fe408a467dd625611ea06 environments/segments/target_audience: ca47151c4f0ddb348e52ec43ce15eb03 environments/segments/this_action_resets_all_filters_in_this_survey: a7b342c25f0d3d6060bfdff38ade0682 @@ -816,6 +885,7 @@ checksums: environments/segments/user_targeting_is_currently_only_available_when: 9785f159fb045607b62461f38e8d3aee environments/segments/value_cannot_be_empty: 99efd449ec19f1ecc5cf0b6807d4f315 environments/segments/value_must_be_a_number: 87516b5c69e08741fa8a6ddf64d60deb + environments/segments/value_must_be_positive: d17ad009f7845a6fbeddeb2aef532e10 environments/segments/view_filters: 791cd4bacb11e3eb0ffccee131270561 environments/segments/where: 23aecda7d27f26121b057ec7f7327069 environments/segments/with_the_formbricks_sdk: 2b185e6242edb69e1bc6e64e10dfc02a @@ -877,7 +947,7 @@ checksums: environments/settings/domain/customize_favicon_with_higher_plan: 43a6b834a8fd013c52923863d62248f3 environments/settings/domain/description: f0b4d8c96da816f793cf1f4fdfaade34 environments/settings/domain/favicon_customization: 5c975be07a2a8e319c2c58c7d8c0c78f - environments/settings/domain/favicon_customization_description: 26c3310c2d85a3daefa3c19c01353efa + environments/settings/domain/favicon_customization_description: ad8845239d2bcc4734acdac2f28fa589 environments/settings/domain/favicon_removed_successfully: cebbf050b77dd7218b622d3a4cc4aa18 environments/settings/domain/favicon_saved_successfully: a6fbbe2f87b241bc5f661896299fc2b6 environments/settings/domain/favicon_size_hint: b3a35625898353380152de0be17dd5f7 @@ -893,11 +963,25 @@ checksums: environments/settings/enterprise/enterprise_features: 3271476140733924b2a2477c4fdf3d12 environments/settings/enterprise/get_an_enterprise_license_to_get_access_to_all_features: afd3c00f19097e88ed051800979eea44 environments/settings/enterprise/keep_full_control_over_your_data_privacy_and_security: 43aa041cc3e2b2fdd35d2d34659a6b7a + environments/settings/enterprise/license_invalid_description: b500c22ab17893fdf9532d2bd94aa526 + environments/settings/enterprise/license_status: f6f85c59074ca2455321bd5288d94be8 + environments/settings/enterprise/license_status_active: 3e1ec025c4a50830bbb9ad57a176630a + environments/settings/enterprise/license_status_description: 828e4527f606471cd8cf58b55ff824f6 + environments/settings/enterprise/license_status_expired: 63b27cccba4ab2143e0f5f3d46e4168a + environments/settings/enterprise/license_status_invalid: a4bfd3787fc0bf0a38db61745bd25cec + environments/settings/enterprise/license_status_unreachable: 202b110dab099f1167b13c326349e570 + environments/settings/enterprise/license_unreachable_grace_period: c0587c9d79ac55ff2035fb8b8eec4433 environments/settings/enterprise/no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form: daef55124d4363526008eb91a0b68246 environments/settings/enterprise/no_credit_card_no_sales_call_just_test_it: 18f9859cdf12537b7019ecdb0a0a2b53 environments/settings/enterprise/on_request: cf9949748c15313a8fd57bf965bec16b environments/settings/enterprise/organization_roles: 731d5028521c2a3a7bdbd7ed215dd861 environments/settings/enterprise/questions_please_reach_out_to: ac4be65ffef9349eaeb137c254d3fee7 + environments/settings/enterprise/recheck_license: b913b64f89df184b5059710f4a0b26fa + environments/settings/enterprise/recheck_license_failed: dd410acbb8887625cf194189f832dd7c + environments/settings/enterprise/recheck_license_invalid: 58f41bc208692b7d53b975dfcf9f4ad8 + environments/settings/enterprise/recheck_license_success: 700ddd805be904a415f614de3df1da78 + environments/settings/enterprise/recheck_license_unreachable: 0ca81bd89595a9da24bc94dcef132175 + environments/settings/enterprise/rechecking: 54c454aa8e4d27363543349b7c2a28bc environments/settings/enterprise/request_30_day_trial_license: 8d5a1b5d9f0790783693122ac30c16ef environments/settings/enterprise/saml_sso: 86b76024524fc585b2c3950126ef6f62 environments/settings/enterprise/service_level_agreement: e31e74f66f5c7c79e82878f4f68abc37 @@ -905,8 +989,7 @@ checksums: environments/settings/enterprise/sso: 95e98e279bb89233d63549b202bd9112 environments/settings/enterprise/teams: 21ab78abcba0f16c3029741563f789ea environments/settings/enterprise/unlock_the_full_power_of_formbricks_free_for_30_days: 104d07b63a42911c9673ceb08a4dbd43 - environments/settings/enterprise/your_enterprise_license_is_active_all_features_unlocked: f03f3c7a81f61eb5cd78fa7ad32896f8 - environments/settings/general/bulk_invite_warning_description: e0bcd1ecc8d034a50722b767a9dfbf1d + environments/settings/general/bulk_invite_warning_description: e8737a2fbd5ff353db5580d17b4b5a37 environments/settings/general/cannot_delete_only_organization: 833cc6848b28f2694a4552b4de91a6ba environments/settings/general/cannot_leave_only_organization: dd8463262e4299fef7ad73512225c55b environments/settings/general/copy_invite_link_to_clipboard: 7af7ad935428a3f9da6b96e2cd211af9 @@ -918,7 +1001,7 @@ checksums: environments/settings/general/delete_organization_description: 578e51577840707a7845c9967d5d4010 environments/settings/general/delete_organization_warning: fcc75b900080d9700ad72ed985072711 environments/settings/general/delete_organization_warning_1: ffa4e85d53abffc214c1dff559c0f605 - environments/settings/general/delete_organization_warning_2: 4983371688c70c711b9b04d1e92b9ea8 + environments/settings/general/delete_organization_warning_2: dbcbdcd5ba92a61598241a419da7a468 environments/settings/general/delete_organization_warning_3: 2ddf440095276a33077c0dad5e0d8382 environments/settings/general/eliminate_branding_with_whitelabel: 2c7c61df65af823fee73f7ae79817b0c environments/settings/general/email_customization_preview_email_heading: 8b798cb8438b3dd356c02dab33b4c897 @@ -927,7 +1010,7 @@ checksums: environments/settings/general/from_your_organization: 4b7970431edb3d0f13c394dbd755a055 environments/settings/general/invitation_sent_once_more: e6e5ea066810f9dcb65788aa4f05d6e2 environments/settings/general/invite_deleted_successfully: 1c7dca6d0f6870d945288e38cfd2f943 - environments/settings/general/invited_on: 83476ce4bcdfc3ccf524d1cd91b758a8 + environments/settings/general/invite_expires_on: 6fd2356ad91a5f189070c43855904bb4 environments/settings/general/invites_failed: 180ffb8db417050227cc2b2ea74b7aae environments/settings/general/leave_organization: e74132cb4a0dc98c41e61ea3b2dd268b environments/settings/general/leave_organization_description: 2d0cd65e4e78a9b2835cf88c4de407fb @@ -940,10 +1023,10 @@ checksums: environments/settings/general/manage_members_description: 9080af8ae6ce0f3c9866524f3a69f4fa environments/settings/general/member_deleted_successfully: 41446978af02c27066852a06902843b6 environments/settings/general/member_invited_successfully: dc9c4838d2e8cec8f89c60aeaecafc33 - environments/settings/general/once_its_gone_its_gone: 011c1be7e218d4841cb986313ef0f474 + environments/settings/general/once_its_gone_its_gone: 6c844e583dd2c0e07337cc758df385b3 environments/settings/general/only_org_owner_can_perform_action: 0244ff3c6de787935e592eac4c5e4f0b - environments/settings/general/organization_created_successfully: a4bc4f86eea1c4d87fa87cbf984c4678 - environments/settings/general/organization_deleted_successfully: f2f82a16f86766c155343825372f726b + environments/settings/general/organization_created_successfully: 1ce874980bdd7d5de8402c276fb97a57 + environments/settings/general/organization_deleted_successfully: e51fd7ee9efda04a373450ea21b242db environments/settings/general/organization_invite_link_ready: e54b37c4ec2e5a9ea9f6bc6e5b512b0b environments/settings/general/organization_name: 73c9b31c9032a22bd84a07881942bb04 environments/settings/general/organization_name_description: ff517b4749a332b94a26110d7c7e771f @@ -972,7 +1055,7 @@ checksums: environments/settings/notifications/set_up_an_alert_to_get_an_email_on_new_responses: 4168ae6f5962244a4894a22b56fbb55f environments/settings/notifications/use_the_integration: 765ec5b4959cb096234e8b9a0d6b01f5 environments/settings/notifications/want_to_loop_in_organization_mates: 147cb4d1ebcc957e5e965914ae9d7d4d - environments/settings/notifications/you_will_not_be_auto_subscribed_to_this_organizations_surveys_anymore: cc9d8a3c5f8e0235b7210aa4b767880e + environments/settings/notifications/you_will_not_be_auto_subscribed_to_this_organizations_surveys_anymore: e158e20fc4fa511ce457d99d0c865c3c environments/settings/notifications/you_will_not_receive_any_more_emails_for_responses_on_this_survey: 7e2a88fe0d0489ebdc8f2621584b1c29 environments/settings/profile/account_deletion_consequences_warning: b87b9eebe0a373eb6debe52db73e5aba environments/settings/profile/backup_code: 41b15061acd32f7f3512d5a27dcc34b9 @@ -1034,15 +1117,13 @@ checksums: environments/settings/teams/please_fill_all_workspace_fields: 190fc5d3c63cc5ec49d77f587e619ed8 environments/settings/teams/read: 2494ca23d10e5b6381eb271aceeb5270 environments/settings/teams/read_write: 278a90dade128198d4c93ac00c345320 - environments/settings/teams/select_member: 7f4a38312aabbbe3fe92756b57bd5d75 - environments/settings/teams/select_workspace: 0ad989c23616c6a04faf23d9e63ed3f3 environments/settings/teams/team_admin: 5df68214685738029af678ae1d5912bb - environments/settings/teams/team_created_successfully: 45f83048fcabf466551144858a761eca - environments/settings/teams/team_deleted_successfully: 972c86b0abe87f229f7bf1a691c0a253 + environments/settings/teams/team_created_successfully: 5b0cc007e18053508fdebc9545cc2c05 + environments/settings/teams/team_deleted_successfully: d0729ad8d982cc5d542f89291bf57c50 environments/settings/teams/team_deletion_not_allowed: 08e49405709ed851cfd7e930c560c524 environments/settings/teams/team_name: d1a5f99dbf503ca53f06b3a98b511d02 environments/settings/teams/team_name_settings_title: af7236cfaadfe794525f9e6e29cb6f0e - environments/settings/teams/team_select_placeholder: bc78d4fd52dc0cbfbaffd2677db63067 + environments/settings/teams/team_select_placeholder: 209b87d4b87db9121d3ff16173d2eee5 environments/settings/teams/team_settings_description: 686f772f08d864d28cde23b900c9facf environments/settings/teams/team_updated_successfully: 5f769a5073aa0fcaad76f8ba73e17e97 environments/settings/teams/teams: b63448c05270497973ac4407047dae02 @@ -1050,15 +1131,15 @@ checksums: environments/settings/teams/unlock_teams_description: 0e5de01e914c0919aff3df797f9f7437 environments/settings/teams/unlock_teams_title: 45b2a27d29d706418fd62540d34a19f3 environments/settings/teams/upgrade_plan_notice_message: 305d6f94381482a8df6e72b74b097e99 - environments/settings/teams/you_are_a_member: 0d13738accc3f85fa99add823a24c102 - environments/surveys/all_set_time_to_create_first_survey: 26ebd295539a7dd8ba25faca8983d754 + 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: e2bf42158c049e8c80ffa4723e04f87c + 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 @@ -1081,11 +1162,10 @@ checksums: environments/surveys/edit/add_fallback_placeholder: 0e77ea487ddd7bc7fc2f1574b018dc08 environments/surveys/edit/add_hidden_field_id: a8f55b51b790cf5f4d898af7770ad1ed environments/surveys/edit/add_highlight_border: 66f52b21fbb9aa6561c98a090abaaf8f - environments/surveys/edit/add_highlight_border_description: 1c04654a393c0fa31d2b58abb6f85b4b environments/surveys/edit/add_logic: f234c9f1393a9ed4792dfbd15838c951 - environments/surveys/edit/add_none_of_the_above: 8e8c3f404204f6ddac2f52e682153202 + environments/surveys/edit/add_none_of_the_above: dbe1ada4512d6c3f80c54c8fac107ec6 environments/surveys/edit/add_option: 143c54f0b201067fe5159284d6daeca2 - environments/surveys/edit/add_other: de75bd3d40f3b5effdbe1c8d536f936b + environments/surveys/edit/add_other: e5f63e9d4c2f3fc57d9a3874d4d73ea4 environments/surveys/edit/add_photo_or_video: 7fd213e807ad060e415d1d4195397473 environments/surveys/edit/add_pin: 1bc282dd7eaea51301655d3e8dd3a9fb environments/surveys/edit/add_question_below: 58e64eb2e013f1175ea0dcf79149109f @@ -1095,7 +1175,7 @@ checksums: environments/surveys/edit/address_fields: 9cabb97c3deaff4f6cb3afc3d5cfaf0a environments/surveys/edit/address_line_1: 44788358e7a7c25b0b79bc3090ed15f5 environments/surveys/edit/address_line_2: fc4b5a87de46ac4a28a6616f47a34135 - environments/surveys/edit/adjust_survey_closed_message: 1a40fc2fa7cd2f8f88448dda46cb0f6e + environments/surveys/edit/adjust_survey_closed_message: ae6f38c9daf08656362bd84459a312fa environments/surveys/edit/adjust_survey_closed_message_description: e906aebd9af6451a2a39c73287927299 environments/surveys/edit/adjust_the_theme_in_the: bccdafda8af5871513266f668b55d690 environments/surveys/edit/all_other_answers_will_continue_to: 9a5d09eea42ff5fd1c18cc58a14dcabd @@ -1115,11 +1195,12 @@ checksums: environments/surveys/edit/automatically_close_the_survey_after_a_certain_number_of_responses: 2beee129dca506f041e5d1e6a1688310 environments/surveys/edit/automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds: 1be3819ffa1db67385357ae933d69a7b environments/surveys/edit/automatically_mark_the_survey_as_complete_after: c6ede2a5515a4ca72b36aec2583f43aa - environments/surveys/edit/back_button_label: 25af945e77336724b5276de291cc92d9 + environments/surveys/edit/back_button_label: 504551d78645d968fcee95e3dfa5586f environments/surveys/edit/background_styling: eb4a06cf54a7271b493fab625d930570 environments/surveys/edit/block_duplicated: dc9e9fab2b1cd91f6c265324b34c6376 environments/surveys/edit/bold: 4d7306bc355ed2befd6a9237c5452ee6 environments/surveys/edit/brand_color: 84ddb5736deb9f5c081ffe4962a6c63e + environments/surveys/edit/brand_color_description: 1cd10092621d375a37e297cc6353bce7 environments/surveys/edit/brightness: 45425b6db1872225bfff71cf619d0e64 environments/surveys/edit/bulk_edit: 59bd1a55587c8cbad716afbf2509e5bb environments/surveys/edit/bulk_edit_description: 9b5b2c6183c6c51689e16d7ba02ec9bb @@ -1137,31 +1218,25 @@ checksums: environments/surveys/edit/capture_new_action: 0aa2a3c399b62b1a52307deedf4922e8 environments/surveys/edit/card_arrangement_for_survey_type_derived: c06b9aaebcc11bc16e57a445b62361fc environments/surveys/edit/card_background_color: acd5d023e1d1a4471b053dce504c7a83 + environments/surveys/edit/card_background_color_description: c96baa7fab5f2dfc41ff2e6a4e0242b0 environments/surveys/edit/card_border_color: 8d7c7f4cbd99f154ce892dfa258eb504 + environments/surveys/edit/card_border_color_description: 57828ef76f8d055c530c1e0b0c0ddc09 environments/surveys/edit/card_styling: 47137a7e809b060ca94418202a8fd3c5 environments/surveys/edit/casual: 6534fe68718fade470a9031f7390409e environments/surveys/edit/caution_edit_duplicate: ee93bccb34fcd707e1ef4735f1c2fc31 environments/surveys/edit/caution_edit_published_survey: faf7fc57c776f2a9104d143e20044486 - environments/surveys/edit/caution_explanation_intro: 93a584724998e2891d4b762b7d55cc4c + environments/surveys/edit/caution_explanation_intro: d6bd8cfdca654353cdced650c1416834 environments/surveys/edit/caution_explanation_new_responses_separated: 387286482b7ecd725ce20521dacbc485 environments/surveys/edit/caution_explanation_only_new_responses_in_summary: 1ad5f649c7784a58bfa9d62304780514 environments/surveys/edit/caution_explanation_responses_are_safe: 090ff00b7922a49c273e67c5f364730d environments/surveys/edit/caution_recommendation: b15090fe878ff17f2ee7cc2082dd9018 environments/surveys/edit/caution_text: 3291e962c0e4c4656832837ddc512918 - environments/surveys/edit/centered_modal_overlay_color: 1124ba61ee2ecb18a7175ff780dc3b60 environments/surveys/edit/change_anyway: 6377497d40373f6d0f082670194981ab environments/surveys/edit/change_background: fa71a993869f7d3ac553c547c12c3e9b environments/surveys/edit/change_question_type: 2d555ae48df8dbedfc6a4e1ad492f4aa environments/surveys/edit/change_survey_type: c26322043a476da6d94adb8b4efe1e93 - environments/surveys/edit/change_the_background_color_of_the_card: 41d805ef753a7d1e272b48519967bbd4 - environments/surveys/edit/change_the_background_color_of_the_input_fields: 4edbc9a9f5d145ed096cf5b4f8bdaac0 environments/surveys/edit/change_the_background_to_a_color_image_or_animation: f1b9c9eb61497dd91b2550dd50c77836 - environments/surveys/edit/change_the_border_color_of_the_card: 64d76b247ab192343bb327f92a5f220c - environments/surveys/edit/change_the_border_color_of_the_input_fields: bb687f41af15a1dd9494c14f97b10425 - environments/surveys/edit/change_the_border_radius_of_the_card_and_the_inputs: 9eccf688a7a67dfeeeed3de5209058b0 - environments/surveys/edit/change_the_brand_color_of_the_survey: ecc420c641fb58daaf4d2d0086357b7f environments/surveys/edit/change_the_placement_of_this_survey: 64359611bfb23bacc614ffe0b08fbe5d - environments/surveys/edit/change_the_question_color_of_the_survey: ab6942138a8c5fc6c8c3b9f8dd95e980 environments/surveys/edit/changes_saved: 90aab363c9e96eaa1295a997c48f97f6 environments/surveys/edit/changing_survey_type_will_remove_existing_distribution_channels: 9ce817be04f13f2f0db981145ec48df4 environments/surveys/edit/checkbox_label: 12a07d6bdf38e283a2e95892ec49b7f8 @@ -1189,7 +1264,7 @@ checksums: environments/surveys/edit/create_group: 4566e056e5217dc02a383105892fe18c environments/surveys/edit/create_your_own_survey: e3ddd53e0cfa409ca8dccfb3d77933e7 environments/surveys/edit/css_selector: 615e9f1b74622df29de28a5b5614c6fe - environments/surveys/edit/cta_button_label: ec070ffba38eae24751bb3a4c1e14c81 + environments/surveys/edit/cta_button_label: add44a57977bc173e235315616827817 environments/surveys/edit/custom_hostname: bc2b1c8de3f9b8ef145b45aeba6ab429 environments/surveys/edit/customize_survey_logo: 7f7e26026c88a727228f2d7a00d914e2 environments/surveys/edit/darken_or_lighten_background_of_your_choice: 304a64a8050ebf501d195e948cd25b6f @@ -1201,6 +1276,7 @@ checksums: environments/surveys/edit/disable_the_visibility_of_survey_progress: 2af631010114307ac2a91612559c9618 environments/surveys/edit/display_an_estimate_of_completion_time_for_survey: 03f0a816569399c1c61d08dbc913de06 environments/surveys/edit/display_number_of_responses_for_survey: 06294567ecba9aba2cce337c669577f6 + environments/surveys/edit/display_type: 68c2deaca48289119f1a988ede39dbad environments/surveys/edit/divide: ca443836e15d0a1cbde5f03cc8edba78 environments/surveys/edit/does_not_contain: d618eb0f854f7efa0d7c644e6628fa42 environments/surveys/edit/does_not_end_with: 885c4c1981b97a4bfa213e185b78b6c4 @@ -1208,6 +1284,7 @@ checksums: environments/surveys/edit/does_not_include_all_of: c18c1a71e6d96c681a3e95c7bd6c9482 environments/surveys/edit/does_not_include_one_of: 91090d2e0667faf654f6a81d9857440f environments/surveys/edit/does_not_start_with: 9395869b54cdfb353a51a7e0864f4fd7 + environments/surveys/edit/dropdown: b4069d14e572b53ca4156d25b0a970c1 environments/surveys/edit/duplicate_block: d4ea4afb5fc5b18a81cbe0302fa05997 environments/surveys/edit/duplicate_question: 910751de01fdd327165968214717711b environments/surveys/edit/edit_link: 40ba9e15beac77a46c5baf30be84ac54 @@ -1220,7 +1297,7 @@ checksums: environments/surveys/edit/end_screen_card: 6146c2bcb87291e25ecb03abd2d9a479 environments/surveys/edit/ending_card: 16d30d3a36472159da8c2dbd374dfe22 environments/surveys/edit/ending_card_used_in_logic: 4f78d75c3ac79821ca5478ecf88f3fe2 - environments/surveys/edit/ending_used_in_quota: 266841b8621aaa20330ac6ffa5798875 + environments/surveys/edit/ending_used_in_quota: e09f0da09730bd16132bc13d4cc17222 environments/surveys/edit/ends_with: c8a5f60f1bd1d8fa018dbbf49806fb5b environments/surveys/edit/enter_fallback_value: 810911dc4a25ca47fc48eed1fde41cb5 environments/surveys/edit/equals: 73439e2839b8049e68079b1b6f2e3c41 @@ -1232,7 +1309,7 @@ checksums: environments/surveys/edit/external_urls_paywall_tooltip: a8860ff0a2ad5f283bc0becba374cd54 environments/surveys/edit/fallback_missing: 43dbedbe1a178d455e5f80783a7b6722 environments/surveys/edit/fieldId_is_used_in_logic_of_question_please_remove_it_from_logic_first: ad4afe2980e1dfeffb20aa78eb892350 - environments/surveys/edit/fieldId_is_used_in_quota_please_remove_it_from_quota_first: 374c563964fc805ab0b8974e781687d9 + environments/surveys/edit/fieldId_is_used_in_quota_please_remove_it_from_quota_first: 70d0e1bdd6336b2cfb92c0121416c8d4 environments/surveys/edit/field_name_eg_score_price: 8984fbe243eb2184639749281193b7d6 environments/surveys/edit/first_name: cf040a5d6a9fd696be400380cc99f54b environments/surveys/edit/five_points_recommended: e2c8118163da89284046638ca9832914 @@ -1270,7 +1347,7 @@ checksums: environments/surveys/edit/follow_ups_modal_create_heading: 16acb3f434ab7ea30a85096453dd68bf environments/surveys/edit/follow_ups_modal_created_successfull_toast: dc7b0201ba65b5d5d8b0c5bbc1335042 environments/surveys/edit/follow_ups_modal_edit_heading: 3384eb55d6ac6d71c4145c35c7490029 - environments/surveys/edit/follow_ups_modal_edit_no_id: 655fcd2e0e7c9e3265e41b8c51aa4556 + environments/surveys/edit/follow_ups_modal_edit_no_id: 8a77ce8526325ef83547465f08b146d7 environments/surveys/edit/follow_ups_modal_name_label: a9e35c94fb5e31f5bcc0e43f748e8a38 environments/surveys/edit/follow_ups_modal_name_placeholder: da1ffedf62f2a9d63e23bc5e467d395a environments/surveys/edit/follow_ups_modal_subheading: 227e0768889d5ac4c9fd19ac6ed0200f @@ -1288,10 +1365,10 @@ checksums: environments/surveys/edit/four_points: b289628a6b8a6cd0f7d17a14ca6cd7bf environments/surveys/edit/heading: 79e9dfa461f38a239d34b9833ca103f1 environments/surveys/edit/hidden_field_added_successfully: 76c289ee9b9b0a271a5cbb278aec8c60 - environments/surveys/edit/hidden_field_used_in_recall: 70dee46bae18209e8861b654ff9a04ae - environments/surveys/edit/hidden_field_used_in_recall_ending_card: a985d03d18e33d83521961c9c981d0ee - environments/surveys/edit/hidden_field_used_in_recall_welcome: 22fef7001d5e60edbf877e7b435c1991 - environments/surveys/edit/hide_back_button: 9f355fb4a8e80485b9de521a952ffeb9 + environments/surveys/edit/hidden_field_used_in_recall: 15d959528c3e817dce95640173d5d6a8 + environments/surveys/edit/hidden_field_used_in_recall_ending_card: ea0d0b12ca1c9400690658cb1b537025 + environments/surveys/edit/hidden_field_used_in_recall_welcome: bb498b6ee69c6311a3977d454866b610 + environments/surveys/edit/hide_back_button: 91355864b3032c3f57689074e2173544 environments/surveys/edit/hide_back_button_description: caaa30cf43c5611577933a1c9f44b9ee environments/surveys/edit/hide_block_settings: c24c3d3892c251792e297cdc036d2fde environments/surveys/edit/hide_logo: eef4de2e3fffe8cbe32bff4f6f7250d8 @@ -1299,7 +1376,6 @@ checksums: environments/surveys/edit/hide_progress_bar: 7eefe7db6a051105bded521d94204933 environments/surveys/edit/hide_question_settings: 99127cd016db2f7fc80333b36473c0ef environments/surveys/edit/hostname: 9bdaa7692869999df51bb60d58d9ef62 - environments/surveys/edit/how_funky_do_you_want_your_cards_in_survey_type_derived_surveys: 3cb16b37510c01af20a80f51b598346e environments/surveys/edit/if_you_need_more_please: a7d208c283caf6b93800b809fca80768 environments/surveys/edit/if_you_really_want_that_answer_ask_until_you_get_it: 31c18a8c7c578db2ba49eed663d1739f environments/surveys/edit/ignore_global_waiting_time: e08db543ace4935625e0961cc6e60489 @@ -1310,7 +1386,9 @@ checksums: environments/surveys/edit/initial_value: 809ee46fd787f4dc0146b3a80af5c2de environments/surveys/edit/inner_text: d1c7c98cfdb2fae3be91b7ee44288847 environments/surveys/edit/input_border_color: d8a68d6b573189c291db6d83496210f6 + environments/surveys/edit/input_border_color_description: d338a4a6556db30ae7d5f8c7027bdcd4 environments/surveys/edit/input_color: 55a0a092d16a1a6899c07b1b00d08604 + environments/surveys/edit/input_color_description: fa9f72ea65480c6b6e9e14b89109af03 environments/surveys/edit/insert_link: c42ce4cb6ed35d5bd1389523585cc57e environments/surveys/edit/invalid_targeting: db9d1143c82a085c5dddf09492ea753c environments/surveys/edit/invalid_video_url_warning: 2e6a8eb121b46d7c3cc79d541b6a3cd5 @@ -1340,6 +1418,7 @@ checksums: environments/surveys/edit/limit_the_maximum_file_size: 6ae5944fe490b9acdaaee92b30381ec0 environments/surveys/edit/limit_upload_file_size_to: 949c48d25ae45259cc19464e95752d29 environments/surveys/edit/link_survey_description: f45569b5e6b78be6bc02bc6a46da948b + environments/surveys/edit/list: 94f13e7ef909a4de9db7abaa1f9f0b61 environments/surveys/edit/load_segment: 5341d3de37ff10f7526152e38e25e3c5 environments/surveys/edit/logic_error_warning: 542fbb918ffdb29e6f9a4a6196ffb558 environments/surveys/edit/logic_error_warning_text: f2afad8852a95ed169a39959efbf592c @@ -1356,9 +1435,9 @@ checksums: environments/surveys/edit/multiply: 89a0bb629167f97750ae1645a46ced0d environments/surveys/edit/needed_for_self_hosted_cal_com_instance: d241e72f0332177d32ce6c35070757dc environments/surveys/edit/next_block: 53eaa5b1c9333455ab1e99bedd222ba2 - environments/surveys/edit/next_button_label: e23522dd38f3eabeeccd3f48f32b73a8 + 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: 90f10f4611ed7b115a49595409b66ebe + 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_option_found: a1a3aa7e6c13b6bb8df20a1a104c7c04 environments/surveys/edit/no_recall_items_found: 729e2b02e412cdc79f5ad94b1918620c @@ -1393,13 +1472,12 @@ checksums: environments/surveys/edit/protect_survey_with_pin_description: 0e55d19b6f3578b1024e03606172a5d2 environments/surveys/edit/publish: 4aa95ba4793bb293e771bd73b4f87c0f environments/surveys/edit/question: 0576462ce60d4263d7c482463fcc9547 - environments/surveys/edit/question_color: 6e69cb5699368bc68b2e1e1501f555c9 environments/surveys/edit/question_deleted: ecdeb22b81ae2d732656a7742c1eec7b environments/surveys/edit/question_duplicated: 3f02439fd0a8b818bc84c1b1b473898c environments/surveys/edit/question_id_updated: e8d94dbefcbad00c7464b3d1fb0ee81a environments/surveys/edit/question_used_in_logic_warning_text: ec78767a7cf335222d41b98cb5baa6be environments/surveys/edit/question_used_in_logic_warning_title: 4bb8528cdc3b8649c194487067737f6d - environments/surveys/edit/question_used_in_quota: 311b93fcecd68a65fdefbea13bec7350 + environments/surveys/edit/question_used_in_quota: ceb5e88f6916e4863e589c6be030bb3b environments/surveys/edit/question_used_in_recall: 00d74a1ede4e75e32d50fe87b85d5a8b environments/surveys/edit/question_used_in_recall_ending_card: ab5b0dc296cecd160a6406cbfab42695 environments/surveys/edit/quotas/add_quota: 3676835c7f6c9ab4609b482c3b2cddd6 @@ -1435,7 +1513,7 @@ checksums: environments/surveys/edit/randomize_all_except_last: af7e113535a14701d782aea698ebaee3 environments/surveys/edit/range: 1fad969ecf3de1c21df046b93053c422 environments/surveys/edit/recall_data: 39beabd626c0af15316885cff5d5d9b8 - environments/surveys/edit/recall_information_from: 884cfd143456fab1a91f0744cc92f0c8 + environments/surveys/edit/recall_information_from: d722d44663660e1a14c7d393ce047252 environments/surveys/edit/recontact_options_section: 57a23e1bcab6baa484b27b615e6c906a environments/surveys/edit/recontact_options_section_description: 58cfdc80c9c4ec69214299d3cd5ee1e9 environments/surveys/edit/redirect_thank_you_card: 09f721c4b62e2584e40a53507092ea83 @@ -1447,12 +1525,13 @@ checksums: environments/surveys/edit/reset_to_theme_styles: f9edc3970ec23d6c4d2d7accc292ef3a environments/surveys/edit/reset_to_theme_styles_main_text: d86fb2213d3b2efbd0361526dc6cb27b environments/surveys/edit/respect_global_waiting_time: 971a6f60986862995f7cc1a5611ed892 - environments/surveys/edit/respect_global_waiting_time_description: bf96a546839d4da7d79442f2be3a1f42 - environments/surveys/edit/response_limit_can_t_be_set_to_0: 278664873ee3b1046dbcb58848efc12a + environments/surveys/edit/respect_global_waiting_time_description: b39daa9c8035e18fcfc3f8c93f2f84f6 + environments/surveys/edit/response_limit_can_t_be_set_to_0: 4588a7ec31020300d3613a79763dc725 environments/surveys/edit/response_limit_needs_to_exceed_number_of_received_responses: 9a9c223c0918ded716ddfaa84fbaa8d9 environments/surveys/edit/response_limits_redirections_and_more: e4f1cf94e56ad0e1b08701158d688802 environments/surveys/edit/response_options: 2988136d5248d7726583108992dcbaee environments/surveys/edit/roundness: 5a161c8f5f258defb57ed1d551737cc4 + environments/surveys/edit/roundness_description: bde131aa5674836416dcdf2ff517d899 environments/surveys/edit/row_used_in_logic_error: f89453ff1b6db77ad84af840fedd9813 environments/surveys/edit/rows: 8f41f34e6ca28221cf1ebd948af4c151 environments/surveys/edit/save_and_close: 6ede705b3f82f30269ff3054a5049e34 @@ -1465,10 +1544,10 @@ checksums: environments/surveys/edit/select_ordering: c8f632a17fe78d8b7f87e82df9351ff9 environments/surveys/edit/select_saved_action: de31ab9cbb2bb67a050df717de7cdde4 environments/surveys/edit/select_type: fa373e47f55ff081982844a853be3a88 - environments/surveys/edit/send_survey_to_audience_who_match: 576cdf933a068c52b970565746f3eb52 + environments/surveys/edit/send_survey_to_audience_who_match: 8bc5660659f6e28cc19b1961897e9878 environments/surveys/edit/send_your_respondents_to_a_page_of_your_choice: 9b77b1afe9a81bdbbb55ae323c5a3175 environments/surveys/edit/set_the_global_placement_in_the_look_feel_settings: e34e579e778a918733702edb041ac929 - environments/surveys/edit/settings_saved_successfully: eb109269bc59dd67ae09fd9eb53652d2 + environments/surveys/edit/settings_saved_successfully: 7f6833d9079e404fb3a5b0aa51fdcf17 environments/surveys/edit/seven_points: 4ead50fdfda45e8710767e1b1a84bf42 environments/surveys/edit/show_block_settings: bad99d99c9908874e45f5c350a88cc79 environments/surveys/edit/show_button: 6b364aac9d7ac71f34a438607c9693bc @@ -1494,7 +1573,6 @@ checksums: environments/surveys/edit/styling_set_to_theme_styles: f2c108bf422372b00cf7c87f1b042f69 environments/surveys/edit/subheading: c0f6f57155692fd8006381518ce4fef0 environments/surveys/edit/subtract: 2d83b8b9ef35110f2583ddc155b6c486 - environments/surveys/edit/suggest_colors: ddc4543b416ab774007b10a3434343cd environments/surveys/edit/survey_completed_heading: dae5ac4a02a886dc9d9fc40927091919 environments/surveys/edit/survey_completed_subheading: db537c356c3ab6564d24de0d11a0fee2 environments/surveys/edit/survey_display_settings: 8ed19e6a8e1376f7a1ba037d82c4ae11 @@ -1505,14 +1583,14 @@ checksums: environments/surveys/edit/targeted: ca615f1fc3b490d5a2187b27fb4a2073 environments/surveys/edit/ten_points: a1317b82003859f77fb3138c55450d63 environments/surveys/edit/the_survey_will_be_shown_multiple_times_until_they_respond: 2d8d7d2351bd7533eb3788cce228c654 - environments/surveys/edit/the_survey_will_be_shown_once_even_if_person_doesnt_respond: 6062aaa5cf8e58e79b75b6b588ae9598 + 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/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/trigger_survey_when_one_of_the_actions_is_fired: 5c51239317ae462d7c739773cb61ad8e - environments/surveys/edit/try_lollipop_or_mountain: 07d662ad628ec9dd00fa5eda73943994 + 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 environments/surveys/edit/underline: aed1340c6de1b829ec96c0d0da99e9ec environments/surveys/edit/unlock_targeting_description: 8e315dc41c2849754839a1460643c5fb @@ -1569,13 +1647,13 @@ checksums: environments/surveys/edit/validation_rules: 0cd99f02684d633196c8b249e857d207 environments/surveys/edit/validation_rules_description: a0a7cee05e18efd462148698e3a93399 environments/surveys/edit/variable_is_used_in_logic_of_question_please_remove_it_from_logic_first: bd9d9c7cf0be671c4e8cf67e2ae6659e - environments/surveys/edit/variable_is_used_in_quota_please_remove_it_from_quota_first: 0d36e5b2713f5450fe346e0af0aaa29c + environments/surveys/edit/variable_is_used_in_quota_please_remove_it_from_quota_first: d6c3cfaa48189ebef5ad9a5594a1f32e environments/surveys/edit/variable_name_conflicts_with_hidden_field: fe2f6a711d5b663790bdd5780ad77bf2 environments/surveys/edit/variable_name_is_already_taken_please_choose_another: 6da42fe8733c6379158bce9a176f76d7 environments/surveys/edit/variable_name_must_start_with_a_letter: f7abbdecf1ba7b822ccabb16981ebcb5 - environments/surveys/edit/variable_used_in_recall: 1c9c354a1233408cc42922eefaa8ce23 + environments/surveys/edit/variable_used_in_recall: 1979c231569117297d1a19972b349617 environments/surveys/edit/variable_used_in_recall_ending_card: e6ab9a124985708dd77067c014b7c514 - environments/surveys/edit/variable_used_in_recall_welcome: 60321b2f40ae01cd10f99ed77bb986ba + environments/surveys/edit/variable_used_in_recall_welcome: 60b995389b488366d8f6f53df35b6d8d environments/surveys/edit/verify_email_before_submission: c05d345dc35f2d33839e4cfd72d11eb2 environments/surveys/edit/verify_email_before_submission_description: 434ab3ee6134367513b633a9d4f7d772 environments/surveys/edit/visibility_and_recontact: c27cb4ff3a4262266902a335c3ad5d84 @@ -1629,7 +1707,7 @@ checksums: environments/surveys/responses/person_attributes: 07ae67ae73d7a2a7c67008694a83f0a3 environments/surveys/responses/phone: b9537ee90fc5b0116942e0af29d926cc environments/surveys/responses/respondent_skipped_questions: d85daf579ade534dc7e639689156fcd5 - environments/surveys/responses/response_deleted_successfully: 6cec5427c271800619fee8c812d7db18 + environments/surveys/responses/response_deleted_successfully: 5dfadb1c0b85de2370f12abe57c888c2 environments/surveys/responses/single_use_id: 48ce6648f5162b6658ecc78d9dd01f17 environments/surveys/responses/source: 6e87903ef260da661b2bf6d858ba68ca environments/surveys/responses/state_region: e3761ce8b4d69f2721d6257d94ca4d18 @@ -1638,7 +1716,7 @@ checksums: environments/surveys/responses/this_response_is_in_progress: 7d785fcb597ea30466467084fd474904 environments/surveys/responses/zip_post_code: ab7dc45bd5f9e37930586e2db17e4304 environments/surveys/search_by_survey_name: 44cf2e6f8ba43d233fb33939431eba99 - environments/surveys/share/anonymous_links/custom_single_use_id_description: 07f0a6bcb5476715ed3de6873bb280f3 + environments/surveys/share/anonymous_links/custom_single_use_id_description: 53c6d2b6cf597115b1f5a280ce7e9cab environments/surveys/share/anonymous_links/custom_single_use_id_title: 9d708fe4ced64ddedd307fb61827cd03 environments/surveys/share/anonymous_links/custom_start_point: 4ea6552b37339d17e02f3bce8c7e4125 environments/surveys/share/anonymous_links/data_prefilling: 82f0e31e90f1f2ca31361df9893e117c @@ -1756,7 +1834,7 @@ checksums: environments/surveys/summary/congrats: 378f06fe96289e527153f8201088ff74 environments/surveys/summary/connect_your_website_or_app_with_formbricks_to_get_started: d24183c86d08b16d58daa8ad887b2837 environments/surveys/summary/current_count: 6a3e59de8559e88e991e0aeafa9cfeec - environments/surveys/summary/custom_range: 237e1a55295982bd94e51ea896b83a9a + environments/surveys/summary/custom_range: 9bc7e02a890644b13b5c0b0bdd96c165 environments/surveys/summary/delete_all_existing_responses_and_displays: e346bcbdb1e0dfbce5925e19fdf0cc78 environments/surveys/summary/download_qr_code: fdc4011f7e3d9b0475eca47f5bdf4bed environments/surveys/summary/downloading_qr_code: 3c46bf636e617848a4fca9b6c5b51dac @@ -1794,7 +1872,7 @@ checksums: environments/surveys/summary/in_app/javascript_sdk: bfa4ecf59e2a5afb2b7dc5d1a09c72c5 environments/surveys/summary/in_app/kotlin_sdk: 33af3c74eb7e105542f3799b873aa5fb environments/surveys/summary/in_app/no_connection_description: 5a000223a7b2e4a55a5617da12ea726a - environments/surveys/summary/in_app/no_connection_title: 9217467742cdcf7edf8d59cc1472ede6 + environments/surveys/summary/in_app/no_connection_title: f19da3cd474b9a3cf28e956fd811fb00 environments/surveys/summary/in_app/react_native_sdk: d7875a274f51ce009da1cb800d99277d environments/surveys/summary/in_app/title: a2d1b633244d0e0504ec6f8f561c7a6b environments/surveys/summary/includes_all: b0e3679282417c62d511c258362f860e @@ -1830,7 +1908,7 @@ checksums: environments/surveys/summary/show_all_responses_that_match: c199f03983d7fcdd5972cc2759558c68 environments/surveys/summary/starts: 3153990a4ade414f501a7e63ab771362 environments/surveys/summary/starts_tooltip: 0a7dd01320490dbbea923053fa1ccad6 - environments/surveys/summary/survey_reset_successfully: bd50acaafccb709527072ac0da6c8bfd + environments/surveys/summary/survey_reset_successfully: f53db36a28980ef4766215cf13f01e51 environments/surveys/summary/this_month: 50845a38865204a97773c44dcd2ebb90 environments/surveys/summary/this_quarter: 9c77d94783dff2269c069389122cd7bd environments/surveys/summary/this_year: 1e69651c2ac722f8ce138f43cf2e02f9 @@ -1838,11 +1916,11 @@ checksums: environments/surveys/summary/ttc_tooltip: 9b1cbe32cc81111314bd3b6fd050c2e7 environments/surveys/summary/unknown_question_type: e4152a7457d2b94f48dcc70aaba9922f environments/surveys/summary/use_personal_links: da2b3e7e1aaf2ea2bd4efed2dda4247c - environments/surveys/summary/whats_next: d920145bfa2147014062f6f2d1d451a4 + environments/surveys/summary/whats_next: 8c84e29aef3a99e374473db999c4dbdd environments/surveys/summary/your_survey_is_public: 3f5cb5949a5f4020a3d4d74fdfc95e83 - environments/surveys/summary/youre_not_plugged_in_yet: 9217467742cdcf7edf8d59cc1472ede6 - environments/surveys/survey_deleted_successfully: 05d54f7c03ef206c6ba0da21c6ce4098 - environments/surveys/survey_duplicated_successfully: fdbe13c215a685a3ecfac1f8b4c984ed + 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 @@ -1861,7 +1939,7 @@ checksums: environments/workspace/api_keys/api_key_updated: 0e03754eb33742b4ee8d5fdad64c9b3f environments/workspace/api_keys/delete_api_key_confirmation: b2f0342d4e55f0cb244fe121eeeb10a3 environments/workspace/api_keys/duplicate_access: 7ac7ac5ba755ce94e6fc81afa5a21997 - environments/workspace/api_keys/no_api_keys_yet: 829cc32ad4fb0bcafd67f07d50c5fba6 + environments/workspace/api_keys/no_api_keys_yet: 58593ed9f7e507dcd7ca7fe069add599 environments/workspace/api_keys/no_env_permissions_found: 97ef49946f3ce15c2ad44dcfd2bce507 environments/workspace/api_keys/organization_access: 96a92fa907b15e0c0e47e33cac15be88 environments/workspace/api_keys/organization_access_description: 773dfeaf6ffbf34dd9a0a3d656a6d83c @@ -1897,7 +1975,7 @@ checksums: environments/workspace/general/custom_scripts_warning: 5faa0f284d48110918a5e8a467e2bcb8 environments/workspace/general/delete_workspace: 3badbc0f4b49644986fc19d8b2d8f317 environments/workspace/general/delete_workspace_confirmation: 54a4ee78867537e0244c7170453cdb3f - environments/workspace/general/delete_workspace_name_includes_surveys_responses_people_and_more: 11e9ac5a799fbec22495f92f42c40d98 + environments/workspace/general/delete_workspace_name_includes_surveys_responses_people_and_more: 1b6c0597fddc5b6604e3a204402ed35e environments/workspace/general/delete_workspace_settings_description: 411ef100f167fc8fca64e833b6c0d030 environments/workspace/general/error_saving_workspace_information: e7b8022785619ef34de1fb1630b3c476 environments/workspace/general/only_owners_or_managers_can_delete_workspaces: 58da180cd2610210302d85a9896d80bd @@ -1906,14 +1984,14 @@ checksums: environments/workspace/general/this_action_cannot_be_undone: 3d8b13374ffd3cefc0f3f7ce077bd9c9 environments/workspace/general/wait_x_days_before_showing_next_survey: d96228788d32ec23dc0d8c8ba77150a6 environments/workspace/general/waiting_period_updated_successfully: fe21f3034e63ed079dc0f9b9b1dfdbdf - environments/workspace/general/whats_your_workspace_called: bcc29ba97a90f656cf8f516d725ddc95 + environments/workspace/general/whats_your_workspace_called: 3dc82d99f2ac9789c5c92b64a4addc0a environments/workspace/general/workspace_deleted_successfully: 5eb380fdb741034a479644b5e7547099 - environments/workspace/general/workspace_name_settings_description: d7fe4ab1709df12d540d7e57710fb4f0 + environments/workspace/general/workspace_name_settings_description: b2754fbb4b117ee7e78271b68255cc05 environments/workspace/general/workspace_name_updated_successfully: ea244cd17b6644e05dbc89374ad0687a environments/workspace/languages/add_language: bf0407850be7b8a56901533bceb4bc64 environments/workspace/languages/alias: 87e592cc48d819d7fdcf4597c1c63e7a environments/workspace/languages/alias_tooltip: 0f681269a71d46a2d988eedaff31515a - environments/workspace/languages/cannot_remove_language_warning: a21bb501338eacefb5cf013aa107f452 + environments/workspace/languages/cannot_remove_language_warning: 24f18b6c2e127b5d967f36229f983089 environments/workspace/languages/conflict_between_identifier_and_alias: e1ebeb085c7f336bb17717765392cd9f environments/workspace/languages/conflict_between_selected_alias_and_another_language: 3df7377a54909e439e38ec76514e5439 environments/workspace/languages/delete_language_confirmation: 4f83321fed95d689f7ff3aa47a82ce68 @@ -1934,9 +2012,71 @@ checksums: 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 + environments/workspace/look/advanced_styling_field_button_bg: fc103ab926721e6213d39cc1f913c018 + environments/workspace/look/advanced_styling_field_button_bg_description: 9f14ec79ed40c0d3eb168cc46a9e0a14 + environments/workspace/look/advanced_styling_field_button_border_radius_description: 5677ee84511896ab9c369c0aced4c352 + environments/workspace/look/advanced_styling_field_button_font_size_description: 59508854b0101a89fab8250f79c0f3ba + environments/workspace/look/advanced_styling_field_button_font_weight_description: d3dab571b0f1bc09d645be66c6686a06 + environments/workspace/look/advanced_styling_field_button_height_description: 1ab13a11281d2c303146e0483f12d948 + environments/workspace/look/advanced_styling_field_button_padding_x_description: 10e14296468321c13fda77fd1ba58dfd + environments/workspace/look/advanced_styling_field_button_padding_y_description: 98b4aeff2940516d05ea61bdc1211d0d + environments/workspace/look/advanced_styling_field_button_text: 3304e88bcc3869f3a306634b541e1e07 + environments/workspace/look/advanced_styling_field_button_text_description: 088f12906c8d2c06d3506f51d8ef8a89 + environments/workspace/look/advanced_styling_field_description_color: e2f4cbc96d3f0b75837a9edc95a5eeda + environments/workspace/look/advanced_styling_field_description_color_description: f69d10a21c9233e0803f024f2e555485 + environments/workspace/look/advanced_styling_field_description_size: a0d51c3ab7dc56320ecedc2b27917842 + environments/workspace/look/advanced_styling_field_description_size_description: ff880ea1beddd1b1ec7416d0b8a69cf3 + environments/workspace/look/advanced_styling_field_description_weight: 514680cc7202ad29835c1cbcde3def1c + environments/workspace/look/advanced_styling_field_description_weight_description: 441ac8db1a32557813eb68fbfd759061 + environments/workspace/look/advanced_styling_field_font_size: ca44d14429b2175a1b194793b4ab8f6b + environments/workspace/look/advanced_styling_field_font_weight: bfef83778146cf40550df9650d8a07da + environments/workspace/look/advanced_styling_field_headline_color: 4ccf3935ad90c88ad4add24f498673ce + environments/workspace/look/advanced_styling_field_headline_color_description: b3fa9c2fc5da9ee11c1f279e4f949600 + environments/workspace/look/advanced_styling_field_headline_size: ddc49fa27fc97ed286d5c4309edd9a3c + environments/workspace/look/advanced_styling_field_headline_size_description: 13debc3855e4edae992c7a1ebff599c3 + environments/workspace/look/advanced_styling_field_headline_weight: 0c8b8262945c61f8e2978502362e0a42 + environments/workspace/look/advanced_styling_field_headline_weight_description: 1a9c40bd76ff5098b1e48b1d3893171b + environments/workspace/look/advanced_styling_field_height: 40ca2224bb2936ad1329091b35a9ffe2 + environments/workspace/look/advanced_styling_field_indicator_bg: 00febda2901af0f1b0c17e44f9917c38 + environments/workspace/look/advanced_styling_field_indicator_bg_description: 7eb3b54a8b331354ec95c0dc1545c620 + environments/workspace/look/advanced_styling_field_input_border_radius_description: 0007f1bb572b35d9a3720daeb7a55617 + environments/workspace/look/advanced_styling_field_input_font_size_description: 5311f95dcbd083623e35c98ea5374c3b + environments/workspace/look/advanced_styling_field_input_height_description: e19ec0dc432478def0fd1199ad765e38 + environments/workspace/look/advanced_styling_field_input_padding_x_description: 10e14296468321c13fda77fd1ba58dfd + environments/workspace/look/advanced_styling_field_input_padding_y_description: 98b4aeff2940516d05ea61bdc1211d0d + environments/workspace/look/advanced_styling_field_input_placeholder_opacity_description: f55a6700884d24014404e58876121ddf + environments/workspace/look/advanced_styling_field_input_shadow_description: b59ea4007756cecda47f216987ba05f6 + environments/workspace/look/advanced_styling_field_input_text: 4999bfded16b7d0bbcc858b399745eaa + environments/workspace/look/advanced_styling_field_input_text_description: 460450df24ea0cc902710118a5000feb + environments/workspace/look/advanced_styling_field_option_bg: 0ceaed10d99ed4ad83cb0934ab970174 + environments/workspace/look/advanced_styling_field_option_bg_description: 6cd6ccecbbb9f2f19439d7c682eb67c1 + environments/workspace/look/advanced_styling_field_option_border_radius_description: 23f81c25b2681a7c9e2c4f2e7d2e0656 + environments/workspace/look/advanced_styling_field_option_font_size_description: 5430fd9b08819972f0a613bf3fa659da + environments/workspace/look/advanced_styling_field_option_label: 2767a5db32742073a01aac16488e93dc + environments/workspace/look/advanced_styling_field_option_label_description: f42c9fc7b19cc2cb9b366a4cd31ae695 + environments/workspace/look/advanced_styling_field_option_padding_x_description: 10e14296468321c13fda77fd1ba58dfd + environments/workspace/look/advanced_styling_field_option_padding_y_description: 98b4aeff2940516d05ea61bdc1211d0d + environments/workspace/look/advanced_styling_field_padding_x: 74b440237b4ba662c9898d92e2e06217 + environments/workspace/look/advanced_styling_field_padding_y: 441d777bdc1cd1e792bf9815cc937c6a + environments/workspace/look/advanced_styling_field_placeholder_opacity: fddcbc6e4fc5757aab807a6282d26627 + environments/workspace/look/advanced_styling_field_shadow: 7b4af1b447ece2b19b5d7717b2e15c4e + environments/workspace/look/advanced_styling_field_track_bg: e569155b24616ba6d0a89a07bc85955c + 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_section_buttons: 3b44d6e2800e7bf3f133f1bce435f4c2 + environments/workspace/look/advanced_styling_section_headlines: 6def704c0ac2ecb5951400c806856a41 + environments/workspace/look/advanced_styling_section_inputs: 76bbeb561122a72fd3ec8c49eff7c563 + environments/workspace/look/advanced_styling_section_options: a92819a15bc8c3eb44bdd82a5075c9e2 environments/workspace/look/app_survey_placement: f09cddac6bbb77d4694df223c6edf6b6 environments/workspace/look/app_survey_placement_settings_description: d81bcff7a866a2f83ff76936dbad4770 - environments/workspace/look/centered_modal_overlay_color: 1124ba61ee2ecb18a7175ff780dc3b60 environments/workspace/look/email_customization: ae399f381183a4fe0ffd41ab496b5d8f environments/workspace/look/email_customization_description: 5ccaf1769b2c39d7e87f3a08d056a374 environments/workspace/look/enable_custom_styling: 4774d8fb009c27044aa0191ebcccdcc2 @@ -1947,6 +2087,9 @@ checksums: environments/workspace/look/formbricks_branding_hidden: fda9ba81f8d7fdaacf8dc1642034e145 environments/workspace/look/formbricks_branding_settings_description: 5bb39206c6412c703895593f465a01f9 environments/workspace/look/formbricks_branding_shown: 6c9861cf8f95e8a68c5c64b2630d96cd + environments/workspace/look/generate_theme_btn: 0345bf322c191e70d01fd6607ec5c2f8 + environments/workspace/look/generate_theme_confirmation: f119dbb85fb2bda1c0bcdc581724ef3b + environments/workspace/look/generate_theme_header: 4df5f30a20cf78e248465915f222fd1b environments/workspace/look/logo_removed_successfully: f3a7f9d226affa91121e90ff360553aa environments/workspace/look/logo_settings_description: da155953f55cb44d0e563d9e740241aa environments/workspace/look/logo_updated_successfully: 170250f18062b79be6ac0481ec9d4368 @@ -1959,8 +2102,10 @@ checksums: environments/workspace/look/reset_styling: f25db45ece8637d660bd9d455c9b0265 environments/workspace/look/reset_styling_confirmation: 9ebc0403ebb8a51ed93ec2711db85796 environments/workspace/look/show_formbricks_branding_in: 80fabfec9b34a13c0445d02b923216ed - environments/workspace/look/show_powered_by_formbricks: 02b84acc3156de24e1aff8321d77603f + environments/workspace/look/show_powered_by_formbricks: a0e96edadec8ef326423feccc9d06be7 environments/workspace/look/styling_updated_successfully: b8b74b50dde95abcd498633e9d0c891f + environments/workspace/look/suggest_colors: ddc4543b416ab774007b10a3434343cd + environments/workspace/look/suggested_colors_applied_please_save: 226fa70af5efc8ffa0a3755909c8163e environments/workspace/look/theme: 21fe00b7a518089576fb83c08631107a environments/workspace/look/theme_settings_description: 9fc45322818c3774ab4a44ea14d7836e environments/workspace/tags/add: 87c4a663507f2bcbbf79934af8164e13 @@ -1971,12 +2116,12 @@ checksums: environments/workspace/tags/manage_tags_description: ce7cc42da3646fba960502d7e4e49cd2 environments/workspace/tags/merge: 95051c859b8778be51226b43be6f1075 environments/workspace/tags/no_tag_found: 119c59a4367dfbd7f24a2f967d7f0f35 - environments/workspace/tags/search_tags: 35066dce3d12ce93705cb2f5c2ea317d + environments/workspace/tags/search_tags: 55dd69009e5e6c91124e70f285379fb2 environments/workspace/tags/tag: 924d12caa9981ef5859395382497d101 environments/workspace/tags/tag_already_exists: 1337fd5cdd04311bfdca4e7538f0a4f3 - environments/workspace/tags/tag_deleted: 54fc251117cddad3ca42d10811ce0dde - environments/workspace/tags/tag_updated: 6685fd5cc501b8fb2fb284aa22c2823e - environments/workspace/tags/tags_merged: 544471de666f93fbb0ab600321d1e553 + environments/workspace/tags/tag_deleted: 8853f809cb59a4a77454533f29c57e14 + environments/workspace/tags/tag_updated: 298ffdd32f40785d451eacba66ba8563 + environments/workspace/tags/tags_merged: 15af2602db92678234ab22db5d288cfe environments/workspace/teams/manage_teams: d7b5f26335cea450c333832adbe0b6ad environments/workspace/teams/no_teams_found: fb6680d4b5b73731697b100713afb50d environments/workspace/teams/permission: cc2ed7274bd8267f9e0a10b079584d8b @@ -1996,7 +2141,7 @@ checksums: environments/xm-templates/smileys: 592b612bd314663f6f6f205886796706 environments/xm-templates/smileys_description: 6670d8cd0d160c3456d2c27bcc3bf787 organizations/landing/no_workspaces_warning_subtitle: 305578d27209f1c4f706fccd53559f52 - organizations/landing/no_workspaces_warning_title: c674555c5f11c93570b5d7f95a13b667 + organizations/landing/no_workspaces_warning_title: ba051776860ec4d73c7d177b1379bbda organizations/workspaces/new/channel/channel_select_subtitle: debeb0acf03f9f7b8a6efee291a89dc6 organizations/workspaces/new/channel/channel_select_title: a7f7e7f36b5fc36883111f5ad78417c4 organizations/workspaces/new/channel/in_product_surveys: 4b9fc41da77c5e36437a0e30fa525de5 @@ -2016,8 +2161,8 @@ checksums: organizations/workspaces/new/settings/workspace_name: 7002a32bc4ce3343ede2076b30649ff7 organizations/workspaces/new/settings/workspace_name_description: 0885c4cd8cc8836e74b6ccdb5d25b43d organizations/workspaces/new/settings/workspace_settings_subtitle: 83a72b3e9c08f2936524fcd11b29421b - organizations/workspaces/new/settings/workspace_settings_title: 00e42231ca647111d3288f57ad3ce53a - s/check_inbox_or_spam: dfaf193f2106045c1bd3f463984317a2 + organizations/workspaces/new/settings/workspace_settings_title: a4ec3549507071e1f7d3c834b019fcce + s/check_inbox_or_spam: c48ac1f7b76052881bb3b6d10615152d s/completed: 98a9cd97b409933edf1991e7d022bea9 s/create_your_own: 27976ec69029d6dd52d146a9b5765bc6 s/enter_pin: 1d902362d8063ca1442bebabaab5115b @@ -2042,14 +2187,14 @@ checksums: setup/intro/made_with_love_in_kiel: 1bbdd6e93bcdf7cbfbcac16db448a2e4 setup/intro/paragraph_1: 41e6a1e7c9a4a1922c7064a89f6733fd setup/intro/paragraph_2: 5b3cce4d8c75bab4d671e2af7fc7ee9f - setup/intro/paragraph_3: 5bf4718d4c44ff27e55e0880331f293d + setup/intro/paragraph_3: 5568badc9cbb2e5571a38340c63e7c8e setup/intro/welcome_to_formbricks: 561427153e3effa108f54407dfc2126f setup/invite/add_another_member: 02947deaa4710893794f3cc6e160c2b4 setup/invite/continue: 3cfba90b4600131e82fc4260c568d044 setup/invite/failed_to_invite: dc5ce0dcdf0df978f1102b729dc15584 setup/invite/invitation_sent_to: 5a79e59f2048ee2f8b660715dcb98191 setup/invite/invite_your_organization_members: 98de00cb78b11297679f1565e0c24517 - setup/invite/life_s_no_fun_alone: fcdf5f2c96459274671cc813fb6fd447 + setup/invite/life_s_no_fun_alone: e4e080c6957a10bf218fbd1b5d8cdecc setup/invite/skip: b7f28dfa2f58b80b149bb82b392d0291 setup/invite/smtp_not_configured: aa1e949a15530733b4b9a5487772c7e4 setup/invite/smtp_not_configured_description: f638e327fed583acc31a762c73a82558 @@ -2064,7 +2209,7 @@ checksums: setup/signup/this_user_has_all_the_power: 1af3a7367d412d17f0f16c0e104b3520 templates/address: 5a9a8bc26f90d84c90105690a2eb23a1 templates/address_description: e45b92c5cfff59ae7381e8bfb43b173f - templates/alignment_and_engagement_survey_description: a959f7abf4c7bc55371381a73d3113aa + templates/alignment_and_engagement_survey_description: b431e87eed8f2bbbcf3ec6cb0bbabb18 templates/alignment_and_engagement_survey_name: 7b9c9bbcd37def2df9a76dcc62bc4b8a templates/alignment_and_engagement_survey_question_1_headline: 994e6993f8c33afbfc63304cc8e8d10c templates/alignment_and_engagement_survey_question_1_lower_label: f390b13b6073fcc4e69cc3912a0abd60 @@ -2075,7 +2220,7 @@ checksums: templates/alignment_and_engagement_survey_question_3_lower_label: 67644387ddca8145f9fbce308d332c5c templates/alignment_and_engagement_survey_question_3_upper_label: c2ebf68029c2591d0ce322eacb93adc3 templates/alignment_and_engagement_survey_question_4_headline: e36be56ce8aad1d0ca04939bea4e39b7 - templates/alignment_and_engagement_survey_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/alignment_and_engagement_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/back: f541015a827e37cb3b1234e56bc2aa3c templates/book_interview: 1cc9c72d1c088b28e5dfa5ec7d7b78c4 templates/build_product_roadmap_description: 6ca163ed3b0095cedcbc11822a0d502a @@ -2083,8 +2228,8 @@ checksums: templates/build_product_roadmap_question_1_headline: 8875cb9cdb0dd7e1f4c471ac0f096e01 templates/build_product_roadmap_question_1_lower_label: 83c1f45c6c471dbab0277948397a1af3 templates/build_product_roadmap_question_1_upper_label: c24f08a4a3b5d3fbfb37eb948d594087 - templates/build_product_roadmap_question_2_headline: 55cf09163207033ba200071150fb7cab - templates/build_product_roadmap_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/build_product_roadmap_question_2_headline: 123c5e407108afe5f5e179aa1f26f012 + templates/build_product_roadmap_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/card_abandonment_survey: 705c3dfcc7f6de3a445aaefe0d68c43f templates/card_abandonment_survey_description: a3db29212b51402a7659a76248299798 templates/card_abandonment_survey_question_1_button_label: 6208ac076107506686eb8eae42ac4450 @@ -2096,7 +2241,7 @@ checksums: templates/card_abandonment_survey_question_2_choice_4: b0f7c6d2b19e60b16046e7425de0ac16 templates/card_abandonment_survey_question_2_choice_5: 39dae468835f43621784cee77c0d9161 templates/card_abandonment_survey_question_2_choice_6: 79acaa6cd481262bea4e743a422529d2 - templates/card_abandonment_survey_question_2_headline: 4a5801849d01a26469df1ebffdf120d3 + templates/card_abandonment_survey_question_2_headline: b043d35bb7746b3de81745ac54eb7c9d templates/card_abandonment_survey_question_2_subheader: b9b478e967930358b0c74324a7c18fc8 templates/card_abandonment_survey_question_3_headline: a74666d5ea7f451583da7c33e73bca76 templates/card_abandonment_survey_question_4_headline: 43e37c60464ba9d9adfb24ce3d7e350e @@ -2135,7 +2280,7 @@ checksums: templates/career_development_survey_question_5_choice_5: cde7c32702e62e47a8f0bda9e41fbd5b templates/career_development_survey_question_5_choice_6: 79acaa6cd481262bea4e743a422529d2 templates/career_development_survey_question_5_headline: df65f9d0cdd71b40a2dfa94b763fa611 - templates/career_development_survey_question_5_subheader: e75daa1c7a51c5f0b1c1aaa055c44fb6 + templates/career_development_survey_question_5_subheader: b9b478e967930358b0c74324a7c18fc8 templates/career_development_survey_question_6_choice_1: 8e17c8235ddfc01749e09ac3602f7422 templates/career_development_survey_question_6_choice_2: 8c0b172e24887a9af27134be98ee79b9 templates/career_development_survey_question_6_choice_3: 4e8961e1cb3fbca067bc7513cdc9fb78 @@ -2143,13 +2288,13 @@ checksums: templates/career_development_survey_question_6_choice_5: 4f4d5a5772566ea830ca2ba90ee80480 templates/career_development_survey_question_6_choice_6: 79acaa6cd481262bea4e743a422529d2 templates/career_development_survey_question_6_headline: 88d2a87cbf2ec21882798890990c2225 - templates/career_development_survey_question_6_subheader: e75daa1c7a51c5f0b1c1aaa055c44fb6 + templates/career_development_survey_question_6_subheader: b9b478e967930358b0c74324a7c18fc8 templates/cess_survey_name: dd706043a56d66f2895cad743935c5b4 templates/cess_survey_question_1_headline: 1f75deab2d159eb8c33c37cc51ef4b3e - templates/cess_survey_question_1_lower_label: f4694395745bbb22dbc38d23094a2b68 - templates/cess_survey_question_1_upper_label: 9aeffb8eb4926ffea4af6bf4f73ea14d + templates/cess_survey_question_1_lower_label: 586eedbc7b53319775e42c7cd4cef4de + templates/cess_survey_question_1_upper_label: a04ddfea06817ade6c56cff126cfca80 templates/cess_survey_question_2_headline: b05b561f04753ae81febee18e08ce2dd - templates/cess_survey_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/cess_survey_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/changing_subscription_experience_description: 85acf4498181a02e6de96bc50f246992 templates/changing_subscription_experience_name: 29b2550a51e5d1cc2621753abd2e07b3 templates/changing_subscription_experience_question_1_choice_1: 7c44d81be04cf6c6a3184762a4169b88 @@ -2165,17 +2310,17 @@ checksums: templates/churn_survey: 5ecbb56a7bc54e945fff08fed017a545 templates/churn_survey_description: e8e8f54a1cbafa0dddab6018fe8847af templates/churn_survey_question_1_choice_1: f157f0937a6cbdd619cb00716a2dc836 - templates/churn_survey_question_1_choice_2: efa573e6020470ea9fa6361381ef020b + templates/churn_survey_question_1_choice_2: 36a0d1195e4c0fa93945995024d8f839 templates/churn_survey_question_1_choice_3: c87a7adda41282676e08fc011fc7fb4a templates/churn_survey_question_1_choice_4: 7b65a1700a856e0584755316ea03ea00 - templates/churn_survey_question_1_choice_5: b7a63c1977baceca4f139f2d317553cb + templates/churn_survey_question_1_choice_5: 8711f4fa5cceec44b5f0ebe015df8f67 templates/churn_survey_question_1_headline: 899d5501fa89e0ca8cff72b050a14420 - templates/churn_survey_question_1_subheader: c657b4b7d562ce7630cabf1c036b777d + templates/churn_survey_question_1_subheader: 1861f78f2e78068dc80980efdce9282a templates/churn_survey_question_2_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/churn_survey_question_2_headline: 17d3e7e2ce62af5ef9332c0d208f9172 templates/churn_survey_question_3_button_label: 43834ccf20c1c7cd49382468abe2edce templates/churn_survey_question_3_headline: 76444078de5c30666ff65f453f60b420 - templates/churn_survey_question_3_html: 4f723d2aea95570d6fc4559519611b8e + templates/churn_survey_question_3_html: b14d1ca18d779f49fc64015b36f1d1ad templates/churn_survey_question_4_headline: c64605fecd9342dffe904d809e9e3762 templates/churn_survey_question_5_button_label: 03e28ea8c2c970cd1b532fee14b22e2b templates/churn_survey_question_5_headline: bab9054d83ebc8c67a5bfe7edcb29c85 @@ -2184,24 +2329,24 @@ checksums: templates/collect_feedback_name: ff528de4685a899c4388f5a01a6cf919 templates/collect_feedback_question_1_headline: 78b55fe093772715aaefe92f3b73ef5c templates/collect_feedback_question_1_lower_label: ff4681be0a94185111459994fe58478c - templates/collect_feedback_question_1_subheader: 176015fc878724fa2322c3776f7fd26b + templates/collect_feedback_question_1_subheader: 359394042f3b70d3667059b053b5fd05 templates/collect_feedback_question_1_upper_label: ac7de1981be78aff08b2133569fd3b95 templates/collect_feedback_question_2_headline: d1f72cdf2057ea93f6f1fce6779ef9d3 - templates/collect_feedback_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/collect_feedback_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/collect_feedback_question_3_headline: 366111cdb1609111c3b09cedd4161fcf - templates/collect_feedback_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/collect_feedback_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/collect_feedback_question_4_headline: 5a0717b5eb2202e8972e4abd1334a86e templates/collect_feedback_question_4_lower_label: ff4681be0a94185111459994fe58478c templates/collect_feedback_question_4_upper_label: ac7de1981be78aff08b2133569fd3b95 - templates/collect_feedback_question_5_headline: 14cd732b8689193a94b0336d7f783586 - templates/collect_feedback_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/collect_feedback_question_5_headline: 61761be80e22bac46f0bf3790ddf3814 + templates/collect_feedback_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/collect_feedback_question_6_choice_1: 6cc462fb53d90d404f48d5a6695c1de1 templates/collect_feedback_question_6_choice_2: 2227508afb0d38a027e323207e47d4a2 templates/collect_feedback_question_6_choice_3: ee7cdcfc1dbeba6d92dbb0328daa3175 templates/collect_feedback_question_6_choice_4: 0cf5d6885b64e75dc28c5374027c4773 templates/collect_feedback_question_6_choice_5: 79acaa6cd481262bea4e743a422529d2 templates/collect_feedback_question_6_headline: 0c3729cebb0edcba676f50d4cf8e8d3b - templates/collect_feedback_question_7_headline: ec17d80127b347201136787207d5e6bd + templates/collect_feedback_question_7_headline: 10a84269df32afc18f0f2054b07300f1 templates/collect_feedback_question_7_placeholder: b2dc5ec104538360c1f2ab2c1697a332 templates/consent: f1b822f5c7f70c57efa8edd5ec06318e templates/consent_description: d76e48fb1e8c291b51e783eaf7fc910d @@ -2210,7 +2355,7 @@ checksums: templates/csat_description: 4dd35d7fecfa9fdf47765c7108c3d535 templates/csat_name: f216066cef52693bbaa842a3305377c7 templates/csat_question_10_headline: b6a9ca9c6c20dced146d817c9a1e9be7 - templates/csat_question_10_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/csat_question_10_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/csat_question_1_headline: bd4894e95695ce5bc9fc5d326c79bc90 templates/csat_question_1_lower_label: 54d464343c0bc17231fd51aa2d73623f templates/csat_question_1_upper_label: 9f000f63949d875ae628fc354a2a7f6a @@ -2219,7 +2364,7 @@ checksums: templates/csat_question_2_choice_3: a7c58d9b8afdaefadeb1f5fdf4d5ad3f templates/csat_question_2_choice_4: d09723c4bc1d85d99c2a9248ed0d4578 templates/csat_question_2_choice_5: a89ca2602a3322e89adf17b3349e03ab - templates/csat_question_2_headline: adf56cf25745334d748828a8c52189b4 + templates/csat_question_2_headline: 4e6dc882f742536accbd36e9a301ab57 templates/csat_question_2_subheader: c1fe8f70be7d41a4daa6c9a9ef60eafa templates/csat_question_3_choice_1: 095b4b4aaf4aee8a77da3bf707a3b6e1 templates/csat_question_3_choice_10: a9886a5199df1d581aa9005a123af060 @@ -2280,22 +2425,22 @@ checksums: templates/csat_survey_question_1_lower_label: 04dc1812c9072b2f32c7b307a2b46e42 templates/csat_survey_question_1_upper_label: c24f08a4a3b5d3fbfb37eb948d594087 templates/csat_survey_question_2_headline: ac0e2dcc8108dd5dd339043958c98464 - templates/csat_survey_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/csat_survey_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/csat_survey_question_3_headline: 25974b7f1692cad41908fe305830b6c0 - templates/csat_survey_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/csat_survey_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/cta_description: bc94a2ddc965b286a8677b0642696c7e templates/custom_survey_block_1_name: 5e1b4dce0cb70662441b663507a69454 templates/custom_survey_description: 0492afdea2ef1bd683eaf48a2bad2caa templates/custom_survey_name: 6fc756927ca9ea22c26368cccd64a67e templates/custom_survey_question_1_headline: 0abf9d41e0b5c5567c3833fd63048398 - templates/custom_survey_question_1_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/custom_survey_question_1_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/customer_effort_score_description: 3427623c1f486fe2f3c9de17ef1ab05d templates/customer_effort_score_name: 4f9aa509c57b7d0eb5773cd6fd62d308 templates/customer_effort_score_question_1_headline: 1f75deab2d159eb8c33c37cc51ef4b3e - templates/customer_effort_score_question_1_lower_label: f4694395745bbb22dbc38d23094a2b68 - templates/customer_effort_score_question_1_upper_label: 9aeffb8eb4926ffea4af6bf4f73ea14d + templates/customer_effort_score_question_1_lower_label: 586eedbc7b53319775e42c7cd4cef4de + templates/customer_effort_score_question_1_upper_label: a04ddfea06817ade6c56cff126cfca80 templates/customer_effort_score_question_2_headline: b05b561f04753ae81febee18e08ce2dd - templates/customer_effort_score_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/customer_effort_score_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/date: 56f41c5d30a76295bb087b20b7bee4c3 templates/date_description: 287ff5689645992c7bcd8e00dd3b0abc templates/default_ending_card_button_label: 6e7f44e19f5fc0538efeb361a2cc08a2 @@ -2303,7 +2448,7 @@ checksums: templates/default_ending_card_subheader: b1c943e39d95af07f76e10b074edef33 templates/default_welcome_card_button_label: 89ddbcf710eba274963494f312bdc8a9 templates/default_welcome_card_headline: 8778dc41547a2778d0f9482da989fc00 - templates/default_welcome_card_html: 5fc24f7cfeba1af9a3fc3ddb6fb67de4 + templates/default_welcome_card_html: a028dc7457dc1ac075c40f26c0c9d20c templates/docs_feedback_description: 48a06bba72a4d89333257183c02bbac7 templates/docs_feedback_name: 8f859a55532f1053baad89b94b5722e1 templates/docs_feedback_question_1_choice_1: 42f48ea5f832af879f9710b0d2a67a45 @@ -2311,20 +2456,20 @@ checksums: templates/docs_feedback_question_1_headline: d0086131c280807e61223382a56d2b55 templates/docs_feedback_question_2_headline: 4b11452e37a6aa2e713252d101541446 templates/docs_feedback_question_3_headline: be321bd63cf738d3fa413cb66b8106d0 - templates/earned_advocacy_score_description: 84afeb38cb4c36700423beb888f4b211 + templates/earned_advocacy_score_description: be9fc25084cb180fb42af4d35e3b5f4d templates/earned_advocacy_score_name: 6f18c3f805a1f1875935784c7efa8639 templates/earned_advocacy_score_question_1_choice_1: ec580fd11a45779b039466f1e35eed2a templates/earned_advocacy_score_question_1_choice_2: 8c708225830b06df2d1141c536f2a0d6 templates/earned_advocacy_score_question_1_headline: d282a886c3d0728917873dd0f2c111b3 templates/earned_advocacy_score_question_2_headline: f1044ad7510b0c1b6671730d277e51fb - templates/earned_advocacy_score_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/earned_advocacy_score_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/earned_advocacy_score_question_3_headline: 2c036ea73e811371f7b9fdce1324da56 - templates/earned_advocacy_score_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/earned_advocacy_score_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/earned_advocacy_score_question_4_choice_1: ec580fd11a45779b039466f1e35eed2a templates/earned_advocacy_score_question_4_choice_2: 8c708225830b06df2d1141c536f2a0d6 templates/earned_advocacy_score_question_4_headline: 3363abab5ef272cc4bf3892808d01aba templates/earned_advocacy_score_question_5_headline: 7aeb3b3873bd940d9a7a055fa67cdfb9 - templates/earned_advocacy_score_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/earned_advocacy_score_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/employee_satisfaction_description: 1b329b7c83094e0fdc4750dc0d1952ad templates/employee_satisfaction_name: 9ea81683bc8342a5e85f8f2362908739 templates/employee_satisfaction_question_1_headline: 2a23ef3f59e669cd097f8b4068866fee @@ -2337,12 +2482,12 @@ checksums: templates/employee_satisfaction_question_2_choice_5: 37976c20a72de7c39c7f999d2ed05813 templates/employee_satisfaction_question_2_headline: 71215b5277fcd2dceb4dbd9add8a76e7 templates/employee_satisfaction_question_3_headline: cefcb4cf26f93000218f2b11edaf34d9 - templates/employee_satisfaction_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/employee_satisfaction_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/employee_satisfaction_question_5_headline: 8bda0d1fe2800fb63079722356d6286e templates/employee_satisfaction_question_5_lower_label: ee43b88abd26e897ad4a612361c2ae2b templates/employee_satisfaction_question_5_upper_label: 16372294108f169683263c353c4071dc templates/employee_satisfaction_question_6_headline: 3062f77adec0d5e40f3e9da9b82dc6d6 - templates/employee_satisfaction_question_6_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/employee_satisfaction_question_6_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/employee_satisfaction_question_7_choice_1: 77d668acd2d8fd6dd1ccc8960380dfb8 templates/employee_satisfaction_question_7_choice_2: 9f000f63949d875ae628fc354a2a7f6a templates/employee_satisfaction_question_7_choice_3: e8b5a52075ee1e8d6fa4904bf637876d @@ -2361,7 +2506,7 @@ checksums: templates/employee_well_being_question_3_lower_label: 517dc292bcb96da534b232c6404cc221 templates/employee_well_being_question_3_upper_label: 4c1d18412e3e0e86447fcd5294ba8dfe templates/employee_well_being_question_4_headline: 90e72116414f061708aebc311ff8ae63 - templates/employee_well_being_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/employee_well_being_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/enps_survey_name: 622654363c13fe5faa1e32448e48a290 templates/enps_survey_question_1_headline: b9f206822f94d13786eaa87dbd0ef00b templates/enps_survey_question_1_lower_label: d9de99c54e06922698076a00c7ae29e1 @@ -2370,35 +2515,35 @@ checksums: templates/enps_survey_question_3_headline: bf279d3f3805ec0d896ea0d4f415776f templates/evaluate_a_product_idea_description: 734295caa08aac718e9ee01a99c3debe templates/evaluate_a_product_idea_name: b0d8039556d686b83dfcd455092b9d9c - templates/evaluate_a_product_idea_question_1_button_label: 102449dc2f516eb6259c39fa4ed9c56a - templates/evaluate_a_product_idea_question_1_headline: c94096ba66ad74fb3bbfaaa06bd709a0 + templates/evaluate_a_product_idea_question_1_button_label: ad840f586c880820e8b52b6860eb57fb + templates/evaluate_a_product_idea_question_1_headline: 7e2aab8a2b62564a5273c82dd92f9f8f templates/evaluate_a_product_idea_question_1_html: bc0dcb887591e018dfeeb65a3a5c4bb9 templates/evaluate_a_product_idea_question_2_headline: 10a50778c4559554336e7289a48d021c templates/evaluate_a_product_idea_question_2_lower_label: c2f05d3610d8879ae503a61d49e32e80 templates/evaluate_a_product_idea_question_2_upper_label: b88eaddaea17a4f285209c2529a9b8f8 - templates/evaluate_a_product_idea_question_3_headline: 69407cff7b3e2706bdc86cb425e88918 - templates/evaluate_a_product_idea_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/evaluate_a_product_idea_question_3_headline: 8506f471b33ee664ee023cb614082e2b + templates/evaluate_a_product_idea_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/evaluate_a_product_idea_question_4_button_label: 89ddbcf710eba274963494f312bdc8a9 - templates/evaluate_a_product_idea_question_4_headline: e7e5b5234f617f38f09b2cac639a7ef8 + templates/evaluate_a_product_idea_question_4_headline: c5d9cd20e1415585f3430d662b5d4359 templates/evaluate_a_product_idea_question_4_html: 8902a0d7738376818d2729644321438f templates/evaluate_a_product_idea_question_5_headline: 1d573c2338e6ba5d3cccb09c785bd8c3 templates/evaluate_a_product_idea_question_5_lower_label: bcaa423e4277a4b22fb5c34cc594acab templates/evaluate_a_product_idea_question_5_upper_label: fca81f9e95ff9736120066f46d8209f1 - templates/evaluate_a_product_idea_question_6_headline: 166111b6829e98d746695f4008744b1e - templates/evaluate_a_product_idea_question_6_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/evaluate_a_product_idea_question_6_headline: 01e159c849ada81b836f489b0d0459d0 + templates/evaluate_a_product_idea_question_6_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/evaluate_a_product_idea_question_7_headline: 59aeeb22e7935a5e1acf77489b99463f - templates/evaluate_a_product_idea_question_7_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/evaluate_a_product_idea_question_7_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/evaluate_a_product_idea_question_8_headline: ab95d5bd151f259ccb6fc0a13b05be3d - templates/evaluate_a_product_idea_question_8_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/evaluate_a_product_idea_question_8_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/evaluate_content_quality_description: 185a1464d12239ee88aa83116d7f6c3a templates/evaluate_content_quality_name: 20e66460dfd50b82521a813337a976b0 templates/evaluate_content_quality_question_1_headline: 80464a9e3e96540b7be3a94d43ee1454 templates/evaluate_content_quality_question_1_lower_label: 2a0c59ef15817ff02fecb0544b144562 templates/evaluate_content_quality_question_1_upper_label: 3af1ce39d4f133a26c4272c8245ded07 templates/evaluate_content_quality_question_2_headline: 35484b4ef605b8b6108489e7de6640f1 - templates/evaluate_content_quality_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/evaluate_content_quality_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/evaluate_content_quality_question_3_headline: e4445ed4a8043f90fbf4a533fb4ba45a - templates/evaluate_content_quality_question_3_placeholder: d5d6da6e4ddaaff29a084f0543af713c + templates/evaluate_content_quality_question_3_placeholder: ae413991ee43fdea326422b96c0ad5e1 templates/fake_door_follow_up_description: b3b98815887e65335df597a964be7a46 templates/fake_door_follow_up_name: 42487287b3f29e4b4b434232b56259c8 templates/fake_door_follow_up_question_1_headline: 412da77aa8e36f4c4e91d23da00d223a @@ -2419,20 +2564,20 @@ checksums: templates/feature_chaser_question_2_choice_3: 9af747b81bfaafab43964ae4297517ab templates/feature_chaser_question_2_choice_4: 0a877b8af4327d0dd3e57baea39b116f templates/feature_chaser_question_2_headline: 8126ddca15f39b20d5efe7dee7c7fe47 - templates/feedback_box_description: 10a187e613d2e87a74adf1874240075a + templates/feedback_box_description: 9eed9d83fc3f14b16faac3edf570397d templates/feedback_box_name: 70a40bbebdb9eef2958e9479360b3dfa templates/feedback_box_question_1_choice_1: 623a65679400250a13d193b4b083384a templates/feedback_box_question_1_choice_2: a9f8f298dcef535fae44214241b2bc1f - templates/feedback_box_question_1_headline: d55648d0fd78febc45a25bc0b7e6ad82 - templates/feedback_box_question_1_subheader: ef6321df4d2bab6a826fa23e92256637 - templates/feedback_box_question_2_headline: 878b8f17dc18877bfbc07823113cd5d5 + templates/feedback_box_question_1_headline: 6d7150f305b0d178cb655390a15af378 + templates/feedback_box_question_1_subheader: 05265b6a4b6fe37a2f4e79411f8286b8 + templates/feedback_box_question_2_headline: c2999996c21367d8b7807110458427a0 templates/feedback_box_question_2_subheader: 476ff43369a72225b01633e1bce59b95 templates/feedback_box_question_3_button_label: c631d5b3f14b581c303b782221582fe7 templates/feedback_box_question_3_headline: 5cfb173d156555227fbc2c97ad921e72 templates/feedback_box_question_3_html: 7e5877860eec80971969ae83c89b30f6 templates/feedback_box_question_4_button_label: 1050569a1ea31d070e0cee55bcab3494 templates/feedback_box_question_4_headline: 8a7f04fd9ff3cee80e080c495f528066 - templates/feedback_box_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/feedback_box_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/feedback_box_question_4_subheader: 94ced1c0931daad25db8148b1983ddb6 templates/file_upload: d83c28574c7b4f3c4b151633ee74b436 templates/file_upload_description: 9f7ab498805860da98100eee9a196f20 @@ -2440,10 +2585,10 @@ checksums: templates/follow_ups_modal_action_body: 7a92f9d27e4c476a56b84fc500a88639 templates/free_text: eb413ebfd6e4ecf5653804016b7771c4 templates/free_text_description: e31985b7c5220d6fe2cac12ccc632fc8 - templates/free_text_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/free_text_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/gauge_feature_satisfaction_description: b8069f2f219abfe5a20ff6e46cb35b90 templates/gauge_feature_satisfaction_name: 8e7d48c732ae6cc35c53785e6d27ae96 - templates/gauge_feature_satisfaction_question_1_headline: a344a70efaab2fd98d52bba5b45f7b14 + templates/gauge_feature_satisfaction_question_1_headline: d0907ec67873caf9c53ba881613d98bc templates/gauge_feature_satisfaction_question_1_lower_label: 916620720a0b0be6ce3c4eb353545ef4 templates/gauge_feature_satisfaction_question_1_upper_label: b88eaddaea17a4f285209c2529a9b8f8 templates/gauge_feature_satisfaction_question_2_headline: 0fcbefbfcf5c21e42de8a36cb2cad854 @@ -2457,22 +2602,22 @@ checksums: templates/identify_sign_up_barriers_question_2_headline: f768ea3053b07f6bbcba977f714ec3da templates/identify_sign_up_barriers_question_2_lower_label: d9de99c54e06922698076a00c7ae29e1 templates/identify_sign_up_barriers_question_2_upper_label: 9f000f63949d875ae628fc354a2a7f6a - templates/identify_sign_up_barriers_question_3_choice_1_label: c55ea18988e9c50db3d656ec8530bf3a + templates/identify_sign_up_barriers_question_3_choice_1_label: 136bb71047ef7aa7306b3624ef383ca0 templates/identify_sign_up_barriers_question_3_choice_2_label: 17cfe7358ec52e40f4558d2ea352bd6c templates/identify_sign_up_barriers_question_3_choice_3_label: 969b81566c6be1a27e9848834b70946a templates/identify_sign_up_barriers_question_3_choice_4_label: e2d3c2ebd342ea84dab7e0e9f2ea1156 templates/identify_sign_up_barriers_question_3_choice_5_label: d4bc66df5a8d842dfca0a176ccddf71e templates/identify_sign_up_barriers_question_3_headline: eb2e0db2c4f9650fc72b931d47aebc58 templates/identify_sign_up_barriers_question_4_headline: d9652977772912124e858228dc8c3034 - templates/identify_sign_up_barriers_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/identify_sign_up_barriers_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/identify_sign_up_barriers_question_5_headline: b97e7e8403f4b4662e72a76e32aa3f47 - templates/identify_sign_up_barriers_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/identify_sign_up_barriers_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/identify_sign_up_barriers_question_6_headline: 90be739b5d47d83be0f16de10e3d6951 - templates/identify_sign_up_barriers_question_6_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/identify_sign_up_barriers_question_6_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/identify_sign_up_barriers_question_7_headline: 4f9f8c9f005f79e0605b6ef30f41fbc3 - templates/identify_sign_up_barriers_question_7_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/identify_sign_up_barriers_question_7_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/identify_sign_up_barriers_question_8_headline: 1f4ee5675d0d84bf049052be26549037 - templates/identify_sign_up_barriers_question_8_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/identify_sign_up_barriers_question_8_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/identify_sign_up_barriers_question_9_button_label: 0dd2ae69be4618c1f9e615774a4509ca templates/identify_sign_up_barriers_question_9_headline: 54d02e5c8eeb10fed40e2e82f7399f8c templates/identify_sign_up_barriers_question_9_html: ed87aa8d325b6063d4150431e9f80ef0 @@ -2485,38 +2630,38 @@ checksums: templates/identify_upsell_opportunities_question_1_headline: 1e1aa49f39aca7873d05bfcc81fa77b7 templates/improve_activation_rate_description: 9587721eb5a40c4b8312e41a1f9fae19 templates/improve_activation_rate_name: ab2908e5166d4addb19093f3cb542835 - templates/improve_activation_rate_question_1_choice_1: 7c427d75a6061c98421dde6cd419154d + templates/improve_activation_rate_question_1_choice_1: 148839adc1e7cee224a765cea67b9f22 templates/improve_activation_rate_question_1_choice_2: 608d16fa8254ff54b17f00a0537c6628 templates/improve_activation_rate_question_1_choice_3: e26f3bc7d47c851e0a7988fa72e45850 - templates/improve_activation_rate_question_1_choice_4: 969ba5aa70bae638f1979a61fbb73b66 + templates/improve_activation_rate_question_1_choice_4: 6dcd4ed2668c72ac1f57b4b0276ebb02 templates/improve_activation_rate_question_1_choice_5: d4bc66df5a8d842dfca0a176ccddf71e - templates/improve_activation_rate_question_1_headline: 3b9d7818e297ff6e32a7865823765650 - templates/improve_activation_rate_question_2_headline: bd0476721bd7431a4eaaf9b3d727c1bf - templates/improve_activation_rate_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/improve_activation_rate_question_1_headline: 22b5234b65cd8be0691a44d66a598150 + templates/improve_activation_rate_question_2_headline: b52a2e4fe00ef7ef0376cba959a4310c + templates/improve_activation_rate_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/improve_activation_rate_question_3_headline: a0b70d840e8a9fba18070cade4ff88a1 - templates/improve_activation_rate_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/improve_activation_rate_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/improve_activation_rate_question_4_headline: 83c2d5428b5cdd6db19e5681f1e08007 - templates/improve_activation_rate_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/improve_activation_rate_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/improve_activation_rate_question_5_headline: eae49402521a5c6b678d33599d880519 - templates/improve_activation_rate_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/improve_activation_rate_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/improve_activation_rate_question_6_headline: 469b09935f1a1d78e145263a8988d68f - templates/improve_activation_rate_question_6_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 - templates/improve_activation_rate_question_6_subheader: d0fb4acce0b4e4a80a5c5543de362b6b + templates/improve_activation_rate_question_6_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee + templates/improve_activation_rate_question_6_subheader: 13bc6551a1e12c055387ed00b96d8fef templates/improve_newsletter_content_description: e4ae07f4370a271b596085e331d77a79 templates/improve_newsletter_content_name: b3c825480d7c83ac5861fb2cca5a94fd templates/improve_newsletter_content_question_1_headline: 2180aac0f72da84336c0019df530be35 templates/improve_newsletter_content_question_1_lower_label: cf0caee1a409de3c233506c5259240f4 templates/improve_newsletter_content_question_1_upper_label: 6194593238f7b88be925f4d35fe60110 templates/improve_newsletter_content_question_2_headline: abbea0e97841b617a878f1de2c968d0e - templates/improve_newsletter_content_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/improve_newsletter_content_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/improve_newsletter_content_question_3_button_label: 5d5352aba5272de9b1337909d49d4a4c templates/improve_newsletter_content_question_3_headline: fcd056a1581f5a538aad57641cd0abad - templates/improve_newsletter_content_question_3_html: 102e73f836fe99b6c333c88c730fa25b + templates/improve_newsletter_content_question_3_html: 40a84a9e3a77b6dc5a9b68b475849a2d templates/improve_trial_conversion_description: 3187c4ac1de993326a988c6665d3d4ae templates/improve_trial_conversion_name: 9d5ac989c70795992f5e1afae73b4109 - templates/improve_trial_conversion_question_1_choice_1: 6093607e6257b465fec7e81ded5ffc7e + templates/improve_trial_conversion_question_1_choice_1: c8fc03a3436fbd026230f7abcc4b5c2e templates/improve_trial_conversion_question_1_choice_2: 95b6a2013ec947d0e160f12924fef3fb - templates/improve_trial_conversion_question_1_choice_3: 51115843745ce633c6f796fbac117b05 + templates/improve_trial_conversion_question_1_choice_3: d30c5badcd72a9b01367edb452fe1e42 templates/improve_trial_conversion_question_1_choice_4: 64a4206720a17e5b35844011d54175d3 templates/improve_trial_conversion_question_1_choice_5: 71458e25e3b769969d9b595fde445f9c templates/improve_trial_conversion_question_1_headline: 6d0a492446be0250f04850de383fec54 @@ -2525,7 +2670,7 @@ checksums: templates/improve_trial_conversion_question_2_headline: 05dd4820f60b9d267a9affc7e662f029 templates/improve_trial_conversion_question_4_button_label: d94a6a11cfdf4ebde4c5332e585e2e96 templates/improve_trial_conversion_question_4_headline: 9b07341f65574c4165086ec107cebb45 - templates/improve_trial_conversion_question_4_html: 95d13979f92aa0e6c5bce6613ad3b417 + 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 @@ -2537,23 +2682,23 @@ checksums: templates/integration_setup_survey_question_1_lower_label: 916620720a0b0be6ce3c4eb353545ef4 templates/integration_setup_survey_question_1_upper_label: b88eaddaea17a4f285209c2529a9b8f8 templates/integration_setup_survey_question_2_headline: 7f98f8b8d4876c7e9e933d2bf874f6a6 - templates/integration_setup_survey_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/integration_setup_survey_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/integration_setup_survey_question_3_headline: 0c16233b11661b4b2ebe046d17baba95 templates/integration_setup_survey_question_3_subheader: fd5ef11f1f5263f03f65c41caebe4c3b templates/interview_prompt_description: d4e9f8cdaf01be900d6a3dc3a1d9e9b7 templates/interview_prompt_name: 9c55f5051d07df6b53f5fa0a95195235 templates/interview_prompt_question_1_button_label: 87812eef6da80af6c8326ab9acb0115f templates/interview_prompt_question_1_headline: 493abf6f5f3caaf2f2edca33ca5f3013 - templates/interview_prompt_question_1_html: 01138a7083acd84a843e942ee1b3251f + templates/interview_prompt_question_1_html: b1dd3cc93d47a743de56e2e43964e954 templates/long_term_retention_check_in_description: 20234b28f1268b05ca3db03fcb3f8a75 templates/long_term_retention_check_in_name: f1e97a69fbfbb0ebd3f740cdefa55f2f templates/long_term_retention_check_in_question_10_headline: 9567068886cfea40a7044705ec7ffb06 - templates/long_term_retention_check_in_question_10_placeholder: 3ee9dee6187050ecf8a9d74d3d960e68 + templates/long_term_retention_check_in_question_10_placeholder: cc69cd219a32056c973e155b3ef9469d templates/long_term_retention_check_in_question_1_headline: aff2f4abe332ba02b04f24b0fee0c0ad templates/long_term_retention_check_in_question_1_lower_label: a6a66fe3d042b1ca404952096a7d3ef7 templates/long_term_retention_check_in_question_1_upper_label: a3a49eb9cc86972bce6dc41a107f472d templates/long_term_retention_check_in_question_2_headline: 8ffadebbe7626554a31f625cd0ce09d5 - templates/long_term_retention_check_in_question_2_placeholder: d8e382e10e2c01b5a7b8090475816f36 + templates/long_term_retention_check_in_question_2_placeholder: 0c87033285f92b09a261977368fd080e templates/long_term_retention_check_in_question_3_choice_1: 341ff316a339b106a178f0b8d362951b templates/long_term_retention_check_in_question_3_choice_2: e3762261a5c81ffcdd00e19af5204ef8 templates/long_term_retention_check_in_question_3_choice_3: bbb93f54e33f2269ae0002b1576e6753 @@ -2564,7 +2709,7 @@ checksums: templates/long_term_retention_check_in_question_4_lower_label: 20b69a5ffcae1d4f52366870b349cf91 templates/long_term_retention_check_in_question_4_upper_label: 48f47b5f80ca0ce8ff7395081d4bb9e1 templates/long_term_retention_check_in_question_5_headline: b1a0d267548ebced5594eb3c24e99e22 - templates/long_term_retention_check_in_question_5_placeholder: e9b53722fe2aa9f69b910ced9274ef84 + templates/long_term_retention_check_in_question_5_placeholder: d9ab7189d17443c03f728f146d5ff92e templates/long_term_retention_check_in_question_6_headline: 89245aac6cbe8f6ac7ce5dea84556ca7 templates/long_term_retention_check_in_question_6_lower_label: 54d464343c0bc17231fd51aa2d73623f templates/long_term_retention_check_in_question_6_upper_label: 9f000f63949d875ae628fc354a2a7f6a @@ -2575,7 +2720,7 @@ checksums: templates/long_term_retention_check_in_question_7_choice_5: e40d9aa9eda5560d8496b806e7a7e832 templates/long_term_retention_check_in_question_7_headline: 3136f67531f1cfbf6e9f8a890472c6a1 templates/long_term_retention_check_in_question_8_headline: 934ea59cbe6b692daecfe462564dbfe2 - templates/long_term_retention_check_in_question_8_placeholder: 17667958427ed9ac060d7eaffa30af68 + templates/long_term_retention_check_in_question_8_placeholder: 863ad0c2bd362ab0c92c3018bdf7090c templates/long_term_retention_check_in_question_9_headline: f69032a93cee667d66f83a0d608eb8ef templates/long_term_retention_check_in_question_9_lower_label: 6dce68b71925d2b0b229f08498109e5e templates/long_term_retention_check_in_question_9_upper_label: 0421cae0edab009d2b950bc93bce6f0e @@ -2591,10 +2736,10 @@ checksums: templates/market_site_clarity_description: 33020577acabfd728e56b07ce932d649 templates/market_site_clarity_name: c848868bdafbbcb3e78e235bb6595f64 templates/market_site_clarity_question_1_choice_1: f1e807ab1684b329d0ffd670239ea37b - templates/market_site_clarity_question_1_choice_2: fa129664d937d8cb0cb4c9bd537fb987 + templates/market_site_clarity_question_1_choice_2: bdb8b4861dfc77d43ff9028c9c651a97 templates/market_site_clarity_question_1_choice_3: 91a0ee10fef5e56fd0adcefe5f1fcacf templates/market_site_clarity_question_1_headline: 8502f3b9799198999ecab126bde938c0 - templates/market_site_clarity_question_2_headline: 4fbce08c9eb9604bcb210aa51c035661 + templates/market_site_clarity_question_2_headline: 092eefd876d5d9460abadfe2109bb1bf templates/market_site_clarity_question_3_button_label: d2eeb44560b2dd61fbc2b9aca2de8363 templates/market_site_clarity_question_3_headline: d6afccb3f411c48039ac15f3b3dcef14 templates/matrix: ff18f26a7020ec89bef25eee6aa800f8 @@ -2605,10 +2750,10 @@ checksums: templates/measure_search_experience_question_1_lower_label: d3fe8e78de8b5072b17f6c2037e50608 templates/measure_search_experience_question_1_upper_label: 42a3bb76d2fbeb3d6cbcb8499d6ccba7 templates/measure_search_experience_question_2_headline: 64d28ab66df5ba11c0af65a20764b14e - templates/measure_search_experience_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/measure_search_experience_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/measure_search_experience_question_3_headline: ac0e2dcc8108dd5dd339043958c98464 - templates/measure_search_experience_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 - templates/measure_task_accomplishment_description: 3fcfcfa385e5956e7c249be6bc367c90 + templates/measure_search_experience_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee + templates/measure_task_accomplishment_description: 06aa97e829e1b0677334f35236246998 templates/measure_task_accomplishment_name: 5de7eb00a4c73421e8792498c4024199 templates/measure_task_accomplishment_question_1_headline: 3f85f89304bb7c6ba35a06a61b403cb3 templates/measure_task_accomplishment_question_1_option_1_label: ec580fd11a45779b039466f1e35eed2a @@ -2618,12 +2763,12 @@ checksums: templates/measure_task_accomplishment_question_2_lower_label: c2f05d3610d8879ae503a61d49e32e80 templates/measure_task_accomplishment_question_2_upper_label: b88eaddaea17a4f285209c2529a9b8f8 templates/measure_task_accomplishment_question_3_headline: 2f945b92a1232fbe207a06d9ba01c112 - templates/measure_task_accomplishment_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/measure_task_accomplishment_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/measure_task_accomplishment_question_4_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/measure_task_accomplishment_question_4_headline: b94c20ff4ba9c1fea9ee18c520a6dbd2 templates/measure_task_accomplishment_question_5_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/measure_task_accomplishment_question_5_headline: 6578d23c3fd71d7709d929a666c2ab37 - templates/measure_task_accomplishment_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/measure_task_accomplishment_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/multi_select: 3a0c9d736b1c1e9a3c717443bb33e5bc templates/multi_select_description: 3a07c6756ed451b90915c6e164115165 templates/new_integration_survey_description: 992d6dd00664fe4fa50fdf1a05fc5ad7 @@ -2664,7 +2809,7 @@ checksums: templates/onboarding_segmentation_question_2_choice_3: dc4c08d6578686d920d0e64a2808fda2 templates/onboarding_segmentation_question_2_choice_4: 43f1e33259ad3cc08784a7513d312428 templates/onboarding_segmentation_question_2_choice_5: 888e2e5a347a427afaaf1fc203bbbee9 - templates/onboarding_segmentation_question_2_headline: f02f19314afaad9c420c957f442495c1 + templates/onboarding_segmentation_question_2_headline: 140234e8b6936b6011a06b5b02cdfdc2 templates/onboarding_segmentation_question_2_subheader: b9b478e967930358b0c74324a7c18fc8 templates/onboarding_segmentation_question_3_choice_1: be51d4c2a648772d3ad7ea6553268f7d templates/onboarding_segmentation_question_3_choice_2: 2227508afb0d38a027e323207e47d4a2 @@ -2686,6 +2831,7 @@ checksums: templates/preview_survey_question_2_choice_1_label: 7885d14d0e01962fd290395ccd96ecfc templates/preview_survey_question_2_choice_2_label: 1af148222f327f28cf0db6513de5989e templates/preview_survey_question_2_headline: 5cfb173d156555227fbc2c97ad921e72 + templates/preview_survey_question_2_subheader: 2e652d8acd68d072e5a0ae686c4011c0 templates/preview_survey_welcome_card_headline: 8778dc41547a2778d0f9482da989fc00 templates/prioritize_features_description: 1eae41fad0e3947f803d8539081e59ec templates/prioritize_features_name: 4ca59ff1f9c319aaa68c3106d820fd6a @@ -2698,8 +2844,8 @@ checksums: templates/prioritize_features_question_2_choice_2: 1e7326a57e8d91ed25a34a98bdc86c81 templates/prioritize_features_question_2_choice_3: 6913aa70491e150337f39bf146692ba2 templates/prioritize_features_question_2_headline: c4525eca2da7061b16b88b7a515266f1 - templates/prioritize_features_question_3_headline: cf6b9e07025e28822a77f0f33255fa01 - templates/prioritize_features_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/prioritize_features_question_3_headline: 17b35f21d32560f745997b78fcebd91a + templates/prioritize_features_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/product_market_fit_short_description: d14c8e7f4eb7c98919de171457d10a31 templates/product_market_fit_short_name: 132d722134fb937204775988ddfd0b34 templates/product_market_fit_short_question_1_choice_1: 074b2a608d4bba5706b5c55dae249edf @@ -2742,7 +2888,7 @@ checksums: templates/professional_development_growth_survey_question_3_lower_label: 675ae5f5e3b1b5b8645c832d8ded308f templates/professional_development_growth_survey_question_3_upper_label: 4f618142d43e555bd0ae09e49fc547ab templates/professional_development_growth_survey_question_4_headline: ff96d4623fcc1e2f9da13e57a4dd6e29 - templates/professional_development_growth_survey_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/professional_development_growth_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/professional_development_survey_description: 9fb0c943037258435fdb1686cc4fec51 templates/professional_development_survey_name: bfcc0887cd547e4c4c95b921039d7d6e templates/professional_development_survey_question_1_choice_1: ec580fd11a45779b039466f1e35eed2a @@ -2777,9 +2923,9 @@ checksums: templates/rate_checkout_experience_question_1_lower_label: c2f05d3610d8879ae503a61d49e32e80 templates/rate_checkout_experience_question_1_upper_label: b88eaddaea17a4f285209c2529a9b8f8 templates/rate_checkout_experience_question_2_headline: 7a9235dc702bdb525da0a2d97ed2d4b0 - templates/rate_checkout_experience_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/rate_checkout_experience_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/rate_checkout_experience_question_3_headline: ac0e2dcc8108dd5dd339043958c98464 - templates/rate_checkout_experience_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/rate_checkout_experience_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/rating: c9fa231054678af1362b7f9c6ab35e23 templates/rating_description: 91459349abfee8f41f4aebaa3ed0ae94 templates/rating_lower_label: ff4681be0a94185111459994fe58478c @@ -2796,7 +2942,7 @@ checksums: templates/recognition_and_reward_survey_question_3_lower_label: 78f3503c2156109defbc78c933df81a1 templates/recognition_and_reward_survey_question_3_upper_label: abe8151cdab8172174886c2054fa6879 templates/recognition_and_reward_survey_question_4_headline: 579a352bf531450318758ac959a912db - templates/recognition_and_reward_survey_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/recognition_and_reward_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/review_prompt_description: 21f19ec762c1319c261764d9cceb9564 templates/review_prompt_name: beb7a913fa25579c937fe5f7570cf4b8 templates/review_prompt_question_1_headline: 6b15d118037b729138c2214cfef49a68 @@ -2807,7 +2953,7 @@ checksums: templates/review_prompt_question_2_html: b2436a55ee2255503e50fe07a06890bd templates/review_prompt_question_3_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/review_prompt_question_3_headline: a76ad381785e46dbc0b1cb45662fa0be - templates/review_prompt_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/review_prompt_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/review_prompt_question_3_subheader: 43098f46dc68b7594522e9d94ba0b51f templates/schedule_a_meeting: 185a75777b713c6699dc823bc9c87c4f templates/schedule_a_meeting_description: 0f39c69b50d4746429a3228f08e26ad2 @@ -2815,16 +2961,16 @@ checksums: templates/single_select_description: b195a55499a3cb0d2bbde5b41dcafaa2 templates/site_abandonment_survey: 0b323ef26a0ecbe159c6c3cf1512d2f3 templates/site_abandonment_survey_description: 46581a9b056f3cbf8c1dc9e630e716b5 - templates/site_abandonment_survey_question_1_html: eec37cddb0c530c72544067712e95670 + templates/site_abandonment_survey_question_1_html: 51cc50565d88ebf6a4b0448877cda55d templates/site_abandonment_survey_question_2_button_label: 6208ac076107506686eb8eae42ac4450 templates/site_abandonment_survey_question_2_headline: e11a5c95e6a4ba0a3fe9bb0ad1da0b46 - templates/site_abandonment_survey_question_3_choice_1: c86306eb379a1b5f4039e27a0a12caca + templates/site_abandonment_survey_question_3_choice_1: 59e4ef424e5bdadd1b770b0148266e25 templates/site_abandonment_survey_question_3_choice_2: fee51e29951105d7650c3da72282db6d templates/site_abandonment_survey_question_3_choice_3: 632156c0d27eb8b5e5952d9be3bee931 templates/site_abandonment_survey_question_3_choice_4: 4c356cc13d5b19a6295ee15c2c67e5db templates/site_abandonment_survey_question_3_choice_5: 2e2ffb3dd6fb6c9b65679a0c263faec4 templates/site_abandonment_survey_question_3_choice_6: 79acaa6cd481262bea4e743a422529d2 - templates/site_abandonment_survey_question_3_headline: 61829a9c5effbc284e85db4318459fe8 + templates/site_abandonment_survey_question_3_headline: f2eb5dc495b8c41a0c1850d974cd9a49 templates/site_abandonment_survey_question_3_subheader: b9b478e967930358b0c74324a7c18fc8 templates/site_abandonment_survey_question_4_headline: fa3ea48b1552e733eceb5fe2a8339dc1 templates/site_abandonment_survey_question_5_headline: e0693bb93ec823cb2aea0e01bc28734f @@ -2850,9 +2996,9 @@ checksums: templates/smileys_survey_question_2_html: b2436a55ee2255503e50fe07a06890bd templates/smileys_survey_question_3_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/smileys_survey_question_3_headline: a76ad381785e46dbc0b1cb45662fa0be - templates/smileys_survey_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/smileys_survey_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/smileys_survey_question_3_subheader: 43098f46dc68b7594522e9d94ba0b51f - templates/star_rating_survey_name: dad1807a7f0270781bd0d08609110aa6 + templates/star_rating_survey_name: a81021d9fb79ed14a7212274c0970a5f templates/star_rating_survey_question_1_headline: 6b15d118037b729138c2214cfef49a68 templates/star_rating_survey_question_1_lower_label: 04dc1812c9072b2f32c7b307a2b46e42 templates/star_rating_survey_question_1_upper_label: c24f08a4a3b5d3fbfb37eb948d594087 @@ -2861,7 +3007,7 @@ checksums: templates/star_rating_survey_question_2_html: b2436a55ee2255503e50fe07a06890bd templates/star_rating_survey_question_3_button_label: 76a8497d7b546628b03bb81d5c1ce995 templates/star_rating_survey_question_3_headline: a76ad381785e46dbc0b1cb45662fa0be - templates/star_rating_survey_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/star_rating_survey_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/star_rating_survey_question_3_subheader: 43098f46dc68b7594522e9d94ba0b51f templates/statement_call_to_action: 7f73614e56c1bb1e2d788db93dc107eb templates/strongly_agree: b04f9b3beb8a50f486bc5ddb5831ef74 @@ -2878,12 +3024,12 @@ checksums: templates/supportive_work_culture_survey_question_3_lower_label: 517dc292bcb96da534b232c6404cc221 templates/supportive_work_culture_survey_question_3_upper_label: fcdc8aaa4032848ab1521a9f7b203bab templates/supportive_work_culture_survey_question_4_headline: 77cbbd789035652199ef9a053d710929 - templates/supportive_work_culture_survey_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 - templates/uncover_strengths_and_weaknesses_description: b65b9b370e0f68bcf68d52627c1b3a9e + templates/supportive_work_culture_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee + templates/uncover_strengths_and_weaknesses_description: e50a4337009a76aaaa6ade44b836285c templates/uncover_strengths_and_weaknesses_name: 87b219e203e3863ab6b02ba7e0a5b257 templates/uncover_strengths_and_weaknesses_question_1_choice_1: df3951ba9175f47b690538c62534933a templates/uncover_strengths_and_weaknesses_question_1_choice_2: db68abbd863987837097aac843c8b6ad - templates/uncover_strengths_and_weaknesses_question_1_choice_3: 89ee6f61e73452d12062f06590d52e21 + templates/uncover_strengths_and_weaknesses_question_1_choice_3: e68c41a832ff03592d6955e30cbe5a86 templates/uncover_strengths_and_weaknesses_question_1_choice_4: 8e7f41cc79df15a3f9e5c285d46129b7 templates/uncover_strengths_and_weaknesses_question_1_choice_5: 79acaa6cd481262bea4e743a422529d2 templates/uncover_strengths_and_weaknesses_question_1_headline: 997d9175bda65bfe83925cccae37b959 @@ -2899,34 +3045,34 @@ checksums: templates/understand_low_engagement_name: 3fec636096e12b5e46c70132b1ea7e81 templates/understand_low_engagement_question_1_choice_1: f157f0937a6cbdd619cb00716a2dc836 templates/understand_low_engagement_question_1_choice_2: 80599c56cc7871227148f55c23187468 - templates/understand_low_engagement_question_1_choice_3: 969ba5aa70bae638f1979a61fbb73b66 + templates/understand_low_engagement_question_1_choice_3: 6dcd4ed2668c72ac1f57b4b0276ebb02 templates/understand_low_engagement_question_1_choice_4: 1735e52f2db8627a8b0b4f293a570b78 templates/understand_low_engagement_question_1_choice_5: 79acaa6cd481262bea4e743a422529d2 - templates/understand_low_engagement_question_1_headline: cb4ccec1c5debd9db5e2e6ad3f797230 - templates/understand_low_engagement_question_2_headline: 09cc2db5f28035dc2e7a2c6233c7bf84 - templates/understand_low_engagement_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_low_engagement_question_1_headline: 5cba0c6c68c586c7a68dfc1483d4d27d + templates/understand_low_engagement_question_2_headline: 4ccc6e00439ccce991bccffd2c6431b2 + templates/understand_low_engagement_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_low_engagement_question_3_headline: ebb675aba5e32a27fdf6a11060fae929 - templates/understand_low_engagement_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_low_engagement_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_low_engagement_question_4_headline: b6fd160077e745782d591dadc3a4b502 - templates/understand_low_engagement_question_4_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_low_engagement_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_low_engagement_question_5_headline: 460a3423c5f0298114afdd098915da9d - templates/understand_low_engagement_question_5_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_low_engagement_question_5_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_low_engagement_question_6_headline: 5e5127c13d65f83b82243898887021f1 - templates/understand_low_engagement_question_6_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_low_engagement_question_6_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_purchase_intention_description: f33a4c36992d7065cc58f90235b1e87b templates/understand_purchase_intention_name: cabbcac652ea59bb71f2ae047d280373 templates/understand_purchase_intention_question_1_headline: 936fd25f95f60ee37395c92561ea4ea0 templates/understand_purchase_intention_question_1_lower_label: d9de99c54e06922698076a00c7ae29e1 templates/understand_purchase_intention_question_1_upper_label: 77d668acd2d8fd6dd1ccc8960380dfb8 - templates/understand_purchase_intention_question_2_headline: f226580499703a5c972772ca31030c2c - templates/understand_purchase_intention_question_2_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 + templates/understand_purchase_intention_question_2_headline: 82271b0aaa3c37ab96d791e3603d347d + templates/understand_purchase_intention_question_2_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/understand_purchase_intention_question_3_headline: 245eba2e8525941321c97183e788131a - templates/understand_purchase_intention_question_3_placeholder: 3ec4d0b6bb1f26bb2c32e9e8bd377ae3 - templates/usability_question_10_headline: 7cdb7551e9ea3a0867fb1422e02a2157 - templates/usability_question_1_headline: bf97d61c07dbf6daba811935d867d204 + templates/understand_purchase_intention_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee + templates/usability_question_10_headline: 25a6645e238c97f9ff616d6b77a70faa + templates/usability_question_1_headline: bcb5bf40da0a71d95ff3395fdc05f354 templates/usability_question_2_headline: 99666a5b24fc933327f062ed043d279b templates/usability_question_3_headline: e8dadf12855b3c1d8f48237722f308f6 - templates/usability_question_4_headline: e274004494d36d42ed572706c66f3fee + templates/usability_question_4_headline: 2895ef89366ca87ca412b9edceb95201 templates/usability_question_5_headline: 0022bbefdecfe382d2fd138a1d6a966f templates/usability_question_6_headline: 03d3c34fee0cf9587acee2b4fd9dd770 templates/usability_question_7_headline: 73c54f73f39ef345773cdfb970cb8af3 diff --git a/apps/web/instrumentation-node.ts b/apps/web/instrumentation-node.ts index fdc9436747..17ec57d6bc 100644 --- a/apps/web/instrumentation-node.ts +++ b/apps/web/instrumentation-node.ts @@ -1,59 +1,205 @@ -// instrumentation-node.ts +// OpenTelemetry instrumentation for Next.js - loaded via instrumentation.ts hook +// Pattern based on: ee/src/opentelemetry.ts (license server) +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; -import { HostMetrics } from "@opentelemetry/host-metrics"; -import { registerInstrumentations } from "@opentelemetry/instrumentation"; -import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; -import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { resourceFromAttributes } from "@opentelemetry/resources"; +import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; +import { NodeSDK } from "@opentelemetry/sdk-node"; import { - detectResources, - envDetector, - hostDetector, - processDetector, - resourceFromAttributes, -} from "@opentelemetry/resources"; -import { MeterProvider } from "@opentelemetry/sdk-metrics"; + AlwaysOffSampler, + AlwaysOnSampler, + BatchSpanProcessor, + ParentBasedSampler, + type Sampler, + TraceIdRatioBasedSampler, +} from "@opentelemetry/sdk-trace-base"; +import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"; +import { PrismaInstrumentation } from "@prisma/instrumentation"; import { logger } from "@formbricks/logger"; -import { env } from "@/lib/env"; -const exporter = new PrometheusExporter({ - port: env.PROMETHEUS_EXPORTER_PORT ? parseInt(env.PROMETHEUS_EXPORTER_PORT) : 9464, - endpoint: "/metrics", - host: "0.0.0.0", // Listen on all network interfaces -}); +// --- Configuration from environment --- +const serviceName = process.env.OTEL_SERVICE_NAME || "formbricks"; +const serviceVersion = process.env.npm_package_version || "0.0.0"; +const environment = process.env.ENVIRONMENT || process.env.NODE_ENV || "development"; +const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; +const prometheusEnabled = process.env.PROMETHEUS_ENABLED === "1"; +const prometheusPort = process.env.PROMETHEUS_EXPORTER_PORT + ? Number.parseInt(process.env.PROMETHEUS_EXPORTER_PORT) + : 9464; -const detectedResources = detectResources({ - detectors: [envDetector, processDetector, hostDetector], -}); +// --- Configure OTLP exporters (conditional on endpoint being set) --- +let traceExporter: OTLPTraceExporter | undefined; +let otlpMetricExporter: OTLPMetricExporter | undefined; -const customResources = resourceFromAttributes({}); - -const resources = detectedResources.merge(customResources); - -const meterProvider = new MeterProvider({ - readers: [exporter], - resource: resources, -}); - -const hostMetrics = new HostMetrics({ - name: `otel-metrics`, - meterProvider, -}); - -registerInstrumentations({ - meterProvider, - instrumentations: [new HttpInstrumentation(), new RuntimeNodeInstrumentation()], -}); - -hostMetrics.start(); - -process.on("SIGTERM", async () => { +if (otlpEndpoint) { try { - // Stop collecting metrics or flush them if needed - await meterProvider.shutdown(); - // Possibly close other instrumentation resources + // OTLPTraceExporter reads OTEL_EXPORTER_OTLP_ENDPOINT from env + // and appends /v1/traces for HTTP transport + // Uses OTEL_EXPORTER_OTLP_HEADERS from env natively (W3C OTel format: key=value,key2=value2) + traceExporter = new OTLPTraceExporter(); + + // OTLPMetricExporter reads OTEL_EXPORTER_OTLP_ENDPOINT from env + // and appends /v1/metrics for HTTP transport + // Uses OTEL_EXPORTER_OTLP_HEADERS from env natively + otlpMetricExporter = new OTLPMetricExporter(); + } catch (error) { + logger.error(error, "Failed to create OTLP exporters. Telemetry will not be exported."); + } +} + +// --- Configure Prometheus exporter (pull-based metrics for ServiceMonitor) --- +let prometheusExporter: PrometheusExporter | undefined; +if (prometheusEnabled) { + prometheusExporter = new PrometheusExporter({ + port: prometheusPort, + endpoint: "/metrics", + host: "0.0.0.0", + }); +} + +// --- Build metric readers array --- +const metricReaders: (PeriodicExportingMetricReader | PrometheusExporter)[] = []; + +if (otlpMetricExporter) { + metricReaders.push( + new PeriodicExportingMetricReader({ + exporter: otlpMetricExporter, + exportIntervalMillis: 60000, // Export every 60 seconds + }) + ); +} + +if (prometheusExporter) { + metricReaders.push(prometheusExporter); +} + +// --- Resource attributes --- +const resourceAttributes: Record = { + [ATTR_SERVICE_NAME]: serviceName, + [ATTR_SERVICE_VERSION]: serviceVersion, + "deployment.environment": environment, +}; + +// --- Configure sampler --- +const samplerType = process.env.OTEL_TRACES_SAMPLER || "always_on"; +const parsedSamplerArg = process.env.OTEL_TRACES_SAMPLER_ARG + ? Number.parseFloat(process.env.OTEL_TRACES_SAMPLER_ARG) + : undefined; +const samplerArg = + parsedSamplerArg !== undefined && !Number.isNaN(parsedSamplerArg) ? parsedSamplerArg : undefined; + +let sampler: Sampler; +switch (samplerType) { + case "always_on": + sampler = new AlwaysOnSampler(); + break; + case "always_off": + sampler = new AlwaysOffSampler(); + break; + case "traceidratio": + sampler = new TraceIdRatioBasedSampler(samplerArg ?? 1); + break; + case "parentbased_traceidratio": + sampler = new ParentBasedSampler({ + root: new TraceIdRatioBasedSampler(samplerArg ?? 1), + }); + break; + case "parentbased_always_on": + sampler = new ParentBasedSampler({ + root: new AlwaysOnSampler(), + }); + break; + case "parentbased_always_off": + sampler = new ParentBasedSampler({ + root: new AlwaysOffSampler(), + }); + break; + default: + logger.warn(`Unknown sampler type: ${samplerType}. Using always_on.`); + sampler = new AlwaysOnSampler(); +} + +// --- Initialize NodeSDK --- +const sdk = new NodeSDK({ + sampler, + resource: resourceFromAttributes(resourceAttributes), + // When no OTLP endpoint is configured (e.g. Prometheus-only setups), pass an empty + // spanProcessors array to prevent the SDK from falling back to its default OTLP exporter + // which would attempt connections to localhost:4318 and cause noisy errors. + spanProcessors: traceExporter + ? [ + new BatchSpanProcessor(traceExporter, { + maxQueueSize: 2048, + maxExportBatchSize: 512, + scheduledDelayMillis: 5000, + exportTimeoutMillis: 30000, + }), + ] + : [], + metricReaders: metricReaders.length > 0 ? metricReaders : undefined, + instrumentations: [ + getNodeAutoInstrumentations({ + // Disable noisy/unnecessary instrumentations + "@opentelemetry/instrumentation-fs": { + enabled: false, + }, + "@opentelemetry/instrumentation-dns": { + enabled: false, + }, + "@opentelemetry/instrumentation-net": { + enabled: false, + }, + // Disable pg instrumentation - PrismaInstrumentation handles DB tracing + "@opentelemetry/instrumentation-pg": { + enabled: false, + }, + "@opentelemetry/instrumentation-http": { + // Ignore health/metrics endpoints to reduce noise + ignoreIncomingRequestHook: (req) => { + const url = req.url || ""; + return url === "/health" || url.startsWith("/metrics") || url === "/api/v2/health"; + }, + }, + // Enable runtime metrics for Node.js process monitoring + "@opentelemetry/instrumentation-runtime-node": { + enabled: true, + }, + }), + // Prisma instrumentation for database query tracing + new PrismaInstrumentation(), + ], +}); + +// Start the SDK +sdk.start(); + +// --- Log initialization status --- +const enabledFeatures: string[] = []; +if (traceExporter) enabledFeatures.push("traces"); +if (otlpMetricExporter) enabledFeatures.push("otlp-metrics"); +if (prometheusExporter) enabledFeatures.push("prometheus-metrics"); + +const samplerArgStr = process.env.OTEL_TRACES_SAMPLER_ARG || ""; +const samplerArgMsg = samplerArgStr ? `, samplerArg=${samplerArgStr}` : ""; + +if (enabledFeatures.length > 0) { + logger.info( + `OpenTelemetry initialized: service=${serviceName}, version=${serviceVersion}, environment=${environment}, exporters=${enabledFeatures.join("+")}, sampler=${samplerType}${samplerArgMsg}` + ); +} else { + logger.info( + `OpenTelemetry initialized (no exporters): service=${serviceName}, version=${serviceVersion}, environment=${environment}` + ); +} + +// --- Graceful shutdown --- +// Run before other SIGTERM listeners (logger flush, etc.) so spans are drained first. +process.prependListener("SIGTERM", async () => { + try { + await sdk.shutdown(); } catch (e) { - logger.error(e, "Error during graceful shutdown"); - } finally { - process.exit(0); + logger.error(e, "Error during OpenTelemetry shutdown"); } }); diff --git a/apps/web/instrumentation.ts b/apps/web/instrumentation.ts index ed4ae703f1..db45ed49a6 100644 --- a/apps/web/instrumentation.ts +++ b/apps/web/instrumentation.ts @@ -5,10 +5,13 @@ export const onRequestError = Sentry.captureRequestError; export const register = async () => { if (process.env.NEXT_RUNTIME === "nodejs") { - if (PROMETHEUS_ENABLED) { + // Load OpenTelemetry instrumentation when Prometheus metrics or OTLP export is enabled + if (PROMETHEUS_ENABLED || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) { await import("./instrumentation-node"); } } + // Sentry init loads after OTEL to avoid TracerProvider conflicts + // Sentry tracing is disabled (tracesSampleRate: 0) -- SigNoz handles distributed tracing if (process.env.NEXT_RUNTIME === "nodejs" && IS_PRODUCTION && SENTRY_DSN) { await import("./sentry.server.config"); } diff --git a/apps/web/lib/constants.ts b/apps/web/lib/constants.ts index 2e9f9fbf89..4f90f7975f 100644 --- a/apps/web/lib/constants.ts +++ b/apps/web/lib/constants.ts @@ -168,19 +168,20 @@ export const MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT = 150; export const DEFAULT_LOCALE = "en-US"; export const AVAILABLE_LOCALES: TUserLocale[] = [ - "en-US", "de-DE", - "pt-BR", + "en-US", + "es-ES", "fr-FR", + "hu-HU", + "ja-JP", "nl-NL", - "zh-Hant-TW", + "pt-BR", "pt-PT", "ro-RO", - "ja-JP", - "zh-Hans-CN", - "es-ES", - "sv-SE", "ru-RU", + "sv-SE", + "zh-Hans-CN", + "zh-Hant-TW", ]; // Billing constants diff --git a/apps/web/lib/env.ts b/apps/web/lib/env.ts index 3dcb30f981..13e812df53 100644 --- a/apps/web/lib/env.ts +++ b/apps/web/lib/env.ts @@ -57,7 +57,6 @@ export const env = createEnv({ OIDC_DISPLAY_NAME: z.string().optional(), OIDC_ISSUER: z.string().optional(), OIDC_SIGNING_ALGORITHM: z.string().optional(), - OPENTELEMETRY_LISTENER_URL: z.string().optional(), REDIS_URL: process.env.NODE_ENV === "test" ? z.string().optional() @@ -178,7 +177,6 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, SENTRY_DSN: process.env.SENTRY_DSN, - OPENTELEMETRY_LISTENER_URL: process.env.OPENTELEMETRY_LISTENER_URL, NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID, NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET, OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID, diff --git a/apps/web/lib/environment/service.ts b/apps/web/lib/environment/service.ts index ca0f6daa3f..b1a2aaa49f 100644 --- a/apps/web/lib/environment/service.ts +++ b/apps/web/lib/environment/service.ts @@ -167,6 +167,12 @@ export const createEnvironment = async ( description: "Your contact's last name", type: "default", }, + { + key: "language", + name: "Language", + description: "The language preference of a contact", + type: "default", + }, ], }, }, diff --git a/apps/web/lib/i18n/utils.ts b/apps/web/lib/i18n/utils.ts index 984e51303a..cb679389af 100644 --- a/apps/web/lib/i18n/utils.ts +++ b/apps/web/lib/i18n/utils.ts @@ -126,12 +126,6 @@ export const addMultiLanguageLabels = (object: unknown, languageSymbols: string[ }; export const appLanguages = [ - { - code: "en-US", - label: { - "en-US": "English (US)", - }, - }, { code: "de-DE", label: { @@ -139,9 +133,15 @@ export const appLanguages = [ }, }, { - code: "pt-BR", + code: "en-US", label: { - "en-US": "Portuguese (Brazil)", + "en-US": "English (US)", + }, + }, + { + code: "es-ES", + label: { + "en-US": "Spanish", }, }, { @@ -151,9 +151,27 @@ export const appLanguages = [ }, }, { - code: "zh-Hant-TW", + code: "hu-HU", label: { - "en-US": "Chinese (Traditional)", + "en-US": "Hungarian", + }, + }, + { + code: "ja-JP", + label: { + "en-US": "Japanese", + }, + }, + { + code: "nl-NL", + label: { + "en-US": "Dutch", + }, + }, + { + code: "pt-BR", + label: { + "en-US": "Portuguese (Brazil)", }, }, { @@ -169,27 +187,9 @@ export const appLanguages = [ }, }, { - code: "ja-JP", + code: "ru-RU", label: { - "en-US": "Japanese", - }, - }, - { - code: "zh-Hans-CN", - label: { - "en-US": "Chinese (Simplified)", - }, - }, - { - code: "nl-NL", - label: { - "en-US": "Dutch", - }, - }, - { - code: "es-ES", - label: { - "en-US": "Spanish", + "en-US": "Russian", }, }, { @@ -199,9 +199,15 @@ export const appLanguages = [ }, }, { - code: "ru-RU", + code: "zh-Hans-CN", label: { - "en-US": "Russian", + "en-US": "Chinese (Simplified)", + }, + }, + { + code: "zh-Hant-TW", + label: { + "en-US": "Chinese (Traditional)", }, }, ]; diff --git a/apps/web/lib/project/service.test.ts b/apps/web/lib/project/service.test.ts index c070688300..fce5c939bf 100644 --- a/apps/web/lib/project/service.test.ts +++ b/apps/web/lib/project/service.test.ts @@ -48,7 +48,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -106,7 +106,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -171,7 +171,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -196,7 +196,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -250,7 +250,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -324,7 +324,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -378,7 +378,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -403,7 +403,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, @@ -448,7 +448,7 @@ describe("Project Service", () => { }, placement: WidgetPlacement.bottomRight, clickOutsideClose: true, - darkOverlay: false, + overlay: "none", environments: [], styling: { allowStyleOverwrite: true, diff --git a/apps/web/lib/project/service.ts b/apps/web/lib/project/service.ts index 2bdc20340b..e7e7f945f9 100644 --- a/apps/web/lib/project/service.ts +++ b/apps/web/lib/project/service.ts @@ -22,7 +22,7 @@ const selectProject = { config: true, placement: true, clickOutsideClose: true, - darkOverlay: true, + overlay: true, environments: true, styling: true, logo: true, diff --git a/apps/web/lib/styling/constants.ts b/apps/web/lib/styling/constants.ts index 3986e18339..b18fcabaa9 100644 --- a/apps/web/lib/styling/constants.ts +++ b/apps/web/lib/styling/constants.ts @@ -1,5 +1,6 @@ // https://github.com/airbnb/javascript/#naming--uppercase import { TProjectStyling } from "@formbricks/types/project"; +import { isLight, mixColor } from "@/lib/utils/colors"; export const COLOR_DEFAULTS = { brandColor: "#64748b", @@ -11,32 +12,209 @@ export const COLOR_DEFAULTS = { highlightBorderColor: "#64748b", } as const; -export const defaultStyling: TProjectStyling = { +const DEFAULT_BRAND_COLOR = "#64748b"; + +/** + * Derives a complete set of suggested color values from a single brand color. + * + * Used by the project-level "Suggest Colors" button **and** to build + * `STYLE_DEFAULTS` so that a fresh install always has colours that are + * visually cohesive with the default brand. + * + * The returned object is a flat map of form-field paths to values so it + * can be spread directly into form defaults or applied via `form.setValue`. + */ +export const getSuggestedColors = (brandColor: string = DEFAULT_BRAND_COLOR) => { + // Question / dark text: brand darkened with black (visible brand tint) + const questionColor = mixColor(brandColor, "#000000", 0.35); + // Input / option background: white with noticeable brand tint + const inputBg = mixColor(brandColor, "#ffffff", 0.92); + // Input border: visible brand-tinted border + const inputBorder = mixColor(brandColor, "#ffffff", 0.6); + // Card tones + const cardBg = mixColor(brandColor, "#ffffff", 0.97); + const cardBorder = mixColor(brandColor, "#ffffff", 0.8); + // Page background + const pageBg = mixColor(brandColor, "#ffffff", 0.855); + + return { + // General + "brandColor.light": brandColor, + "questionColor.light": questionColor, + + // Headlines & Descriptions — use questionColor to match the legacy behaviour + // where all text elements derived their color from questionColor. + "elementHeadlineColor.light": questionColor, + "elementDescriptionColor.light": questionColor, + "elementUpperLabelColor.light": questionColor, + + // Buttons — use the brand color so the button matches the user's intent. + "buttonBgColor.light": brandColor, + "buttonTextColor.light": isLight(brandColor) ? "#0f172a" : "#ffffff", + + // Inputs + "inputColor.light": inputBg, + "inputBorderColor.light": inputBorder, + "inputTextColor.light": questionColor, + + // Options (Radio / Checkbox) + "optionBgColor.light": inputBg, + "optionLabelColor.light": questionColor, + + // Card + "cardBackgroundColor.light": cardBg, + "cardBorderColor.light": cardBorder, + + // Highlight / Focus + "highlightBorderColor.light": mixColor(brandColor, "#ffffff", 0.25), + + // Progress Bar — indicator uses the brand color; track is a lighter tint. + "progressIndicatorBgColor.light": brandColor, + "progressTrackBgColor.light": mixColor(brandColor, "#ffffff", 0.8), + + // Background + background: { bg: pageBg, bgType: "color" as const, brightness: 100 }, + }; +}; + +// Pre-compute colors derived from the default brand color. +const _colors = getSuggestedColors(DEFAULT_BRAND_COLOR); + +/** + * Single source of truth for every styling default. + * + * Color values are derived from the default brand color (#64748b) via + * `getSuggestedColors()`. Non-color values (dimensions, weights, sizes) + * are hardcoded here and must be kept in sync with globals.css. + * + * Used everywhere: form defaults, preview rendering, email templates, + * and as the reset target for "Restore defaults". + */ +export const STYLE_DEFAULTS: TProjectStyling = { allowStyleOverwrite: true, - brandColor: { - light: COLOR_DEFAULTS.brandColor, - }, - questionColor: { - light: COLOR_DEFAULTS.questionColor, - }, - inputColor: { - light: COLOR_DEFAULTS.inputColor, - }, - inputBorderColor: { - light: COLOR_DEFAULTS.inputBorderColor, - }, - cardBackgroundColor: { - light: COLOR_DEFAULTS.cardBackgroundColor, - }, - cardBorderColor: { - light: COLOR_DEFAULTS.cardBorderColor, - }, + brandColor: { light: _colors["brandColor.light"] }, + questionColor: { light: _colors["questionColor.light"] }, + inputColor: { light: _colors["inputColor.light"] }, + inputBorderColor: { light: _colors["inputBorderColor.light"] }, + cardBackgroundColor: { light: _colors["cardBackgroundColor.light"] }, + cardBorderColor: { light: _colors["cardBorderColor.light"] }, isLogoHidden: false, - highlightBorderColor: undefined, + highlightBorderColor: { light: _colors["highlightBorderColor.light"] }, isDarkModeEnabled: false, roundness: 8, - cardArrangement: { - linkSurveys: "straight", - appSurveys: "straight", - }, + cardArrangement: { linkSurveys: "simple", appSurveys: "simple" }, + + // Headlines & Descriptions + elementHeadlineColor: { light: _colors["elementHeadlineColor.light"] }, + elementHeadlineFontSize: 16, + elementHeadlineFontWeight: 600, + elementDescriptionColor: { light: _colors["elementDescriptionColor.light"] }, + elementDescriptionFontSize: 14, + elementDescriptionFontWeight: 400, + elementUpperLabelColor: { light: _colors["elementUpperLabelColor.light"] }, + elementUpperLabelFontSize: 12, + elementUpperLabelFontWeight: 400, + + // Inputs + inputTextColor: { light: _colors["inputTextColor.light"] }, + inputBorderRadius: 8, + inputHeight: 20, + inputFontSize: 14, + inputPaddingX: 8, + inputPaddingY: 8, + inputPlaceholderOpacity: 0.5, + inputShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", + + // Buttons + buttonBgColor: { light: _colors["buttonBgColor.light"] }, + buttonTextColor: { light: _colors["buttonTextColor.light"] }, + buttonBorderRadius: 8, + buttonHeight: "auto", + buttonFontSize: 16, + buttonFontWeight: 500, + buttonPaddingX: 12, + buttonPaddingY: 12, + + // Options + optionBgColor: { light: _colors["optionBgColor.light"] }, + optionLabelColor: { light: _colors["optionLabelColor.light"] }, + optionBorderRadius: 8, + optionPaddingX: 16, + optionPaddingY: 16, + optionFontSize: 14, + + // Progress Bar + progressTrackHeight: 8, + progressTrackBgColor: { light: _colors["progressTrackBgColor.light"] }, + progressIndicatorBgColor: { light: _colors["progressIndicatorBgColor.light"] }, +}; + +/** + * Fills in new v4.7 color fields from legacy v4.6 fields when they are missing. + * + * v4.6 stored: brandColor, questionColor, inputColor, inputBorderColor. + * v4.7 adds: elementHeadlineColor, buttonBgColor, optionBgColor, etc. + * + * When loading v4.6 data the new fields are absent. Without this helper the + * form would fall back to STYLE_DEFAULTS (derived from the *default* brand + * colour), causing a visible mismatch. This function derives the new fields + * from the actually-saved legacy fields so the preview and form stay coherent. + * + * Only sets a field when the legacy source exists AND the new field is absent. + */ +export const deriveNewFieldsFromLegacy = (saved: Record): Record => { + const light = (key: string): string | undefined => + (saved[key] as { light?: string } | null | undefined)?.light; + + const q = light("questionColor"); + const b = light("brandColor"); + const i = light("inputColor"); + + return { + ...(q && !saved.elementHeadlineColor && { elementHeadlineColor: { light: q } }), + ...(q && !saved.elementDescriptionColor && { elementDescriptionColor: { light: q } }), + ...(q && !saved.elementUpperLabelColor && { elementUpperLabelColor: { light: q } }), + ...(q && !saved.inputTextColor && { inputTextColor: { light: q } }), + ...(q && !saved.optionLabelColor && { optionLabelColor: { light: q } }), + ...(b && !saved.buttonBgColor && { buttonBgColor: { light: b } }), + ...(b && !saved.buttonTextColor && { buttonTextColor: { light: isLight(b) ? "#0f172a" : "#ffffff" } }), + ...(i && !saved.optionBgColor && { optionBgColor: { light: i } }), + ...(b && !saved.progressIndicatorBgColor && { progressIndicatorBgColor: { light: b } }), + ...(b && !saved.progressTrackBgColor && { progressTrackBgColor: { light: mixColor(b, "#ffffff", 0.8) } }), + }; +}; + +/** + * Builds a complete TProjectStyling object from a single brand color. + * + * Uses STYLE_DEFAULTS for all non-color properties (dimensions, weights, etc.) + * and derives every color from the given brand color via getSuggestedColors(). + * + * Useful when only a brand color is known (e.g. onboarding) and a fully + * coherent styling object is needed for both preview rendering and persistence. + */ +export const buildStylingFromBrandColor = (brandColor: string = DEFAULT_BRAND_COLOR): TProjectStyling => { + const colors = getSuggestedColors(brandColor); + + return { + ...STYLE_DEFAULTS, + brandColor: { light: colors["brandColor.light"] }, + questionColor: { light: colors["questionColor.light"] }, + elementHeadlineColor: { light: colors["elementHeadlineColor.light"] }, + elementDescriptionColor: { light: colors["elementDescriptionColor.light"] }, + elementUpperLabelColor: { light: colors["elementUpperLabelColor.light"] }, + buttonBgColor: { light: colors["buttonBgColor.light"] }, + buttonTextColor: { light: colors["buttonTextColor.light"] }, + inputColor: { light: colors["inputColor.light"] }, + inputBorderColor: { light: colors["inputBorderColor.light"] }, + inputTextColor: { light: colors["inputTextColor.light"] }, + optionBgColor: { light: colors["optionBgColor.light"] }, + optionLabelColor: { light: colors["optionLabelColor.light"] }, + cardBackgroundColor: { light: colors["cardBackgroundColor.light"] }, + cardBorderColor: { light: colors["cardBorderColor.light"] }, + highlightBorderColor: { light: colors["highlightBorderColor.light"] }, + progressIndicatorBgColor: { light: colors["progressIndicatorBgColor.light"] }, + progressTrackBgColor: { light: colors["progressTrackBgColor.light"] }, + background: colors.background, + }; }; diff --git a/apps/web/lib/survey/__mock__/survey.mock.ts b/apps/web/lib/survey/__mock__/survey.mock.ts index eedbefa7bc..33c0e00669 100644 --- a/apps/web/lib/survey/__mock__/survey.mock.ts +++ b/apps/web/lib/survey/__mock__/survey.mock.ts @@ -85,7 +85,7 @@ export const mockProject: TProject = { inAppSurveyBranding: false, placement: "bottomRight", clickOutsideClose: false, - darkOverlay: false, + overlay: "none", environments: [], languages: [], config: { @@ -168,6 +168,7 @@ export const mockContactAttributeKey: TContactAttributeKey = { type: "custom", description: "mock action class", isUnique: false, + dataType: "string", ...commonMockProperties, }; diff --git a/apps/web/lib/time.test.ts b/apps/web/lib/time.test.ts index 9b28d12acb..fac65e4b73 100644 --- a/apps/web/lib/time.test.ts +++ b/apps/web/lib/time.test.ts @@ -141,5 +141,68 @@ describe("Time Utilities", () => { expect(convertDatesInObject("string")).toBe("string"); expect(convertDatesInObject(123)).toBe(123); }); + + test("should not convert dates in ignored keys when keysToIgnore is provided", () => { + const keysToIgnore = new Set(["contactAttributes", "variables", "data", "meta"]); + const input = { + createdAt: "2024-03-20T15:30:00", + contactAttributes: { + createdAt: "2024-03-20T16:30:00", + email: "test@example.com", + }, + }; + + const result = convertDatesInObject(input, keysToIgnore); + expect(result.createdAt).toBeInstanceOf(Date); + expect(result.contactAttributes.createdAt).toBe("2024-03-20T16:30:00"); + expect(result.contactAttributes.email).toBe("test@example.com"); + }); + + test("should not convert dates in variables when keysToIgnore is provided", () => { + const keysToIgnore = new Set(["contactAttributes", "variables", "data", "meta"]); + const input = { + updatedAt: "2024-03-20T15:30:00", + variables: { + createdAt: "2024-03-20T16:30:00", + userId: "123", + }, + }; + + const result = convertDatesInObject(input, keysToIgnore); + expect(result.updatedAt).toBeInstanceOf(Date); + expect(result.variables.createdAt).toBe("2024-03-20T16:30:00"); + expect(result.variables.userId).toBe("123"); + }); + + test("should not convert dates in data or meta when keysToIgnore is provided", () => { + const keysToIgnore = new Set(["contactAttributes", "variables", "data", "meta"]); + const input = { + createdAt: "2024-03-20T15:30:00", + data: { + createdAt: "2024-03-20T16:30:00", + }, + meta: { + updatedAt: "2024-03-20T17:30:00", + }, + }; + + const result = convertDatesInObject(input, keysToIgnore); + expect(result.createdAt).toBeInstanceOf(Date); + expect(result.data.createdAt).toBe("2024-03-20T16:30:00"); + expect(result.meta.updatedAt).toBe("2024-03-20T17:30:00"); + }); + + test("should recurse into all keys when keysToIgnore is not provided", () => { + const input = { + createdAt: "2024-03-20T15:30:00", + contactAttributes: { + createdAt: "2024-03-20T16:30:00", + }, + }; + + const result = convertDatesInObject(input); + expect(result.createdAt).toBeInstanceOf(Date); + expect(result.contactAttributes.createdAt).toBeInstanceOf(Date); + }); }); }); diff --git a/apps/web/lib/time.ts b/apps/web/lib/time.ts index c5a55cc699..0309423f8b 100644 --- a/apps/web/lib/time.ts +++ b/apps/web/lib/time.ts @@ -1,5 +1,5 @@ import { formatDistance, intlFormat } from "date-fns"; -import { de, enUS, es, fr, 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, zhCN, zhTW } from "date-fns/locale"; import { TUserLocale } from "@formbricks/types/user"; export const convertDateString = (dateString: string | null) => { @@ -87,28 +87,30 @@ const getLocaleForTimeSince = (locale: TUserLocale) => { return de; case "en-US": return enUS; - case "pt-BR": - return ptBR; + case "es-ES": + return es; case "fr-FR": return fr; + case "hu-HU": + return hu; + case "ja-JP": + return ja; case "nl-NL": return nl; - case "sv-SE": - return sv; - case "zh-Hant-TW": - return zhTW; + case "pt-BR": + return ptBR; case "pt-PT": return pt; case "ro-RO": return ro; - case "ja-JP": - return ja; - case "zh-Hans-CN": - return zhCN; - case "es-ES": - return es; case "ru-RU": return ru; + case "sv-SE": + return sv; + case "zh-Hans-CN": + return zhCN; + case "zh-Hant-TW": + return zhTW; } }; @@ -149,16 +151,20 @@ export const getTodaysDateTimeFormatted = (seperator: string) => { return [formattedDate, formattedTime].join(seperator); }; -export const convertDatesInObject = (obj: T): T => { +export const convertDatesInObject = (obj: T, keysToIgnore?: Set): T => { if (obj === null || typeof obj !== "object") { return obj; // Return if obj is not an object } if (Array.isArray(obj)) { // Handle arrays by mapping each element through the function - return obj.map((item) => convertDatesInObject(item)) as unknown as T; + return obj.map((item) => convertDatesInObject(item, keysToIgnore)) as unknown as T; } - const newObj: any = {}; + const newObj: Record = {}; for (const key in obj) { + if (keysToIgnore?.has(key)) { + newObj[key] = obj[key]; + continue; + } if ( (key === "createdAt" || key === "updatedAt") && typeof obj[key] === "string" && @@ -166,10 +172,10 @@ export const convertDatesInObject = (obj: T): T => { ) { newObj[key] = new Date(obj[key] as unknown as string); } else if (typeof obj[key] === "object" && obj[key] !== null) { - newObj[key] = convertDatesInObject(obj[key]); + newObj[key] = convertDatesInObject(obj[key], keysToIgnore); } else { newObj[key] = obj[key]; } } - return newObj; + return newObj as T; }; diff --git a/apps/web/lib/utils/action-client/index.test.ts b/apps/web/lib/utils/action-client/index.test.ts new file mode 100644 index 0000000000..89ef831b4c --- /dev/null +++ b/apps/web/lib/utils/action-client/index.test.ts @@ -0,0 +1,261 @@ +import * as Sentry from "@sentry/nextjs"; +import { getServerSession } from "next-auth"; +import { DEFAULT_SERVER_ERROR_MESSAGE } from "next-safe-action"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { + AuthenticationError, + AuthorizationError, + EXPECTED_ERROR_NAMES, + InvalidInputError, + OperationNotAllowedError, + ResourceNotFoundError, + TooManyRequestsError, + UnknownError, + ValidationError, + isExpectedError, +} from "@formbricks/types/errors"; + +// Mock Sentry +vi.mock("@sentry/nextjs", () => ({ + captureException: vi.fn(), +})); + +// Mock logger — use plain functions for chained calls so vi.resetAllMocks() doesn't break them +vi.mock("@formbricks/logger", () => ({ + logger: { + withContext: () => ({ error: vi.fn() }), + warn: vi.fn(), + }, +})); + +// Mock next-auth +vi.mock("next-auth", () => ({ + getServerSession: vi.fn(), +})); + +// Mock authOptions +vi.mock("@/modules/auth/lib/authOptions", () => ({ + authOptions: {}, +})); + +// Mock user service +vi.mock("@/lib/user/service", () => ({ + getUser: vi.fn(), +})); + +// Mock client IP +vi.mock("@/lib/utils/client-ip", () => ({ + getClientIpFromHeaders: vi.fn(), +})); + +// Mock constants +vi.mock("@/lib/constants", () => ({ + AUDIT_LOG_ENABLED: false, + AUDIT_LOG_GET_USER_IP: false, +})); + +// Mock audit log types +vi.mock("@/modules/ee/audit-logs/types/audit-log", () => ({ + UNKNOWN_DATA: "unknown", +})); + +// ── shared helper tests (pure logic, no action client needed) ────────── + +describe("isExpectedError (shared helper)", () => { + test("EXPECTED_ERROR_NAMES contains exactly the right error names", () => { + const expected = [ + "ResourceNotFoundError", + "AuthorizationError", + "InvalidInputError", + "ValidationError", + "AuthenticationError", + "OperationNotAllowedError", + "TooManyRequestsError", + ]; + + expect(EXPECTED_ERROR_NAMES.size).toBe(expected.length); + for (const name of expected) { + expect(EXPECTED_ERROR_NAMES.has(name)).toBe(true); + } + }); + + test.each([ + { ErrorClass: AuthorizationError, args: ["Not authorized"] }, + { ErrorClass: AuthenticationError, args: ["Not authenticated"] }, + { ErrorClass: TooManyRequestsError, args: ["Rate limit exceeded"] }, + { ErrorClass: ResourceNotFoundError, args: ["Survey", "123"] }, + { ErrorClass: InvalidInputError, args: ["Invalid input"] }, + { ErrorClass: ValidationError, args: ["Invalid data"] }, + { ErrorClass: OperationNotAllowedError, args: ["Not allowed"] }, + ])("returns true for $ErrorClass.name", ({ ErrorClass, args }) => { + const error = new (ErrorClass as any)(...args); + expect(isExpectedError(error)).toBe(true); + }); + + test("returns true for serialised errors that only have a matching name", () => { + const serialisedError = new Error("Auth failed"); + serialisedError.name = "AuthorizationError"; + expect(isExpectedError(serialisedError)).toBe(true); + }); + + test.each([ + { error: new Error("Something broke"), label: "Error" }, + { error: new TypeError("Cannot read properties"), label: "TypeError" }, + { error: new RangeError("Maximum call stack"), label: "RangeError" }, + { error: new UnknownError("Unknown"), label: "UnknownError" }, + ])("returns false for $label", ({ error }) => { + expect(isExpectedError(error)).toBe(false); + }); +}); + +// ── integration tests against the real actionClient / authenticatedActionClient ── + +describe("actionClient handleServerError", () => { + // Lazily import so mocks are in place first + let actionClient: (typeof import("./index"))["actionClient"]; + + beforeEach(async () => { + vi.clearAllMocks(); + const mod = await import("./index"); + actionClient = mod.actionClient; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + // Helper: create and execute an action that throws the given error + const executeThrowingAction = async (error: Error) => { + const action = actionClient.action(async () => { + throw error; + }); + return action(); + }; + + describe("expected errors should NOT be reported to Sentry", () => { + test("AuthorizationError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new AuthorizationError("Not authorized")); + expect(result?.serverError).toBe("Not authorized"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("AuthenticationError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new AuthenticationError("Not authenticated")); + expect(result?.serverError).toBe("Not authenticated"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("TooManyRequestsError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new TooManyRequestsError("Rate limit exceeded")); + expect(result?.serverError).toBe("Rate limit exceeded"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("ResourceNotFoundError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new ResourceNotFoundError("Survey", "123")); + expect(result?.serverError).toContain("Survey"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("InvalidInputError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new InvalidInputError("Invalid input")); + expect(result?.serverError).toBe("Invalid input"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("ValidationError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new ValidationError("Invalid data")); + expect(result?.serverError).toBe("Invalid data"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("OperationNotAllowedError returns its message and is not sent to Sentry", async () => { + const result = await executeThrowingAction(new OperationNotAllowedError("Not allowed")); + expect(result?.serverError).toBe("Not allowed"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + }); + + describe("unexpected errors SHOULD be reported to Sentry", () => { + test("generic Error is sent to Sentry and returns default message", async () => { + const error = new Error("Something broke"); + const result = await executeThrowingAction(error); + expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE); + expect(Sentry.captureException).toHaveBeenCalledWith( + error, + expect.objectContaining({ extra: expect.any(Object) }) + ); + }); + + test("TypeError is sent to Sentry and returns default message", async () => { + const error = new TypeError("Cannot read properties of undefined"); + const result = await executeThrowingAction(error); + expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE); + expect(Sentry.captureException).toHaveBeenCalledWith( + error, + expect.objectContaining({ extra: expect.any(Object) }) + ); + }); + + test("UnknownError is sent to Sentry (not an expected business-logic error)", async () => { + const error = new UnknownError("Unknown error"); + const result = await executeThrowingAction(error); + expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE); + expect(Sentry.captureException).toHaveBeenCalledWith( + error, + expect.objectContaining({ extra: expect.any(Object) }) + ); + }); + }); +}); + +describe("authenticatedActionClient", () => { + let authenticatedActionClient: (typeof import("./index"))["authenticatedActionClient"]; + let getUser: (typeof import("@/lib/user/service"))["getUser"]; + + beforeEach(async () => { + vi.clearAllMocks(); + const mod = await import("./index"); + authenticatedActionClient = mod.authenticatedActionClient; + const userService = await import("@/lib/user/service"); + getUser = userService.getUser; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test("throws AuthenticationError when there is no session", async () => { + vi.mocked(getServerSession).mockResolvedValue(null); + + const action = authenticatedActionClient.action(async () => "ok"); + const result = await action(); + + // handleServerError catches AuthenticationError and returns its message + expect(result?.serverError).toBe("Not authenticated"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("throws AuthorizationError when user is not found", async () => { + vi.mocked(getServerSession).mockResolvedValue({ user: { id: "user-1" } }); + vi.mocked(getUser).mockResolvedValue(null as any); + + const action = authenticatedActionClient.action(async () => "ok"); + const result = await action(); + + expect(result?.serverError).toBe("User not found"); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + test("executes action successfully when session and user exist", async () => { + vi.mocked(getServerSession).mockResolvedValue({ user: { id: "user-1" } }); + vi.mocked(getUser).mockResolvedValue({ id: "user-1", name: "Test" } as any); + + const action = authenticatedActionClient.action(async () => "success"); + const result = await action(); + + expect(result?.data).toBe("success"); + expect(result?.serverError).toBeUndefined(); + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/web/lib/utils/action-client/index.ts b/apps/web/lib/utils/action-client/index.ts index 9698b8d6b9..b1b85fb576 100644 --- a/apps/web/lib/utils/action-client/index.ts +++ b/apps/web/lib/utils/action-client/index.ts @@ -3,15 +3,7 @@ import { getServerSession } from "next-auth"; import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action"; import { v4 as uuidv4 } from "uuid"; import { logger } from "@formbricks/logger"; -import { - AuthenticationError, - AuthorizationError, - InvalidInputError, - OperationNotAllowedError, - ResourceNotFoundError, - TooManyRequestsError, - UnknownError, -} from "@formbricks/types/errors"; +import { AuthenticationError, AuthorizationError, isExpectedError } from "@formbricks/types/errors"; import { AUDIT_LOG_ENABLED, AUDIT_LOG_GET_USER_IP } from "@/lib/constants"; import { getUser } from "@/lib/user/service"; import { getClientIpFromHeaders } from "@/lib/utils/client-ip"; @@ -22,24 +14,18 @@ import { ActionClientCtx } from "./types/context"; export const actionClient = createSafeActionClient({ handleServerError(e, utils) { const eventId = (utils.ctx as Record)?.auditLoggingCtx?.eventId ?? undefined; // keep explicit fallback + + if (isExpectedError(e)) { + return e.message; + } + + // Only capture unexpected errors to Sentry Sentry.captureException(e, { extra: { eventId, }, }); - if ( - e instanceof ResourceNotFoundError || - e instanceof AuthorizationError || - e instanceof InvalidInputError || - e instanceof UnknownError || - e instanceof AuthenticationError || - e instanceof OperationNotAllowedError || - e instanceof TooManyRequestsError - ) { - return e.message; - } - // eslint-disable-next-line no-console -- This error needs to be logged for debugging server-side errors logger.withContext({ eventId }).error(e, "SERVER ERROR"); return DEFAULT_SERVER_ERROR_MESSAGE; diff --git a/apps/web/lib/utils/safe-identifier.ts b/apps/web/lib/utils/safe-identifier.ts index c3feed1a1c..ade41657cd 100644 --- a/apps/web/lib/utils/safe-identifier.ts +++ b/apps/web/lib/utils/safe-identifier.ts @@ -11,3 +11,16 @@ export const isSafeIdentifier = (value: string): boolean => { // Can only contain lowercase letters, numbers, and underscores return /^[a-z0-9_]+$/.test(value); }; + +/** + * Converts a snake_case string to Title Case for display as a label. + * Example: "job_description" -> "Job Description" + * "api_key" -> "Api Key" + * "signup_date" -> "Signup Date" + */ +export const formatSnakeCaseToTitleCase = (key: string): string => { + return key + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +}; diff --git a/apps/web/lib/utils/validate.ts b/apps/web/lib/utils/validate.ts index 06cb149cf5..7031da2021 100644 --- a/apps/web/lib/utils/validate.ts +++ b/apps/web/lib/utils/validate.ts @@ -12,11 +12,18 @@ export function validateInputs[]>( for (const [value, schema] of pairs) { const inputValidation = schema.safeParse(value); if (!inputValidation.success) { + const zodDetails = inputValidation.error.issues + .map((issue) => { + const path = issue?.path?.join(".") ?? ""; + return `${path}${issue.message}`; + }) + .join("; "); + logger.error( inputValidation.error, `Validation failed for ${JSON.stringify(value).substring(0, 100)} and ${JSON.stringify(schema)}` ); - throw new ValidationError("Validation failed"); + throw new ValidationError(`Validation failed: ${zodDetails}`); } parsedData.push(inputValidation.data); } diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index 917d023c7b..d5e735480a 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -187,6 +187,7 @@ "customer_success": "Kundenerfolg", "dark_overlay": "Dunkle Überlagerung", "date": "Datum", + "days": "Tage", "default": "Standard", "delete": "Löschen", "description": "Beschreibung", @@ -253,6 +254,7 @@ "label": "Bezeichnung", "language": "Sprache", "learn_more": "Mehr erfahren", + "license_expired": "License Expired", "light_overlay": "Helle Überlagerung", "limits_reached": "Limits erreicht", "link": "Link", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks funktioniert am besten auf einem größeren Bildschirm. Um Umfragen zu verwalten oder zu erstellen, wechsle zu einem anderen Gerät.", "mobile_overlay_surveys_look_good": "Keine Sorge – deine Umfragen sehen auf jedem Gerät und jeder Bildschirmgröße großartig aus!", "mobile_overlay_title": "Oops, Bildschirm zu klein erkannt!", + "months": "Monate", "move_down": "Nach unten bewegen", "move_up": "Nach oben bewegen", "multiple_languages": "Mehrsprachigkeit", @@ -283,6 +286,7 @@ "no_background_image_found": "Kein Hintergrundbild gefunden.", "no_code": "No Code", "no_files_uploaded": "Keine Dateien hochgeladen", + "no_overlay": "Kein Overlay", "no_quotas_found": "Keine Kontingente gefunden", "no_result_found": "Kein Ergebnis gefunden", "no_results": "Keine Ergebnisse", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Organisations-Teams nicht gefunden", "other": "Andere", "others": "Andere", + "overlay_color": "Overlay-Farbe", "overview": "Überblick", "password": "Passwort", "paused": "Pausiert", @@ -348,6 +353,7 @@ "request_trial_license": "Testlizenz anfordern", "reset_to_default": "Auf Standard zurücksetzen", "response": "Antwort", + "response_id": "Antwort-ID", "responses": "Antworten", "restart": "Neustart", "role": "Rolle", @@ -388,6 +394,7 @@ "status": "Status", "step_by_step_manual": "Schritt-für-Schritt-Anleitung", "storage_not_configured": "Dateispeicher nicht eingerichtet, Uploads werden wahrscheinlich fehlschlagen", + "string": "Text", "styling": "Styling", "submit": "Abschicken", "summary": "Zusammenfassung", @@ -443,6 +450,7 @@ "website_and_app_connection": "Website & App Verbindung", "website_app_survey": "Website- & App-Umfrage", "website_survey": "Website-Umfrage", + "weeks": "Wochen", "welcome_card": "Willkommenskarte", "workspace_configuration": "Projektkonfiguration", "workspace_created_successfully": "Projekt erfolgreich erstellt", @@ -453,13 +461,15 @@ "workspace_not_found": "Projekt nicht gefunden", "workspace_permission_not_found": "Projektberechtigung nicht gefunden", "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.", "you_have_reached_your_monthly_miu_limit_of": "Du hast dein monatliches MIU-Limit erreicht", "you_have_reached_your_monthly_response_limit_of": "Du hast dein monatliches Antwortlimit erreicht", - "you_will_be_downgraded_to_the_community_edition_on_date": "Du wirst am {date} auf die Community Edition herabgestuft." + "you_will_be_downgraded_to_the_community_edition_on_date": "Du wirst am {date} auf die Community Edition herabgestuft.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Annehmen", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Attribut erfolgreich aktualisiert", "attribute_value": "Wert", "attribute_value_placeholder": "Attributwert", + "attributes_msg_attribute_limit_exceeded": "Es konnten {count} neue Attribute nicht erstellt werden, da dies das maximale Limit von {limit} Attributklassen überschreiten würde. Bestehende Attribute wurden erfolgreich aktualisiert.", + "attributes_msg_attribute_type_validation_error": "{error} (Attribut '{key}' hat dataType: {dataType})", + "attributes_msg_email_already_exists": "Die E-Mail existiert bereits für diese Umgebung und wurde nicht aktualisiert.", + "attributes_msg_email_or_userid_required": "Entweder E-Mail oder userId ist erforderlich. Die bestehenden Werte wurden beibehalten.", + "attributes_msg_new_attribute_created": "Neues Attribut '{key}' mit Typ '{dataType}' erstellt", + "attributes_msg_userid_already_exists": "Die userId existiert bereits für diese Umgebung und wurde nicht aktualisiert.", "contact_deleted_successfully": "Kontakt erfolgreich gelöscht", "contact_not_found": "Kein solcher Kontakt gefunden", "contacts_table_refresh": "Kontakte aktualisieren", @@ -631,6 +647,11 @@ "create_key": "Schlüssel erstellen", "create_new_attribute": "Neues Attribut erstellen", "create_new_attribute_description": "Erstellen Sie ein neues Attribut für Segmentierungszwecke.", + "custom_attributes": "Benutzerdefinierte Attribute", + "data_type": "Datentyp", + "data_type_cannot_be_changed": "Der Datentyp kann nach der Erstellung nicht mehr geändert werden", + "data_type_description": "Wähle aus, wie dieses Attribut gespeichert und gefiltert werden soll", + "date_value_required": "Ein Datumswert ist erforderlich. Verwende die Löschen-Schaltfläche, um dieses Attribut zu entfernen, wenn du kein Datum festlegen möchtest.", "delete_attribute_confirmation": "{value, plural, one {Dadurch wird das ausgewählte Attribut gelöscht. Alle mit diesem Attribut verknüpften Kontaktdaten gehen verloren.} other {Dadurch werden die ausgewählten Attribute gelöscht. Alle mit diesen Attributen verknüpften Kontaktdaten gehen verloren.}}", "delete_contact_confirmation": "Dies wird alle Umfrageantworten und Kontaktattribute löschen, die mit diesem Kontakt verbunden sind. Jegliche zielgerichtete Kommunikation und Personalisierung basierend auf den Daten dieses Kontakts gehen verloren.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Dies wird alle Umfrageantworten und Kontaktattribute löschen, die mit diesem Kontakt verbunden sind. Jegliche zielgerichtete Kommunikation und Personalisierung basierend auf den Daten dieses Kontakts gehen verloren. Wenn dieser Kontakt Antworten hat, die zu den Umfragequoten zählen, werden die Quotenstände reduziert, aber die Quotenlimits bleiben unverändert.} other {Dies wird alle Umfrageantworten und Kontaktattribute löschen, die mit diesen Kontakten verbunden sind. Jegliche zielgerichtete Kommunikation und Personalisierung basierend auf den Daten dieses Kontakts gehen verloren. Wenn diesen Kontakten Antworten haben, die zu den Umfragequoten zählen, werden die Quotenstände reduziert, aber die Quotenlimits bleiben unverändert.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Aktualisieren Sie die Bezeichnung und Beschreibung für dieses Attribut.", "edit_attribute_values": "Attribute bearbeiten", "edit_attribute_values_description": "Ändern Sie die Werte für bestimmte Attribute dieses Kontakts.", + "edit_attributes": "Attribute bearbeiten", "edit_attributes_success": "Kontaktattribute erfolgreich aktualisiert", "generate_personal_link": "Persönlichen Link generieren", "generate_personal_link_description": "Wähle eine veröffentlichte Umfrage aus, um einen personalisierten Link für diesen Kontakt zu generieren.", + "invalid_csv_column_names": "Ungültige CSV-Spaltennamen: {columns}. Spaltennamen, die zu neuen Attributen werden, dürfen nur Kleinbuchstaben, Zahlen und Unterstriche enthalten und müssen mit einem Buchstaben beginnen.", + "invalid_date_format": "Ungültiges Datumsformat. Bitte verwende ein gültiges Datum.", + "invalid_number_format": "Ungültiges Zahlenformat. Bitte gib eine gültige Zahl ein.", "no_published_link_surveys_available": "Keine veröffentlichten Link-Umfragen verfügbar. Bitte veröffentliche zuerst eine Link-Umfrage.", "no_published_surveys": "Keine veröffentlichten Umfragen", "no_responses_found": "Keine Antworten gefunden", "not_provided": "Nicht angegeben", + "number_value_required": "Zahlenwert ist erforderlich. Verwende die Löschen-Schaltfläche, um dieses Attribut zu entfernen.", "personal_link_generated": "Persönlicher Link erfolgreich generiert", "personal_link_generated_but_clipboard_failed": "Persönlicher Link wurde generiert, konnte aber nicht in die Zwischenablage kopiert werden: {url}", "personal_survey_link": "Link zur persönlichen Umfrage", @@ -653,13 +679,22 @@ "search_contact": "Kontakt suchen", "select_a_survey": "Wähle eine Umfrage aus", "select_attribute": "Attribut auswählen", + "select_attribute_key": "Attributschlüssel auswählen", + "system_attributes": "Systemattribute", "unlock_contacts_description": "Verwalte Kontakte und sende gezielte Umfragen", "unlock_contacts_title": "Kontakte mit einem höheren Plan freischalten", + "upload_contacts_error_attribute_type_mismatch": "Attribut \"{key}\" ist als \"{dataType}\" definiert, aber die CSV-Datei enthält ungültige Werte: {values}", + "upload_contacts_error_duplicate_mappings": "Doppelte Zuordnungen für folgende Attribute gefunden: {attributes}", + "upload_contacts_error_file_too_large": "Dateigröße überschreitet das maximale Limit von 800KB", + "upload_contacts_error_generic": "Beim Hochladen der Kontakte ist ein Fehler aufgetreten. Bitte versuche es später erneut.", + "upload_contacts_error_invalid_file_type": "Bitte lade eine CSV-Datei hoch", + "upload_contacts_error_no_valid_contacts": "Die hochgeladene CSV-Datei enthält keine gültigen Kontakte. Bitte schaue dir die Beispiel-CSV-Datei für das richtige Format an.", + "upload_contacts_modal_attribute_header": "Formbricks-Attribut", "upload_contacts_modal_attributes_description": "Ordne die Spalten in deiner CSV den Attributen in Formbricks zu.", "upload_contacts_modal_attributes_new": "Neues Attribut", "upload_contacts_modal_attributes_search_or_add": "Attribut suchen oder hinzufügen", - "upload_contacts_modal_attributes_should_be_mapped_to": "sollte zugeordnet werden zu", "upload_contacts_modal_attributes_title": "Attribute", + "upload_contacts_modal_csv_column_header": "CSV-Spalte", "upload_contacts_modal_description": "Lade eine CSV hoch, um Kontakte mit Attributen schnell zu importieren", "upload_contacts_modal_download_example_csv": "Beispiel-CSV herunterladen", "upload_contacts_modal_duplicates_description": "Wie sollen wir vorgehen, wenn ein Kontakt bereits existiert?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Noch keine Attribute", "no_filters_yet": "Es gibt noch keine Filter", "no_segments_yet": "Du hast momentan keine gespeicherten Segmente.", + "operator_contains": "enthält", + "operator_does_not_contain": "enthält nicht", + "operator_ends_with": "endet mit", + "operator_is_after": "ist nach", + "operator_is_before": "ist vor", + "operator_is_between": "ist zwischen", + "operator_is_newer_than": "ist neuer als", + "operator_is_not_set": "ist nicht festgelegt", + "operator_is_older_than": "ist älter als", + "operator_is_same_day": "ist am selben Tag", + "operator_is_set": "ist festgelegt", + "operator_starts_with": "fängt an mit", + "operator_title_contains": "Enthält", + "operator_title_does_not_contain": "Enthält nicht", + "operator_title_ends_with": "Endet mit", + "operator_title_equals": "Gleich", + "operator_title_greater_equal": "Größer als oder gleich", + "operator_title_greater_than": "Größer als", + "operator_title_is_after": "Ist nach", + "operator_title_is_before": "Ist vor", + "operator_title_is_between": "Ist zwischen", + "operator_title_is_newer_than": "Ist neuer als", + "operator_title_is_not_set": "Ist nicht festgelegt", + "operator_title_is_older_than": "Ist älter als", + "operator_title_is_same_day": "Ist am selben Tag", + "operator_title_is_set": "Ist festgelegt", + "operator_title_less_equal": "Kleiner oder gleich", + "operator_title_less_than": "Kleiner als", + "operator_title_not_equals": "Ist nicht gleich", + "operator_title_starts_with": "Fängt an mit", + "operator_title_user_is_in": "Nutzer ist in", + "operator_title_user_is_not_in": "Nutzer ist nicht in", + "operator_user_is_in": "Nutzer ist in", + "operator_user_is_not_in": "Nutzer ist nicht in", "person_and_attributes": "Person & Attribute", "phone": "Handy", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Bitte entferne das Segment aus diesen Umfragen, um es zu löschen.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "Benutzerzielgruppen sind derzeit nur verfügbar, wenn", "value_cannot_be_empty": "Wert darf nicht leer sein.", "value_must_be_a_number": "Wert muss eine Zahl sein.", + "value_must_be_positive": "Wert muss eine positive Zahl sein.", "view_filters": "Filter anzeigen", "where": "Wo", "with_the_formbricks_sdk": "mit dem Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "Unternehmensfunktionen", "get_an_enterprise_license_to_get_access_to_all_features": "Hol dir eine Enterprise-Lizenz, um Zugriff auf alle Funktionen zu erhalten.", "keep_full_control_over_your_data_privacy_and_security": "Behalte die volle Kontrolle über deine Daten, Privatsphäre und Sicherheit.", + "license_invalid_description": "Der Lizenzschlüssel in deiner ENTERPRISE_LICENSE_KEY-Umgebungsvariable ist nicht gültig. Bitte überprüfe auf Tippfehler oder fordere einen neuen Schlüssel an.", + "license_status": "Lizenzstatus", + "license_status_active": "Aktiv", + "license_status_description": "Status deiner Enterprise-Lizenz.", + "license_status_expired": "Abgelaufen", + "license_status_invalid": "Ungültige Lizenz", + "license_status_unreachable": "Nicht erreichbar", + "license_unreachable_grace_period": "Der Lizenzserver ist nicht erreichbar. Deine Enterprise-Funktionen bleiben während einer 3-tägigen Kulanzfrist bis zum {gracePeriodEnd} aktiv.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Ganz unkompliziert: Fordere eine kostenlose 30-Tage-Testlizenz an, um alle Funktionen zu testen, indem Du dieses Formular ausfüllst:", "no_credit_card_no_sales_call_just_test_it": "Keine Kreditkarte. Kein Verkaufsgespräch. Einfach testen :)", "on_request": "Auf Anfrage", "organization_roles": "Organisationsrollen (Admin, Editor, Entwickler, etc.)", "questions_please_reach_out_to": "Fragen? Bitte melde Dich bei", + "recheck_license": "Lizenz erneut prüfen", + "recheck_license_failed": "Lizenzprüfung fehlgeschlagen. Der Lizenzserver ist möglicherweise nicht erreichbar.", + "recheck_license_invalid": "Der Lizenzschlüssel ist ungültig. Bitte überprüfe deinen ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Lizenzprüfung erfolgreich", + "recheck_license_unreachable": "Lizenzserver ist nicht erreichbar. Bitte versuche es später erneut.", + "rechecking": "Wird erneut geprüft...", "request_30_day_trial_license": "30-Tage-Testlizenz anfordern", "saml_sso": "SAML-SSO", "service_level_agreement": "Service-Level-Vereinbarung", "soc2_hipaa_iso_27001_compliance_check": "SOC2-, HIPAA- und ISO 27001-Konformitätsprüfung", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Teams & Zugriffskontrolle (Lesen, Lesen & Schreiben, Verwalten)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos.", - "your_enterprise_license_is_active_all_features_unlocked": "Deine Unternehmenslizenz ist aktiv. Alle Funktionen freigeschaltet." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos." }, "general": { "bulk_invite_warning_description": "Bitte beachte, dass im Free-Plan alle Organisationsmitglieder automatisch die Rolle \"Owner\" zugewiesen bekommen, unabhängig von der im CSV-File angegebenen Rolle.", @@ -986,7 +1069,7 @@ "from_your_organization": "von deiner Organisation", "invitation_sent_once_more": "Einladung nochmal gesendet.", "invite_deleted_successfully": "Einladung erfolgreich gelöscht", - "invited_on": "Eingeladen am {date}", + "invite_expires_on": "Einladung läuft ab am {date}", "invites_failed": "Einladungen fehlgeschlagen", "leave_organization": "Organisation verlassen", "leave_organization_description": "Du wirst diese Organisation verlassen und den Zugriff auf alle Umfragen und Antworten verlieren. Du kannst nur wieder beitreten, wenn Du erneut eingeladen wirst.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Bitte füllen Sie alle Felder aus, um einen neuen Workspace hinzuzufügen.", "read": "Lesen", "read_write": "Lesen & Schreiben", - "select_member": "Mitglied auswählen", - "select_workspace": "Workspace auswählen", "team_admin": "Team-Admin", "team_created_successfully": "Team erfolgreich erstellt.", "team_deleted_successfully": "Team erfolgreich gelöscht.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Platzhalter hinzufügen, falls kein Wert zur Verfügung steht.", "add_hidden_field_id": "Verstecktes Feld ID hinzufügen", "add_highlight_border": "Rahmen hinzufügen", - "add_highlight_border_description": "Füge deiner Umfragekarte einen äußeren Rahmen hinzu.", "add_logic": "Logik hinzufügen", "add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu", "add_option": "Option hinzufügen", @@ -1189,6 +1269,7 @@ "block_duplicated": "Block dupliziert.", "bold": "Fett", "brand_color": "Markenfarbe", + "brand_color_description": "Wird auf Buttons, Links und Hervorhebungen angewendet.", "brightness": "Helligkeit", "bulk_edit": "Massenbearbeitung", "bulk_edit_description": "Bearbeiten Sie alle Optionen unten, eine pro Zeile. Leere Zeilen werden übersprungen und Duplikate entfernt.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Neue Aktion erfassen", "card_arrangement_for_survey_type_derived": "Kartenanordnung für {surveyTypeDerived} Umfragen", "card_background_color": "Hintergrundfarbe der Karte", + "card_background_color_description": "Füllt den Bereich der Umfragekarte.", "card_border_color": "Farbe des Kartenrandes", + "card_border_color_description": "Umrandet die Umfragekarte.", "card_styling": "Kartengestaltung", "casual": "Lässig", "caution_edit_duplicate": "Duplizieren & bearbeiten", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Ältere und neuere Antworten vermischen sich, was zu irreführenden Datensummen führen kann.", "caution_recommendation": "Dies kann im Umfrageübersicht zu Dateninkonsistenzen führen. Wir empfehlen stattdessen, die Umfrage zu duplizieren.", "caution_text": "Änderungen werden zu Inkonsistenzen führen", - "centered_modal_overlay_color": "Zentrierte modale Überlagerungsfarbe", "change_anyway": "Trotzdem ändern", "change_background": "Hintergrund ändern", "change_question_type": "Fragetyp ändern", "change_survey_type": "Die Änderung des Umfragetypen kann vorhandenen Zugriff beeinträchtigen", - "change_the_background_color_of_the_card": "Hintergrundfarbe der Karte ändern.", - "change_the_background_color_of_the_input_fields": "Hintergrundfarbe der Eingabefelder ändern.", "change_the_background_to_a_color_image_or_animation": "Hintergrund zu einer Farbe, einem Bild oder einer Animation ändern.", - "change_the_border_color_of_the_card": "Randfarbe der Karte ändern.", - "change_the_border_color_of_the_input_fields": "Randfarbe der Eingabefelder ändern.", - "change_the_border_radius_of_the_card_and_the_inputs": "Radius der Ränder der Karte und der Eingabefelder ändern.", - "change_the_brand_color_of_the_survey": "Markenfarbe der Umfrage ändern.", "change_the_placement_of_this_survey": "Platzierung dieser Umfrage ändern.", - "change_the_question_color_of_the_survey": "Fragefarbe der Umfrage ändern.", "changes_saved": "Änderungen gespeichert.", "changing_survey_type_will_remove_existing_distribution_channels": "\"Das Ändern des Umfragetypen beeinflusst, wie er geteilt werden kann. Wenn Teilnehmer bereits Zugriffslinks für den aktuellen Typ haben, könnten sie das Zugriffsrecht nach dem Wechsel verlieren.\"", "checkbox_label": "Checkbox-Beschriftung", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Deaktiviere die Sichtbarkeit des Umfragefortschritts.", "display_an_estimate_of_completion_time_for_survey": "Zeige eine Schätzung der Fertigstellungszeit für die Umfrage an", "display_number_of_responses_for_survey": "Anzahl der Antworten für Umfrage anzeigen", + "display_type": "Anzeigetyp", "divide": "Teilen /", "does_not_contain": "Enthält nicht", "does_not_end_with": "Endet nicht mit", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Enthält nicht alle von", "does_not_include_one_of": "Enthält nicht eines von", "does_not_start_with": "Fängt nicht an mit", + "dropdown": "Dropdown", "duplicate_block": "Block duplizieren", "duplicate_question": "Frage duplizieren", "edit_link": "Bearbeitungslink", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Fortschrittsbalken ausblenden", "hide_question_settings": "Frageeinstellungen ausblenden", "hostname": "Hostname", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Wie funky sollen deine Karten in {surveyTypeDerived} Umfragen sein", "if_you_need_more_please": "Wenn Sie mehr benötigen, bitte", "if_you_really_want_that_answer_ask_until_you_get_it": "Weiterhin anzeigen, wenn ausgelöst, bis eine Antwort abgegeben wird.", "ignore_global_waiting_time": "Abkühlphase ignorieren", @@ -1379,7 +1455,9 @@ "initial_value": "Anfangswert", "inner_text": "Innerer Text", "input_border_color": "Randfarbe des Eingabefelds", + "input_border_color_description": "Umrandet Texteingaben und Textbereiche.", "input_color": "Farbe des Eingabefelds", + "input_color_description": "Füllt das Innere von Texteingaben.", "insert_link": "Link einfügen", "invalid_targeting": "Ungültiges Targeting: Bitte überprüfe deine Zielgruppenfilter", "invalid_video_url_warning": "Bitte gib eine gültige YouTube-, Vimeo- oder Loom-URL ein. Andere Video-Plattformen werden derzeit nicht unterstützt.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Begrenzen Sie die maximale Dateigröße für Uploads.", "limit_upload_file_size_to": "Upload-Dateigröße begrenzen auf", "link_survey_description": "Teile einen Link zu einer Umfrageseite oder bette ihn in eine Webseite oder E-Mail ein.", + "list": "Liste", "load_segment": "Segment laden", "logic_error_warning": "Änderungen werden zu Logikfehlern führen", "logic_error_warning_text": "Das Ändern des Fragetypen entfernt die Logikbedingungen von dieser Frage", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Nur Benutzer, die die PIN haben, können auf die Umfrage zugreifen.", "publish": "Veröffentlichen", "question": "Frage", - "question_color": "Fragefarbe", "question_deleted": "Frage gelöscht.", "question_duplicated": "Frage dupliziert.", "question_id_updated": "Frage-ID aktualisiert", "question_used_in_logic_warning_text": "Elemente aus diesem Block werden in einer Logikregel verwendet. Möchten Sie ihn wirklich löschen?", "question_used_in_logic_warning_title": "Logikinkonsistenz", - "question_used_in_quota": "Diese Frage wird in der \"{quotaName}\" Quote verwendet", + "question_used_in_quota": "Diese Frage wird in der “{quotaName}” Quote verwendet", "question_used_in_recall": "Diese Frage wird in Frage {questionIndex} abgerufen.", "question_used_in_recall_ending_card": "Diese Frage wird in der Abschlusskarte abgerufen.", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Antwort Limits, Weiterleitungen und mehr.", "response_options": "Antwortoptionen", "roundness": "Rundheit", + "roundness_description": "Steuert, wie abgerundet die Kartenecken sind.", "row_used_in_logic_error": "Diese Zeile wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.", "rows": "Zeilen", "save_and_close": "Speichern & Schließen", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Styling auf Themenstile eingestellt", "subheading": "Zwischenüberschrift", "subtract": "Subtrahieren -", - "suggest_colors": "Farben vorschlagen", "survey_completed_heading": "Umfrage abgeschlossen", "survey_completed_subheading": "Diese kostenlose und quelloffene Umfrage wurde geschlossen", "survey_display_settings": "Einstellungen zur Anzeige der Umfrage", @@ -1642,7 +1720,7 @@ "validation_rules": "Validierungsregeln", "validation_rules_description": "Nur Antworten akzeptieren, die die folgenden Kriterien erfüllen", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne es zuerst aus der Logik.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable \"{variableName}\" wird in der \"{quotaName}\" Quote verwendet", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable “{variableName}” wird in der “{quotaName}” Quote verwendet", "variable_name_conflicts_with_hidden_field": "Der Variablenname steht im Konflikt mit einer vorhandenen Hidden-Field-ID.", "variable_name_is_already_taken_please_choose_another": "Variablenname ist bereits vergeben, bitte wähle einen anderen.", "variable_name_must_start_with_a_letter": "Variablenname muss mit einem Buchstaben beginnen.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Hintergrundfarbe hinzufügen", "add_background_color_description": "Füge dem Logo-Container eine Hintergrundfarbe hinzu.", + "advanced_styling_field_border_radius": "Rahmenradius", + "advanced_styling_field_button_bg": "Button-Hintergrund", + "advanced_styling_field_button_bg_description": "Füllt den Weiter-/Absenden-Button.", + "advanced_styling_field_button_border_radius_description": "Rundet die Button-Ecken ab.", + "advanced_styling_field_button_font_size_description": "Skaliert den Text der Button-Beschriftung.", + "advanced_styling_field_button_font_weight_description": "Macht den Button-Text heller oder fetter.", + "advanced_styling_field_button_height_description": "Steuert die Button-Höhe.", + "advanced_styling_field_button_padding_x_description": "Fügt links und rechts Abstand hinzu.", + "advanced_styling_field_button_padding_y_description": "Fügt oben und unten Abstand hinzu.", + "advanced_styling_field_button_text": "Button-Text", + "advanced_styling_field_button_text_description": "Färbt die Beschriftung innerhalb von Buttons.", + "advanced_styling_field_description_color": "Beschreibungsfarbe", + "advanced_styling_field_description_color_description": "Färbt den Text unterhalb jeder Überschrift.", + "advanced_styling_field_description_size": "Schriftgröße der Beschreibung", + "advanced_styling_field_description_size_description": "Skaliert den Beschreibungstext.", + "advanced_styling_field_description_weight": "Schriftstärke der Beschreibung", + "advanced_styling_field_description_weight_description": "Macht den Beschreibungstext heller oder fetter.", + "advanced_styling_field_font_size": "Schriftgröße", + "advanced_styling_field_font_weight": "Schriftstärke", + "advanced_styling_field_headline_color": "Überschriftsfarbe", + "advanced_styling_field_headline_color_description": "Färbt den Hauptfragetext.", + "advanced_styling_field_headline_size": "Schriftgröße der Überschrift", + "advanced_styling_field_headline_size_description": "Skaliert den Überschriftentext.", + "advanced_styling_field_headline_weight": "Schriftstärke der Überschrift", + "advanced_styling_field_headline_weight_description": "Macht den Überschriftentext heller oder fetter.", + "advanced_styling_field_height": "Mindesthöhe", + "advanced_styling_field_indicator_bg": "Indikator-Hintergrund", + "advanced_styling_field_indicator_bg_description": "Färbt den gefüllten Teil des Balkens.", + "advanced_styling_field_input_border_radius_description": "Rundet die Eingabeecken ab.", + "advanced_styling_field_input_font_size_description": "Skaliert den eingegebenen Text in Eingabefeldern.", + "advanced_styling_field_input_height_description": "Legt die Mindesthöhe des Eingabefelds fest.", + "advanced_styling_field_input_padding_x_description": "Fügt links und rechts Abstand hinzu.", + "advanced_styling_field_input_padding_y_description": "Fügt oben und unten Abstand hinzu.", + "advanced_styling_field_input_placeholder_opacity_description": "Blendet den Platzhaltertext aus.", + "advanced_styling_field_input_shadow_description": "Fügt einen Schlagschatten um Eingabefelder hinzu.", + "advanced_styling_field_input_text": "Eingabetext", + "advanced_styling_field_input_text_description": "Färbt den eingegebenen Text in Eingabefeldern.", + "advanced_styling_field_option_bg": "Hintergrund", + "advanced_styling_field_option_bg_description": "Füllt die Optionselemente.", + "advanced_styling_field_option_border_radius_description": "Rundet die Ecken der Optionen ab.", + "advanced_styling_field_option_font_size_description": "Skaliert den Text der Optionsbeschriftung.", + "advanced_styling_field_option_label": "Label-Farbe", + "advanced_styling_field_option_label_description": "Färbt den Text der Optionsbeschriftung.", + "advanced_styling_field_option_padding_x_description": "Fügt links und rechts Abstand hinzu.", + "advanced_styling_field_option_padding_y_description": "Fügt oben und unten Abstand hinzu.", + "advanced_styling_field_padding_x": "Innenabstand X", + "advanced_styling_field_padding_y": "Innenabstand Y", + "advanced_styling_field_placeholder_opacity": "Platzhalter-Deckkraft", + "advanced_styling_field_shadow": "Schatten", + "advanced_styling_field_track_bg": "Track-Hintergrund", + "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_section_buttons": "Buttons", + "advanced_styling_section_headlines": "Überschriften & Beschreibungen", + "advanced_styling_section_inputs": "Eingabefelder", + "advanced_styling_section_options": "Optionen (Radio/Checkbox)", "app_survey_placement": "Platzierung der App-Umfrage", "app_survey_placement_settings_description": "Ändere, wo Umfragen in deiner Web-App oder Website angezeigt werden.", - "centered_modal_overlay_color": "Zentrierte modale Überlagerungsfarbe", "email_customization": "E-Mail-Anpassung", "email_customization_description": "Ändere das Aussehen und die Gestaltung von E-Mails, die Formbricks in deinem Namen versendet.", "enable_custom_styling": "Benutzerdefiniertes Styling aktivieren", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks-Branding ist ausgeblendet.", "formbricks_branding_settings_description": "Wir freuen uns über deine Unterstützung, haben aber Verständnis, wenn du es ausschaltest.", "formbricks_branding_shown": "Formbricks-Branding wird angezeigt.", + "generate_theme_btn": "Generieren", + "generate_theme_confirmation": "Möchtest du ein passendes Farbschema basierend auf deiner Markenfarbe generieren? Dies überschreibt deine aktuellen Farbeinstellungen.", + "generate_theme_header": "Farbschema generieren?", "logo_removed_successfully": "Logo erfolgreich entfernt", "logo_settings_description": "Lade dein Firmenlogo hoch, um Umfragen und Link-Vorschauen zu branden.", "logo_updated_successfully": "Logo erfolgreich aktualisiert", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Formbricks-Branding in {type}-Umfragen anzeigen", "show_powered_by_formbricks": "\"Powered by Formbricks\"-Signatur anzeigen", "styling_updated_successfully": "Styling erfolgreich aktualisiert", + "suggest_colors": "Farben vorschlagen", + "suggested_colors_applied_please_save": "Vorgeschlagene Farben erfolgreich generiert. Drücke \"Speichern\", um die Änderungen zu übernehmen.", "theme": "Theme", "theme_settings_description": "Erstelle ein Style-Theme für alle Umfragen. Du kannst für jede Umfrage individuelles Styling aktivieren." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Ja, halte mich auf dem Laufenden.", "preview_survey_question_2_choice_2_label": "Nein, danke!", "preview_survey_question_2_headline": "Möchtest Du auf dem Laufenden bleiben?", + "preview_survey_question_2_subheader": "Dies ist eine Beispielbeschreibung.", "preview_survey_welcome_card_headline": "Willkommen!", "prioritize_features_description": "Identifiziere die Funktionen, die deine Nutzer am meisten und am wenigsten brauchen.", "prioritize_features_name": "Funktionen priorisieren", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index e14d6e53d2..b9e04639b6 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -12,7 +12,7 @@ "email_change_success": "Email changed successfully", "email_change_success_description": "You have successfully changed your email address. Please log in with your new email address.", "email_verification_failed": "Email verification failed", - "email_verification_loading": "Email verification in progress...", + "email_verification_loading": "Email verification in progress…", "email_verification_loading_description": "We are updating your email address in our system. This may take a few seconds.", "invalid_or_expired_token": "Email change failed. Your token is invalid or expired.", "new_email": "New Email", @@ -30,7 +30,7 @@ "no_token_provided": "No token provided", "passwords_do_not_match": "Passwords do not match", "success": { - "heading": "Password successfully reset", + "heading": "Password reset successfully", "text": "You can now log in with your new password" } }, @@ -39,17 +39,17 @@ }, "invite": { "create_account": "Create an account", - "email_does_not_match": "Ooops! Wrong email \uD83E\uDD26", + "email_does_not_match": "Ooops! Wrong email 🤦", "email_does_not_match_description": "The email in the invitation does not match yours.", "go_to_app": "Go to app", - "happy_to_have_you": "Happy to have you \uD83E\uDD17", + "happy_to_have_you": "Happy to have you 🤗", "happy_to_have_you_description": "Please create an account or login.", - "invite_expired": "Invite expired \uD83D\uDE25", + "invite_expired": "Invite expired 😥", "invite_expired_description": "Invites are valid for 7 days. Please request a new invite.", - "invite_not_found": "Invite not found \uD83D\uDE25", + "invite_not_found": "Invite not found 😥", "invite_not_found_description": "The invitation code cannot be found or has already been used.", "login": "Login", - "welcome_to_organization": "You’re in \uD83C\uDF89", + "welcome_to_organization": "You are in \uD83C\uDF89", "welcome_to_organization_description": "Welcome to the organization." }, "last_used": "Last Used", @@ -83,8 +83,8 @@ "title": "Create your Formbricks account" }, "signup_without_verification_success": { - "user_successfully_created": "User successfully created", - "user_successfully_created_info": "We’ve checked for an account associated with {email}. If none existed, we’ve created one for you. If an account already existed, no changes were made. Please log in below to continue." + "user_successfully_created": "User created successfully", + "user_successfully_created_info": "We have checked for an account associated with {email}. If none existed, we have created one for you. If an account already existed, no changes were made. Please log in below to continue." }, "verification-requested": { "invalid_email_address": "Invalid email address", @@ -94,12 +94,12 @@ "please_confirm_your_email_address": "Please confirm your email address", "resend_verification_email": "Resend verification email", "verification_email_resent_successfully": "Verification email sent! Please check your inbox.", - "verification_email_successfully_sent_info": "If there’s an account associated with {email}, we’ve sent a verification link to that address. Please check your inbox to complete the sign-up.", - "you_didnt_receive_an_email_or_your_link_expired": "You didn't receive an email or your link expired?" + "verification_email_successfully_sent_info": "If there is an account associated with {email}, we have sent a verification link to that address. Please check your inbox to complete the sign-up.", + "you_didnt_receive_an_email_or_your_link_expired": "You did not receive an email or your link expired?" }, "verify": { "no_token_provided": "No Token provided", - "verifying": "Verifying..." + "verifying": "Verifying…" } }, "billing_confirmation": { @@ -187,14 +187,15 @@ "customer_success": "Customer Success", "dark_overlay": "Dark overlay", "date": "Date", + "days": "days", "default": "Default", "delete": "Delete", "description": "Description", "dev_env": "Dev Environment", "development": "Development", - "development_environment_banner": "You're in a development environment. Set it up to test surveys, actions and attributes.", + "development_environment_banner": "You are in a development environment. Set it up to test surveys, actions and attributes.", "disable": "Disable", - "disallow": "Don't allow", + "disallow": "Do not allow", "discard": "Discard", "dismissed": "Dismissed", "docs": "Documentation", @@ -212,9 +213,9 @@ "enterprise_license": "Enterprise License", "environment": "Environment", "environment_not_found": "Environment not found", - "environment_notice": "You're currently in the {environment} environment.", + "environment_notice": "You are currently in the {environment} environment.", "error": "Error", - "error_component_description": "This resource doesn't exist or you don't have the necessary rights to access it.", + "error_component_description": "This resource does not exist or you do not have the necessary rights to access it.", "error_component_title": "Error loading resources", "error_rate_limit_description": "Maximum number of requests reached. Please try again later.", "error_rate_limit_title": "Rate Limit Exceeded", @@ -253,6 +254,7 @@ "label": "Label", "language": "Language", "learn_more": "Learn more", + "license_expired": "License Expired", "light_overlay": "Light overlay", "limits_reached": "Limits Reached", "link": "Link", @@ -271,8 +273,9 @@ "membership_not_found": "Membership not found", "metadata": "Metadata", "mobile_overlay_app_works_best_on_desktop": "Formbricks works best on a bigger screen. To manage or build surveys, switch to another device.", - "mobile_overlay_surveys_look_good": "Don't worry – your surveys look great on every device and screen size!", + "mobile_overlay_surveys_look_good": "Do not worry – your surveys look great on every device and screen size!", "mobile_overlay_title": "Oops, tiny screen detected!", + "months": "months", "move_down": "Move down", "move_up": "Move up", "multiple_languages": "Multiple languages", @@ -283,6 +286,7 @@ "no_background_image_found": "No background image found.", "no_code": "No code", "no_files_uploaded": "No files were uploaded", + "no_overlay": "No overlay", "no_quotas_found": "No quotas found", "no_result_found": "No result found", "no_results": "No results", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Organization teams not found", "other": "Other", "others": "Others", + "overlay_color": "Overlay color", "overview": "Overview", "password": "Password", "paused": "Paused", @@ -348,6 +353,7 @@ "request_trial_license": "Request trial license", "reset_to_default": "Reset to default", "response": "Response", + "response_id": "Response ID", "responses": "Responses", "restart": "Restart", "role": "Role", @@ -388,6 +394,7 @@ "status": "Status", "step_by_step_manual": "Step by step manual", "storage_not_configured": "File storage not set up, uploads will likely fail", + "string": "Text", "styling": "Styling", "submit": "Submit", "summary": "Summary", @@ -443,6 +450,7 @@ "website_and_app_connection": "Website & App Connection", "website_app_survey": "Website & App Survey", "website_survey": "Website Survey", + "weeks": "weeks", "welcome_card": "Welcome card", "workspace_configuration": "Workspace Configuration", "workspace_created_successfully": "Workspace created successfully", @@ -453,13 +461,15 @@ "workspace_not_found": "Workspace not found", "workspace_permission_not_found": "Workspace permission not found", "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.", "you_have_reached_your_monthly_miu_limit_of": "You have reached your monthly MIU limit of", "you_have_reached_your_monthly_response_limit_of": "You have reached your monthly response limit of", - "you_will_be_downgraded_to_the_community_edition_on_date": "You will be downgraded to the Community Edition on {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "You will be downgraded to the Community Edition on {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Accept", @@ -470,14 +480,14 @@ "email_footer_text_1": "Have a great day!", "email_footer_text_2": "The Formbricks Team", "email_template_text_1": "This email was sent via Formbricks.", - "embed_survey_preview_email_didnt_request": "Didn't request this?", + "embed_survey_preview_email_didnt_request": "Did not request this?", "embed_survey_preview_email_environment_id": "Environment ID", "embed_survey_preview_email_fight_spam": "Help us fight spam and forward this mail to hola@formbricks.com", "embed_survey_preview_email_heading": "Preview Email Embed", "embed_survey_preview_email_subject": "Formbricks Email Survey Preview", "embed_survey_preview_email_text": "This is how the code snippet looks embedded into an email:", "forgot_password_email_change_password": "Change password", - "forgot_password_email_did_not_request": "If you didn't request this, please ignore this email.", + "forgot_password_email_did_not_request": "If you did not request this, please ignore this email.", "forgot_password_email_heading": "Change password", "forgot_password_email_link_valid_for_24_hours": "The link is valid for 24 hours.", "forgot_password_email_subject": "Reset your Formbricks password", @@ -485,12 +495,12 @@ "hidden_field": "Hidden field", "imprint": "Imprint", "invite_accepted_email_heading": "Hey {inviterName}", - "invite_accepted_email_subject": "You've got a new organization member!", + "invite_accepted_email_subject": "You have got a new organization member!", "invite_accepted_email_text": "Just letting you know that {inviteeName} accepted your invitation. Have fun collaborating!", "invite_email_button_label": "Join organization", "invite_email_heading": "Hey {inviteeName}", "invite_email_text": "Your colleague {inviterName} invited you to join them at Formbricks. To accept the invitation, please click the link below:", - "invite_member_email_subject": "You're invited to collaborate on Formbricks!", + "invite_member_email_subject": "You are invited to collaborate on Formbricks!", "new_email_verification_text": "To verify your new email address, please click the button below:", "number_variable": "Number variable", "password_changed_email_heading": "Password changed", @@ -505,7 +515,7 @@ "schedule_your_meeting": "Schedule your meeting", "select_a_date": "Select a date", "survey_response_finished_email_congrats": "Congrats, you received a new response to your survey! Someone just completed your survey: {surveyName}", - "survey_response_finished_email_dont_want_notifications": "Don't want to get these notifications?", + "survey_response_finished_email_dont_want_notifications": "Do not want to get these notifications?", "survey_response_finished_email_hey": "Hey \uD83D\uDC4B", "survey_response_finished_email_turn_off_notifications_for_all_new_forms": "Turn off notifications for all newly created forms", "survey_response_finished_email_turn_off_notifications_for_this_form": "Turn off notifications for this form", @@ -514,7 +524,7 @@ "text_variable": "Text variable", "verification_email_click_on_this_link": "You can also click on this link:", "verification_email_heading": "Almost there!", - "verification_email_hey": "Hey \uD83D\uDC4B", + "verification_email_hey": "Hey 👋", "verification_email_if_expired_request_new_token": "If it has expired please request a new token here:", "verification_email_link_valid_for_24_hours": "The link is valid for 24 hours.", "verification_email_request_new_verification": "Request new verification", @@ -598,14 +608,14 @@ }, "connect": { "congrats": "Congrats!", - "connection_successful_message": "Well done! We're connected.", - "do_it_later": "I'll do it later", + "connection_successful_message": "Well done! We are connected.", + "do_it_later": "I will do it later", "finish_onboarding": "Finish Onboarding", "headline": "Connect your app or website", "import_formbricks_and_initialize_the_widget_in_your_component": "Import Formbricks and initialize the widget in your Component (e.g. App.tsx):", "insert_this_code_into_the_head_tag_of_your_website": "Insert this code into the head tag of your website:", "subtitle": "It takes less than 4 minutes.", - "waiting_for_your_signal": "Waiting for your signal..." + "waiting_for_your_signal": "Waiting for your signal…" }, "contacts": { "add_attribute": "Add Attribute", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Attribute updated successfully", "attribute_value": "Value", "attribute_value_placeholder": "Attribute Value", + "attributes_msg_attribute_limit_exceeded": "Could not create {count} new attribute(s) as it would exceed the maximum limit of {limit} attribute classes. Existing attributes were updated successfully.", + "attributes_msg_attribute_type_validation_error": "{error} (attribute '{key}' has dataType: {dataType})", + "attributes_msg_email_already_exists": "The email already exists for this environment and was not updated.", + "attributes_msg_email_or_userid_required": "Either email or userId is required. The existing values were preserved.", + "attributes_msg_new_attribute_created": "Created new attribute '{key}' with type '{dataType}'", + "attributes_msg_userid_already_exists": "The userId already exists for this environment and was not updated.", "contact_deleted_successfully": "Contact deleted successfully", "contact_not_found": "No such contact found", "contacts_table_refresh": "Refresh contacts", @@ -631,35 +647,54 @@ "create_key": "Create Key", "create_new_attribute": "Create new attribute", "create_new_attribute_description": "Create a new attribute for segmentation purposes.", + "custom_attributes": "Custom Attributes", + "data_type": "Data Type", + "data_type_cannot_be_changed": "Data type cannot be changed after creation", + "data_type_description": "Choose how this attribute should be stored and filtered", + "date_value_required": "Date value is required. Use the delete button to remove this attribute if you don't want to set a date.", "delete_attribute_confirmation": "{value, plural, one {This will delete the selected attribute. Any contact data associated with this attribute will be lost.} other {This will delete the selected attributes. Any contact data associated with these attributes will be lost.}}", - "delete_contact_confirmation": "This will delete all survey responses and contact attributes associated with this contact. Any targeting and personalization based on this contact's data will be lost.", - "delete_contact_confirmation_with_quotas": "{value, plural, one {This will delete all survey responses and contact attributes associated with this contact. Any targeting and personalization based on this contact's data will be lost. If this contact has responses that count towards survey quotas, the quota counts will be reduced but the quota limits will remain unchanged.} other {This will delete all survey responses and contact attributes associated with these contacts. Any targeting and personalization based on these contacts' data will be lost. If these contacts have responses that count towards survey quotas, the quota counts will be reduced but the quota limits will remain unchanged.}}", + "delete_contact_confirmation": "This will delete all survey responses and contact attributes associated with this contact. Any targeting and personalization based on this contact’s data will be lost.", + "delete_contact_confirmation_with_quotas": "{value, plural, one {This will delete all survey responses and contact attributes associated with this contact. Any targeting and personalization based on this contact’s data will be lost. If this contact has responses that count towards survey quotas, the quota counts will be reduced but the quota limits will remain unchanged.} other {This will delete all survey responses and contact attributes associated with these contacts. Any targeting and personalization based on these contacts’ data will be lost. If these contacts have responses that count towards survey quotas, the quota counts will be reduced but the quota limits will remain unchanged.}}", "edit_attribute": "Edit attribute", "edit_attribute_description": "Update the label and description for this attribute.", "edit_attribute_values": "Edit attributes", "edit_attribute_values_description": "Change the values for specific attributes for this contact.", + "edit_attributes": "Edit Attributes", "edit_attributes_success": "Contact attributes updated successfully", "generate_personal_link": "Generate Personal Link", "generate_personal_link_description": "Select a published survey to generate a personalized link for this contact.", + "invalid_csv_column_names": "Invalid CSV column name(s): {columns}. Column names that will become new attributes must only contain lowercase letters, numbers, and underscores, and must start with a letter.", + "invalid_date_format": "Invalid date format. Please use a valid date.", + "invalid_number_format": "Invalid number format. Please enter a valid number.", "no_published_link_surveys_available": "No published link surveys available. Please publish a link survey first.", "no_published_surveys": "No published surveys", "no_responses_found": "No responses found", "not_provided": "Not provided", + "number_value_required": "Number value is required. Use the delete button to remove this attribute.", "personal_link_generated": "Personal link generated successfully", "personal_link_generated_but_clipboard_failed": "Personal link generated but failed to copy to clipboard: {url}", "personal_survey_link": "Personal Survey Link", "please_select_a_survey": "Please select a survey", - "search_attribute_keys": "Search attribute keys...", + "search_attribute_keys": "Search attribute keys…", "search_contact": "Search contact", "select_a_survey": "Select a survey", "select_attribute": "Select Attribute", + "select_attribute_key": "Select attribute key", + "system_attributes": "System Attributes", "unlock_contacts_description": "Manage contacts and send out targeted surveys", "unlock_contacts_title": "Unlock contacts with a higher plan", + "upload_contacts_error_attribute_type_mismatch": "Attribute \"{key}\" is typed as \"{dataType}\" but CSV contains invalid values: {values}", + "upload_contacts_error_duplicate_mappings": "Duplicate mappings found for the following attributes: {attributes}", + "upload_contacts_error_file_too_large": "File size exceeds the maximum limit of 800KB", + "upload_contacts_error_generic": "An error occurred while uploading the contacts. Please try again later.", + "upload_contacts_error_invalid_file_type": "Please upload a CSV file", + "upload_contacts_error_no_valid_contacts": "The uploaded CSV file does not contain any valid contacts, please see the sample CSV file for the correct format.", + "upload_contacts_modal_attribute_header": "Formbricks Attribute", "upload_contacts_modal_attributes_description": "Map the columns in your CSV to the attributes in Formbricks.", "upload_contacts_modal_attributes_new": "New attribute", "upload_contacts_modal_attributes_search_or_add": "Search or add attribute", - "upload_contacts_modal_attributes_should_be_mapped_to": "should be mapped to", "upload_contacts_modal_attributes_title": "Attributes", + "upload_contacts_modal_csv_column_header": "CSV Column", "upload_contacts_modal_description": "Upload a CSV to quickly import contacts with attributes", "upload_contacts_modal_download_example_csv": "Download example CSV", "upload_contacts_modal_duplicates_description": "How should we handle if a contact already exists in your contacts?", @@ -671,7 +706,7 @@ "upload_contacts_modal_duplicates_update_description": "Updates the existing contacts", "upload_contacts_modal_duplicates_update_title": "Update", "upload_contacts_modal_pick_different_file": "Pick a different file", - "upload_contacts_modal_preview": "Here's a preview of your data.", + "upload_contacts_modal_preview": "Here is a preview of your data.", "upload_contacts_modal_upload_btn": "Upload contacts", "upload_contacts_success": "Contacts uploaded successfully" }, @@ -729,7 +764,7 @@ "manage_webhooks": "Manage Webhooks", "n8n_integration_description": "Integrate Formbricks with 350+ apps via n8n", "notion": { - "col_name_of_type_is_not_supported": "{col_name} of type {type} is not supported by notion API. The data won't be reflected in your notion database.", + "col_name_of_type_is_not_supported": "{col_name} of type {type} is not supported by notion API. The data will not be reflected in your notion database.", "connect_with_notion": "Connect with Notion", "connected_with_workspace": "Connected with {workspace} workspace", "create_at_least_one_database_to_setup_this_integration": "You have to create at least one database to be able to setup this integration", @@ -748,7 +783,7 @@ "please_resolve_mapping_errors": "Please resolve the mapping errors", "please_select_a_database": "Please select a database", "please_select_at_least_one_mapping": "Please select at least one mapping", - "que_name_of_type_cant_be_mapped_to": "{que_name} of type {question_label} can't be mapped to the column {col_name} of type {col_type}. Instead use column of type {mapped_type}.", + "que_name_of_type_cant_be_mapped_to": "{que_name} of type {question_label} cannot be mapped to the column {col_name} of type {col_type}. Instead use column of type {mapped_type}.", "select_a_database": "Select Database", "select_a_field_to_map": "Select a field to map", "select_a_survey_question": "Select a survey question", @@ -765,7 +800,7 @@ "connect_your_first_slack_channel": "Connect your first Slack channel to get started.", "connected_with_team": "Connected with {team}", "create_at_least_one_channel_error": "You have to create at least one channel to be able to setup this integration", - "dont_see_your_channel": "Don't see your channel?", + "dont_see_your_channel": "Do not see your channel?", "link_channel": "Link channel", "link_slack_channel": "Link Slack Channel", "please_select_a_channel": "Please select a channel", @@ -808,7 +843,7 @@ "webhook_deleted_successfully": "Webhook deleted successfully", "webhook_name_placeholder": "Optional: Label your webhook for easy identification", "webhook_test_failed_due_to": "Webhook Test Failed due to", - "webhook_updated_successfully": "Webhook updated successfully.", + "webhook_updated_successfully": "Webhook updated successfully", "webhook_url_placeholder": "Paste the URL you want the event to trigger on" }, "website_or_app_integration_description": "Integrate Formbricks into your Website or App", @@ -817,7 +852,7 @@ "segments": { "add_filter_below": "Add filter below", "add_your_first_filter_to_get_started": "Add your first filter to get started", - "cannot_delete_segment_used_in_surveys": "You cannot delete this segment since it’s still used in these surveys:", + "cannot_delete_segment_used_in_surveys": "You cannot delete this segment since it is still used in these surveys:", "clone_and_edit_segment": "Clone & Edit Segment", "create_group": "Create group", "create_your_first_segment": "Create your first Segment to get started", @@ -840,6 +875,40 @@ "no_attributes_yet": "No attributes yet!", "no_filters_yet": "There are no filters yet!", "no_segments_yet": "You currently have no saved segments.", + "operator_contains": "contains", + "operator_does_not_contain": "does not contain", + "operator_ends_with": "ends with", + "operator_is_after": "is after", + "operator_is_before": "is before", + "operator_is_between": "is between", + "operator_is_newer_than": "is newer than", + "operator_is_not_set": "is not set", + "operator_is_older_than": "is older than", + "operator_is_same_day": "is same day", + "operator_is_set": "is set", + "operator_starts_with": "starts with", + "operator_title_contains": "Contains", + "operator_title_does_not_contain": "Does not contain", + "operator_title_ends_with": "Ends with", + "operator_title_equals": "Equals", + "operator_title_greater_equal": "Greater than or equal to", + "operator_title_greater_than": "Greater than", + "operator_title_is_after": "Is after", + "operator_title_is_before": "Is before", + "operator_title_is_between": "Is between", + "operator_title_is_newer_than": "Is newer than", + "operator_title_is_not_set": "Is not set", + "operator_title_is_older_than": "Is older than", + "operator_title_is_same_day": "Is same day", + "operator_title_is_set": "Is set", + "operator_title_less_equal": "Less than or equal to", + "operator_title_less_than": "Less than", + "operator_title_not_equals": "Not equals to", + "operator_title_starts_with": "Starts with", + "operator_title_user_is_in": "User is in", + "operator_title_user_is_not_in": "User is not in", + "operator_user_is_in": "User is in", + "operator_user_is_not_in": "User is not in", "person_and_attributes": "Person & Attributes", "phone": "Phone", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Please remove the segment from these surveys in order to delete it.", @@ -848,11 +917,11 @@ "reset_all_filters": "Reset all filters", "save_as_new_segment": "Save as new segment", "save_your_filters_as_a_segment_to_use_it_in_other_surveys": "Save your filters as a Segment to use it in other surveys", - "segment_created_successfully": "Segment created successfully!", - "segment_deleted_successfully": "Segment deleted successfully!", + "segment_created_successfully": "Segment created successfully", + "segment_deleted_successfully": "Segment deleted successfully", "segment_id": "Segment ID", "segment_saved_successfully": "Segment saved successfully", - "segment_updated_successfully": "Segment updated successfully!", + "segment_updated_successfully": "Segment updated successfully", "segments_help_you_target_users_with_same_characteristics_easily": "Segments help you target users with the same characteristics easily", "target_audience": "Target Audience", "this_action_resets_all_filters_in_this_survey": "This action resets all filters in this survey.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "User targeting is currently only available when", "value_cannot_be_empty": "Value cannot be empty.", "value_must_be_a_number": "Value must be a number.", + "value_must_be_positive": "Value must be a positive number.", "view_filters": "View filters", "where": "Where", "with_the_formbricks_sdk": "with the Formbricks SDK" @@ -932,7 +1002,7 @@ "customize_favicon_with_higher_plan": "Customize favicon with a higher plan", "description": "Overview of all surveys using Pretty URLs across your organization", "favicon_customization": "Favicon", - "favicon_customization_description": "Customize the favicon displayed in browser tabs for your link surveys. This won't affect the admin app.", + "favicon_customization_description": "Customize the favicon displayed in browser tabs for your link surveys. This will not affect the admin app.", "favicon_removed_successfully": "Favicon removed successfully", "favicon_saved_successfully": "Favicon saved successfully", "favicon_size_hint": "Max 100KB, square images work best", @@ -950,22 +1020,35 @@ "enterprise_features": "Enterprise Features", "get_an_enterprise_license_to_get_access_to_all_features": "Get an Enterprise license to get access to all features.", "keep_full_control_over_your_data_privacy_and_security": "Keep full control over your data privacy and security.", + "license_invalid_description": "The license key in your ENTERPRISE_LICENSE_KEY environment variable is not valid. Please check for typos or request a new key.", + "license_status": "License Status", + "license_status_active": "Active", + "license_status_description": "Status of your enterprise license.", + "license_status_expired": "Expired", + "license_status_invalid": "Invalid License", + "license_status_unreachable": "Unreachable", + "license_unreachable_grace_period": "License server cannot be reached. Your enterprise features remain active during a 3-day grace period ending {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "No call needed, no strings attached: Request a free 30-day trial license to test all features by filling out this form:", "no_credit_card_no_sales_call_just_test_it": "No credit card. No sales call. Just test it :)", "on_request": "On request", "organization_roles": "Organization Roles (Admin, Editor, Developer, etc.)", "questions_please_reach_out_to": "Questions? Please reach out to", + "recheck_license": "Recheck license", + "recheck_license_failed": "License check failed. The license server may be unreachable.", + "recheck_license_invalid": "The license key is invalid. Please verify your ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "License check successful", + "recheck_license_unreachable": "License server is unreachable. Please try again later.", + "rechecking": "Rechecking...", "request_30_day_trial_license": "Request 30-day Trial License", "saml_sso": "SAML SSO", "service_level_agreement": "Service Level Agreement", "soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 Compliance check", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Teams & Access Roles (Read, Read & Write, Manage)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Unlock the full power of Formbricks. Free for 30 days.", - "your_enterprise_license_is_active_all_features_unlocked": "Your Enterprise License is active. All features unlocked." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Unlock the full power of Formbricks. Free for 30 days." }, "general": { - "bulk_invite_warning_description": "On the free plan, all organization members are always assigned the \"Owner\" role.", + "bulk_invite_warning_description": "On the free plan, all organization members are always assigned the “Owner” role.", "cannot_delete_only_organization": "This is your only organization, it cannot be deleted. Create a new organization first.", "cannot_leave_only_organization": "You cannot leave this organization as it is your only organization. Create a new organization first.", "copy_invite_link_to_clipboard": "Copy invite link to clipboard", @@ -977,7 +1060,7 @@ "delete_organization_description": "Delete organization with all its workspaces including all surveys, responses, people, actions and attributes", "delete_organization_warning": "Before you proceed with deleting this organization, please be aware of the following consequences:", "delete_organization_warning_1": "Permanent removal of all workspaces linked to this organization.", - "delete_organization_warning_2": "This action cannot be undone. If it's gone, it's gone.", + "delete_organization_warning_2": "This action cannot be undone. If it is gone, it is gone.", "delete_organization_warning_3": "Please enter {organizationName} in the following field to confirm the definitive deletion of this organization:", "eliminate_branding_with_whitelabel": "Eliminate Formbricks branding and enable additional white-label customization options.", "email_customization_preview_email_heading": "Hey {userName}", @@ -986,7 +1069,7 @@ "from_your_organization": "from your organization", "invitation_sent_once_more": "Invitation sent once more.", "invite_deleted_successfully": "Invite deleted successfully", - "invited_on": "Invited on {date}", + "invite_expires_on": "Invite expires on {date}", "invites_failed": "Invites failed", "leave_organization": "Leave organization", "leave_organization_description": "You wil leave this organization and loose access to all surveys and responses. You can only rejoin if you are invited again.", @@ -999,10 +1082,10 @@ "manage_members_description": "Add or remove members in your organization.", "member_deleted_successfully": "Member deleted successfully", "member_invited_successfully": "Member invited successfully", - "once_its_gone_its_gone": "Once it's gone, it's gone.", + "once_its_gone_its_gone": "Once it is gone, it is gone.", "only_org_owner_can_perform_action": "Only organization owners can access this setting.", - "organization_created_successfully": "Organization created successfully!", - "organization_deleted_successfully": "Organization deleted successfully.", + "organization_created_successfully": "Organization created successfully", + "organization_deleted_successfully": "Organization deleted successfully", "organization_invite_link_ready": "Your organization invite link is ready!", "organization_name": "Organization Name", "organization_name_description": "Give your organization a descriptive name.", @@ -1033,7 +1116,7 @@ "set_up_an_alert_to_get_an_email_on_new_responses": "Set up an alert to get an email on new responses", "use_the_integration": "Use the integration", "want_to_loop_in_organization_mates": "Want to loop in organization mates", - "you_will_not_be_auto_subscribed_to_this_organizations_surveys_anymore": "You will not be auto-subscribed to this organization's surveys anymore!", + "you_will_not_be_auto_subscribed_to_this_organizations_surveys_anymore": "You will not be auto-subscribed to this organization’s surveys anymore!", "you_will_not_receive_any_more_emails_for_responses_on_this_survey": "You will not receive any more emails for responses on this survey!" }, "profile": { @@ -1099,15 +1182,13 @@ "please_fill_all_workspace_fields": "Please fill all the fields to add a new workspace.", "read": "Read", "read_write": "Read & Write", - "select_member": "Select member", - "select_workspace": "Select workspace", "team_admin": "Team Admin", - "team_created_successfully": "Team created successfully.", - "team_deleted_successfully": "Team deleted successfully.", + "team_created_successfully": "Team created successfully", + "team_deleted_successfully": "Team deleted successfully", "team_deletion_not_allowed": "You are not allowed to delete this team.", "team_name": "Team Name", "team_name_settings_title": "{teamName} Settings", - "team_select_placeholder": "Search team name...", + "team_select_placeholder": "Search team name…", "team_settings_description": "Manage team members, access rights, and more.", "team_updated_successfully": "Team updated successfully", "teams": "Teams", @@ -1115,18 +1196,18 @@ "unlock_teams_description": "Manage which organization members have access to specific workspaces and surveys.", "unlock_teams_title": "Unlock Teams with a higher plan.", "upgrade_plan_notice_message": "Unlock Organization Roles with a higher plan.", - "you_are_a_member": "You're a member" + "you_are_a_member": "You are a member" } }, "surveys": { - "all_set_time_to_create_first_survey": "You're all set! Time to create your first survey", + "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!", + "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:", @@ -1150,11 +1231,10 @@ "add_fallback_placeholder": "Add a placeholder to show if there is no value to recall.", "add_hidden_field_id": "Add hidden field ID", "add_highlight_border": "Add highlight border", - "add_highlight_border_description": "Add an outer border to your survey card.", "add_logic": "Add logic", - "add_none_of_the_above": "Add \"None of the Above\"", + "add_none_of_the_above": "Add “None of the Above”", "add_option": "Add option", - "add_other": "Add \"Other\"", + "add_other": "Add “Other”", "add_photo_or_video": "Add photo or video", "add_pin": "Add PIN", "add_question_below": "Add question below", @@ -1164,7 +1244,7 @@ "address_fields": "Address Fields", "address_line_1": "Address Line 1", "address_line_2": "Address Line 2", - "adjust_survey_closed_message": "Adjust 'Survey Closed' message", + "adjust_survey_closed_message": "Adjust “Survey Closed” message", "adjust_survey_closed_message_description": "Change the message visitors see when the survey is closed.", "adjust_the_theme_in_the": "Adjust the theme in the", "all_other_answers_will_continue_to": "All other answers will continue to", @@ -1184,11 +1264,12 @@ "automatically_close_the_survey_after_a_certain_number_of_responses": "Automatically close the survey after a certain number of responses.", "automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Automatically close the survey if the user does not respond after certain number of seconds.", "automatically_mark_the_survey_as_complete_after": "Automatically mark the survey as complete after", - "back_button_label": "\"Back\" Button Label", + "back_button_label": "“Back” Button Label", "background_styling": "Background styling", "block_duplicated": "Block duplicated.", "bold": "Bold", "brand_color": "Brand color", + "brand_color_description": "Applied to buttons, links and highlights.", "brightness": "Brightness", "bulk_edit": "Bulk edit", "bulk_edit_description": "Edit all options below, one per line. Empty lines will be skipped and duplicates removed.", @@ -1206,31 +1287,25 @@ "capture_new_action": "Capture new action", "card_arrangement_for_survey_type_derived": "Card Arrangement for {surveyTypeDerived} Surveys", "card_background_color": "Card background color", + "card_background_color_description": "Fills the survey card area.", "card_border_color": "Card border color", + "card_border_color_description": "Outlines the survey card.", "card_styling": "Card styling", "casual": "Casual", "caution_edit_duplicate": "Duplicate & edit", "caution_edit_published_survey": "Edit a published survey?", - "caution_explanation_intro": "We understand you might still want to make changes. Here’s what happens if you do: ", + "caution_explanation_intro": "We understand you might still want to make changes. Here is what happens if you do: ", "caution_explanation_new_responses_separated": "Responses before the change may not or only partially be included in the survey summary.", "caution_explanation_only_new_responses_in_summary": "All data, including past responses, remain available as download on the survey summary page.", "caution_explanation_responses_are_safe": "Older and newer responses get mixed which can lead to misleading data summaries.", "caution_recommendation": "This may cause data inconsistencies in the survey summary. We recommend duplicating the survey instead.", "caution_text": "Changes will lead to inconsistencies", - "centered_modal_overlay_color": "Centered modal overlay color", "change_anyway": "Change anyway", "change_background": "Change background", "change_question_type": "Change question type", "change_survey_type": "Switching survey type affects existing access", - "change_the_background_color_of_the_card": "Change the background color of the card.", - "change_the_background_color_of_the_input_fields": "Change the background color of the input fields.", "change_the_background_to_a_color_image_or_animation": "Change the background to a color, image or animation.", - "change_the_border_color_of_the_card": "Change the border color of the card.", - "change_the_border_color_of_the_input_fields": "Change the border color of the input fields.", - "change_the_border_radius_of_the_card_and_the_inputs": "Change the border radius of the card and the inputs.", - "change_the_brand_color_of_the_survey": "Change the brand color of the survey.", "change_the_placement_of_this_survey": "Change the placement of this survey.", - "change_the_question_color_of_the_survey": "Change the question color of the survey.", "changes_saved": "Changes saved.", "changing_survey_type_will_remove_existing_distribution_channels": "Changing the survey type will affect how it can be shared. If respondents already have access links for the current type, they may lose access after the switch.", "checkbox_label": "Checkbox Label", @@ -1258,7 +1333,7 @@ "create_group": "Create group", "create_your_own_survey": "Create your own survey", "css_selector": "CSS Selector", - "cta_button_label": "\"CTA\" button label", + "cta_button_label": "“CTA” button label", "custom_hostname": "Custom hostname", "customize_survey_logo": "Customize the survey logo", "darken_or_lighten_background_of_your_choice": "Darken or lighten background of your choice.", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Disable the visibility of survey progress.", "display_an_estimate_of_completion_time_for_survey": "Display an estimate of completion time for survey", "display_number_of_responses_for_survey": "Display number of responses for survey", + "display_type": "Display type", "divide": "Divide /", "does_not_contain": "Does not contain", "does_not_end_with": "Does not end with", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Does not include all of", "does_not_include_one_of": "Does not include one of", "does_not_start_with": "Does not start with", + "dropdown": "Dropdown", "duplicate_block": "Duplicate block", "duplicate_question": "Duplicate question", "edit_link": "Edit link", @@ -1289,7 +1366,7 @@ "end_screen_card": "End screen card", "ending_card": "Ending card", "ending_card_used_in_logic": "This ending card is used in logic of question {questionIndex}.", - "ending_used_in_quota": "This ending is being used in \"{quotaName}\" quota", + "ending_used_in_quota": "This ending is being used in “{quotaName}” quota", "ends_with": "Ends with", "enter_fallback_value": "Enter fallback value", "equals": "Equals", @@ -1301,7 +1378,7 @@ "external_urls_paywall_tooltip": "Please upgrade to Startup plan to customize external URLs. This helps us prevent phishing.", "fallback_missing": "Fallback missing", "fieldId_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{fieldId} is used in logic of question {questionIndex}. Please remove it from logic first.", - "fieldId_is_used_in_quota_please_remove_it_from_quota_first": "Hidden field \"{fieldId}\" is being used in \"{quotaName}\" quota", + "fieldId_is_used_in_quota_please_remove_it_from_quota_first": "Hidden field “{fieldId}” is being used in “{quotaName}” quota", "field_name_eg_score_price": "Field name e.g, score, price", "first_name": "First Name", "five_points_recommended": "5 points (recommended)", @@ -1339,7 +1416,7 @@ "follow_ups_modal_create_heading": "Create a new follow-up", "follow_ups_modal_created_successfull_toast": "Follow-up created and will be saved once you save the survey.", "follow_ups_modal_edit_heading": "Edit this follow-up", - "follow_ups_modal_edit_no_id": "No survey follow up id provided, can't update the survey follow up", + "follow_ups_modal_edit_no_id": "No survey follow up id provided, cannot update the survey follow up", "follow_ups_modal_name_label": "Follow-up name", "follow_ups_modal_name_placeholder": "Name your follow-up", "follow_ups_modal_subheading": "Send messages to respondents, yourself or team mates", @@ -1357,10 +1434,10 @@ "four_points": "4 points", "heading": "Heading", "hidden_field_added_successfully": "Hidden field added successfully", - "hidden_field_used_in_recall": "Hidden field \"{hiddenField}\" is being recalled in question {questionIndex}.", - "hidden_field_used_in_recall_ending_card": "Hidden field \"{hiddenField}\" is being recalled in Ending Card", - "hidden_field_used_in_recall_welcome": "Hidden field \"{hiddenField}\" is being recalled in Welcome card.", - "hide_back_button": "Hide 'Back' button", + "hidden_field_used_in_recall": "Hidden field “{hiddenField}” is being recalled in question {questionIndex}.", + "hidden_field_used_in_recall_ending_card": "Hidden field “{hiddenField}” is being recalled in Ending Card", + "hidden_field_used_in_recall_welcome": "Hidden field “{hiddenField}” is being recalled in Welcome card.", + "hide_back_button": "Hide “Back” button", "hide_back_button_description": "Do not display the back button in the survey", "hide_block_settings": "Hide Block settings", "hide_logo": "Hide logo", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Hide progress bar", "hide_question_settings": "Hide Question settings", "hostname": "Hostname", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "How funky do you want your cards in {surveyTypeDerived} Surveys", "if_you_need_more_please": "If you need more, please", "if_you_really_want_that_answer_ask_until_you_get_it": "Keep showing whenever triggered until a response is submitted.", "ignore_global_waiting_time": "Ignore Cooldown Period", @@ -1379,7 +1455,9 @@ "initial_value": "Initial value", "inner_text": "Inner Text", "input_border_color": "Input border color", + "input_border_color_description": "Outlines text inputs and textareas.", "input_color": "Input color", + "input_color_description": "Fills the inside of text inputs.", "insert_link": "Insert link", "invalid_targeting": "Invalid targeting: Please check your audience filters", "invalid_video_url_warning": "Please enter a valid YouTube, Vimeo, or Loom URL. We currently do not support other video hosting providers.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limit the maximum file size for uploads.", "limit_upload_file_size_to": "Limit upload file size to", "link_survey_description": "Share a link to a survey page or embed it in a web page or email.", + "list": "List", "load_segment": "Load segment", "logic_error_warning": "Changing will cause logic errors", "logic_error_warning_text": "Changing the question type will remove the logic conditions from this question", @@ -1425,9 +1504,9 @@ "multiply": "Multiply *", "needed_for_self_hosted_cal_com_instance": "Needed for a self-hosted Cal.com instance", "next_block": "Next block", - "next_button_label": "\"Next\" button label", + "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_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_option_found": "No option found", "no_recall_items_found": "No recall items found", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Only users who have the PIN can access the survey.", "publish": "Publish", "question": "Question", - "question_color": "Question color", "question_deleted": "Question deleted.", "question_duplicated": "Question duplicated.", "question_id_updated": "Question ID updated", "question_used_in_logic_warning_text": "Elements from this block are used in a logic rule, are you sure you want to delete it?", "question_used_in_logic_warning_title": "Logic Inconsistency", - "question_used_in_quota": "This question is being used in \"{quotaName}\" quota", + "question_used_in_quota": "This question is being used in “{quotaName}” quota", "question_used_in_recall": "This question is being recalled in question {questionIndex}.", "question_used_in_recall_ending_card": "This question is being recalled in Ending Card", "quotas": { @@ -1506,7 +1584,7 @@ "randomize_all_except_last": "Randomize all except last", "range": "Range", "recall_data": "Recall data", - "recall_information_from": "Recall information from ...", + "recall_information_from": "Recall information from …", "recontact_options_section": "Recontact options", "recontact_options_section_description": "If the Cooldown Period allows, choose how often this survey can be shown to a person.", "redirect_thank_you_card": "Redirect thank you card", @@ -1518,12 +1596,13 @@ "reset_to_theme_styles": "Reset to theme styles", "reset_to_theme_styles_main_text": "Are you sure you want to reset the styling to the theme styles? This will remove all custom styling.", "respect_global_waiting_time": "Use Cooldown Period", - "respect_global_waiting_time_description": "This survey follows the Cooldown Period set in the workspace's configuration. It only shows if no other survey has appeared during that period.", - "response_limit_can_t_be_set_to_0": "Response limit can't be set to 0", + "respect_global_waiting_time_description": "This survey follows the Cooldown Period set in the workspace’s configuration. It only shows if no other survey has appeared during that period.", + "response_limit_can_t_be_set_to_0": "Response limit cannot be set to 0", "response_limit_needs_to_exceed_number_of_received_responses": "Response limit needs to exceed number of received responses ({responseCount}).", "response_limits_redirections_and_more": "Response limits, redirections and more.", "response_options": "Response Options", "roundness": "Roundness", + "roundness_description": "Controls how rounded the card corners are.", "row_used_in_logic_error": "This row is used in logic of question {questionIndex}. Please remove it from logic first.", "rows": "Rows", "save_and_close": "Save & Close", @@ -1536,10 +1615,10 @@ "select_ordering": "Select ordering", "select_saved_action": "Select saved action", "select_type": "Select type", - "send_survey_to_audience_who_match": "Send survey to audience who match...", + "send_survey_to_audience_who_match": "Send survey to audience who match…", "send_your_respondents_to_a_page_of_your_choice": "Send your respondents to a page of your choice.", "set_the_global_placement_in_the_look_feel_settings": "Set the global placement in the Look & Feel settings.", - "settings_saved_successfully": "Settings saved successfully.", + "settings_saved_successfully": "Settings saved successfully", "seven_points": "7 points", "show_block_settings": "Show Block settings", "show_button": "Show Button", @@ -1565,25 +1644,24 @@ "styling_set_to_theme_styles": "Styling set to theme styles", "subheading": "Subheading", "subtract": "Subtract -", - "suggest_colors": "Suggest colors", "survey_completed_heading": "Survey Completed", "survey_completed_subheading": "This free & open-source survey has been closed", "survey_display_settings": "Survey Display Settings", "survey_placement": "Survey Placement", "survey_trigger": "Survey Trigger", - "switch_multi_language_on_to_get_started": "Switch multi-language on to get started \uD83D\uDC49", + "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", "the_survey_will_be_shown_multiple_times_until_they_respond": "Show at most the specified number of times, or until they respond (whichever comes first).", - "the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Show a single time, even if they don't respond.", + "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.", "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", - "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'...", + "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", "underline": "Underline", "unlock_targeting_description": "Target specific user groups based on attributes or device information", @@ -1642,13 +1720,13 @@ "validation_rules": "Validation rules", "validation_rules_description": "Only accept responses that meet the following criteria", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} is used in logic of question {questionIndex}. Please remove it from logic first.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable \"{variableName}\" is being used in \"{quotaName}\" quota", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable “{variableName}” is being used in “{quotaName}” quota", "variable_name_conflicts_with_hidden_field": "Variable name conflicts with an existing hidden field ID.", "variable_name_is_already_taken_please_choose_another": "Variable name is already taken, please choose another.", "variable_name_must_start_with_a_letter": "Variable name must start with a letter.", - "variable_used_in_recall": "Variable \"{variable}\" is being recalled in question {questionIndex}.", + "variable_used_in_recall": "Variable “{variable}” is being recalled in question {questionIndex}.", "variable_used_in_recall_ending_card": "Variable {variable} is being recalled in Ending Card", - "variable_used_in_recall_welcome": "Variable \"{variable}\" is being recalled in Welcome Card.", + "variable_used_in_recall_welcome": "Variable “{variable}” is being recalled in Welcome Card.", "verify_email_before_submission": "Verify email before submission", "verify_email_before_submission_description": "Only let people with a real email respond.", "visibility_and_recontact": "Visibility & Recontact", @@ -1706,7 +1784,7 @@ "person_attributes": "Person attributes at time of submission", "phone": "Phone", "respondent_skipped_questions": "Respondent skipped these questions.", - "response_deleted_successfully": "Response deleted successfully.", + "response_deleted_successfully": "Response deleted successfully", "single_use_id": "SingleUse ID", "source": "Source", "state_region": "State / Region", @@ -1718,7 +1796,7 @@ "search_by_survey_name": "Search by survey name", "share": { "anonymous_links": { - "custom_single_use_id_description": "If you don’t encrypt single-use ID, any value for “suid=...” works for one response.", + "custom_single_use_id_description": "If you do not encrypt single-use IDs, any value for “suid=…” works for one response.", "custom_single_use_id_title": "You can set any value as single-use ID in the URL.", "custom_start_point": "Custom start point", "data_prefilling": "Data prefilling", @@ -1855,7 +1933,7 @@ "congrats": "Congrats! Your survey is live.", "connect_your_website_or_app_with_formbricks_to_get_started": "Connect your website or app with Formbricks to get started.", "current_count": "Current count", - "custom_range": "Custom range...", + "custom_range": "Custom range…", "delete_all_existing_responses_and_displays": "Delete all existing responses and displays", "download_qr_code": "Download QR code", "downloading_qr_code": "Downloading QR code", @@ -1894,7 +1972,7 @@ "javascript_sdk": "JavaScript SDK", "kotlin_sdk": "Kotlin SDK for Android apps", "no_connection_description": "Connect your website or app with Formbricks to publish intercept surveys.", - "no_connection_title": "You're not plugged in yet!", + "no_connection_title": "You are not plugged in yet!", "react_native_sdk": "React Native SDK for RN apps.", "title": "Intercept survey settings" }, @@ -1931,7 +2009,7 @@ "show_all_responses_that_match": "Show all responses that match", "starts": "Starts", "starts_tooltip": "Number of times the survey has been started.", - "survey_reset_successfully": "Survey reset successfully! {responseCount} responses and {displayCount} displays were deleted.", + "survey_reset_successfully": "Survey reset successfully. {responseCount} responses and {displayCount} displays were deleted.", "this_month": "This month", "this_quarter": "This quarter", "this_year": "This year", @@ -1939,12 +2017,12 @@ "ttc_tooltip": "Average time to complete the question.", "unknown_question_type": "Unknown Question Type", "use_personal_links": "Use personal links", - "whats_next": "What's next?", + "whats_next": "What is next?", "your_survey_is_public": "Your survey is public", - "youre_not_plugged_in_yet": "You're not plugged in yet!" + "youre_not_plugged_in_yet": "You are not plugged in yet!" }, - "survey_deleted_successfully": "Survey deleted successfully!", - "survey_duplicated_successfully": "Survey duplicated successfully.", + "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", @@ -1968,7 +2046,7 @@ "api_key_updated": "API Key updated", "delete_api_key_confirmation": "Any applications using this key will no longer be able to access your Formbricks data.", "duplicate_access": "Duplicate workspace access not allowed", - "no_api_keys_yet": "You don't have any API keys yet", + "no_api_keys_yet": "You do not have any API keys yet", "no_env_permissions_found": "No environment permissions found", "organization_access": "Organization Access", "organization_access_description": "Select read or write privileges for organization-wide resources.", @@ -1989,7 +2067,7 @@ "formbricks_sdk_not_connected_description": "Add the Formbricks SDK to your website or app to connect it with Formbricks", "how_to_setup": "How to setup", "how_to_setup_description": "Follow these steps to setup the Formbricks widget within your app.", - "receiving_data": "Receiving data \uD83D\uDC83\uD83D\uDD7A", + "receiving_data": "Receiving data 💃🕺", "recheck": "Re-check", "sdk_connection_details": "SDK Connection Details", "sdk_connection_details_description": "Your unique environment ID and SDK connection URL for integrating Formbricks with your application.", @@ -2008,7 +2086,7 @@ "custom_scripts_warning": "Scripts execute with full browser access. Only add scripts from trusted sources.", "delete_workspace": "Delete Workspace", "delete_workspace_confirmation": "Are you sure you want to delete {projectName}? This action cannot be undone.", - "delete_workspace_name_includes_surveys_responses_people_and_more": "Delete {projectName} incl. all surveys, responses, people, actions and attributes.", + "delete_workspace_name_includes_surveys_responses_people_and_more": "Delete {projectName} including all surveys, responses, people, actions and attributes.", "delete_workspace_settings_description": "Delete workspace with all surveys, responses, people, actions and attributes. This cannot be undone.", "error_saving_workspace_information": "Error saving workspace information", "only_owners_or_managers_can_delete_workspaces": "Only owners or managers can delete workspaces", @@ -2017,16 +2095,16 @@ "this_action_cannot_be_undone": "This action cannot be undone.", "wait_x_days_before_showing_next_survey": "Wait X days before showing next survey:", "waiting_period_updated_successfully": "Waiting period updated successfully", - "whats_your_workspace_called": "What's your workspace called?", + "whats_your_workspace_called": "What is your workspace called?", "workspace_deleted_successfully": "Workspace deleted successfully", - "workspace_name_settings_description": "Change your workspace's name.", + "workspace_name_settings_description": "Change your workspace’s name.", "workspace_name_updated_successfully": "Workspace name updated successfully" }, "languages": { "add_language": "Add language", "alias": "Alias", "alias_tooltip": "The alias is an alternate name to identify the language in link surveys and the SDK (optional)", - "cannot_remove_language_warning": "You cannot remove this language since it’s still used in these surveys:", + "cannot_remove_language_warning": "You cannot remove this language since it is still used in these surveys:", "conflict_between_identifier_and_alias": "There is a conflict between the identifier of an added language and one for your aliases. Aliases and identifiers cannot be identical.", "conflict_between_selected_alias_and_another_language": "There is a conflict between the selected alias and another language that has this identifier. Please add the language with this identifier to your workspace instead to avoid inconsistencies.", "delete_language_confirmation": "Are you sure you want to delete this language? This action cannot be undone.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Add background color", "add_background_color_description": "Add a background color to the logo container.", + "advanced_styling_field_border_radius": "Border Radius", + "advanced_styling_field_button_bg": "Button Background", + "advanced_styling_field_button_bg_description": "Fills the Next / Submit button.", + "advanced_styling_field_button_border_radius_description": "Rounds the button corners.", + "advanced_styling_field_button_font_size_description": "Scales the button label text.", + "advanced_styling_field_button_font_weight_description": "Makes button text lighter or bolder.", + "advanced_styling_field_button_height_description": "Controls the button height.", + "advanced_styling_field_button_padding_x_description": "Adds space on the left and right.", + "advanced_styling_field_button_padding_y_description": "Adds space on the top and bottom.", + "advanced_styling_field_button_text": "Button Text", + "advanced_styling_field_button_text_description": "Colors the label inside buttons.", + "advanced_styling_field_description_color": "Description Color", + "advanced_styling_field_description_color_description": "Colors the text below each headline.", + "advanced_styling_field_description_size": "Description Font Size", + "advanced_styling_field_description_size_description": "Scales the description text.", + "advanced_styling_field_description_weight": "Description Font Weight", + "advanced_styling_field_description_weight_description": "Makes description text lighter or bolder.", + "advanced_styling_field_font_size": "Font Size", + "advanced_styling_field_font_weight": "Font Weight", + "advanced_styling_field_headline_color": "Headline Color", + "advanced_styling_field_headline_color_description": "Colors the main question text.", + "advanced_styling_field_headline_size": "Headline Font Size", + "advanced_styling_field_headline_size_description": "Scales the headline text.", + "advanced_styling_field_headline_weight": "Headline Font Weight", + "advanced_styling_field_headline_weight_description": "Makes headline text lighter or bolder.", + "advanced_styling_field_height": "Minimum Height", + "advanced_styling_field_indicator_bg": "Indicator Background", + "advanced_styling_field_indicator_bg_description": "Colors the filled portion of the bar.", + "advanced_styling_field_input_border_radius_description": "Rounds the input corners.", + "advanced_styling_field_input_font_size_description": "Scales the typed text in inputs.", + "advanced_styling_field_input_height_description": "Controls the minimum height of the input field.", + "advanced_styling_field_input_padding_x_description": "Adds space on the left and right.", + "advanced_styling_field_input_padding_y_description": "Adds space on the top and bottom.", + "advanced_styling_field_input_placeholder_opacity_description": "Fades the placeholder hint text.", + "advanced_styling_field_input_shadow_description": "Adds a drop shadow around inputs.", + "advanced_styling_field_input_text": "Input Text", + "advanced_styling_field_input_text_description": "Colors the typed text in inputs.", + "advanced_styling_field_option_bg": "Background", + "advanced_styling_field_option_bg_description": "Fills the option items.", + "advanced_styling_field_option_border_radius_description": "Rounds the option corners.", + "advanced_styling_field_option_font_size_description": "Scales the option label text.", + "advanced_styling_field_option_label": "Label Color", + "advanced_styling_field_option_label_description": "Colors the option label text.", + "advanced_styling_field_option_padding_x_description": "Adds space on the left and right.", + "advanced_styling_field_option_padding_y_description": "Adds space on the top and bottom.", + "advanced_styling_field_padding_x": "Padding X", + "advanced_styling_field_padding_y": "Padding Y", + "advanced_styling_field_placeholder_opacity": "Placeholder Opacity", + "advanced_styling_field_shadow": "Shadow", + "advanced_styling_field_track_bg": "Track Background", + "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_section_buttons": "Buttons", + "advanced_styling_section_headlines": "Headlines & Descriptions", + "advanced_styling_section_inputs": "Inputs", + "advanced_styling_section_options": "Options (Radio/Checkbox)", "app_survey_placement": "App Survey Placement", "app_survey_placement_settings_description": "Change where surveys will be shown in your web app or website.", - "centered_modal_overlay_color": "Centered modal overlay color", "email_customization": "Email Customization", "email_customization_description": "Change the look and feel of emails Formbricks sends out on your behalf.", "enable_custom_styling": "Enable custom styling", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks branding is hidden.", "formbricks_branding_settings_description": "We love your support but understand if you toggle it off.", "formbricks_branding_shown": "Formbricks branding is shown.", + "generate_theme_btn": "Generate", + "generate_theme_confirmation": "Would you like to generate a matching color theme based on your brand color? This will overwrite your current color settings.", + "generate_theme_header": "Generate Color Theme?", "logo_removed_successfully": "Logo removed successfully", "logo_settings_description": "Upload your company logo to brand surveys and link previews.", "logo_updated_successfully": "Logo updated successfully", @@ -2074,8 +2217,10 @@ "reset_styling": "Reset styling", "reset_styling_confirmation": "Are you sure you want to reset the styling to default?", "show_formbricks_branding_in": "Show Formbricks Branding in {type} surveys", - "show_powered_by_formbricks": "Show 'Powered by Formbricks' Signature", + "show_powered_by_formbricks": "Show “Powered by Formbricks” Signature", "styling_updated_successfully": "Styling updated successfully", + "suggest_colors": "Suggest colors", + "suggested_colors_applied_please_save": "Suggested colors generated successfully. Press \"Save\" to persist the changes.", "theme": "Theme", "theme_settings_description": "Create a style theme for all surveys. You can enable custom styling for each survey." }, @@ -2088,12 +2233,12 @@ "manage_tags_description": "Merge and remove response tags.", "merge": "Merge", "no_tag_found": "No tag found", - "search_tags": "Search Tags...", + "search_tags": "Search Tags…", "tag": "Tag", "tag_already_exists": "Tag already exists", - "tag_deleted": "Tag deleted", - "tag_updated": "Tag updated", - "tags_merged": "Tags merged" + "tag_deleted": "Tag deleted successfully", + "tag_updated": "Tag updated successfully", + "tags_merged": "Tags merged successfully" }, "teams": { "manage_teams": "Manage teams", @@ -2122,7 +2267,7 @@ "organizations": { "landing": { "no_workspaces_warning_subtitle": "Reach out to your organization owner to get access to workspaces. Or create an own organization to get started.", - "no_workspaces_warning_title": "Your account doesn't have access to any workspaces yet." + "no_workspaces_warning_title": "Your account does not have access to any workspaces yet." }, "workspaces": { "new": { @@ -2150,13 +2295,13 @@ "workspace_name": "Product name", "workspace_name_description": "What is your product called?", "workspace_settings_subtitle": "When people recognize your brand, they are much more likely to start and complete responses.", - "workspace_settings_title": "Let respondents know it's you" + "workspace_settings_title": "Let respondents know it is you" } } } }, "s": { - "check_inbox_or_spam": "Please also check your spam folder if you don't see the email in your inbox.", + "check_inbox_or_spam": "Please also check your spam folder if you do not see the email in your inbox.", "completed": "This survey is closed.", "create_your_own": "Create your own open-source survey", "enter_pin": "This survey is protected. Enter the PIN below", @@ -2181,10 +2326,10 @@ "setup": { "intro": { "get_started": "Get started", - "made_with_love_in_kiel": "Made with \uD83E\uDD0D in Germany", + "made_with_love_in_kiel": "Made with 🤍 in Germany", "paragraph_1": "Formbricks is an Experience Management Suite built on the fastest growing open-source survey platform worldwide.", "paragraph_2": "Run targeted surveys on websites, in apps or anywhere online. Gather valuable insights to craft irresistible experiences for customers, users and employees.", - "paragraph_3": "We're committed to the highest degree of data privacy. Self-host to keep full control over your data.", + "paragraph_3": "We are committed to the highest degree of data privacy. Self-host to keep full control over your data.", "welcome_to_formbricks": "Welcome to Formbricks!" }, "invite": { @@ -2193,7 +2338,7 @@ "failed_to_invite": "Failed to invite", "invitation_sent_to": "Invitation sent to", "invite_your_organization_members": "Invite your Organization members", - "life_s_no_fun_alone": "Life's no fun alone.", + "life_s_no_fun_alone": "Life is no fun alone.", "skip": "Skip", "smtp_not_configured": "SMTP not configured", "smtp_not_configured_description": "Invitations cannot be sent at this time because the email service is not configured. You can copy the invite link in the organization settings later." @@ -2217,7 +2362,7 @@ "templates": { "address": "Address", "address_description": "Ask for a mailing address", - "alignment_and_engagement_survey_description": "Gauge employee alignment with the company's vision, strategy, and communication, as well as team collaboration.", + "alignment_and_engagement_survey_description": "Gauge employee alignment with the company’s vision, strategy, and communication, as well as team collaboration.", "alignment_and_engagement_survey_name": "Alignment and Engagement with Company Vision", "alignment_and_engagement_survey_question_1_headline": "I understand how my role contributes to the company’s overall strategy.", "alignment_and_engagement_survey_question_1_lower_label": "No understanding", @@ -2228,7 +2373,7 @@ "alignment_and_engagement_survey_question_3_lower_label": "Poor collaboration", "alignment_and_engagement_survey_question_3_upper_label": "Excellent collaboration", "alignment_and_engagement_survey_question_4_headline": "How can the company improve its vision and strategy alignment?", - "alignment_and_engagement_survey_question_4_placeholder": "Type your answer here...", + "alignment_and_engagement_survey_question_4_placeholder": "Type your answer here…", "back": "Back", "book_interview": "Book interview", "build_product_roadmap_description": "Identify the ONE thing your users want the most and build it.", @@ -2236,8 +2381,8 @@ "build_product_roadmap_question_1_headline": "How satisfied are you with the features and functionality of $[projectName]?", "build_product_roadmap_question_1_lower_label": "Not at all satisfied", "build_product_roadmap_question_1_upper_label": "Extremely satisfied", - "build_product_roadmap_question_2_headline": "What's ONE change we could make to improve your $[projectName] experience most?", - "build_product_roadmap_question_2_placeholder": "Type your answer here...", + "build_product_roadmap_question_2_headline": "What is ONE change we could make to improve your $[projectName] experience most?", + "build_product_roadmap_question_2_placeholder": "Type your answer here…", "card_abandonment_survey": "Cart Abandonment Survey", "card_abandonment_survey_description": "Understand the reasons behind cart abandonment in your web shop.", "card_abandonment_survey_question_1_button_label": "Sure!", @@ -2249,7 +2394,7 @@ "card_abandonment_survey_question_2_choice_4": "Decided not to buy", "card_abandonment_survey_question_2_choice_5": "Payment issues", "card_abandonment_survey_question_2_choice_6": "Other", - "card_abandonment_survey_question_2_headline": "What was the primary reason you didn't complete your purchase?", + "card_abandonment_survey_question_2_headline": "What was the primary reason you did not complete your purchase?", "card_abandonment_survey_question_2_subheader": "Please select one of the following options:", "card_abandonment_survey_question_3_headline": "Please elaborate on your reason for not completing the purchase:", "card_abandonment_survey_question_4_headline": "How would you rate your overall shopping experience?", @@ -2288,7 +2433,7 @@ "career_development_survey_question_5_choice_5": "Operations", "career_development_survey_question_5_choice_6": "Other", "career_development_survey_question_5_headline": "Which function do you work in?", - "career_development_survey_question_5_subheader": "Please select one of the following", + "career_development_survey_question_5_subheader": "Please select one of the following options:", "career_development_survey_question_6_choice_1": "Individual Contributor", "career_development_survey_question_6_choice_2": "Manager", "career_development_survey_question_6_choice_3": "Senior Manager", @@ -2296,13 +2441,13 @@ "career_development_survey_question_6_choice_5": "Executive", "career_development_survey_question_6_choice_6": "Other", "career_development_survey_question_6_headline": "Which of the following best describes your current job level?", - "career_development_survey_question_6_subheader": "Please select one of the following", + "career_development_survey_question_6_subheader": "Please select one of the following options:", "cess_survey_name": "CES Survey", "cess_survey_question_1_headline": "$[projectName] makes it easy for me to [ADD GOAL]", - "cess_survey_question_1_lower_label": "Disagree strongly", - "cess_survey_question_1_upper_label": "Agree strongly", + "cess_survey_question_1_lower_label": "Strongly disagree", + "cess_survey_question_1_upper_label": "Strongly agree", "cess_survey_question_2_headline": "Thanks! How could we make it easier for you to [ADD GOAL]?", - "cess_survey_question_2_placeholder": "Type your answer here...", + "cess_survey_question_2_placeholder": "Type your answer here…", "changing_subscription_experience_description": "Find out what goes through peoples minds when changing their subscriptions.", "changing_subscription_experience_name": "Changing Subscription Experience", "changing_subscription_experience_question_1_choice_1": "Extremely difficult", @@ -2318,43 +2463,43 @@ "churn_survey": "Churn Survey", "churn_survey_description": "Find out why people cancel their subscriptions. These insights are pure gold!", "churn_survey_question_1_choice_1": "Difficult to use", - "churn_survey_question_1_choice_2": "It's too expensive", + "churn_survey_question_1_choice_2": "It is too expensive", "churn_survey_question_1_choice_3": "I am missing features", "churn_survey_question_1_choice_4": "Poor customer service", - "churn_survey_question_1_choice_5": "I just didn't need it anymore", + "churn_survey_question_1_choice_5": "I just did not need it anymore", "churn_survey_question_1_headline": "Why did you cancel your subscription?", - "churn_survey_question_1_subheader": "We're sorry to see you leave. Help us do better:", + "churn_survey_question_1_subheader": "We are sorry to see you leave. Help us do better:", "churn_survey_question_2_button_label": "Send", "churn_survey_question_2_headline": "What would have made $[projectName] easier to use?", "churn_survey_question_3_button_label": "Get 30% off", "churn_survey_question_3_headline": "Get 30% off for the next year!", - "churn_survey_question_3_html": "

    We'd love to keep you as a customer. Happy to offer a 30% discount for the next year.

    ", + "churn_survey_question_3_html": "

    We would love to keep you as a customer. Happy to offer a 30% discount for the next year.

    ", "churn_survey_question_4_headline": "What features are you missing?", "churn_survey_question_5_button_label": "Send email to CEO", - "churn_survey_question_5_headline": "So sorry to hear \uD83D\uDE14 Talk to our CEO directly!", + "churn_survey_question_5_headline": "So sorry to hear 😔 Talk to our CEO directly!", "churn_survey_question_5_html": "

    We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.

    ", "collect_feedback_description": "Gather comprehensive feedback on your product or service.", "collect_feedback_name": "Collect Feedback", "collect_feedback_question_1_headline": "How do you rate your overall experience?", "collect_feedback_question_1_lower_label": "Not good", - "collect_feedback_question_1_subheader": "Don't worry, be honest.", + "collect_feedback_question_1_subheader": "Do not worry, be honest.", "collect_feedback_question_1_upper_label": "Very good", "collect_feedback_question_2_headline": "Lovely! What did you like about it?", - "collect_feedback_question_2_placeholder": "Type your answer here...", + "collect_feedback_question_2_placeholder": "Type your answer here…", "collect_feedback_question_3_headline": "Thanks for sharing! What did you not like?", - "collect_feedback_question_3_placeholder": "Type your answer here...", + "collect_feedback_question_3_placeholder": "Type your answer here…", "collect_feedback_question_4_headline": "How do you rate our communication?", "collect_feedback_question_4_lower_label": "Not good", "collect_feedback_question_4_upper_label": "Very good", - "collect_feedback_question_5_headline": "Anything else you'd like to share with our team?", - "collect_feedback_question_5_placeholder": "Type your answer here...", + "collect_feedback_question_5_headline": "Anything else you would like to share with our team?", + "collect_feedback_question_5_placeholder": "Type your answer here…", "collect_feedback_question_6_choice_1": "Google", "collect_feedback_question_6_choice_2": "Social Media", "collect_feedback_question_6_choice_3": "Friends", "collect_feedback_question_6_choice_4": "Podcast", "collect_feedback_question_6_choice_5": "Other", "collect_feedback_question_6_headline": "How did you hear about us?", - "collect_feedback_question_7_headline": "Lastly, we'd love to respond to your feedback. Please share your email:", + "collect_feedback_question_7_headline": "Lastly, we would love to respond to your feedback. Please share your email:", "collect_feedback_question_7_placeholder": "example@email.com", "consent": "Consent", "consent_description": "Ask to agree to terms, conditions, or data usage", @@ -2363,7 +2508,7 @@ "csat_description": "Measure the Customer Satisfaction Score of your product or service.", "csat_name": "Customer Satisfaction Score (CSAT)", "csat_question_10_headline": "Do you have any other comments, questions or concerns?", - "csat_question_10_placeholder": "Type your answer here...", + "csat_question_10_placeholder": "Type your answer here…", "csat_question_1_headline": "How likely is it that you would recommend this $[projectName] to a friend or colleague?", "csat_question_1_lower_label": "Not likely", "csat_question_1_upper_label": "Very likely", @@ -2372,7 +2517,7 @@ "csat_question_2_choice_3": "Neither satisfied nor dissatisfied", "csat_question_2_choice_4": "Somewhat dissatisfied", "csat_question_2_choice_5": "Very dissatisfied", - "csat_question_2_headline": "Overall, how satisfied or dissatisfied are you with our $[projectName]", + "csat_question_2_headline": "Overall, how satisfied or dissatisfied are you with our $[projectName]?", "csat_question_2_subheader": "Please select one:", "csat_question_3_choice_1": "Ineffective", "csat_question_3_choice_10": "Unique", @@ -2433,22 +2578,22 @@ "csat_survey_question_1_lower_label": "Extremely dissatisfied", "csat_survey_question_1_upper_label": "Extremely satisfied", "csat_survey_question_2_headline": "Lovely! Is there anything we can do to improve your experience?", - "csat_survey_question_2_placeholder": "Type your answer here...", + "csat_survey_question_2_placeholder": "Type your answer here…", "csat_survey_question_3_headline": "Ugh, sorry! Is there anything we can do to improve your experience?", - "csat_survey_question_3_placeholder": "Type your answer here...", + "csat_survey_question_3_placeholder": "Type your answer here…", "cta_description": "Display information and prompt users to take a specific action", "custom_survey_block_1_name": "Block 1", "custom_survey_description": "Create a survey without template.", "custom_survey_name": "Start from scratch", "custom_survey_question_1_headline": "What would you like to know?", - "custom_survey_question_1_placeholder": "Type your answer here...", + "custom_survey_question_1_placeholder": "Type your answer here…", "customer_effort_score_description": "Determine how easy it is to use a feature.", "customer_effort_score_name": "Customer Effort Score (CES)", "customer_effort_score_question_1_headline": "$[projectName] makes it easy for me to [ADD GOAL]", - "customer_effort_score_question_1_lower_label": "Disagree strongly", - "customer_effort_score_question_1_upper_label": "Agree strongly", + "customer_effort_score_question_1_lower_label": "Strongly disagree", + "customer_effort_score_question_1_upper_label": "Strongly agree", "customer_effort_score_question_2_headline": "Thanks! How could we make it easier for you to [ADD GOAL]?", - "customer_effort_score_question_2_placeholder": "Type your answer here...", + "customer_effort_score_question_2_placeholder": "Type your answer here…", "date": "Date", "date_description": "Ask for a date selection", "default_ending_card_button_label": "Create your own Survey", @@ -2456,28 +2601,28 @@ "default_ending_card_subheader": "We appreciate your feedback.", "default_welcome_card_button_label": "Next", "default_welcome_card_headline": "Welcome!", - "default_welcome_card_html": "Thanks for providing your feedback - let's go!", + "default_welcome_card_html": "Thanks for providing your feedback - let’s go!", "docs_feedback_description": "Measure how clear each page of your developer documentation is.", "docs_feedback_name": "Docs Feedback", - "docs_feedback_question_1_choice_1": "Yes \uD83D\uDC4D", - "docs_feedback_question_1_choice_2": "No \uD83D\uDC4E", + "docs_feedback_question_1_choice_1": "Yes 👍", + "docs_feedback_question_1_choice_2": "No 👎", "docs_feedback_question_1_headline": "Was this page helpful?", "docs_feedback_question_2_headline": "Please elaborate:", "docs_feedback_question_3_headline": "Page URL", - "earned_advocacy_score_description": "The EAS is a riff off the NPS but asking for actual past behaviour instead of lofty intentions.", + "earned_advocacy_score_description": "The EAS is a riff off the NPS but asking for actual past behavior instead of lofty intentions.", "earned_advocacy_score_name": "Earned Advocacy Score (EAS)", "earned_advocacy_score_question_1_choice_1": "Yes", "earned_advocacy_score_question_1_choice_2": "No", "earned_advocacy_score_question_1_headline": "Have you actively recommended $[projectName] to others?", "earned_advocacy_score_question_2_headline": "Why did you recommend us?", - "earned_advocacy_score_question_2_placeholder": "Type your answer here...", + "earned_advocacy_score_question_2_placeholder": "Type your answer here…", "earned_advocacy_score_question_3_headline": "So sad. Why not?", - "earned_advocacy_score_question_3_placeholder": "Type your answer here...", + "earned_advocacy_score_question_3_placeholder": "Type your answer here…", "earned_advocacy_score_question_4_choice_1": "Yes", "earned_advocacy_score_question_4_choice_2": "No", "earned_advocacy_score_question_4_headline": "Have you actively discouraged others from choosing $[projectName]?", "earned_advocacy_score_question_5_headline": "What made you discourage them?", - "earned_advocacy_score_question_5_placeholder": "Type your answer here...", + "earned_advocacy_score_question_5_placeholder": "Type your answer here…", "employee_satisfaction_description": "Gauge employee satisfaction and identify areas for improvement.", "employee_satisfaction_name": "Employee Satisfaction", "employee_satisfaction_question_1_headline": "How satisfied are you with your current role?", @@ -2490,12 +2635,12 @@ "employee_satisfaction_question_2_choice_5": "Not at all meaningful", "employee_satisfaction_question_2_headline": "How meaningful do you find your work?", "employee_satisfaction_question_3_headline": "What do you enjoy most about working here?", - "employee_satisfaction_question_3_placeholder": "Type your answer here...", + "employee_satisfaction_question_3_placeholder": "Type your answer here…", "employee_satisfaction_question_5_headline": "Rate the support you receive from your manager.", "employee_satisfaction_question_5_lower_label": "Poor", "employee_satisfaction_question_5_upper_label": "Excellent", "employee_satisfaction_question_6_headline": "What improvements would you suggest for our workplace?", - "employee_satisfaction_question_6_placeholder": "Type your answer here...", + "employee_satisfaction_question_6_placeholder": "Type your answer here…", "employee_satisfaction_question_7_choice_1": "Extremely likely", "employee_satisfaction_question_7_choice_2": "Very likely", "employee_satisfaction_question_7_choice_3": "Moderately likely", @@ -2514,7 +2659,7 @@ "employee_well_being_question_3_lower_label": "Not supportive", "employee_well_being_question_3_upper_label": "Highly supportive", "employee_well_being_question_4_headline": "What changes, if any, would improve your overall well-being at work?", - "employee_well_being_question_4_placeholder": "Type your answer here...", + "employee_well_being_question_4_placeholder": "Type your answer here…", "enps_survey_name": "eNPS Survey", "enps_survey_question_1_headline": "How likely are you to recommend working at this company to a friend or colleague?", "enps_survey_question_1_lower_label": "Not at all likely", @@ -2523,35 +2668,35 @@ "enps_survey_question_3_headline": "Any other comments, feedback, or concerns?", "evaluate_a_product_idea_description": "Survey users about product or feature ideas. Get feedback rapidly.", "evaluate_a_product_idea_name": "Evaluate a Product Idea", - "evaluate_a_product_idea_question_1_button_label": "Let's do it!", - "evaluate_a_product_idea_question_1_headline": "We love how you use $[projectName]! We'd love to pick your brain on a feature idea. Got a minute?", + "evaluate_a_product_idea_question_1_button_label": "Let’s do it!", + "evaluate_a_product_idea_question_1_headline": "We love how you use $[projectName]! We would love to pick your brain on a feature idea. Got a minute?", "evaluate_a_product_idea_question_1_html": "

    We respect your time and kept it short \uD83E\uDD38

    ", "evaluate_a_product_idea_question_2_headline": "Thanks! How difficult or easy is it for you to [PROBLEM AREA] today?", "evaluate_a_product_idea_question_2_lower_label": "Very difficult", "evaluate_a_product_idea_question_2_upper_label": "Very easy", - "evaluate_a_product_idea_question_3_headline": "What's most difficult for you when it comes to [PROBLEM AREA]?", - "evaluate_a_product_idea_question_3_placeholder": "Type your answer here...", + "evaluate_a_product_idea_question_3_headline": "What is most difficult for you when it comes to [PROBLEM AREA]?", + "evaluate_a_product_idea_question_3_placeholder": "Type your answer here…", "evaluate_a_product_idea_question_4_button_label": "Next", - "evaluate_a_product_idea_question_4_headline": "We're working on an idea to help with [PROBLEM AREA].", + "evaluate_a_product_idea_question_4_headline": "We are working on an idea to help with [PROBLEM AREA].", "evaluate_a_product_idea_question_4_html": "

    Insert concept brief here. Add necessary details but keep it concise and easy to understand.

    ", "evaluate_a_product_idea_question_5_headline": "How valuable would this feature be to you?", "evaluate_a_product_idea_question_5_lower_label": "Not valuable", "evaluate_a_product_idea_question_5_upper_label": "Very valuable", - "evaluate_a_product_idea_question_6_headline": "Got it. Why wouldn't this feature be valuable to you?", - "evaluate_a_product_idea_question_6_placeholder": "Type your answer here...", + "evaluate_a_product_idea_question_6_headline": "Got it. Why would not this feature be valuable to you?", + "evaluate_a_product_idea_question_6_placeholder": "Type your answer here…", "evaluate_a_product_idea_question_7_headline": "What would be most valuable to you in this feature?", - "evaluate_a_product_idea_question_7_placeholder": "Type your answer here...", + "evaluate_a_product_idea_question_7_placeholder": "Type your answer here…", "evaluate_a_product_idea_question_8_headline": "Anything else we should keep in mind?", - "evaluate_a_product_idea_question_8_placeholder": "Type your answer here...", + "evaluate_a_product_idea_question_8_placeholder": "Type your answer here…", "evaluate_content_quality_description": "Measure if your content marketing pieces hit right.", "evaluate_content_quality_name": "Evaluate Content Quality", "evaluate_content_quality_question_1_headline": "How well did this article address what you were hoping to learn?", "evaluate_content_quality_question_1_lower_label": "Not at all well", "evaluate_content_quality_question_1_upper_label": "Extremely well", "evaluate_content_quality_question_2_headline": "Hmpft! What were you hoping for?", - "evaluate_content_quality_question_2_placeholder": "Type your answer here...", + "evaluate_content_quality_question_2_placeholder": "Type your answer here…", "evaluate_content_quality_question_3_headline": "Lovely! Is there anything else you would like us to cover?", - "evaluate_content_quality_question_3_placeholder": "Topics, trends, tutorials...", + "evaluate_content_quality_question_3_placeholder": "Topics, trends, tutorials…", "fake_door_follow_up_description": "Follow up with users who ran into one of your Fake Door experiments.", "fake_door_follow_up_name": "Fake Door Follow-Up", "fake_door_follow_up_question_1_headline": "How important is this feature for you?", @@ -2572,31 +2717,31 @@ "feature_chaser_question_2_choice_3": "Aspect 3", "feature_chaser_question_2_choice_4": "Aspect 4", "feature_chaser_question_2_headline": "Which aspect is most important?", - "feedback_box_description": "Give your users the chance to seamlessly share what's on their minds.", + "feedback_box_description": "Give your users the chance to seamlessly share what is on their minds.", "feedback_box_name": "Feedback Box", "feedback_box_question_1_choice_1": "Bug report \uD83D\uDC1E", "feedback_box_question_1_choice_2": "Feature Request \uD83D\uDCA1", - "feedback_box_question_1_headline": "What's on your mind, boss?", - "feedback_box_question_1_subheader": "Thanks for sharing. We'll get back to you asap.", - "feedback_box_question_2_headline": "What's broken?", + "feedback_box_question_1_headline": "What is on your mind, boss?", + "feedback_box_question_1_subheader": "Thanks for sharing. We will get back to you asap.", + "feedback_box_question_2_headline": "What is broken?", "feedback_box_question_2_subheader": "The more detail, the better :)", "feedback_box_question_3_button_label": "Yes, notify me", "feedback_box_question_3_headline": "Want to stay in the loop?", "feedback_box_question_3_html": "

    We will fix this as soon as possible. Do you want to be notified when we did?

    ", "feedback_box_question_4_button_label": "Request feature", "feedback_box_question_4_headline": "Lovely, tell us more!", - "feedback_box_question_4_placeholder": "Type your answer here...", + "feedback_box_question_4_placeholder": "Type your answer here…", "feedback_box_question_4_subheader": "What problem do you want us to solve?", "file_upload": "File Upload", "file_upload_description": "Enable respondents to upload documents, images, or other files", "finish": "Finish", - "follow_ups_modal_action_body": "

    Hey \uD83D\uDC4B

    Thanks for taking the time to respond, we will be in touch shortly.

    Have a great day!

    ", + "follow_ups_modal_action_body": "

    Hey 👋

    Thanks for taking the time to respond, we will be in touch shortly.

    Have a great day!

    ", "free_text": "Free text", "free_text_description": "Collect open-ended feedback", - "free_text_placeholder": "Type your answer here...", + "free_text_placeholder": "Type your answer here…", "gauge_feature_satisfaction_description": "Evaluate the satisfaction of specific features of your product.", "gauge_feature_satisfaction_name": "Gauge Feature Satisfaction", - "gauge_feature_satisfaction_question_1_headline": "How easy was it to achieve ... ?", + "gauge_feature_satisfaction_question_1_headline": "How easy was it to achieve … ?", "gauge_feature_satisfaction_question_1_lower_label": "Not easy", "gauge_feature_satisfaction_question_1_upper_label": "Very easy", "gauge_feature_satisfaction_question_2_headline": "What is one thing we could do better?", @@ -2610,25 +2755,25 @@ "identify_sign_up_barriers_question_2_headline": "How likely are you to sign up for $[projectName]?", "identify_sign_up_barriers_question_2_lower_label": "Not at all likely", "identify_sign_up_barriers_question_2_upper_label": "Very likely", - "identify_sign_up_barriers_question_3_choice_1_label": "May not have what I'm looking for", + "identify_sign_up_barriers_question_3_choice_1_label": "May not have what I am looking for", "identify_sign_up_barriers_question_3_choice_2_label": "Still comparing options", "identify_sign_up_barriers_question_3_choice_3_label": "Seems complicated", "identify_sign_up_barriers_question_3_choice_4_label": "Pricing is a concern", "identify_sign_up_barriers_question_3_choice_5_label": "Something else", "identify_sign_up_barriers_question_3_headline": "What is holding you back from trying $[projectName]?", "identify_sign_up_barriers_question_4_headline": "What do you need but $[projectName] does not offer?", - "identify_sign_up_barriers_question_4_placeholder": "Type your answer here...", + "identify_sign_up_barriers_question_4_placeholder": "Type your answer here…", "identify_sign_up_barriers_question_5_headline": "What options are you looking at?", - "identify_sign_up_barriers_question_5_placeholder": "Type your answer here...", + "identify_sign_up_barriers_question_5_placeholder": "Type your answer here…", "identify_sign_up_barriers_question_6_headline": "What seems complicated to you?", - "identify_sign_up_barriers_question_6_placeholder": "Type your answer here...", + "identify_sign_up_barriers_question_6_placeholder": "Type your answer here…", "identify_sign_up_barriers_question_7_headline": "What are you concerned about regarding pricing?", - "identify_sign_up_barriers_question_7_placeholder": "Type your answer here...", + "identify_sign_up_barriers_question_7_placeholder": "Type your answer here…", "identify_sign_up_barriers_question_8_headline": "Please explain:", - "identify_sign_up_barriers_question_8_placeholder": "Type your answer here...", + "identify_sign_up_barriers_question_8_placeholder": "Type your answer here…", "identify_sign_up_barriers_question_9_button_label": "Sign Up", "identify_sign_up_barriers_question_9_headline": "Thanks! Here is your code: SIGNUPNOW10", - "identify_sign_up_barriers_question_9_html": "

    Thanks a lot for taking the time to share feedback \uD83D\uDE4F

    ", + "identify_sign_up_barriers_question_9_html": "

    Thanks a lot for taking the time to share feedback 🙏

    ", "identify_upsell_opportunities_description": "Find out how much time your product saves your user. Use it to upsell.", "identify_upsell_opportunities_name": "Identify Upsell Opportunities", "identify_upsell_opportunities_question_1_choice_1": "Less than 1 hour", @@ -2638,38 +2783,38 @@ "identify_upsell_opportunities_question_1_headline": "How many hours does your team save per week by using $[projectName]?", "improve_activation_rate_description": "Identify weaknesses in your onboarding flow to increase user activation.", "improve_activation_rate_name": "Improve Activation Rate", - "improve_activation_rate_question_1_choice_1": "Didn't seem useful to me", + "improve_activation_rate_question_1_choice_1": "Did not seem useful to me", "improve_activation_rate_question_1_choice_2": "Difficult to set up or use", "improve_activation_rate_question_1_choice_3": "Lacked features/functionality", - "improve_activation_rate_question_1_choice_4": "Just haven't had the time", + "improve_activation_rate_question_1_choice_4": "Just have not had the time", "improve_activation_rate_question_1_choice_5": "Something else", - "improve_activation_rate_question_1_headline": "What's the main reason why you haven't finished setting up $[projectName]?", - "improve_activation_rate_question_2_headline": "What made you think $[projectName] wouldn't be useful?", - "improve_activation_rate_question_2_placeholder": "Type your answer here...", + "improve_activation_rate_question_1_headline": "What is the main reason why you have not finished setting up $[projectName]?", + "improve_activation_rate_question_2_headline": "What made you think $[projectName] would not be useful?", + "improve_activation_rate_question_2_placeholder": "Type your answer here…", "improve_activation_rate_question_3_headline": "What was difficult about setting up or using $[projectName]?", - "improve_activation_rate_question_3_placeholder": "Type your answer here...", + "improve_activation_rate_question_3_placeholder": "Type your answer here…", "improve_activation_rate_question_4_headline": "What features or functionality were missing?", - "improve_activation_rate_question_4_placeholder": "Type your answer here...", + "improve_activation_rate_question_4_placeholder": "Type your answer here…", "improve_activation_rate_question_5_headline": "How could we make it easier for you to get started?", - "improve_activation_rate_question_5_placeholder": "Type your answer here...", + "improve_activation_rate_question_5_placeholder": "Type your answer here…", "improve_activation_rate_question_6_headline": "What was it? Please explain:", - "improve_activation_rate_question_6_placeholder": "Type your answer here...", - "improve_activation_rate_question_6_subheader": "We're eager to fix it asap.", + "improve_activation_rate_question_6_placeholder": "Type your answer here…", + "improve_activation_rate_question_6_subheader": "We are eager to fix it asap.", "improve_newsletter_content_description": "Find out how your subscribers like your newsletter content.", "improve_newsletter_content_name": "Improve Newsletter Content", "improve_newsletter_content_question_1_headline": "How would you rate this weeks newsletter?", "improve_newsletter_content_question_1_lower_label": "Meh", "improve_newsletter_content_question_1_upper_label": "Great", "improve_newsletter_content_question_2_headline": "What would have made this weeks newsletter more helpful?", - "improve_newsletter_content_question_2_placeholder": "Type your answer here...", + "improve_newsletter_content_question_2_placeholder": "Type your answer here…", "improve_newsletter_content_question_3_button_label": "Happy to help!", "improve_newsletter_content_question_3_headline": "Thanks! ❤️ Spread the love with ONE friend.", - "improve_newsletter_content_question_3_html": "

    Who thinks like you? You'd do us a huge favor if you'd share this weeks episode with your brain friend!

    ", + "improve_newsletter_content_question_3_html": "

    Who thinks like you? You would do us a huge favor if you would share this weeks episode with your brain friend!

    ", "improve_trial_conversion_description": "Find out why people stopped their trial. These insights help you improve your funnel.", "improve_trial_conversion_name": "Improve Trial Conversion", - "improve_trial_conversion_question_1_choice_1": "I didn't get much value out of it", + "improve_trial_conversion_question_1_choice_1": "I did not get much value out of it", "improve_trial_conversion_question_1_choice_2": "I expected something else", - "improve_trial_conversion_question_1_choice_3": "It's too expensive for what it does", + "improve_trial_conversion_question_1_choice_3": "It is too expensive for what it does", "improve_trial_conversion_question_1_choice_4": "I am missing a feature", "improve_trial_conversion_question_1_choice_5": "I was just looking around", "improve_trial_conversion_question_1_headline": "Why did you stop your trial?", @@ -2678,7 +2823,7 @@ "improve_trial_conversion_question_2_headline": "Sorry to hear. What was the biggest problem using $[projectName]?", "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": "

    We're happy to offer you a 20% discount on a yearly plan.

    ", + "improve_trial_conversion_question_4_html": "

    We are happy to offer you a 20% discount on a yearly plan.

    ", "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:", @@ -2690,23 +2835,23 @@ "integration_setup_survey_question_1_lower_label": "Not easy", "integration_setup_survey_question_1_upper_label": "Very easy", "integration_setup_survey_question_2_headline": "Why was it hard?", - "integration_setup_survey_question_2_placeholder": "Type your answer here...", + "integration_setup_survey_question_2_placeholder": "Type your answer here…", "integration_setup_survey_question_3_headline": "What other tools would you like to use with $[projectName]?", "integration_setup_survey_question_3_subheader": "We keep building integrations, yours can be next:", "interview_prompt_description": "Invite a specific subset of your users to schedule an interview with your product team.", "interview_prompt_name": "Interview Prompt", "interview_prompt_question_1_button_label": "Book slot", "interview_prompt_question_1_headline": "Do you have 15 min to talk to us? \uD83D\uDE4F", - "interview_prompt_question_1_html": "You're one of our power users. We would love to interview you briefly!", + "interview_prompt_question_1_html": "You are one of our power users. We would love to interview you briefly!", "long_term_retention_check_in_description": "Gauge long-term user satisfaction, loyalty, and areas for improvement to retain loyal users.", "long_term_retention_check_in_name": "Long-Term Retention Check-In", "long_term_retention_check_in_question_10_headline": "Any additional feedback or comments?", - "long_term_retention_check_in_question_10_placeholder": "Share any thoughts or feedback that might help us improve...", + "long_term_retention_check_in_question_10_placeholder": "Share any thoughts or feedback that might help us improve…", "long_term_retention_check_in_question_1_headline": "How satisfied are you with $[projectName] overall?", "long_term_retention_check_in_question_1_lower_label": "Not satisfied", "long_term_retention_check_in_question_1_upper_label": "Very satisfied", "long_term_retention_check_in_question_2_headline": "What do you find most valuable about $[projectName]?", - "long_term_retention_check_in_question_2_placeholder": "Describe the feature or benefit you value most...", + "long_term_retention_check_in_question_2_placeholder": "Describe the feature or benefit you value most…", "long_term_retention_check_in_question_3_choice_1": "Features", "long_term_retention_check_in_question_3_choice_2": "Customer support", "long_term_retention_check_in_question_3_choice_3": "User experience", @@ -2717,7 +2862,7 @@ "long_term_retention_check_in_question_4_lower_label": "Falls short", "long_term_retention_check_in_question_4_upper_label": "Exceeds expectations", "long_term_retention_check_in_question_5_headline": "What challenges or frustrations have you faced while using $[projectName]?", - "long_term_retention_check_in_question_5_placeholder": "Describe any challenges or improvements you’d like to see...", + "long_term_retention_check_in_question_5_placeholder": "Describe any challenges or improvements you would like to see…", "long_term_retention_check_in_question_6_headline": "How likely are you to recommend $[projectName] to a friend or colleague?", "long_term_retention_check_in_question_6_lower_label": "Not likely", "long_term_retention_check_in_question_6_upper_label": "Very likely", @@ -2728,7 +2873,7 @@ "long_term_retention_check_in_question_7_choice_5": "User experience refinements", "long_term_retention_check_in_question_7_headline": "What would make you more likely to remain a long-term user?", "long_term_retention_check_in_question_8_headline": "If you could change one thing about $[projectName], what would it be?", - "long_term_retention_check_in_question_8_placeholder": "Share any changes or features you wish we’d consider...", + "long_term_retention_check_in_question_8_placeholder": "Share any changes or features you wish we would consider…", "long_term_retention_check_in_question_9_headline": "How happy are you with our product updates and frequency?", "long_term_retention_check_in_question_9_lower_label": "Not happy", "long_term_retention_check_in_question_9_upper_label": "Very happy", @@ -2744,10 +2889,10 @@ "market_site_clarity_description": "Identify users dropping off your marketing site. Improve your messaging.", "market_site_clarity_name": "Marketing Site Clarity", "market_site_clarity_question_1_choice_1": "Yes, totally", - "market_site_clarity_question_1_choice_2": "Kind of...", + "market_site_clarity_question_1_choice_2": "Kind of…", "market_site_clarity_question_1_choice_3": "No, not at all", "market_site_clarity_question_1_headline": "Do you have all the info you need to give $[projectName] a try?", - "market_site_clarity_question_2_headline": "What's missing or unclear to you about $[projectName]?", + "market_site_clarity_question_2_headline": "What is missing or unclear to you about $[projectName]?", "market_site_clarity_question_3_button_label": "Get discount", "market_site_clarity_question_3_headline": "Thanks for your answer! Get 25% off your first 6 months:", "matrix": "Matrix", @@ -2758,10 +2903,10 @@ "measure_search_experience_question_1_lower_label": "Not at all relevant", "measure_search_experience_question_1_upper_label": "Very relevant", "measure_search_experience_question_2_headline": "Ugh! What makes the results irrelevant for you?", - "measure_search_experience_question_2_placeholder": "Type your answer here...", + "measure_search_experience_question_2_placeholder": "Type your answer here…", "measure_search_experience_question_3_headline": "Lovely! Is there anything we can do to improve your experience?", - "measure_search_experience_question_3_placeholder": "Type your answer here...", - "measure_task_accomplishment_description": "See if people get their 'Job To Be Done' done. Successful people are better customers.", + "measure_search_experience_question_3_placeholder": "Type your answer here…", + "measure_task_accomplishment_description": "See if people get their “Job To Be Done” done. Successful people are better customers.", "measure_task_accomplishment_name": "Measure Task Accomplishment", "measure_task_accomplishment_question_1_headline": "Were you able to accomplish what you came here to do today?", "measure_task_accomplishment_question_1_option_1_label": "Yes", @@ -2771,12 +2916,12 @@ "measure_task_accomplishment_question_2_lower_label": "Very difficult", "measure_task_accomplishment_question_2_upper_label": "Very easy", "measure_task_accomplishment_question_3_headline": "What made it hard?", - "measure_task_accomplishment_question_3_placeholder": "Type your answer here...", + "measure_task_accomplishment_question_3_placeholder": "Type your answer here…", "measure_task_accomplishment_question_4_button_label": "Send", "measure_task_accomplishment_question_4_headline": "Great! What did you come here to do today?", "measure_task_accomplishment_question_5_button_label": "Send", "measure_task_accomplishment_question_5_headline": "What stopped you?", - "measure_task_accomplishment_question_5_placeholder": "Type your answer here...", + "measure_task_accomplishment_question_5_placeholder": "Type your answer here…", "multi_select": "Multi-Select", "multi_select_description": "Ask respondents to choose one or more options", "new_integration_survey_description": "Find out which integrations your users would like to see next.", @@ -2817,7 +2962,7 @@ "onboarding_segmentation_question_2_choice_3": "6-10 employees", "onboarding_segmentation_question_2_choice_4": "11-100 employees", "onboarding_segmentation_question_2_choice_5": "over 100 employees", - "onboarding_segmentation_question_2_headline": "What's your company size?", + "onboarding_segmentation_question_2_headline": "What is your company size?", "onboarding_segmentation_question_2_subheader": "Please select one of the following options:", "onboarding_segmentation_question_3_choice_1": "Recommendation", "onboarding_segmentation_question_3_choice_2": "Social Media", @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Yes, keep me informed.", "preview_survey_question_2_choice_2_label": "No, thank you!", "preview_survey_question_2_headline": "Want to stay in the loop?", + "preview_survey_question_2_subheader": "This is an example description.", "preview_survey_welcome_card_headline": "Welcome!", "prioritize_features_description": "Identify features your users need most and least.", "prioritize_features_name": "Prioritize Features", @@ -2851,8 +2997,8 @@ "prioritize_features_question_2_choice_2": "Feature 2", "prioritize_features_question_2_choice_3": "Feature 3", "prioritize_features_question_2_headline": "Which of these features would be LEAST valuable to you?", - "prioritize_features_question_3_headline": "How else could we improve you experience with $[projectName]?", - "prioritize_features_question_3_placeholder": "Type your answer here...", + "prioritize_features_question_3_headline": "How else could we improve your experience with $[projectName]?", + "prioritize_features_question_3_placeholder": "Type your answer here…", "product_market_fit_short_description": "Measure PMF by assessing how disappointed users would be if your product disappeared.", "product_market_fit_short_name": "Product Market Fit Survey (Short)", "product_market_fit_short_question_1_choice_1": "Not at all disappointed", @@ -2895,7 +3041,7 @@ "professional_development_growth_survey_question_3_lower_label": "Unclear goals", "professional_development_growth_survey_question_3_upper_label": "Clear and aligned goals", "professional_development_growth_survey_question_4_headline": "What could be improved to support your professional growth?", - "professional_development_growth_survey_question_4_placeholder": "Type your answer here...", + "professional_development_growth_survey_question_4_placeholder": "Type your answer here…", "professional_development_survey_description": "Assess employee satisfaction with professional growth and development opportunities.", "professional_development_survey_name": "Professional Development Survey", "professional_development_survey_question_1_choice_1": "Yes", @@ -2930,9 +3076,9 @@ "rate_checkout_experience_question_1_lower_label": "Very difficult", "rate_checkout_experience_question_1_upper_label": "Very easy", "rate_checkout_experience_question_2_headline": "Sorry about that! What would have made it easier for you?", - "rate_checkout_experience_question_2_placeholder": "Type your answer here...", + "rate_checkout_experience_question_2_placeholder": "Type your answer here…", "rate_checkout_experience_question_3_headline": "Lovely! Is there anything we can do to improve your experience?", - "rate_checkout_experience_question_3_placeholder": "Type your answer here...", + "rate_checkout_experience_question_3_placeholder": "Type your answer here…", "rating": "Rating", "rating_description": "Ask respondents for a rating (stars, smileys, numbers)", "rating_lower_label": "Not good", @@ -2949,18 +3095,18 @@ "recognition_and_reward_survey_question_3_lower_label": "Not comfortable", "recognition_and_reward_survey_question_3_upper_label": "Very comfortable", "recognition_and_reward_survey_question_4_headline": "How could the organization improve recognition and rewards?", - "recognition_and_reward_survey_question_4_placeholder": "Type your answer here...", + "recognition_and_reward_survey_question_4_placeholder": "Type your answer here…", "review_prompt_description": "Invite users who love your product to review it publicly.", "review_prompt_name": "Review Prompt", "review_prompt_question_1_headline": "How do you like $[projectName]?", "review_prompt_question_1_lower_label": "Not good", "review_prompt_question_1_upper_label": "Very satisfied", "review_prompt_question_2_button_label": "Write review", - "review_prompt_question_2_headline": "Happy to hear \uD83D\uDE4F Please write a review for us!", + "review_prompt_question_2_headline": "Happy to hear 🙏 Please write a review for us!", "review_prompt_question_2_html": "

    This helps us a lot.

    ", "review_prompt_question_3_button_label": "Send", "review_prompt_question_3_headline": "Sorry to hear! What is ONE thing we can do better?", - "review_prompt_question_3_placeholder": "Type your answer here...", + "review_prompt_question_3_placeholder": "Type your answer here…", "review_prompt_question_3_subheader": "Help us improve your experience.", "schedule_a_meeting": "Schedule a meeting", "schedule_a_meeting_description": "Ask respondents to book a time slot for meetings or calls", @@ -2968,16 +3114,16 @@ "single_select_description": "Offer a list of options (choose one)", "site_abandonment_survey": "Site Abandonment Survey", "site_abandonment_survey_description": "Understand the reasons behind site abandonment in your web shop.", - "site_abandonment_survey_question_1_html": "

    We noticed you're leaving our site without making a purchase. We would love to understand why.

    ", + "site_abandonment_survey_question_1_html": "

    We noticed you are leaving our site without making a purchase. We would love to understand why.

    ", "site_abandonment_survey_question_2_button_label": "Sure!", "site_abandonment_survey_question_2_headline": "Do you have a minute?", - "site_abandonment_survey_question_3_choice_1": "Can't find what I am looking for", + "site_abandonment_survey_question_3_choice_1": "Cannot find what I am looking for", "site_abandonment_survey_question_3_choice_2": "Found a better site", "site_abandonment_survey_question_3_choice_3": "Site is too slow", "site_abandonment_survey_question_3_choice_4": "Just browsing", "site_abandonment_survey_question_3_choice_5": "Found a better price elsewhere", "site_abandonment_survey_question_3_choice_6": "Other", - "site_abandonment_survey_question_3_headline": "What's the primary reason you're leaving our site?", + "site_abandonment_survey_question_3_headline": "What is the primary reason you are leaving our site?", "site_abandonment_survey_question_3_subheader": "Please select one of the following options:", "site_abandonment_survey_question_4_headline": "Please elaborate on your reason for leaving the site:", "site_abandonment_survey_question_5_headline": "How would you rate your overall experience on our site?", @@ -2999,22 +3145,22 @@ "smileys_survey_question_1_lower_label": "Not good", "smileys_survey_question_1_upper_label": "Very satisfied", "smileys_survey_question_2_button_label": "Write review", - "smileys_survey_question_2_headline": "Happy to hear \uD83D\uDE4F Please write a review for us!", + "smileys_survey_question_2_headline": "Happy to hear 🙏 Please write a review for us!", "smileys_survey_question_2_html": "

    This helps us a lot.

    ", "smileys_survey_question_3_button_label": "Send", "smileys_survey_question_3_headline": "Sorry to hear! What is ONE thing we can do better?", - "smileys_survey_question_3_placeholder": "Type your answer here...", + "smileys_survey_question_3_placeholder": "Type your answer here…", "smileys_survey_question_3_subheader": "Help us improve your experience.", - "star_rating_survey_name": "$[projectName]'s Rating Survey", + "star_rating_survey_name": "$[projectName]’s Rating Survey", "star_rating_survey_question_1_headline": "How do you like $[projectName]?", "star_rating_survey_question_1_lower_label": "Extremely dissatisfied", "star_rating_survey_question_1_upper_label": "Extremely satisfied", "star_rating_survey_question_2_button_label": "Write review", - "star_rating_survey_question_2_headline": "Happy to hear \uD83D\uDE4F Please write a review for us!", + "star_rating_survey_question_2_headline": "Happy to hear 🙏 Please write a review for us!", "star_rating_survey_question_2_html": "

    This helps us a lot.

    ", "star_rating_survey_question_3_button_label": "Send", "star_rating_survey_question_3_headline": "Sorry to hear! What is ONE thing we can do better?", - "star_rating_survey_question_3_placeholder": "Type your answer here...", + "star_rating_survey_question_3_placeholder": "Type your answer here…", "star_rating_survey_question_3_subheader": "Help us improve your experience.", "statement_call_to_action": "Statement (Call to Action)", "strongly_agree": "Strongly Agree", @@ -3031,12 +3177,12 @@ "supportive_work_culture_survey_question_3_lower_label": "Not supportive", "supportive_work_culture_survey_question_3_upper_label": "Very supportive", "supportive_work_culture_survey_question_4_headline": "How could the work culture be improved to better support you?", - "supportive_work_culture_survey_question_4_placeholder": "Type your answer here...", - "uncover_strengths_and_weaknesses_description": "Find out what users like and don't like about your product or offering.", + "supportive_work_culture_survey_question_4_placeholder": "Type your answer here…", + "uncover_strengths_and_weaknesses_description": "Find out what users like and do not like about your product or offering.", "uncover_strengths_and_weaknesses_name": "Uncover Strengths & Weaknesses", "uncover_strengths_and_weaknesses_question_1_choice_1": "Ease of use", "uncover_strengths_and_weaknesses_question_1_choice_2": "Good value for money", - "uncover_strengths_and_weaknesses_question_1_choice_3": "It's open-source", + "uncover_strengths_and_weaknesses_question_1_choice_3": "It is open-source", "uncover_strengths_and_weaknesses_question_1_choice_4": "The founders are cute", "uncover_strengths_and_weaknesses_question_1_choice_5": "Other", "uncover_strengths_and_weaknesses_question_1_headline": "What do you value most about $[projectName]?", @@ -3052,34 +3198,34 @@ "understand_low_engagement_name": "Understand Low Engagement", "understand_low_engagement_question_1_choice_1": "Difficult to use", "understand_low_engagement_question_1_choice_2": "Found a better alternative", - "understand_low_engagement_question_1_choice_3": "Just haven't had the time", + "understand_low_engagement_question_1_choice_3": "Just have not had the time", "understand_low_engagement_question_1_choice_4": "Lacked features I need", "understand_low_engagement_question_1_choice_5": "Other", - "understand_low_engagement_question_1_headline": "What's the main reason you haven't been back to $[projectName] recently?", - "understand_low_engagement_question_2_headline": "What's difficult about using $[projectName]?", - "understand_low_engagement_question_2_placeholder": "Type your answer here...", + "understand_low_engagement_question_1_headline": "What is the main reason you have not been back to $[projectName] recently?", + "understand_low_engagement_question_2_headline": "What is difficult about using $[projectName]?", + "understand_low_engagement_question_2_placeholder": "Type your answer here…", "understand_low_engagement_question_3_headline": "Got it. Which alternative are you using instead?", - "understand_low_engagement_question_3_placeholder": "Type your answer here...", + "understand_low_engagement_question_3_placeholder": "Type your answer here…", "understand_low_engagement_question_4_headline": "Got it. How could we make it easier for you to get started?", - "understand_low_engagement_question_4_placeholder": "Type your answer here...", + "understand_low_engagement_question_4_placeholder": "Type your answer here…", "understand_low_engagement_question_5_headline": "Got it. What features or functionality were missing?", - "understand_low_engagement_question_5_placeholder": "Type your answer here...", + "understand_low_engagement_question_5_placeholder": "Type your answer here…", "understand_low_engagement_question_6_headline": "Please add more details:", - "understand_low_engagement_question_6_placeholder": "Type your answer here...", + "understand_low_engagement_question_6_placeholder": "Type your answer here…", "understand_purchase_intention_description": "Find out how close your visitors are to buy or subscribe.", "understand_purchase_intention_name": "Understand Purchase Intention", "understand_purchase_intention_question_1_headline": "How likely are you to shop from us today?", "understand_purchase_intention_question_1_lower_label": "Not at all likely", "understand_purchase_intention_question_1_upper_label": "Extremely likely", - "understand_purchase_intention_question_2_headline": "Got it. What's your primary reason for visiting today?", - "understand_purchase_intention_question_2_placeholder": "Type your answer here...", + "understand_purchase_intention_question_2_headline": "Got it. What is your primary reason for visiting today?", + "understand_purchase_intention_question_2_placeholder": "Type your answer here…", "understand_purchase_intention_question_3_headline": "What, if anything, is holding you back from making a purchase today?", - "understand_purchase_intention_question_3_placeholder": "Type your answer here...", - "usability_question_10_headline": " I had to learn a lot before I could start using the system properly.", - "usability_question_1_headline": "I’d probably use this system often.", + "understand_purchase_intention_question_3_placeholder": "Type your answer here…", + "usability_question_10_headline": "I had to learn a lot before I could start using the system properly.", + "usability_question_1_headline": "I would probably use this system often.", "usability_question_2_headline": "The system felt more complicated than it needed to be.", "usability_question_3_headline": "The system was easy to figure out.", - "usability_question_4_headline": "I think I’d need help from a tech expert to use this system.", + "usability_question_4_headline": "I think I would need help from a tech expert to use this system.", "usability_question_5_headline": "Everything in the system seemed to work well together.", "usability_question_6_headline": "The system felt inconsistent in how things worked.", "usability_question_7_headline": "I think most people could learn to use this system quickly.", diff --git a/apps/web/locales/es-ES.json b/apps/web/locales/es-ES.json index 46262595cb..baf70fa7f8 100644 --- a/apps/web/locales/es-ES.json +++ b/apps/web/locales/es-ES.json @@ -187,6 +187,7 @@ "customer_success": "Éxito del cliente", "dark_overlay": "Superposición oscura", "date": "Fecha", + "days": "días", "default": "Predeterminado", "delete": "Eliminar", "description": "Descripción", @@ -253,6 +254,7 @@ "label": "Etiqueta", "language": "Idioma", "learn_more": "Saber más", + "license_expired": "License Expired", "light_overlay": "Superposición clara", "limits_reached": "Límites alcanzados", "link": "Enlace", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks funciona mejor en una pantalla más grande. Para gestionar o crear encuestas, cambia a otro dispositivo.", "mobile_overlay_surveys_look_good": "No te preocupes – ¡tus encuestas se ven geniales en todos los dispositivos y tamaños de pantalla!", "mobile_overlay_title": "¡Ups, pantalla pequeña detectada!", + "months": "meses", "move_down": "Mover hacia abajo", "move_up": "Mover hacia arriba", "multiple_languages": "Múltiples idiomas", @@ -283,6 +286,7 @@ "no_background_image_found": "No se encontró imagen de fondo.", "no_code": "Sin código", "no_files_uploaded": "No se subieron archivos", + "no_overlay": "Sin superposición", "no_quotas_found": "No se encontraron cuotas", "no_result_found": "No se encontró resultado", "no_results": "Sin resultados", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Equipos de la organización no encontrados", "other": "Otro", "others": "Otros", + "overlay_color": "Color de superposición", "overview": "Resumen", "password": "Contraseña", "paused": "Pausado", @@ -348,6 +353,7 @@ "request_trial_license": "Solicitar licencia de prueba", "reset_to_default": "Restablecer a valores predeterminados", "response": "Respuesta", + "response_id": "ID de respuesta", "responses": "Respuestas", "restart": "Reiniciar", "role": "Rol", @@ -388,6 +394,7 @@ "status": "Estado", "step_by_step_manual": "Manual paso a paso", "storage_not_configured": "Almacenamiento de archivos no configurado, es probable que fallen las subidas", + "string": "Texto", "styling": "Estilo", "submit": "Enviar", "summary": "Resumen", @@ -443,6 +450,7 @@ "website_and_app_connection": "Conexión de sitio web y aplicación", "website_app_survey": "Encuesta de sitio web y aplicación", "website_survey": "Encuesta de sitio web", + "weeks": "semanas", "welcome_card": "Tarjeta de bienvenida", "workspace_configuration": "Configuración del proyecto", "workspace_created_successfully": "Proyecto creado correctamente", @@ -453,13 +461,15 @@ "workspace_not_found": "Proyecto no encontrado", "workspace_permission_not_found": "Permiso del proyecto no encontrado", "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.", "you_have_reached_your_monthly_miu_limit_of": "Has alcanzado tu límite mensual de MIU de", "you_have_reached_your_monthly_response_limit_of": "Has alcanzado tu límite mensual de respuestas de", - "you_will_be_downgraded_to_the_community_edition_on_date": "Serás degradado a la edición Community el {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Serás degradado a la edición Community el {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Aceptar", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Atributo actualizado con éxito", "attribute_value": "Valor", "attribute_value_placeholder": "Valor del atributo", + "attributes_msg_attribute_limit_exceeded": "No se pudieron crear {count} atributo(s) nuevo(s) ya que se excedería el límite máximo de {limit} clases de atributos. Los atributos existentes se actualizaron correctamente.", + "attributes_msg_attribute_type_validation_error": "{error} (el atributo '{key}' tiene dataType: {dataType})", + "attributes_msg_email_already_exists": "El email ya existe para este entorno y no se actualizó.", + "attributes_msg_email_or_userid_required": "Se requiere email o userId. Se conservaron los valores existentes.", + "attributes_msg_new_attribute_created": "Se creó el atributo nuevo '{key}' con tipo '{dataType}'", + "attributes_msg_userid_already_exists": "El userId ya existe para este entorno y no se actualizó.", "contact_deleted_successfully": "Contacto eliminado correctamente", "contact_not_found": "No se ha encontrado dicho contacto", "contacts_table_refresh": "Actualizar contactos", @@ -631,6 +647,11 @@ "create_key": "Crear clave", "create_new_attribute": "Crear atributo nuevo", "create_new_attribute_description": "Crea un atributo nuevo para fines de segmentación.", + "custom_attributes": "Atributos personalizados", + "data_type": "Tipo de dato", + "data_type_cannot_be_changed": "El tipo de dato no se puede cambiar después de la creación", + "data_type_description": "Elige cómo debe almacenarse y filtrarse este atributo", + "date_value_required": "Se requiere un valor de fecha. Usa el botón de eliminar para quitar este atributo si no quieres establecer una fecha.", "delete_attribute_confirmation": "{value, plural, one {Esto eliminará el atributo seleccionado. Se perderán todos los datos de contacto asociados con este atributo.} other {Esto eliminará los atributos seleccionados. Se perderán todos los datos de contacto asociados con estos atributos.}}", "delete_contact_confirmation": "Esto eliminará todas las respuestas de encuestas y atributos de contacto asociados con este contacto. Cualquier segmentación y personalización basada en los datos de este contacto se perderá.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Esto eliminará todas las respuestas de encuestas y atributos de contacto asociados con este contacto. Cualquier segmentación y personalización basada en los datos de este contacto se perderá. Si este contacto tiene respuestas que cuentan para las cuotas de encuesta, los recuentos de cuota se reducirán pero los límites de cuota permanecerán sin cambios.} other {Esto eliminará todas las respuestas de encuestas y atributos de contacto asociados con estos contactos. Cualquier segmentación y personalización basada en los datos de estos contactos se perderá. Si estos contactos tienen respuestas que cuentan para las cuotas de encuesta, los recuentos de cuota se reducirán pero los límites de cuota permanecerán sin cambios.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Actualiza la etiqueta y la descripción de este atributo.", "edit_attribute_values": "Editar atributos", "edit_attribute_values_description": "Cambia los valores de atributos específicos para este contacto.", + "edit_attributes": "Editar atributos", "edit_attributes_success": "Atributos del contacto actualizados correctamente", "generate_personal_link": "Generar enlace personal", "generate_personal_link_description": "Selecciona una encuesta publicada para generar un enlace personalizado para este contacto.", + "invalid_csv_column_names": "Nombre(s) de columna CSV no válido(s): {columns}. Los nombres de columna que se convertirán en nuevos atributos solo deben contener letras minúsculas, números y guiones bajos, y deben comenzar con una letra.", + "invalid_date_format": "Formato de fecha no válido. Por favor, usa una fecha válida.", + "invalid_number_format": "Formato de número no válido. Por favor, introduce un número válido.", "no_published_link_surveys_available": "No hay encuestas de enlace publicadas disponibles. Por favor, publica primero una encuesta de enlace.", "no_published_surveys": "No hay encuestas publicadas", "no_responses_found": "No se encontraron respuestas", "not_provided": "No proporcionado", + "number_value_required": "Se requiere un valor numérico. Usa el botón de eliminar para quitar este atributo.", "personal_link_generated": "Enlace personal generado correctamente", "personal_link_generated_but_clipboard_failed": "Enlace personal generado pero falló al copiar al portapapeles: {url}", "personal_survey_link": "Enlace personal de encuesta", @@ -653,13 +679,22 @@ "search_contact": "Buscar contacto", "select_a_survey": "Selecciona una encuesta", "select_attribute": "Seleccionar atributo", + "select_attribute_key": "Seleccionar clave de atributo", + "system_attributes": "Atributos del sistema", "unlock_contacts_description": "Gestiona contactos y envía encuestas dirigidas", "unlock_contacts_title": "Desbloquea contactos con un plan superior", + "upload_contacts_error_attribute_type_mismatch": "El atributo \"{key}\" está tipado como \"{dataType}\" pero el CSV contiene valores no válidos: {values}", + "upload_contacts_error_duplicate_mappings": "Se encontraron mapeos duplicados para los siguientes atributos: {attributes}", + "upload_contacts_error_file_too_large": "El tamaño del archivo supera el límite máximo de 800 KB", + "upload_contacts_error_generic": "Se produjo un error al cargar los contactos. Por favor, inténtalo de nuevo más tarde.", + "upload_contacts_error_invalid_file_type": "Por favor, carga un archivo CSV", + "upload_contacts_error_no_valid_contacts": "El archivo CSV cargado no contiene ningún contacto válido, por favor consulta el archivo CSV de ejemplo para ver el formato correcto.", + "upload_contacts_modal_attribute_header": "Atributo de Formbricks", "upload_contacts_modal_attributes_description": "Asigna las columnas de tu CSV a los atributos en Formbricks.", "upload_contacts_modal_attributes_new": "Nuevo atributo", "upload_contacts_modal_attributes_search_or_add": "Buscar o añadir atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "debe asignarse a", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Columna CSV", "upload_contacts_modal_description": "Sube un CSV para importar rápidamente contactos con atributos", "upload_contacts_modal_download_example_csv": "Descargar CSV de ejemplo", "upload_contacts_modal_duplicates_description": "¿Cómo deberíamos manejar si un contacto ya existe en tus contactos?", @@ -840,6 +875,40 @@ "no_attributes_yet": "¡Aún no hay atributos!", "no_filters_yet": "¡Aún no hay filtros!", "no_segments_yet": "Actualmente no tienes segmentos guardados.", + "operator_contains": "contiene", + "operator_does_not_contain": "no contiene", + "operator_ends_with": "termina con", + "operator_is_after": "es después de", + "operator_is_before": "es antes de", + "operator_is_between": "está entre", + "operator_is_newer_than": "es más reciente que", + "operator_is_not_set": "no está establecido", + "operator_is_older_than": "es más antiguo que", + "operator_is_same_day": "es el mismo día", + "operator_is_set": "está establecido", + "operator_starts_with": "comienza con", + "operator_title_contains": "Contiene", + "operator_title_does_not_contain": "No contiene", + "operator_title_ends_with": "Termina con", + "operator_title_equals": "Es igual a", + "operator_title_greater_equal": "Mayor o igual que", + "operator_title_greater_than": "Mayor que", + "operator_title_is_after": "Es después de", + "operator_title_is_before": "Es antes de", + "operator_title_is_between": "Está entre", + "operator_title_is_newer_than": "Es más reciente que", + "operator_title_is_not_set": "No está establecido", + "operator_title_is_older_than": "Es más antiguo que", + "operator_title_is_same_day": "Es el mismo día", + "operator_title_is_set": "Está establecido", + "operator_title_less_equal": "Menor o igual que", + "operator_title_less_than": "Menor que", + "operator_title_not_equals": "No es igual a", + "operator_title_starts_with": "Comienza con", + "operator_title_user_is_in": "El usuario está en", + "operator_title_user_is_not_in": "El usuario no está en", + "operator_user_is_in": "El usuario está en", + "operator_user_is_not_in": "El usuario no está en", "person_and_attributes": "Persona y atributos", "phone": "Teléfono", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Por favor, elimina el segmento de estas encuestas para poder borrarlo.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "La segmentación de usuarios actualmente solo está disponible cuando", "value_cannot_be_empty": "El valor no puede estar vacío.", "value_must_be_a_number": "El valor debe ser un número.", + "value_must_be_positive": "El valor debe ser un número positivo.", "view_filters": "Ver filtros", "where": "Donde", "with_the_formbricks_sdk": "con el SDK de Formbricks" @@ -950,19 +1020,32 @@ "enterprise_features": "Características empresariales", "get_an_enterprise_license_to_get_access_to_all_features": "Obtén una licencia empresarial para acceder a todas las características.", "keep_full_control_over_your_data_privacy_and_security": "Mantén el control total sobre la privacidad y seguridad de tus datos.", + "license_invalid_description": "La clave de licencia en tu variable de entorno ENTERPRISE_LICENSE_KEY no es válida. Por favor, comprueba si hay errores tipográficos o solicita una clave nueva.", + "license_status": "Estado de la licencia", + "license_status_active": "Activa", + "license_status_description": "Estado de tu licencia enterprise.", + "license_status_expired": "Caducada", + "license_status_invalid": "Licencia no válida", + "license_status_unreachable": "Inaccesible", + "license_unreachable_grace_period": "No se puede acceder al servidor de licencias. Tus funciones empresariales permanecen activas durante un período de gracia de 3 días que finaliza el {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sin necesidad de llamadas, sin compromisos: solicita una licencia de prueba gratuita de 30 días para probar todas las características rellenando este formulario:", "no_credit_card_no_sales_call_just_test_it": "Sin tarjeta de crédito. Sin llamada de ventas. Solo pruébalo :)", "on_request": "Bajo petición", "organization_roles": "Roles de organización (administrador, editor, desarrollador, etc.)", "questions_please_reach_out_to": "¿Preguntas? Por favor, contacta con", + "recheck_license": "Volver a comprobar licencia", + "recheck_license_failed": "Error al comprobar la licencia. Es posible que el servidor de licencias no esté disponible.", + "recheck_license_invalid": "La clave de licencia no es válida. Por favor, verifica tu ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Comprobación de licencia correcta", + "recheck_license_unreachable": "El servidor de licencias no está disponible. Inténtalo de nuevo más tarde.", + "rechecking": "Comprobando...", "request_30_day_trial_license": "Solicitar licencia de prueba de 30 días", "saml_sso": "SAML SSO", "service_level_agreement": "Acuerdo de nivel de servicio", "soc2_hipaa_iso_27001_compliance_check": "Verificación de cumplimiento SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Equipos y roles de acceso (lectura, lectura y escritura, gestión)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días.", - "your_enterprise_license_is_active_all_features_unlocked": "Tu licencia empresarial está activa. Todas las características desbloqueadas." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días." }, "general": { "bulk_invite_warning_description": "En el plan gratuito, a todos los miembros de la organización se les asigna siempre el rol de \"Propietario\".", @@ -986,7 +1069,7 @@ "from_your_organization": "de tu organización", "invitation_sent_once_more": "Invitación enviada una vez más.", "invite_deleted_successfully": "Invitación eliminada correctamente", - "invited_on": "Invitado el {date}", + "invite_expires_on": "La invitación expira el {date}", "invites_failed": "Las invitaciones fallaron", "leave_organization": "Abandonar organización", "leave_organization_description": "Abandonarás esta organización y perderás acceso a todas las encuestas y respuestas. Solo podrás volver a unirte si te invitan de nuevo.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Por favor, rellena todos los campos para añadir un proyecto nuevo.", "read": "Lectura", "read_write": "Lectura y escritura", - "select_member": "Seleccionar miembro", - "select_workspace": "Seleccionar proyecto", "team_admin": "Administrador de equipo", "team_created_successfully": "Equipo creado con éxito.", "team_deleted_successfully": "Equipo eliminado correctamente.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Añadir un marcador de posición para mostrar si no hay valor que recuperar.", "add_hidden_field_id": "Añadir ID de campo oculto", "add_highlight_border": "Añadir borde destacado", - "add_highlight_border_description": "Añadir un borde exterior a tu tarjeta de encuesta.", "add_logic": "Añadir lógica", "add_none_of_the_above": "Añadir \"Ninguna de las anteriores\"", "add_option": "Añadir opción", @@ -1189,6 +1269,7 @@ "block_duplicated": "Bloque duplicado.", "bold": "Negrita", "brand_color": "Color de marca", + "brand_color_description": "Se aplica a botones, enlaces y resaltados.", "brightness": "Brillo", "bulk_edit": "Edición masiva", "bulk_edit_description": "Edita todas las opciones a continuación, una por línea. Las líneas vacías se omitirán y los duplicados se eliminarán.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Capturar nueva acción", "card_arrangement_for_survey_type_derived": "Disposición de tarjetas para encuestas de tipo {surveyTypeDerived}", "card_background_color": "Color de fondo de la tarjeta", + "card_background_color_description": "Rellena el área de la tarjeta de encuesta.", "card_border_color": "Color del borde de la tarjeta", + "card_border_color_description": "Delinea la tarjeta de encuesta.", "card_styling": "Estilo de la tarjeta", "casual": "Informal", "caution_edit_duplicate": "Duplicar y editar", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Las respuestas antiguas y nuevas se mezclan, lo que puede llevar a resúmenes de datos engañosos.", "caution_recommendation": "Esto puede causar inconsistencias de datos en el resumen de la encuesta. Recomendamos duplicar la encuesta en su lugar.", "caution_text": "Los cambios provocarán inconsistencias", - "centered_modal_overlay_color": "Color de superposición del modal centrado", "change_anyway": "Cambiar de todos modos", "change_background": "Cambiar fondo", "change_question_type": "Cambiar tipo de pregunta", "change_survey_type": "Cambiar el tipo de encuesta afecta al acceso existente", - "change_the_background_color_of_the_card": "Cambiar el color de fondo de la tarjeta.", - "change_the_background_color_of_the_input_fields": "Cambiar el color de fondo de los campos de entrada.", "change_the_background_to_a_color_image_or_animation": "Cambiar el fondo a un color, imagen o animación.", - "change_the_border_color_of_the_card": "Cambiar el color del borde de la tarjeta.", - "change_the_border_color_of_the_input_fields": "Cambiar el color del borde de los campos de entrada.", - "change_the_border_radius_of_the_card_and_the_inputs": "Cambiar el radio del borde de la tarjeta y las entradas.", - "change_the_brand_color_of_the_survey": "Cambiar el color de marca de la encuesta.", "change_the_placement_of_this_survey": "Cambiar la ubicación de esta encuesta.", - "change_the_question_color_of_the_survey": "Cambiar el color de las preguntas de la encuesta.", "changes_saved": "Cambios guardados.", "changing_survey_type_will_remove_existing_distribution_channels": "Cambiar el tipo de encuesta afectará a cómo se puede compartir. Si los encuestados ya tienen enlaces de acceso para el tipo actual, podrían perder el acceso después del cambio.", "checkbox_label": "Etiqueta de casilla de verificación", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Desactivar la visibilidad del progreso de la encuesta.", "display_an_estimate_of_completion_time_for_survey": "Mostrar una estimación del tiempo de finalización de la encuesta", "display_number_of_responses_for_survey": "Mostrar número de respuestas para la encuesta", + "display_type": "Tipo de visualización", "divide": "Dividir /", "does_not_contain": "No contiene", "does_not_end_with": "No termina con", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "No incluye todos los", "does_not_include_one_of": "No incluye uno de", "does_not_start_with": "No comienza con", + "dropdown": "Desplegable", "duplicate_block": "Duplicar bloque", "duplicate_question": "Duplicar pregunta", "edit_link": "Editar enlace", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Ocultar barra de progreso", "hide_question_settings": "Ocultar ajustes de la pregunta", "hostname": "Nombre de host", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "¿Cuánto estilo quieres darle a tus tarjetas en las encuestas de tipo {surveyTypeDerived}?", "if_you_need_more_please": "Si necesitas más, por favor", "if_you_really_want_that_answer_ask_until_you_get_it": "Seguir mostrando cuando se active hasta que se envíe una respuesta.", "ignore_global_waiting_time": "Ignorar periodo de espera", @@ -1379,7 +1455,9 @@ "initial_value": "Valor inicial", "inner_text": "Texto interior", "input_border_color": "Color del borde de entrada", + "input_border_color_description": "Delinea los campos de texto y áreas de texto.", "input_color": "Color de entrada", + "input_color_description": "Rellena el interior de los campos de texto.", "insert_link": "Insertar enlace", "invalid_targeting": "Segmentación no válida: por favor, comprueba tus filtros de audiencia", "invalid_video_url_warning": "Por favor, introduce una URL válida de YouTube, Vimeo o Loom. Actualmente no admitimos otros proveedores de alojamiento de vídeos.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limita el tamaño máximo de archivo para las subidas.", "limit_upload_file_size_to": "Limitar el tamaño de archivo de subida a", "link_survey_description": "Comparte un enlace a una página de encuesta o incrústala en una página web o correo electrónico.", + "list": "Lista", "load_segment": "Cargar segmento", "logic_error_warning": "El cambio causará errores lógicos", "logic_error_warning_text": "Cambiar el tipo de pregunta eliminará las condiciones lógicas de esta pregunta", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Solo los usuarios que tengan el PIN pueden acceder a la encuesta.", "publish": "Publicar", "question": "Pregunta", - "question_color": "Color de la pregunta", "question_deleted": "Pregunta eliminada.", "question_duplicated": "Pregunta duplicada.", "question_id_updated": "ID de pregunta actualizado", "question_used_in_logic_warning_text": "Los elementos de este bloque se usan en una regla de lógica, ¿estás seguro de que quieres eliminarlo?", "question_used_in_logic_warning_title": "Inconsistencia de lógica", - "question_used_in_quota": "Esta pregunta se está utilizando en la cuota \"{quotaName}\"", + "question_used_in_quota": "Esta pregunta se está utilizando en la cuota “{quotaName}”", "question_used_in_recall": "Esta pregunta se está recordando en la pregunta {questionIndex}.", "question_used_in_recall_ending_card": "Esta pregunta se está recordando en la Tarjeta Final", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Límites de respuestas, redirecciones y más.", "response_options": "Opciones de respuesta", "roundness": "Redondez", + "roundness_description": "Controla qué tan redondeadas están las esquinas de la tarjeta.", "row_used_in_logic_error": "Esta fila se utiliza en la lógica de la pregunta {questionIndex}. Por favor, elimínala de la lógica primero.", "rows": "Filas", "save_and_close": "Guardar y cerrar", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Estilo configurado según los estilos del tema", "subheading": "Subtítulo", "subtract": "Restar -", - "suggest_colors": "Sugerir colores", "survey_completed_heading": "Encuesta completada", "survey_completed_subheading": "Esta encuesta gratuita y de código abierto ha sido cerrada", "survey_display_settings": "Ajustes de visualización de la encuesta", @@ -1642,7 +1720,7 @@ "validation_rules": "Reglas de validación", "validation_rules_description": "Solo aceptar respuestas que cumplan los siguientes criterios", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} se usa en la lógica de la pregunta {questionIndex}. Por favor, elimínala primero de la lógica.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable \"{variableName}\" se está utilizando en la cuota \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable “{variableName}” se está utilizando en la cuota “{quotaName}”", "variable_name_conflicts_with_hidden_field": "El nombre de la variable entra en conflicto con un ID de campo oculto existente.", "variable_name_is_already_taken_please_choose_another": "El nombre de la variable ya está en uso, por favor elige otro.", "variable_name_must_start_with_a_letter": "El nombre de la variable debe comenzar con una letra.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Añadir color de fondo", "add_background_color_description": "Añade un color de fondo al contenedor del logotipo.", + "advanced_styling_field_border_radius": "Radio del borde", + "advanced_styling_field_button_bg": "Fondo del botón", + "advanced_styling_field_button_bg_description": "Rellena el botón siguiente / enviar.", + "advanced_styling_field_button_border_radius_description": "Redondea las esquinas del botón.", + "advanced_styling_field_button_font_size_description": "Escala el texto de la etiqueta del botón.", + "advanced_styling_field_button_font_weight_description": "Hace el texto del botón más ligero o más grueso.", + "advanced_styling_field_button_height_description": "Controla la altura del botón.", + "advanced_styling_field_button_padding_x_description": "Añade espacio a la izquierda y a la derecha.", + "advanced_styling_field_button_padding_y_description": "Añade espacio arriba y abajo.", + "advanced_styling_field_button_text": "Texto del botón", + "advanced_styling_field_button_text_description": "Colorea la etiqueta dentro de los botones.", + "advanced_styling_field_description_color": "Color de la descripción", + "advanced_styling_field_description_color_description": "Colorea el texto debajo de cada titular.", + "advanced_styling_field_description_size": "Tamaño de fuente de la descripción", + "advanced_styling_field_description_size_description": "Escala el texto de la descripción.", + "advanced_styling_field_description_weight": "Grosor de fuente de la descripción", + "advanced_styling_field_description_weight_description": "Hace el texto de la descripción más ligero o más grueso.", + "advanced_styling_field_font_size": "Tamaño de fuente", + "advanced_styling_field_font_weight": "Grosor de fuente", + "advanced_styling_field_headline_color": "Color del titular", + "advanced_styling_field_headline_color_description": "Colorea el texto principal de la pregunta.", + "advanced_styling_field_headline_size": "Tamaño de fuente del titular", + "advanced_styling_field_headline_size_description": "Escala el texto del titular.", + "advanced_styling_field_headline_weight": "Grosor de fuente del titular", + "advanced_styling_field_headline_weight_description": "Hace el texto del titular más ligero o más grueso.", + "advanced_styling_field_height": "Altura mínima", + "advanced_styling_field_indicator_bg": "Fondo del indicador", + "advanced_styling_field_indicator_bg_description": "Colorea la porción rellena de la barra.", + "advanced_styling_field_input_border_radius_description": "Redondea las esquinas del campo.", + "advanced_styling_field_input_font_size_description": "Escala el texto escrito en los campos.", + "advanced_styling_field_input_height_description": "Controla la altura mínima del campo de entrada.", + "advanced_styling_field_input_padding_x_description": "Añade espacio a la izquierda y a la derecha.", + "advanced_styling_field_input_padding_y_description": "Añade espacio en la parte superior e inferior.", + "advanced_styling_field_input_placeholder_opacity_description": "Atenúa el texto de sugerencia del marcador de posición.", + "advanced_styling_field_input_shadow_description": "Añade una sombra alrededor de los campos de entrada.", + "advanced_styling_field_input_text": "Texto de entrada", + "advanced_styling_field_input_text_description": "Colorea el texto escrito en los campos de entrada.", + "advanced_styling_field_option_bg": "Fondo", + "advanced_styling_field_option_bg_description": "Rellena los elementos de opción.", + "advanced_styling_field_option_border_radius_description": "Redondea las esquinas de las opciones.", + "advanced_styling_field_option_font_size_description": "Escala el texto de la etiqueta de opción.", + "advanced_styling_field_option_label": "Color de la etiqueta", + "advanced_styling_field_option_label_description": "Colorea el texto de la etiqueta de opción.", + "advanced_styling_field_option_padding_x_description": "Añade espacio a la izquierda y a la derecha.", + "advanced_styling_field_option_padding_y_description": "Añade espacio en la parte superior e inferior.", + "advanced_styling_field_padding_x": "Relleno X", + "advanced_styling_field_padding_y": "Relleno Y", + "advanced_styling_field_placeholder_opacity": "Opacidad del marcador de posición", + "advanced_styling_field_shadow": "Sombra", + "advanced_styling_field_track_bg": "Fondo de la pista", + "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_section_buttons": "Botones", + "advanced_styling_section_headlines": "Títulos y descripciones", + "advanced_styling_section_inputs": "Campos de entrada", + "advanced_styling_section_options": "Opciones (radio/casilla de verificación)", "app_survey_placement": "Ubicación de encuesta de aplicación", "app_survey_placement_settings_description": "Cambia dónde se mostrarán las encuestas en tu aplicación web o sitio web.", - "centered_modal_overlay_color": "Color de superposición del modal centrado", "email_customization": "Personalización de correo electrónico", "email_customization_description": "Cambia el aspecto de los correos electrónicos que Formbricks envía en tu nombre.", "enable_custom_styling": "Habilitar estilo personalizado", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "La marca de Formbricks está oculta.", "formbricks_branding_settings_description": "Nos encanta tu apoyo, pero lo entendemos si lo desactivas.", "formbricks_branding_shown": "La marca de Formbricks se muestra.", + "generate_theme_btn": "Generar", + "generate_theme_confirmation": "¿Te gustaría generar un tema de colores que combine con el color de tu marca? Esto sobrescribirá tu configuración de colores actual.", + "generate_theme_header": "¿Generar tema de colores?", "logo_removed_successfully": "Logotipo eliminado correctamente", "logo_settings_description": "Sube el logotipo de tu empresa para personalizar las encuestas y las vistas previas de enlaces.", "logo_updated_successfully": "Logotipo actualizado correctamente", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Mostrar marca de Formbricks en encuestas de {type}", "show_powered_by_formbricks": "Mostrar firma 'Powered by Formbricks'", "styling_updated_successfully": "Estilo actualizado correctamente", + "suggest_colors": "Sugerir colores", + "suggested_colors_applied_please_save": "Colores sugeridos generados correctamente. Pulsa \"Guardar\" para conservar los cambios.", "theme": "Tema", "theme_settings_description": "Crea un tema de estilo para todas las encuestas. Puedes activar el estilo personalizado para cada encuesta." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Sí, mantenme informado.", "preview_survey_question_2_choice_2_label": "¡No, gracias!", "preview_survey_question_2_headline": "¿Quieres estar al tanto?", + "preview_survey_question_2_subheader": "Esta es una descripción de ejemplo.", "preview_survey_welcome_card_headline": "¡Bienvenido!", "prioritize_features_description": "Identifica las funciones que tus usuarios necesitan más y menos.", "prioritize_features_name": "Priorizar funciones", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index 144a8edc7d..8b0df8e208 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -187,6 +187,7 @@ "customer_success": "Succès Client", "dark_overlay": "Foncée", "date": "Date", + "days": "jours", "default": "Par défaut", "delete": "Supprimer", "description": "Description", @@ -253,6 +254,7 @@ "label": "Étiquette", "language": "Langue", "learn_more": "En savoir plus", + "license_expired": "License Expired", "light_overlay": "Claire", "limits_reached": "Limites atteints", "link": "Lien", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks fonctionne mieux sur un écran plus grand. Pour gérer ou créer des sondages, passez à un autre appareil.", "mobile_overlay_surveys_look_good": "Ne t'inquiète pas – tes enquêtes sont superbes sur tous les appareils et tailles d'écran!", "mobile_overlay_title": "Oups, écran minuscule détecté!", + "months": "mois", "move_down": "Déplacer vers le bas", "move_up": "Déplacer vers le haut", "multiple_languages": "Plusieurs langues", @@ -283,6 +286,7 @@ "no_background_image_found": "Aucune image de fond trouvée.", "no_code": "Sans code", "no_files_uploaded": "Aucun fichier n'a été téléchargé.", + "no_overlay": "Aucune superposition", "no_quotas_found": "Aucun quota trouvé", "no_result_found": "Aucun résultat trouvé", "no_results": "Aucun résultat", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Équipes d'organisation non trouvées", "other": "Autre", "others": "Autres", + "overlay_color": "Couleur de superposition", "overview": "Aperçu", "password": "Mot de passe", "paused": "En pause", @@ -348,6 +353,7 @@ "request_trial_license": "Demander une licence d'essai", "reset_to_default": "Réinitialiser par défaut", "response": "Réponse", + "response_id": "ID de réponse", "responses": "Réponses", "restart": "Recommencer", "role": "Rôle", @@ -388,6 +394,7 @@ "status": "Statut", "step_by_step_manual": "Manuel étape par étape", "storage_not_configured": "Stockage de fichiers non configuré, les téléchargements risquent d'échouer", + "string": "Texte", "styling": "Style", "submit": "Soumettre", "summary": "Résumé", @@ -443,6 +450,7 @@ "website_and_app_connection": "Connexion de sites Web et d'applications", "website_app_survey": "Sondage sur le site Web et l'application", "website_survey": "Sondage de site web", + "weeks": "semaines", "welcome_card": "Carte de bienvenue", "workspace_configuration": "Configuration du projet", "workspace_created_successfully": "Projet créé avec succès", @@ -453,13 +461,15 @@ "workspace_not_found": "Projet introuvable", "workspace_permission_not_found": "Permission du projet introuvable", "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.", "you_have_reached_your_monthly_miu_limit_of": "Vous avez atteint votre limite mensuelle de MIU de", "you_have_reached_your_monthly_response_limit_of": "Vous avez atteint votre limite de réponses mensuelle de", - "you_will_be_downgraded_to_the_community_edition_on_date": "Vous serez rétrogradé à l'édition communautaire le {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Vous serez rétrogradé à l'édition communautaire le {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Accepter", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Attribut mis à jour avec succès", "attribute_value": "Valeur", "attribute_value_placeholder": "Valeur d'attribut", + "attributes_msg_attribute_limit_exceeded": "Impossible de créer {count, plural, one {# nouvel attribut} other {# nouveaux attributs}} car cela dépasserait la limite maximale de {limit} classes d'attributs. Les attributs existants ont été mis à jour avec succès.", + "attributes_msg_attribute_type_validation_error": "{error} (l'attribut « {key} » a le type de données : {dataType})", + "attributes_msg_email_already_exists": "L'adresse e-mail existe déjà pour cet environnement et n'a pas été mise à jour.", + "attributes_msg_email_or_userid_required": "L'adresse e-mail ou l'identifiant utilisateur est requis. Les valeurs existantes ont été conservées.", + "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", "contact_not_found": "Aucun contact trouvé", "contacts_table_refresh": "Actualiser les contacts", @@ -631,6 +647,11 @@ "create_key": "Créer une clé", "create_new_attribute": "Créer un nouvel attribut", "create_new_attribute_description": "Créez un nouvel attribut à des fins de segmentation.", + "custom_attributes": "Attributs personnalisés", + "data_type": "Type de données", + "data_type_cannot_be_changed": "Le type de données ne peut pas être modifié après la création", + "data_type_description": "Choisis comment cet attribut doit être stocké et filtré", + "date_value_required": "Une valeur de date est requise. Utilise le bouton supprimer pour retirer cet attribut si tu ne veux pas définir de date.", "delete_attribute_confirmation": "{value, plural, one {Cela supprimera l'attribut sélectionné. Toutes les données de contact associées à cet attribut seront perdues.} other {Cela supprimera les attributs sélectionnés. Toutes les données de contact associées à ces attributs seront perdues.}}", "delete_contact_confirmation": "Cela supprimera toutes les réponses aux enquêtes et les attributs de contact associés à ce contact. Toute la personnalisation et le ciblage basés sur les données de ce contact seront perdus.", "delete_contact_confirmation_with_quotas": "{value, plural, other {Cela supprimera toutes les réponses aux enquêtes et les attributs de contact associés à ce contact. Toute la personnalisation et le ciblage basés sur les données de ce contact seront perdus. Si ce contact a des réponses qui comptent dans les quotas de l'enquête, les comptes de quotas seront réduits mais les limites de quota resteront inchangées.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Mettez à jour l'étiquette et la description de cet attribut.", "edit_attribute_values": "Modifier les attributs", "edit_attribute_values_description": "Modifiez les valeurs d'attributs spécifiques pour ce contact.", + "edit_attributes": "Modifier les attributs", "edit_attributes_success": "Attributs du contact mis à jour avec succès", "generate_personal_link": "Générer un lien personnel", "generate_personal_link_description": "Sélectionnez une enquête publiée pour générer un lien personnalisé pour ce contact.", + "invalid_csv_column_names": "Nom(s) de colonne CSV invalide(s) : {columns}. Les noms de colonnes qui deviendront de nouveaux attributs ne doivent contenir que des lettres minuscules, des chiffres et des underscores, et doivent commencer par une lettre.", + "invalid_date_format": "Format de date invalide. Merci d'utiliser une date valide.", + "invalid_number_format": "Format de nombre invalide. Veuillez saisir un nombre valide.", "no_published_link_surveys_available": "Aucune enquête par lien publiée n'est disponible. Veuillez d'abord publier une enquête par lien.", "no_published_surveys": "Aucune enquête publiée", "no_responses_found": "Aucune réponse trouvée", "not_provided": "Non fourni", + "number_value_required": "La valeur numérique est requise. Utilisez le bouton supprimer pour retirer cet attribut.", "personal_link_generated": "Lien personnel généré avec succès", "personal_link_generated_but_clipboard_failed": "Lien personnel généré mais échec de la copie dans le presse-papiers : {url}", "personal_survey_link": "Lien vers le sondage personnel", @@ -653,13 +679,22 @@ "search_contact": "Rechercher un contact", "select_a_survey": "Sélectionner une enquête", "select_attribute": "Sélectionner un attribut", + "select_attribute_key": "Sélectionner une clé d'attribut", + "system_attributes": "Attributs système", "unlock_contacts_description": "Gérer les contacts et envoyer des enquêtes ciblées", "unlock_contacts_title": "Débloquez des contacts avec un plan supérieur.", + "upload_contacts_error_attribute_type_mismatch": "L'attribut « {key} » est de type « {dataType} » mais le CSV contient des valeurs invalides : {values}", + "upload_contacts_error_duplicate_mappings": "Mappages en double trouvés pour les attributs suivants : {attributes}", + "upload_contacts_error_file_too_large": "La taille du fichier dépasse la limite maximale de 800 Ko", + "upload_contacts_error_generic": "Une erreur s'est produite lors de l'importation des contacts. Veuillez réessayer plus tard.", + "upload_contacts_error_invalid_file_type": "Veuillez importer un fichier CSV", + "upload_contacts_error_no_valid_contacts": "Le fichier CSV importé ne contient aucun contact valide, veuillez consulter l'exemple de fichier CSV pour le format correct.", + "upload_contacts_modal_attribute_header": "Attribut Formbricks", "upload_contacts_modal_attributes_description": "Mappez les colonnes de votre CSV aux attributs dans Formbricks.", "upload_contacts_modal_attributes_new": "Nouvel attribut", "upload_contacts_modal_attributes_search_or_add": "Rechercher ou ajouter un attribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "devrait être mappé à", "upload_contacts_modal_attributes_title": "Attributs", + "upload_contacts_modal_csv_column_header": "Colonne CSV", "upload_contacts_modal_description": "Téléchargez un fichier CSV pour importer rapidement des contacts avec des attributs.", "upload_contacts_modal_download_example_csv": "Télécharger un exemple de CSV", "upload_contacts_modal_duplicates_description": "Que faire si un contact existe déjà ?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Aucun attribut pour le moment !", "no_filters_yet": "Il n'y a pas encore de filtres !", "no_segments_yet": "Aucun segment n'est actuellement enregistré.", + "operator_contains": "contient", + "operator_does_not_contain": "ne contient pas", + "operator_ends_with": "se termine par", + "operator_is_after": "est après", + "operator_is_before": "est avant", + "operator_is_between": "est entre", + "operator_is_newer_than": "est plus récent que", + "operator_is_not_set": "n'est pas défini", + "operator_is_older_than": "est plus ancien que", + "operator_is_same_day": "est le même jour", + "operator_is_set": "est défini", + "operator_starts_with": "commence par", + "operator_title_contains": "Contient", + "operator_title_does_not_contain": "Ne contient pas", + "operator_title_ends_with": "Se termine par", + "operator_title_equals": "Égal", + "operator_title_greater_equal": "Supérieur ou égal à", + "operator_title_greater_than": "Supérieur à", + "operator_title_is_after": "Est après", + "operator_title_is_before": "Est avant", + "operator_title_is_between": "Est entre", + "operator_title_is_newer_than": "Est plus récent que", + "operator_title_is_not_set": "N'est pas défini", + "operator_title_is_older_than": "Est plus ancien que", + "operator_title_is_same_day": "Est le même jour", + "operator_title_is_set": "Est défini", + "operator_title_less_equal": "Inférieur ou égal à", + "operator_title_less_than": "Inférieur à", + "operator_title_not_equals": "N'est pas égal à", + "operator_title_starts_with": "Commence par", + "operator_title_user_is_in": "L'utilisateur est dans", + "operator_title_user_is_not_in": "L'utilisateur n'est pas dans", + "operator_user_is_in": "L'utilisateur est dans", + "operator_user_is_not_in": "L'utilisateur n'est pas dans", "person_and_attributes": "Personne et attributs", "phone": "Téléphone", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Veuillez supprimer le segment de ces enquêtes afin de le supprimer.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "La ciblage des utilisateurs est actuellement disponible uniquement lorsque", "value_cannot_be_empty": "La valeur ne peut pas être vide.", "value_must_be_a_number": "La valeur doit être un nombre.", + "value_must_be_positive": "La valeur doit être un nombre positif.", "view_filters": "Filtres de vue", "where": "Où", "with_the_formbricks_sdk": "avec le SDK Formbricks" @@ -950,19 +1020,32 @@ "enterprise_features": "Fonctionnalités d'entreprise", "get_an_enterprise_license_to_get_access_to_all_features": "Obtenez une licence Entreprise pour accéder à toutes les fonctionnalités.", "keep_full_control_over_your_data_privacy_and_security": "Gardez un contrôle total sur la confidentialité et la sécurité de vos données.", + "license_invalid_description": "La clé de licence dans votre variable d'environnement ENTERPRISE_LICENSE_KEY n'est pas valide. Veuillez vérifier les fautes de frappe ou demander une nouvelle clé.", + "license_status": "Statut de la licence", + "license_status_active": "Active", + "license_status_description": "Statut de votre licence entreprise.", + "license_status_expired": "Expirée", + "license_status_invalid": "Licence invalide", + "license_status_unreachable": "Inaccessible", + "license_unreachable_grace_period": "Le serveur de licence est injoignable. Vos fonctionnalités entreprise restent actives pendant une période de grâce de 3 jours se terminant le {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Aucun appel nécessaire, aucune obligation : Demandez une licence d'essai gratuite de 30 jours pour tester toutes les fonctionnalités en remplissant ce formulaire :", "no_credit_card_no_sales_call_just_test_it": "Aucune carte de crédit. Aucun appel de vente. Testez-le simplement :)", "on_request": "Sur demande", "organization_roles": "Rôles d'organisation (Administrateur, Éditeur, Développeur, etc.)", "questions_please_reach_out_to": "Des questions ? Veuillez contacter", + "recheck_license": "Revérifier la licence", + "recheck_license_failed": "La vérification de la licence a échoué. Le serveur de licences est peut-être inaccessible.", + "recheck_license_invalid": "La clé de licence est invalide. Veuillez vérifier votre ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Vérification de la licence réussie", + "recheck_license_unreachable": "Le serveur de licences est inaccessible. Veuillez réessayer plus tard.", + "rechecking": "Revérification en cours...", "request_30_day_trial_license": "Demander une licence d'essai de 30 jours", "saml_sso": "SAML SSO", "service_level_agreement": "Accord de niveau de service", "soc2_hipaa_iso_27001_compliance_check": "Vérification de conformité SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Équipes et Rôles d'Accès (Lire, Lire et Écrire, Gérer)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours.", - "your_enterprise_license_is_active_all_features_unlocked": "Votre licence d'entreprise est active. Toutes les fonctionnalités sont déverrouillées." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours." }, "general": { "bulk_invite_warning_description": "Dans le plan gratuit, tous les membres de l'organisation se voient toujours attribuer le rôle \"Owner\".", @@ -986,7 +1069,7 @@ "from_your_organization": "de votre organisation", "invitation_sent_once_more": "Invitation envoyée une fois de plus.", "invite_deleted_successfully": "Invitation supprimée avec succès", - "invited_on": "Invité le {date}", + "invite_expires_on": "L'invitation expire le {date}", "invites_failed": "Invitations échouées", "leave_organization": "Quitter l'organisation", "leave_organization_description": "Vous quitterez cette organisation et perdrez l'accès à toutes les enquêtes et réponses. Vous ne pourrez revenir que si vous êtes de nouveau invité.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Veuillez remplir tous les champs pour ajouter un nouvel espace de travail.", "read": "Lire", "read_write": "Lire et Écrire", - "select_member": "Sélectionner membre", - "select_workspace": "Sélectionner un espace de travail", "team_admin": "Administrateur d'équipe", "team_created_successfully": "Équipe créée avec succès.", "team_deleted_successfully": "Équipe supprimée avec succès.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Ajouter un espace réservé à afficher s'il n'y a pas de valeur à rappeler.", "add_hidden_field_id": "Ajouter un champ caché ID", "add_highlight_border": "Ajouter une bordure de surlignage", - "add_highlight_border_description": "Ajoutez une bordure extérieure à votre carte d'enquête.", "add_logic": "Ajouter de la logique", "add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"", "add_option": "Ajouter une option", @@ -1189,6 +1269,7 @@ "block_duplicated": "Bloc dupliqué.", "bold": "Gras", "brand_color": "Couleur de marque", + "brand_color_description": "Appliqué aux boutons, liens et éléments mis en évidence.", "brightness": "Luminosité", "bulk_edit": "Modification en masse", "bulk_edit_description": "Modifiez toutes les options ci-dessous, une par ligne. Les lignes vides seront ignorées et les doublons supprimés.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Capturer une nouvelle action", "card_arrangement_for_survey_type_derived": "Disposition des cartes pour les enquêtes {surveyTypeDerived}", "card_background_color": "Couleur de fond de la carte", + "card_background_color_description": "Remplit la zone de la carte d'enquête.", "card_border_color": "Couleur de la bordure de la carte", + "card_border_color_description": "Délimite la carte d'enquête.", "card_styling": "Style de carte", "casual": "Décontracté", "caution_edit_duplicate": "Dupliquer et modifier", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Les réponses anciennes et nouvelles se mélangent, ce qui peut entraîner des résumés de données trompeurs.", "caution_recommendation": "Cela peut entraîner des incohérences de données dans le résumé du sondage. Nous recommandons de dupliquer le sondage à la place.", "caution_text": "Les changements entraîneront des incohérences.", - "centered_modal_overlay_color": "Couleur de superposition modale centrée", "change_anyway": "Changer de toute façon", "change_background": "Changer l'arrière-plan", "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_color_of_the_card": "Changez la couleur de fond de la carte.", - "change_the_background_color_of_the_input_fields": "Vous pouvez modifier la couleur d'arrière-plan des champs de saisie.", "change_the_background_to_a_color_image_or_animation": "Changez l'arrière-plan en une couleur, une image ou une animation.", - "change_the_border_color_of_the_card": "Changez la couleur de la bordure de la carte.", - "change_the_border_color_of_the_input_fields": "Vous pouvez modifier la couleur de la bordure des champs de saisie.", - "change_the_border_radius_of_the_card_and_the_inputs": "Vous pouvez arrondir la bordure des encadrés et des champs de saisie.", - "change_the_brand_color_of_the_survey": "Vous pouvez modifier la couleur dominante d'une enquête.", "change_the_placement_of_this_survey": "Changez le placement de cette enquête.", - "change_the_question_color_of_the_survey": "Vous pouvez modifier la couleur des questions d'une enquête.", "changes_saved": "Modifications enregistrées.", "changing_survey_type_will_remove_existing_distribution_channels": "Le changement du type de sondage affectera la façon dont il peut être partagé. Si les répondants ont déjà des liens d'accès pour le type actuel, ils peuvent perdre l'accès après le changement.", "checkbox_label": "Étiquette de case à cocher", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Désactiver la visibilité de la progression du sondage.", "display_an_estimate_of_completion_time_for_survey": "Afficher une estimation du temps de complétion pour l'enquête.", "display_number_of_responses_for_survey": "Afficher le nombre de réponses pour l'enquête", + "display_type": "Type d'affichage", "divide": "Diviser /", "does_not_contain": "Ne contient pas", "does_not_end_with": "Ne se termine pas par", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "n'inclut pas tout", "does_not_include_one_of": "n'inclut pas un de", "does_not_start_with": "Ne commence pas par", + "dropdown": "Menu déroulant", "duplicate_block": "Dupliquer le bloc", "duplicate_question": "Dupliquer la question", "edit_link": "Modifier le lien", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Cacher la barre de progression", "hide_question_settings": "Masquer les paramètres de la question", "hostname": "Nom d'hôte", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "À quel point voulez-vous que vos cartes soient funky dans les enquêtes {surveyTypeDerived}", "if_you_need_more_please": "Si vous avez besoin de plus, veuillez", "if_you_really_want_that_answer_ask_until_you_get_it": "Continuer à afficher à chaque déclenchement jusqu'à ce qu'une réponse soit soumise.", "ignore_global_waiting_time": "Ignorer la période de refroidissement", @@ -1379,7 +1455,9 @@ "initial_value": "Valeur initiale", "inner_text": "Texte interne", "input_border_color": "Couleur de la bordure des champs de saisie", + "input_border_color_description": "Délimite les champs de texte et les zones de texte.", "input_color": "Couleur d'arrière-plan des champs de saisie", + "input_color_description": "Remplit l'intérieur des champs de texte.", "insert_link": "Insérer un lien", "invalid_targeting": "Ciblage invalide : Veuillez vérifier vos filtres d'audience", "invalid_video_url_warning": "Merci d'entrer une URL YouTube, Vimeo ou Loom valide. Les autres plateformes vidéo ne sont pas encore supportées.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limiter la taille maximale des fichiers pour les téléversements.", "limit_upload_file_size_to": "Limiter la taille de téléversement des fichiers à", "link_survey_description": "Partagez un lien vers une page d'enquête ou intégrez-le dans une page web ou un e-mail.", + "list": "Liste", "load_segment": "Segment de chargement", "logic_error_warning": "Changer causera des erreurs logiques", "logic_error_warning_text": "Changer le type de question supprimera les conditions logiques de cette question.", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Seules les personnes ayant le code PIN peuvent accéder à l'enquête.", "publish": "Publier", "question": "Question", - "question_color": "Couleur des questions", "question_deleted": "Question supprimée.", "question_duplicated": "Question dupliquée.", "question_id_updated": "ID de la question mis à jour", "question_used_in_logic_warning_text": "Des éléments de ce bloc sont utilisés dans une règle logique, êtes-vous sûr de vouloir le supprimer ?", "question_used_in_logic_warning_title": "Incohérence de logique", - "question_used_in_quota": "Cette question est utilisée dans le quota \"{quotaName}\"", + "question_used_in_quota": "Cette question est utilisée dans le quota “{quotaName}”", "question_used_in_recall": "Cette question est rappelée dans la question {questionIndex}.", "question_used_in_recall_ending_card": "Cette question est rappelée dans la carte de fin.", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Limites de réponse, redirections et plus.", "response_options": "Options de réponse", "roundness": "Rondeur", + "roundness_description": "Contrôle l'arrondi des coins de la carte.", "row_used_in_logic_error": "Cette ligne est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.", "rows": "Lignes", "save_and_close": "Enregistrer et fermer", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Style défini sur les styles du thème", "subheading": "Sous-titre", "subtract": "Soustraire -", - "suggest_colors": "Suggérer des couleurs", "survey_completed_heading": "Enquête terminée", "survey_completed_subheading": "Cette enquête gratuite et open-source a été fermée", "survey_display_settings": "Paramètres d'affichage de l'enquête", @@ -1642,7 +1720,7 @@ "validation_rules": "Règles de validation", "validation_rules_description": "Accepter uniquement les réponses qui répondent aux critères suivants", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} est utilisé dans la logique de la question {questionIndex}. Veuillez d'abord le supprimer de la logique.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable \"{variableName}\" est utilisée dans le quota \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable “{variableName}” est utilisée dans le quota “{quotaName}”", "variable_name_conflicts_with_hidden_field": "Le nom de la variable est en conflit avec un ID de champ masqué existant.", "variable_name_is_already_taken_please_choose_another": "Le nom de la variable est déjà pris, veuillez en choisir un autre.", "variable_name_must_start_with_a_letter": "Le nom de la variable doit commencer par une lettre.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Ajouter une couleur d'arrière-plan", "add_background_color_description": "Ajoutez une couleur d'arrière-plan au conteneur du logo.", + "advanced_styling_field_border_radius": "Rayon de bordure", + "advanced_styling_field_button_bg": "Arrière-plan du bouton", + "advanced_styling_field_button_bg_description": "Remplit le bouton Suivant / Envoyer.", + "advanced_styling_field_button_border_radius_description": "Arrondit les coins du bouton.", + "advanced_styling_field_button_font_size_description": "Ajuste la taille du texte du libellé du bouton.", + "advanced_styling_field_button_font_weight_description": "Rend le texte du bouton plus léger ou plus gras.", + "advanced_styling_field_button_height_description": "Contrôle la hauteur du bouton.", + "advanced_styling_field_button_padding_x_description": "Ajoute de l'espace à gauche et à droite.", + "advanced_styling_field_button_padding_y_description": "Ajoute de l'espace en haut et en bas.", + "advanced_styling_field_button_text": "Texte du bouton", + "advanced_styling_field_button_text_description": "Colore le libellé à l'intérieur des boutons.", + "advanced_styling_field_description_color": "Couleur de la description", + "advanced_styling_field_description_color_description": "Colore le texte sous chaque titre.", + "advanced_styling_field_description_size": "Taille de police de la description", + "advanced_styling_field_description_size_description": "Ajuste la taille du texte de description.", + "advanced_styling_field_description_weight": "Graisse de police de la description", + "advanced_styling_field_description_weight_description": "Rend le texte de description plus léger ou plus gras.", + "advanced_styling_field_font_size": "Taille de police", + "advanced_styling_field_font_weight": "Graisse de police", + "advanced_styling_field_headline_color": "Couleur du titre", + "advanced_styling_field_headline_color_description": "Colore le texte principal de la question.", + "advanced_styling_field_headline_size": "Taille de police du titre", + "advanced_styling_field_headline_size_description": "Ajuste la taille du texte du titre.", + "advanced_styling_field_headline_weight": "Graisse de police du titre", + "advanced_styling_field_headline_weight_description": "Rend le texte du titre plus léger ou plus gras.", + "advanced_styling_field_height": "Hauteur minimale", + "advanced_styling_field_indicator_bg": "Arrière-plan de l'indicateur", + "advanced_styling_field_indicator_bg_description": "Colore la partie remplie de la barre.", + "advanced_styling_field_input_border_radius_description": "Arrondit les coins du champ de saisie.", + "advanced_styling_field_input_font_size_description": "Ajuste la taille du texte saisi dans les champs.", + "advanced_styling_field_input_height_description": "Contrôle la hauteur minimale du champ de saisie.", + "advanced_styling_field_input_padding_x_description": "Ajoute de l'espace à gauche et à droite.", + "advanced_styling_field_input_padding_y_description": "Ajoute de l'espace en haut et en bas.", + "advanced_styling_field_input_placeholder_opacity_description": "Atténue le texte d'indication du placeholder.", + "advanced_styling_field_input_shadow_description": "Ajoute une ombre portée autour des champs de saisie.", + "advanced_styling_field_input_text": "Texte de saisie", + "advanced_styling_field_input_text_description": "Colore le texte saisi dans les champs.", + "advanced_styling_field_option_bg": "Arrière-plan", + "advanced_styling_field_option_bg_description": "Remplit les éléments d'option.", + "advanced_styling_field_option_border_radius_description": "Arrondit les coins des options.", + "advanced_styling_field_option_font_size_description": "Ajuste la taille du texte des libellés d'option.", + "advanced_styling_field_option_label": "Couleur de l'étiquette", + "advanced_styling_field_option_label_description": "Colore le texte des libellés d'option.", + "advanced_styling_field_option_padding_x_description": "Ajoute de l'espace à gauche et à droite.", + "advanced_styling_field_option_padding_y_description": "Ajoute de l'espace en haut et en bas.", + "advanced_styling_field_padding_x": "Marge intérieure X", + "advanced_styling_field_padding_y": "Marge intérieure Y", + "advanced_styling_field_placeholder_opacity": "Opacité du placeholder", + "advanced_styling_field_shadow": "Ombre", + "advanced_styling_field_track_bg": "Arrière-plan de la piste", + "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_section_buttons": "Boutons", + "advanced_styling_section_headlines": "Titres et descriptions", + "advanced_styling_section_inputs": "Champs de saisie", + "advanced_styling_section_options": "Options (boutons radio/cases à cocher)", "app_survey_placement": "Placement du sondage d'application", "app_survey_placement_settings_description": "Modifiez l'emplacement où les sondages seront affichés dans votre application web ou site web.", - "centered_modal_overlay_color": "Couleur de superposition modale centrée", "email_customization": "Personnalisation des e-mails", "email_customization_description": "Modifiez l'apparence des e-mails que Formbricks envoie en votre nom.", "enable_custom_styling": "Activer le style personnalisé", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Le logo Formbricks est masqué.", "formbricks_branding_settings_description": "Nous apprécions votre soutien mais comprenons si vous choisissez de le désactiver.", "formbricks_branding_shown": "Le logo Formbricks est affiché.", + "generate_theme_btn": "Générer", + "generate_theme_confirmation": "Souhaitez-vous générer un thème de couleurs assorti basé sur votre couleur de marque ? Cela écrasera vos paramètres de couleur actuels.", + "generate_theme_header": "Générer un thème de couleurs ?", "logo_removed_successfully": "Logo supprimé avec succès", "logo_settings_description": "Téléchargez le logo de votre entreprise pour personnaliser les enquêtes et les aperçus de liens.", "logo_updated_successfully": "Logo mis à jour avec succès", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Afficher le logo Formbricks dans les enquêtes {type}", "show_powered_by_formbricks": "Afficher la signature « Propulsé par Formbricks »", "styling_updated_successfully": "Style mis à jour avec succès", + "suggest_colors": "Suggérer des couleurs", + "suggested_colors_applied_please_save": "Couleurs suggérées générées avec succès. Appuyez sur « Enregistrer » pour conserver les modifications.", "theme": "Thème", "theme_settings_description": "Créez un thème de style pour toutes les enquêtes. Vous pouvez activer un style personnalisé pour chaque enquête." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Oui, tenez-moi au courant.", "preview_survey_question_2_choice_2_label": "Non, merci !", "preview_survey_question_2_headline": "Souhaitez-vous être informé ?", + "preview_survey_question_2_subheader": "Ceci est un exemple de description.", "preview_survey_welcome_card_headline": "Bienvenue !", "prioritize_features_description": "Identifiez les fonctionnalités dont vos utilisateurs ont le plus et le moins besoin.", "prioritize_features_name": "Prioriser les fonctionnalités", diff --git a/apps/web/locales/hu-HU.json b/apps/web/locales/hu-HU.json new file mode 100644 index 0000000000..50a56a19b7 --- /dev/null +++ b/apps/web/locales/hu-HU.json @@ -0,0 +1,3237 @@ +{ + "auth": { + "continue_with_azure": "Folytatás Microsoft használatával", + "continue_with_email": "Folytatás e-mail-címmel", + "continue_with_github": "Folytatás GitHub használatával", + "continue_with_google": "Folytatás Google használatával", + "continue_with_oidc": "Folytatás {oidcDisplayName} használatával", + "continue_with_openid": "Folytatás OpenID használatával", + "continue_with_saml": "Folytatás SAML SSO használatával", + "email-change": { + "confirm_password_description": "Erősítse meg a jelszavát az e-mail-címe megváltoztatása előtt", + "email_change_success": "Az e-mail-cím sikeresen megváltoztatva", + "email_change_success_description": "Sikeresen megváltoztatta az e-mail-címét. Jelentkezzen be az új e-mail-címével.", + "email_verification_failed": "Az e-mail-cím ellenőrzése sikertelen", + "email_verification_loading": "Az e-mail-cím ellenőrzése folyamatban van…", + "email_verification_loading_description": "Frissítjük az e-mail-címét a rendszerünkben. Ez eltarthat néhány másodpercig.", + "invalid_or_expired_token": "Az e-mail-cím megváltoztatása nem sikerült. A tokenje érvénytelen vagy lejárt.", + "new_email": "Új e-mail-cím", + "old_email": "Régi e-mail-cím" + }, + "forgot-password": { + "back_to_login": "Vissza a bejelentkezéshez", + "email-sent": { + "heading": "A jelszó-visszaállítás sikeresen kérve", + "text": "Ha létezik ilyen e-mail-címmel rendelkező fiók, akkor hamarosan megkapja a jelszó visszaállításához szükséges utasításokat." + }, + "reset": { + "confirm_password": "Jelszó megerősítése", + "new_password": "Új jelszó", + "no_token_provided": "Nincs token megadva", + "passwords_do_not_match": "A jelszavak nem egyeznek", + "success": { + "heading": "A jelszó sikeresen visszaállítva", + "text": "Most már bejelentkezhet az új jelszavával" + } + }, + "reset_password": "Jelszó visszaállítása", + "reset_password_description": "Ki lesz jelentkeztetve a jelszó visszaállításához." + }, + "invite": { + "create_account": "Fiók létrehozása", + "email_does_not_match": "Hoppá! Hibás e-mail-cím 🤦", + "email_does_not_match_description": "A meghívóban lévő e-mail-cím nem egyezik az Önével.", + "go_to_app": "Ugrás az alkalmazáshoz", + "happy_to_have_you": "Örülünk, hogy itt van 🤗", + "happy_to_have_you_description": "Hozzon létre fiókot vagy jelentkezzen be.", + "invite_expired": "A meghívó lejárt 😥", + "invite_expired_description": "A meghívók 7 napig érvényesek. Kérjen új meghívót.", + "invite_not_found": "A meghívó nem található 😥", + "invite_not_found_description": "A meghívó kód nem található, vagy már felhasználásra került.", + "login": "Bejelentkezés", + "welcome_to_organization": "Ön bent van 🎉", + "welcome_to_organization_description": "Üdvözöljük a szervezetben!" + }, + "last_used": "Legutóbb használt", + "login": { + "backup_code": "Visszaszerzési kód", + "create_an_account": "Fiók létrehozása", + "enter_your_backup_code": "Visszaszerzési kód megadása", + "enter_your_two_factor_authentication_code": "Kétfaktoros hitelesítési kód megadása", + "forgot_your_password": "Elfelejtette a jelszavát?", + "login_to_your_account": "Bejelentkezés a fiókjába", + "login_with_email": "Bejelentkezés e-mail-címmel", + "lost_access": "Elvesztette a hozzáférést?", + "new_to_formbricks": "Új a Formbicksen?", + "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.", + "signup": { + "captcha_failed": "A captcha sikertelen", + "have_an_account": "Van fiókja?", + "log_in": "Bejelentkezés", + "password_validation_contain_at_least_1_number": "Legalább 1 számot tartalmazzon", + "password_validation_minimum_8_and_maximum_128_characters": "Legalább 8 és legfeljebb 128 karakter", + "password_validation_uppercase_and_lowercase": "Nagybetűk és kisbetűk vegyesen", + "please_verify_captcha": "Ellenőrizze a reCAPTCHA-t", + "privacy_policy": "Adatvédelmi irányelvek", + "product_updates_description": "Havi termékhírek és funkciófrissítések, adatvédelmi irányelvek alkalmazása.", + "product_updates_title": "Termékfrissítések", + "security_updates_description": "Csak biztonságra vonatkozó információk, adatvédelmi irányelvek alkalmazása.", + "security_updates_title": "Biztonsági frissítések", + "terms_of_service": "Használati feltételek", + "title": "Formbricks-fiók létrehozása" + }, + "signup_without_verification_success": { + "user_successfully_created": "A felhasználó sikeresen létrehozva", + "user_successfully_created_info": "Ellenőriztük, hogy létezik-e a(z) {email} címhez rendelt fiók. Ha nem létezett, akkor létrehoztunk egyet Önnek. Ha a fiók már létezett, akkor nem történt változtatás. Jelentkezzen be lent a folytatáshoz." + }, + "verification-requested": { + "invalid_email_address": "Érvénytelen e-mail-cím", + "invalid_token": "Érvénytelen token ☹️", + "new_email_verification_success": "Ha a cím érvényes, akkor egy ellenőrző e-mail került elküldésre.", + "no_email_provided": "Nincs e-mail-cím megadva", + "please_confirm_your_email_address": "Erősítse meg az e-mail-címét", + "resend_verification_email": "Ellenőrző e-mail újraküldése", + "verification_email_resent_successfully": "Az ellenőrző e-mail elküldve! Nézze meg a beérkezett üzeneteit.", + "verification_email_successfully_sent_info": "Ha létezik a(z) {email} címhez rendelt fiók, akkor elküldtünk egy ellenőrző hivatkozást arra a címre. Nézze meg a beérkezett üzeneteit a regisztráció befejezéséhez.", + "you_didnt_receive_an_email_or_your_link_expired": "Nem kapott e-mailt vagy a hivatkozása lejárt?" + }, + "verify": { + "no_token_provided": "Nincs token megadva", + "verifying": "Ellenőrzés…" + } + }, + "billing_confirmation": { + "back_to_billing_overview": "Vissza a számlázási áttekintésre", + "thanks_for_upgrading": "Nagyon köszönjük, hogy frissítette a Formbricks előfizetését.", + "upgrade_successful": "A frissítés sikeres" + }, + "c": { + "link_expired": "A hivatkozása lejárt.", + "link_expired_description": "Az Ön által használt hivatkozás már nem érvényes." + }, + "common": { + "accepted": "Elfogadva", + "account": "Fiók", + "account_settings": "Fiókbeállítások", + "action": "Művelet", + "actions": "Műveletek", + "actions_description": "A kód vagy kód nélküli műveleteket arra használják, hogy aktiválják a kérdőívek alkalmazásokon és webhelyeken belüli elfogását.", + "active_surveys": "Aktív kérdőívek", + "activity": "Tevékenység", + "add": "Hozzáadás", + "add_action": "Művelet hozzáadása", + "add_filter": "Szűrő hozzáadása", + "add_logo": "Logo hozzáadása", + "add_member": "Tag hozzáadása", + "add_new_workspace": "Új munkaterület hozzáadása", + "add_to_team": "Hozzáadás csapathoz", + "add_workspace": "Munkaterület hozzáadása", + "all": "Összes", + "all_questions": "Összes kérdés", + "allow": "Engedélyezés", + "allow_users_to_exit_by_clicking_outside_the_survey": "Lehetővé tétel a felhasználók számára, hogy a kérdőíven kívülre kattintva kilépjenek", + "an_unknown_error_occurred_while_deleting_table_items": "{type} típusok törlésekor ismeretlen hiba történt", + "and": "És", + "and_response_limit_of": "és kérdéskorlátja ennek:", + "anonymous": "Névtelen", + "api_keys": "API-kulcsok", + "app": "Alkalmazás", + "app_survey": "Alkalmazás-kérdőív", + "apply_filters": "Szűrők alkalmazása", + "are_you_sure": "Biztos benne?", + "attributes": "Attribútumok", + "back": "Vissza", + "billing": "Számlázás", + "booked": "Foglalva", + "bottom_left": "Balra lent", + "bottom_right": "Jobbra lent", + "cancel": "Mégse", + "centered_modal": "Középre helyezett kizárólagos", + "choices": "Választási lehetőségek", + "choose_environment": "Környezet kiválasztása", + "choose_organization": "Szervezet kiválasztása", + "choose_workspace": "Munkaterület kiválasztása", + "clear_all": "Összes törlése", + "clear_filters": "Szűrők törlése", + "clear_selection": "Kijelölés törlése", + "click": "Kattintás", + "click_to_filter": "Kattintás a szűréshez", + "clicks": "Kattintások", + "close": "Bezárás", + "code": "Kód", + "collapse_rows": "Sorok összecsukása", + "completed": "Befejezve", + "confirm": "Megerősítés", + "connect": "Kapcsolódás", + "connect_formbricks": "Kapcsolódás a Formbrickshez", + "connected": "Kapcsolódva", + "contacts": "Partnerek", + "continue": "Folytatás", + "copied": "Másolva", + "copied_to_clipboard": "Vágólapra másolva", + "copy": "Másolás", + "copy_code": "Kód másolása", + "copy_link": "Hivatkozás másolása", + "count_attributes": "{value, plural, one {{value} attribútum} other {{value} attribútum}}", + "count_contacts": "{value, plural, one {{value} partner} other {{value} partner}}", + "count_responses": "{value, plural, one {{value} válasz} other {{value} válasz}}", + "create_new_organization": "Új szervezet létrehozása", + "create_segment": "Szakasz létrehozása", + "create_survey": "Kérdőív létrehozása", + "create_workspace": "Munkaterület létrehozása", + "created": "Létrehozva", + "created_at": "Létrehozva", + "created_by": "Létrehozta", + "customer_success": "Ügyfélsiker", + "dark_overlay": "Sötét rávetítés", + "date": "Dátum", + "days": "napok", + "default": "Alapértelmezett", + "delete": "Törlés", + "description": "Leírás", + "dev_env": "Fejlesztői környezet", + "development": "Fejlesztés", + "development_environment_banner": "Ön egy fejlesztői környezetben van. Állítson be tesztkérdőíveket, műveleteket és attribútumokat.", + "disable": "Letiltás", + "disallow": "Ne engedélyezze", + "discard": "Elvetés", + "dismissed": "Eltüntetve", + "docs": "Dokumentáció", + "documentation": "Dokumentáció", + "domain": "Tartomány", + "done": "Kész", + "download": "Letöltés", + "draft": "Piszkozat", + "duplicate": "Kettőzés", + "e_commerce": "E-kereskedelem", + "edit": "Szerkesztés", + "email": "E-mail", + "ending_card": "Befejező kártya", + "enter_url": "URL megadása", + "enterprise_license": "Vállalati licenc", + "environment": "Környezet", + "environment_not_found": "A környezet nem található", + "environment_notice": "Ön jelenleg a(z) {environment} környezetben van.", + "error": "Hiba", + "error_component_description": "Ez az erőforrás nem létezik, vagy nem rendelkezik a hozzáféréshez szükséges jogosultságokkal.", + "error_component_title": "Hiba az erőforrások betöltésekor", + "error_rate_limit_description": "A kérések legnagyobb száma elérve. Próbálja meg később újra.", + "error_rate_limit_title": "A sebességkorlát elérve", + "expand_rows": "Sorok kinyitása", + "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", + "finish": "Befejezés", + "follow_these": "Ezek követése", + "formbricks_version": "Formbricks verziója", + "full_name": "Teljes név", + "gathering_responses": "Válaszok összegyűjtése", + "general": "Általános", + "generate": "Előállítás", + "go_back": "Vissza", + "go_to_dashboard": "Ugrás a vezérlőpultra", + "hidden": "Rejtett", + "hidden_field": "Rejtett mező", + "hidden_fields": "Rejtett mezők", + "hide_column": "Oszlop elrejtése", + "image": "Kép", + "images": "Képek", + "import": "Importálás", + "impressions": "Benyomások", + "imprint": "Impresszum", + "in_progress": "Folyamatban", + "inactive_surveys": "Inaktív kérdőívek", + "integration": "integráció", + "integrations": "Integrációk", + "invalid_date": "Érvénytelen dátum", + "invalid_file_name": "Érvénytelen fájlnév, nevezze át a fájlt, és próbálja újra", + "invalid_file_type": "Érvénytelen fájltípus", + "invite": "Meghívás", + "invite_them": "Meghívó nekik", + "key": "Kulcs", + "label": "Címke", + "language": "Nyelv", + "learn_more": "Tudjon meg többet", + "license_expired": "A licenc lejárt", + "light_overlay": "Világos rávetítés", + "limits_reached": "Korlátok elérve", + "link": "Összekapcsolás", + "link_survey": "Kérdőív összekapcsolása", + "link_surveys": "Kérdőívek összekapcsolása", + "load_more": "Továbbiak betöltése", + "loading": "Betöltés", + "logo": "Logó", + "logout": "Kijelentkezés", + "look_and_feel": "Megjelenés", + "manage": "Kezelés", + "marketing": "Marketing", + "member": "Tag", + "members": "Tagok", + "members_and_teams": "Tagok és csapatok", + "membership_not_found": "A tagság nem található", + "metadata": "Metaadatok", + "mobile_overlay_app_works_best_on_desktop": "A Formbricks nagyobb képernyőn működik a legjobban. A kérdőívek kezeléséhez vagy összeállításához váltson másik eszközre.", + "mobile_overlay_surveys_look_good": "Ne aggódjon – a kérdőívei minden eszközön és képernyőméretnél remekül néznek ki!", + "mobile_overlay_title": "Hoppá, apró képernyő észlelve!", + "months": "hónapok", + "move_down": "Mozgatás le", + "move_up": "Mozgatás fel", + "multiple_languages": "Több nyelv", + "name": "Név", + "new": "Új", + "new_version_available": "A Formbricks {version} megérkezett. Frissítsen most!", + "next": "Következő", + "no_background_image_found": "Nem található háttérkép.", + "no_code": "Kód nélkül", + "no_files_uploaded": "Nem lettek fájlok feltöltve", + "no_overlay": "Nincs rávetítés", + "no_quotas_found": "Nem találhatók kvóták", + "no_result_found": "Nem található eredmény", + "no_results": "Nincs találat", + "no_surveys_found": "Nem találhatók kérdőívek.", + "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", + "not_connected": "Nincs kapcsolódva", + "note": "Jegyzet", + "notifications": "Értesítések", + "number": "Szám", + "off": "Ki", + "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.", + "option_id": "Választásazonosító", + "option_ids": "Választásazonosítók", + "optional": "Elhagyható", + "or": "vagy", + "organization": "Szervezet", + "organization_id": "Szervezetazonosító", + "organization_not_found": "A szervezet nem található", + "organization_settings": "Szervezet beállításai", + "organization_teams_not_found": "A szervezeti csapatok nem találhatók", + "other": "Egyéb", + "others": "Egyebek", + "overlay_color": "Rávetítés színe", + "overview": "Áttekintés", + "password": "Jelszó", + "paused": "Szüneteltetve", + "pending_downgrade": "Régebbi verzió telepítésére várakozik", + "people_manager": "Emberierőforrás-menedzser", + "person": "Személy", + "phone": "Telefon", + "photo_by": "Fénykép készítője", + "pick_a_date": "Dátum kiválasztása", + "picture": "Fénykép", + "placeholder": "Helykitöltő", + "please_select_at_least_one_survey": "Válasszon legalább egy kérdőívet", + "please_select_at_least_one_trigger": "Válasszon legalább egy aktiválót", + "please_upgrade_your_plan": "Váltson magasabb csomagra", + "preview": "Előnézet", + "preview_survey": "Kérdőív előnézete", + "privacy": "Adatvédelmi irányelvek", + "product_manager": "Termékmenedzser", + "production": "Produktív", + "profile": "Profil", + "profile_id": "Profilazonosító", + "progress": "Folyamat", + "question": "kérdés", + "question_id": "Kérdésazonosító", + "questions": "Kérdések", + "quota": "Kvóta", + "quotas": "Kvóták", + "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", + "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", + "replace": "Csere", + "report_survey": "Kérdőív jelentése", + "request_pricing": "Árazás kérése", + "request_trial_license": "Próbalicenc kérése", + "reset_to_default": "Visszaállítás az alapértelmezettre", + "response": "Válasz", + "response_id": "Válaszazonosító", + "responses": "Válaszok", + "restart": "Újraindítás", + "role": "Szerep", + "saas": "SaaS", + "sales": "Értékesítés", + "save": "Mentés", + "save_as_draft": "Mentés piszkozatként", + "save_changes": "Változtatások mentése", + "saving": "Mentés", + "search": "Keresés", + "security": "Biztonság", + "segment": "Szakasz", + "segments": "Szakaszok", + "select": "Kiválasztás", + "select_all": "Összes kiválasztása", + "select_filter": "Szűrő kiválasztása", + "select_survey": "Kérdőív kiválasztása", + "select_teams": "Csapatok kiválasztása", + "selected": "Kiválasztva", + "selected_questions": "Kiválasztott kérdések", + "selection": "Kiválasztás", + "selections": "Kiválasztások", + "send_test_email": "Teszt e-mail küldése", + "session_not_found": "A munkamenet nem található", + "settings": "Beállítások", + "share_feedback": "Visszajelzés megosztása", + "show": "Megjelenítés", + "show_response_count": "Válaszok számának megjelenítése", + "shown": "Megjelenítve", + "size": "Méret", + "skipped": "Kihagyva", + "skips": "Kihagyja", + "some_files_failed_to_upload": "Néhány fájlt nem sikerült feltölteni", + "something_went_wrong": "Valami probléma történt", + "something_went_wrong_please_try_again": "Valami probléma történt. Próbálja meg újra.", + "sort_by": "Rendezési sorrend", + "start_free_trial": "Ingyenes próba indítása", + "status": "Állapot", + "step_by_step_manual": "Lépésenkénti kézikönyv", + "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", + "submit": "Elküldés", + "summary": "Összegzés", + "survey": "Kérdőív", + "survey_completed": "A kérdőív kitöltve.", + "survey_id": "Kérdőív-azonosító", + "survey_languages": "Kérdőív nyelvei", + "survey_live": "A kérdőív élő", + "survey_not_found": "A kérdőív nem található", + "survey_paused": "A kérdőív szüneteltetve.", + "survey_type": "Kérdőív típusa", + "surveys": "Kérdőívek", + "switch_to": "Váltás {environment} környezetre", + "table_items_deleted_successfully": "{type}s sikeresen törölve", + "table_settings": "Táblázat beállításai", + "tags": "Címkék", + "targeting": "Célzás", + "team": "Csapat", + "team_access": "Csapathozzáférés", + "team_id": "Csapatazonosító", + "team_name": "Csapat neve", + "team_role": "Csapatszerep", + "teams": "Csapatok", + "teams_not_found": "A csapatok nem találhatók", + "text": "Szöveg", + "time": "Idő", + "time_to_finish": "Idő a befejezésig", + "title": "Cím", + "top_left": "Balra fent", + "top_right": "Jobbra fent", + "try_again": "Próbálja újra", + "type": "Típus", + "unlock_more_workspaces_with_a_higher_plan": "Több munkaterület feloldása egy magasabb csomaggal.", + "update": "Frissítés", + "updated": "Frissítve", + "updated_at": "Frissítve", + "upload": "Feltöltés", + "upload_failed": "A feltöltés nem sikerült. Próbálja meg újra.", + "upload_input_description": "Kattintson vagy húzza ide a fájlok feltöltéséhez.", + "url": "URL", + "user": "Felhasználó", + "user_id": "Felhasználó-azonosító", + "user_not_found": "A felhasználó nem található", + "variable": "Változó", + "variable_ids": "Változóazonosítók", + "variables": "Változók", + "verified_email": "Ellenőrzött e-mail-cím", + "video": "Videó", + "warning": "Figyelmeztetés", + "we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Nem tudtuk ellenőrizni a licencét, mert a licenckiszolgáló nem érhető el.", + "webhook": "Webhorog", + "webhooks": "Webhorgok", + "website_and_app_connection": "Webhely és alkalmazáskapcsolódás", + "website_app_survey": "Webhely és alkalmazás-kérdőív", + "website_survey": "Webhely kérdőív", + "weeks": "hetek", + "welcome_card": "Üdvözlő kártya", + "workspace_configuration": "Munkaterület beállítása", + "workspace_created_successfully": "A munkaterület sikeresen létrehozva", + "workspace_creation_description": "Kérdőívek munkaterületekre szervezése a jobb hozzáférés-vezérlés érdekében.", + "workspace_id": "Munkaterület-azonosító", + "workspace_name": "Munkaterület neve", + "workspace_name_placeholder": "például Formbricks", + "workspace_not_found": "A munkaterület nem található", + "workspace_permission_not_found": "A munkaterület-jogosultság nem található", + "workspaces": "Munkaterületek", + "years": "évek", + "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.", + "you_have_reached_your_monthly_miu_limit_of": "Elérte a havi MIU-korlátját ennek:", + "you_have_reached_your_monthly_response_limit_of": "Elérte a havi válaszkorlátját ennek:", + "you_will_be_downgraded_to_the_community_edition_on_date": "Vissza lesz állítva a közösségi kiadásra ekkor: {date}.", + "your_license_has_expired_please_renew": "A vállalati licence lejárt. Újítsa meg, hogy továbbra is használhassa a vállalati funkciókat." + }, + "emails": { + "accept": "Elfogadás", + "click_or_drag_to_upload_files": "Kattintson vagy húzza ide a fájlok feltöltéséhez.", + "email_customization_preview_email_heading": "Helló {userName}", + "email_customization_preview_email_subject": "Formbricks e-mail személyre szabási előnézet", + "email_customization_preview_email_text": "Ez egy e-mail előnézet, amely azt mutatja meg, hogy melyik logó fog megjelenni az e-mailekben.", + "email_footer_text_1": "Legyen szép napja!", + "email_footer_text_2": "A Formbricks csapata", + "email_template_text_1": "Ez az e-mail a Formbricks által lett elküldve.", + "embed_survey_preview_email_didnt_request": "Nem kérte ezt?", + "embed_survey_preview_email_environment_id": "Környezeti azonosító", + "embed_survey_preview_email_fight_spam": "Segítsen nekünk a levélszemét elleni küzdelemben, és továbbítsa ezt a levelet a hola@formbricks.com címre.", + "embed_survey_preview_email_heading": "Beágyazott e-mail előnézete", + "embed_survey_preview_email_subject": "Formbricks e-mail-kérdőív előnézet", + "embed_survey_preview_email_text": "Így néz ki a kódrészlet egy e-mailbe ágyazva:", + "forgot_password_email_change_password": "Jelszó megváltoztatása", + "forgot_password_email_did_not_request": "Ha Ön nem kérte ezt, akkor hagyja figyelmen kívül ezt a levelet.", + "forgot_password_email_heading": "Jelszó megváltoztatása", + "forgot_password_email_link_valid_for_24_hours": "A hivatkozás 24 órán keresztül érvényes.", + "forgot_password_email_subject": "A Formbricks-jelszó visszaállítása", + "forgot_password_email_text": "Hivatkozást kért a jelszava megváltoztatásához. Ezt a lenti hivatkozásra kattintva teheti meg:", + "hidden_field": "Rejtett mező", + "imprint": "Impresszum", + "invite_accepted_email_heading": "Helló, {inviterName}!", + "invite_accepted_email_subject": "Van egy új szervezeti tagja!", + "invite_accepted_email_text": "Csak hogy tudassuk Önnel, {inviteeName} elfogadta a meghívását. Jó szórakozást az együttműködéshez!", + "invite_email_button_label": "Csatlakozás a szervezethez", + "invite_email_heading": "Helló, {inviteeName}!", + "invite_email_text": "A kollégája, {inviterName} meghívta Önt, hogy csatlakozzon a Formbickshez. A meghívás elfogadásához kattintson a lenti hivatkozásra:", + "invite_member_email_subject": "Meghívták Önt a Formbricksen való együttműködésre!", + "new_email_verification_text": "Az új e-mail-címe ellenőrzéséhez kattintson a lenti gombra:", + "number_variable": "Szám változó", + "password_changed_email_heading": "Jelszó megváltoztatva", + "password_changed_email_text": "A jelszava sikeresen megváltoztatva.", + "password_reset_notify_email_subject": "A Formbricks-jelszava megváltozott", + "privacy_policy": "Adatvédelmi irányelvek", + "reject": "Elutasítás", + "render_email_response_value_file_upload_response_link_not_included": "Adatvédelmi okokból a feltöltött fájlra mutató hivatkozást nem tartalmazza", + "response_data": "Válasz adatai", + "response_finished_email_subject": "A(z) {surveyName} kérdőívre adott válasz befejeződött ✅", + "response_finished_email_subject_with_email": "{personEmail} épp most töltötte ki a(z) {surveyName} kérdőívet ✅", + "schedule_your_meeting": "Megbeszélés ütemezése", + "select_a_date": "Dátum kiválasztása", + "survey_response_finished_email_congrats": "Gratulálunk, új válasz érkezett a kérdőívére! Valaki épp most töltötte ki ezt a kérdőívet: {surveyName}", + "survey_response_finished_email_dont_want_notifications": "Nem szeretné megkapni ezeket az értesítéseket?", + "survey_response_finished_email_hey": "Helló 👋", + "survey_response_finished_email_turn_off_notifications_for_all_new_forms": "Értesítések kikapcsolása az összes újonnan létrehozott űrlapnál", + "survey_response_finished_email_turn_off_notifications_for_this_form": "Értesítések kikapcsolása ennél az űrlapnál", + "survey_response_finished_email_view_more_responses": "További {responseCount} válasz megtekintése", + "survey_response_finished_email_view_survey_summary": "Kérdőív összegzésének megtekintése", + "text_variable": "Szöveg változó", + "verification_email_click_on_this_link": "Erre a hivatkozásra is kattinthat:", + "verification_email_heading": "Már majdnem megvagyunk!", + "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_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", + "verification_email_take_survey": "Kérdőív kitöltése", + "verification_email_text": "A Formbricks használatának megkezdéséhez ellenőrizze a lenti e-mail-címét:", + "verification_email_thanks": "Köszönjük, hogy ellenőrizte az e-mail-címét!", + "verification_email_to_fill_survey": "A kérdőív kitöltéséhez kattintson a lenti gombra:", + "verification_email_verify_email": "E-mail-cím ellenőrzése", + "verification_new_email_subject": "E-mail-cím megváltoztatásának ellenőrzése", + "verification_security_notice": "Ha nem Ön kérte ennek az e-mail-címnek a megváltoztatását, akkor hagyja figyelmen kívül ezt az e-mailt, vagy azonnal vegye fel a kapcsolatot a támogatással.", + "verified_link_survey_email_subject": "A kérdőív kitöltésre kész." + }, + "environments": { + "actions": { + "action_copied_successfully": "A művelet sikeresen másolva", + "action_copy_failed": "A művelet másolása nem sikerült", + "action_created_successfully": "A művelet sikeresen létrehozva", + "action_deleted_successfully": "A művelet sikeresen törölve", + "action_type": "Művelet típusa", + "action_updated_successfully": "A művelet sikeresen frissítve", + "action_with_key_already_exists": "A(z) {key} kulccsal rendelkező művelet már létezik", + "action_with_name_already_exists": "A(z) {name} névvel rendelkező művelet már létezik", + "add_css_class_or_id": "CSS-osztály vagy azonosító hozzáadása", + "add_regular_expression_here": "Reguláris kifejezés hozzáadása ide", + "add_url": "URL hozzáadása", + "and": "ÉS", + "click": "Kattintás", + "contains": "Tartalmazza", + "create_action": "Művelet létrehozása", + "css_selector": "CSS-kiválasztó", + "delete_action_text": "Biztosan törölni szeretné ezt a műveletet? Ez aktiválóként is eltávolítja ezt a műveletet az összes kérdőívéből.", + "does_not_contain": "Nem tartalmazza", + "does_not_exactly_match": "Nem pontosan egyezik", + "eg_clicked_download": "Például letöltésre kattintva", + "eg_download_cta_click_on_home": "például download_cta_click_on_home", + "eg_install_app": "Például alkalmazás telepítése", + "ends_with": "Ezzel végződik", + "enter_a_url_to_see_if_a_user_visiting_it_would_be_tracked": "Írjon be egy URL-t, hogy megnézze, ha egy felhasználó meglátogatja, akkor nyomon követik-e.", + "enter_url": "például https://app.com/dashboard", + "exactly_matches": "Pontosan egyezik", + "exit_intent": "Kilépési szándék", + "fifty_percent_scroll": "50% görgetés", + "how_do_code_actions_work": "Hogyan működnek a kódműveletek?", + "if_a_user_clicks_a_button_with_a_specific_css_class_or_id": "Ha egy felhasználó rákattint egy bizonyos CSS-osztállyal vagy azonosítóval rendelkező gombra", + "if_a_user_clicks_a_button_with_a_specific_text": "Ha egy felhasználó rákattint egy bizonyos szöveggel rendelkező gombra", + "in_your_code_read_more_in_our": "a kódban. Tudjon meg többet innen:", + "inner_text": "Belső szöveg", + "invalid_action_type_code": "Érvénytelen művelettípus a kódművelethez.", + "invalid_action_type_no_code": "Érvénytelen művelettípus a kód nélküli művelethez.", + "invalid_css_selector": "Érvénytelen CSS-kiválasztó", + "invalid_match_type": "A kiválasztott lehetőség nem érhető el.", + "invalid_regex": "Használjon érvényes reguláris kifejezést.", + "limit_the_pages_on_which_this_action_gets_captured": "Azon oldalak korlátozása, amelyeken ez a művelet elfogásra kerül", + "limit_to_specific_pages": "Korlátozás bizonyos oldalakra", + "matches_regex": "Reguláris kifejezést illeszt", + "on_all_pages": "Az összes oldalon", + "or": "VAGY", + "page_filter": "Oldalszűrő", + "page_view": "Oldal nézet", + "select_match_type": "Illesztési típus kiválasztása", + "starts_with": "Ezzel kezdődik", + "test_match": "Illeszkedés tesztelése", + "test_your_url": "URL tesztelése", + "this_action_was_created_automatically_you_cannot_make_changes_to_it": "Ez a művelet automatikusan lett létrehozva. Nem végezhet változtatásokat rajta.", + "this_action_will_be_triggered_when_the_page_is_loaded": "Ez a művelet akkor lesz aktiválva, ha az oldal betöltődik.", + "this_action_will_be_triggered_when_the_user_scrolls_50_percent_of_the_page": "Ez a művelet akkor lesz aktiválva, ha a felhasználó az oldal 50%-áig görget.", + "this_action_will_be_triggered_when_the_user_tries_to_leave_the_page": "Ez a művelet akkor lesz aktiválva, ha a felhasználó megpróbálja elhagyni az oldalt.", + "this_is_a_code_action_please_make_changes_in_your_code_base": "Ez egy kódművelet. A változtatásokat a kódbázisban hajtsa végre.", + "track_new_user_action": "Új felhasználói művelet követése", + "track_user_action_to_display_surveys_or_create_user_segment": "Felhasználói művelet követése a kérdőívek megjelenítéséhez vagy felhasználói szakasz létrehozásához.", + "url": "URL", + "user_actions": "Felhasználói műveletek", + "user_clicked_download_button": "A felhasználó rákattintott a letöltés gombra", + "what_did_your_user_do": "Mit csinált a felhasználó?", + "what_is_the_user_doing": "Mit csinál a felhasználó?", + "you_can_track_code_action_anywhere_in_your_app_using": "A kódműveletet bárhol követheti az alkalmazásában a következő használatával:", + "your_survey_would_be_shown_on_this_url": "A kérdőív ezen az URL-en jelenne meg.", + "your_survey_would_not_be_shown": "A kérdőív nem jelenne meg." + }, + "connect": { + "congrats": "Gratulálunk!", + "connection_successful_message": "Szép munka! Kapcsolódtunk.", + "do_it_later": "Később megcsinálom", + "finish_onboarding": "Bevezetés befejezése", + "headline": "Alkalmazás vagy webhely csatlakoztatása", + "import_formbricks_and_initialize_the_widget_in_your_component": "Importálja a Formbricks alkalmazást, és készítse elő a felületi elemet az összetevőjében (például App.tsx):", + "insert_this_code_into_the_head_tag_of_your_website": "Illessze be ezt a kódot webhelye head címkéjébe:", + "subtitle": "Kevesebb mint 4 percet vesz igénybe.", + "waiting_for_your_signal": "Várakozás a jelzésére…" + }, + "contacts": { + "add_attribute": "Attribútum hozzáadása", + "attribute_created_successfully": "Az attribútum sikeresen létrehozva", + "attribute_description": "Leírás", + "attribute_description_placeholder": "Rövid leírás", + "attribute_key": "Kulcs", + "attribute_key_cannot_be_changed": "A kulcsot nem lehet megváltoztatni a létrehozás után", + "attribute_key_hint": "Csak ékezet nélküli kisbetűk, számok és aláhúzásjelek használhatók. Betűvel kell kezdődnie.", + "attribute_key_placeholder": "például: szuletesi_ido", + "attribute_key_required": "A kulcs kötelező", + "attribute_key_safe_identifier_required": "A kulcs csak biztonságos azonosító lehet: csak ékezet nélküli kisbetűk, számok és aláhúzásjelek használhatók, és betűvel kell kezdődnie", + "attribute_label": "Címke", + "attribute_label_placeholder": "például: Születési idő", + "attribute_updated_successfully": "Az attribútum sikeresen frissítve", + "attribute_value": "Érték", + "attribute_value_placeholder": "Attribútum értéke", + "attributes_msg_attribute_limit_exceeded": "Nem sikerült létrehozni {count} új attribútumot, mivel az meghaladná a maximális {limit} attribútumosztály-korlátot. A meglévő attribútumok sikeresen frissítve lettek.", + "attributes_msg_attribute_type_validation_error": "{error} (a(z) '{key}' attribútum adattípusa: {dataType})", + "attributes_msg_email_already_exists": "Az e-mail cím már létezik ebben a környezetben, és nem lett frissítve.", + "attributes_msg_email_or_userid_required": "E-mail cím vagy felhasználói azonosító megadása kötelező. A meglévő értékek megmaradtak.", + "attributes_msg_new_attribute_created": "Új '{key}' attribútum létrehozva '{dataType}' típussal", + "attributes_msg_userid_already_exists": "A felhasználói azonosító már létezik ebben a környezetben, és nem lett frissítve.", + "contact_deleted_successfully": "A partner sikeresen törölve", + "contact_not_found": "Nem található ilyen partner", + "contacts_table_refresh": "Partnerek frissítése", + "contacts_table_refresh_success": "A partnerek sikeresen frissítve", + "create_attribute": "Attribútum létrehozása", + "create_key": "Kulcs 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.", + "custom_attributes": "Egyéni attribútumok", + "data_type": "Adattípus", + "data_type_cannot_be_changed": "Az adattípus létrehozás után nem módosítható", + "data_type_description": "Válaszd ki, hogyan legyen tárolva és szűrve ez az attribútum", + "date_value_required": "Dátum érték megadása kötelező. Használd a törlés gombot az attribútum eltávolításához, ha nem szeretnél dátumot megadni.", + "delete_attribute_confirmation": "{value, plural, one {Ez törölni fogja a kiválasztott attribútumot. Az ehhez az attribútumhoz hozzárendelt összes partneradat el fog veszni.} other {Ez törölni fogja a kiválasztott attribútumokat. Az ezekhez az attribútumokhoz hozzárendelt összes partneradat el fog veszni.}}", + "delete_contact_confirmation": "Ez törölni fogja az ehhez a partnerhez tartozó összes kérdőívválaszt és partnerattribútumot. A partner adatain alapuló bármilyen célzás és személyre szabás el fog veszni.", + "delete_contact_confirmation_with_quotas": "{value, plural, one {Ez törölni fogja az ehhez a partnerhez tartozó összes kérdőívválaszt és partnerattribútumot. A partner adatain alapuló bármilyen célzás és személyre szabás el fog veszni. Ha ez a partner olyan válaszokkal rendelkezik, amelyek a kérdőívkvótákba beletartoznak, akkor a kvóta számlálója csökkentve lesz, de a kvóta korlátai változatlanok maradnak.} other {Ez törölni fogja az ezekhez a partnerekhez tartozó összes kérdőívválaszt és partnerattribútumot. A partnerek adatain alapuló bármilyen célzás és személyre szabás el fog veszni. Ha ezek a partnerek olyan válaszokkal rendelkeznek, amelyek a kérdőívkvótákba beletartoznak, akkor a kvóta számlálója csökkentve lesz, de a kvóta korlátai változatlanok maradnak.}}", + "edit_attribute": "Attribútum szerkesztése", + "edit_attribute_description": "Az attribútum címkéjének és leírásának frissítése.", + "edit_attribute_values": "Attribútumok szerkesztése", + "edit_attribute_values_description": "Bizonyos attribútumok értékének megváltoztatása ennél a partnernél.", + "edit_attributes": "Attribútumok szerkesztése", + "edit_attributes_success": "A partner attribútumai sikeresen frissítve", + "generate_personal_link": "Személyes hivatkozás előállítása", + "generate_personal_link_description": "Válasszon egy közzétett kérdőívet, hogy személyre szabott hivatkozást állítson elő ehhez a partnerhez.", + "invalid_csv_column_names": "Érvénytelen CSV oszlopnév(nevek): {columns}. Az új attribútumokká váló oszlopnevek csak kisbetűket, számokat és aláhúzásjeleket tartalmazhatnak, és betűvel kell kezdődniük.", + "invalid_date_format": "Érvénytelen dátumformátum. Kérlek, adj meg egy érvényes dátumot.", + "invalid_number_format": "Érvénytelen számformátum. Kérlek, adj meg egy érvényes számot.", + "no_published_link_surveys_available": "Nem érhetők el közzétett hivatkozás-kérdőívek. Először tegyen közzé egy hivatkozás-kérdőívet.", + "no_published_surveys": "Nincsenek közzétett kérdőívek", + "no_responses_found": "Nem találhatók válaszok", + "not_provided": "Nincs megadva", + "number_value_required": "Szám érték megadása kötelező. Használd a törlés gombot az attribútum eltávolításához.", + "personal_link_generated": "A személyes hivatkozás sikeresen előállítva", + "personal_link_generated_but_clipboard_failed": "A személyes hivatkozás előállítva, de nem sikerült a vágólapra másolni: {url}", + "personal_survey_link": "Személyes kérdőív-hivatkozás", + "please_select_a_survey": "Válasszon egy kérdőívet", + "search_attribute_keys": "Attribútumkulcsok keresése…", + "search_contact": "Partner keresése", + "select_a_survey": "Kérdőív kiválasztása", + "select_attribute": "Attribútum kiválasztása", + "select_attribute_key": "Attribútum kulcs kiválasztása", + "system_attributes": "Rendszer attribútumok", + "unlock_contacts_description": "Partnerek kezelése és célzott kérdőívek kiküldése", + "unlock_contacts_title": "Partnerek feloldása egy magasabb csomaggal", + "upload_contacts_error_attribute_type_mismatch": "A(z) \"{key}\" attribútum típusa \"{dataType}\", de a CSV érvénytelen értékeket tartalmaz: {values}", + "upload_contacts_error_duplicate_mappings": "Duplikált leképezések találhatók a következő attribútumokhoz: {attributes}", + "upload_contacts_error_file_too_large": "A fájl mérete meghaladja a maximális 800KB-os limitet", + "upload_contacts_error_generic": "Hiba történt a kapcsolatok feltöltése során. Kérjük, próbáld újra később.", + "upload_contacts_error_invalid_file_type": "Kérjük, tölts fel egy CSV fájlt", + "upload_contacts_error_no_valid_contacts": "A feltöltött CSV fájl nem tartalmaz érvényes kapcsolatokat, kérjük, nézd meg a minta CSV fájlt a helyes formátumhoz.", + "upload_contacts_modal_attribute_header": "Formbricks attribútum", + "upload_contacts_modal_attributes_description": "A CSV-ben lévő oszlopok leképezése a Formbricksben lévő attribútumokra.", + "upload_contacts_modal_attributes_new": "Új attribútum", + "upload_contacts_modal_attributes_search_or_add": "Attribútum keresése vagy hozzáadása", + "upload_contacts_modal_attributes_title": "Attribútumok", + "upload_contacts_modal_csv_column_header": "CSV oszlop", + "upload_contacts_modal_description": "CSV feltöltése a partnerek attribútumokkal együtt történő gyors importálásához", + "upload_contacts_modal_download_example_csv": "Példa CSV letöltése", + "upload_contacts_modal_duplicates_description": "Hogyan kell kezelnünk, ha egy partner már szerepel a partnerek között?", + "upload_contacts_modal_duplicates_overwrite_description": "Felülírja a meglévő partnereket", + "upload_contacts_modal_duplicates_overwrite_title": "Felülírás", + "upload_contacts_modal_duplicates_skip_description": "Kihagyja a kettőzött partnereket", + "upload_contacts_modal_duplicates_skip_title": "Kihagyás", + "upload_contacts_modal_duplicates_title": "Kettőzések", + "upload_contacts_modal_duplicates_update_description": "Frissíti a meglévő partnereket", + "upload_contacts_modal_duplicates_update_title": "Frissítés", + "upload_contacts_modal_pick_different_file": "Válasszon egy másik fájlt", + "upload_contacts_modal_preview": "Itt van az adatok előnézete.", + "upload_contacts_modal_upload_btn": "Partnerek feltöltése", + "upload_contacts_success": "A partnerek sikeresen feltöltve" + }, + "formbricks_logo": "Formbricks logó", + "integrations": { + "activepieces_integration_description": "A Formbricks azonnali összekapcsolása népszerű alkalmazásokkal a feladatok kódolás nélküli automatizálásához.", + "additional_settings": "További beállítások", + "airtable": { + "airtable_base": "Airtable alap", + "airtable_integration": "Airtable integrációja", + "airtable_integration_description": "Válaszok szinkronizálása közvetlenül az Airtable platformmal.", + "airtable_integration_is_not_configured": "Az Airtable integrálása nincs beállítva", + "airtable_logo": "Airtable logó", + "connect_with_airtable": "Kapcsolódás az Airtable platformhoz", + "link_airtable_table": "Airtable táblázat hozzákapcsolása", + "link_new_table": "Új táblázat létrehozása", + "no_bases_found": "Nem találhatók Airtable alapok", + "no_integrations_yet": "Az Airtable integrációi itt fognak megjelenni, amint hozzáadja azokat. ⏲️", + "please_create_a_base": "Hozzon létre egy alapot az Airtable platformon", + "please_select_a_base": "Válasszon egy alapot", + "please_select_a_table": "Válasszon egy táblázatot", + "sync_responses_with_airtable": "Válaszok szinkronizálása egy Airtable platformmal", + "table_name": "Táblázat neve" + }, + "airtable_integration_description": "Az Airtable táblázat azonnali feltöltése a kérdőív adataival", + "connected_with_email": "Kapcsolódva a(z) {email} e-mail-címmel", + "connecting_integration_failed_please_try_again": "Az integráció kapcsolása nem sikerült. Próbálja újra!", + "create_survey_warning": "Létre kell hoznia egy kérdőívet, hogy képes legyen beállítani ezt az integrációt", + "delete_integration": "Integráció törlése", + "delete_integration_confirmation": "Biztosan törölni szeretné ezt az integrációt?", + "google_sheet_integration_description": "A táblázatok azonnali feltöltése a kérdőív adataival", + "google_sheets": { + "connect_with_google_sheets": "Kapcsolódás a Google Táblázatokhoz", + "enter_a_valid_spreadsheet_url_error": "Adjon meg egy érvényes táblázat URL-t", + "google_connection": "Google-kapcsolat", + "google_connection_deletion_description": "Válaszok szinkronizálása közvetlenül a Google Táblázatokkal.", + "google_sheet_integration_is_not_configured": "A Google Táblázatok integrációja nincs beállítva a Formbricks-példányán.", + "google_sheet_logo": "Google Táblázatok logó", + "google_sheet_name": "Google Táblázatok neve", + "google_sheets_integration": "Google Táblázatok integrálása", + "google_sheets_integration_description": "Válaszok szinkronizálása közvetlenül a Google Táblázatokkal.", + "link_google_sheet": "Google Táblázatok összekapcsolása", + "link_new_sheet": "Új táblázat összekapcsolása", + "no_integrations_yet": "A Google Táblázatok integrációi itt fognak megjelenni, amint hozzáadja azokat. ⏲️", + "spreadsheet_url": "Táblázat URL-e" + }, + "include_created_at": "Létrehozva felvétele", + "include_hidden_fields": "Rejtett mezők felvétele", + "include_metadata": "Metaadatok (böngésző, ország stb.) felvétele", + "include_variables": "Változók felvétele", + "integration_added_successfully": "Az integráció sikeresen hozzáadva", + "integration_removed_successfully": "Az integráció sikeresen eltávolítva", + "integration_updated_successfully": "Az integráció sikeresen frissítve", + "make_integration_description": "A Formbricks integrálása 1000+ alkalmazással Make segítségével", + "manage_webhooks": "Webhorgok kezelése", + "n8n_integration_description": "A Formbricks integrálása 350+ alkalmazással n8n segítségével", + "notion": { + "col_name_of_type_is_not_supported": "A Notion API nem támogatja a(z) {type} típus {col_name} oszlopnevét. Az adatok nem jelennek meg a Notion adatbázisában.", + "connect_with_notion": "Kapcsolódás a Notion platformhoz", + "connected_with_workspace": "Kapcsolódva {workspace} munkaterülettel", + "create_at_least_one_database_to_setup_this_integration": "Létre kell hoznia legalább egy adatbázist, hogy képes legyen beállítani ezt az integrációt", + "database_name": "Adatbázis neve", + "duplicate_connection_warning": "Az adatbázissal való kapcsolat aktív. Óvatosan végezzen változtatásokat.", + "link_database": "Adatbázis összekapcsolása", + "link_new_database": "Új adatbázis összekapcsolása", + "link_notion_database": "Notion-adatbázis összekapcsolása", + "map_formbricks_fields_to_notion_property": "Formbricks-mezők leképezése Notion-tulajdonságra", + "no_databases_found": "A Notion integrációi itt fognak megjelenni, amint hozzáadja azokat. ⏲️", + "notion_integration": "Notion integrálása", + "notion_integration_description": "Válaszok küldése közvetlenül a Notion alkalmazásba.", + "notion_integration_is_not_configured": "A Notion integrációja nincs beállítva a Formbricks-példányán.", + "notion_logo": "Notion logó", + "please_complete_mapping_fields_with_notion_property": "Fejezze be a mezők leképezését Notion-tulajdonságra", + "please_resolve_mapping_errors": "Oldja meg a leképezési hibákat", + "please_select_a_database": "Válasszon adatbázist", + "please_select_at_least_one_mapping": "Válasszon legalább egy leképezést", + "que_name_of_type_cant_be_mapped_to": "A(z) {question_label} típus {que_name} kérdése nem képezhető le a(z) {col_type} típus {col_name} oszlopára. Használjon inkább {mapped_type} típusú oszlopot.", + "select_a_database": "Adatbázis kiválasztása", + "select_a_field_to_map": "Leképezendő mező kiválasztása", + "select_a_survey_question": "Kérdőívkérdés kiválasztása", + "update_connection": "Notion újracsatlakoztatása", + "update_connection_tooltip": "Csatlakoztassa újra az integrációt az újonnan hozzáadott adatbázisok felvételéhez. A meglévő integrációi érintetlenek maradnak." + }, + "notion_integration_description": "Adatok küldése a Notion-adatbázisba", + "please_select_a_survey_error": "Válasszon kérdőívet", + "select_at_least_one_question_error": "Válasszon legalább egy kérdést", + "slack": { + "already_connected_another_survey": "Már hozzákapcsolt egy másik kérdőívet ehhez a csatornához.", + "channel_name": "Csatorna neve", + "connect_with_slack": "Kapcsolódás a Slack platformhoz", + "connect_your_first_slack_channel": "Csatlakoztassa az első Slack-csatornáját a kezdéshez.", + "connected_with_team": "Kapcsolódva a(z) {team} csapattal", + "create_at_least_one_channel_error": "Létre kell hoznia legalább egy csatornát, hogy képes legyen beállítani ezt az integrációt", + "dont_see_your_channel": "Nem látja a csatornáját?", + "link_channel": "Csatorna összekapcsolása", + "link_slack_channel": "Slack-csatorna összekapcsolása", + "please_select_a_channel": "Válasszon egy csatornát", + "select_channel": "Csatorna kiválasztása", + "slack_integration": "Slack integrálása", + "slack_integration_description": "Válaszok küldése közvetlenül a Slack alkalmazásba.", + "slack_integration_is_not_configured": "A Slack integrációja nincs beállítva a Formbricks-példányán.", + "slack_logo": "Slack logó", + "slack_reconnect_button": "Újrakapcsolódás", + "slack_reconnect_button_description": "Megjegyzés: nemrég megváltoztattuk a Slack-integrációnkat, hogy a személyes csatornákat is támogassa. Csatlakoztassa újra a Slack-munkaterületét." + }, + "slack_integration_description": "A Slack-munkaterület azonnali csatlakoztatása a Formbrickshez", + "to_configure_it": "a beállításához.", + "webhook_integration_description": "Webhorgok aktiválása a kérdőívekben lévő műveletek alapján", + "webhooks": { + "add_webhook": "Webhorog hozzáadása", + "add_webhook_description": "A kérdőív válaszadatainak küldése egy egyéni végpontra", + "all_current_and_new_surveys": "Az összes jelenlegi és új kérdőív", + "copy_secret_now": "Aláírási titok másolása", + "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_pinged": "Hurrá! Képesek vagyunk pingelni a webhorgot!", + "endpoint_pinged_error": "Nem lehet pingelni a webhorgot!", + "learn_to_verify": "Tudja meg, hogy kell ellenőrizni a webhorog aláírásait", + "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", + "response_finished": "Válasz befejezve", + "response_updated": "Válasz frissítve", + "secret_copy_warning": "Tárolja ezt a titkot biztonságosan. Újra megtekintheti a webhorog beállításaiban.", + "secret_description": "Használja ezt a titkot a webhorogkérések ellenőrzéséhez. Az aláírás ellenőrzéséhez nézze meg a dokumentációt.", + "signing_secret": "Aláírási titok", + "source": "Forrás", + "test_endpoint": "Végpont tesztelése", + "triggers": "Aktiválók", + "webhook_added_successfully": "A webhorog sikeresen hozzáadva", + "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_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é" + }, + "website_or_app_integration_description": "A Formbricks integrálása a webhelyébe vagy alkalmazásába", + "zapier_integration_description": "A Formbricks integrálása 5000+ alkalmazással Zapier segítségével" + }, + "segments": { + "add_filter_below": "Szűrő hozzáadása lent", + "add_your_first_filter_to_get_started": "Adja hozzá az első szűrőt a kezdéshez", + "cannot_delete_segment_used_in_surveys": "Nem tudja törölni ezt a szakaszt, mert még mindig használatban van ezekben a kérdőívekben:", + "clone_and_edit_segment": "Szakasz klónozása és szerkesztése", + "create_group": "Csoport létrehozása", + "create_your_first_segment": "Hozza létre az első szakaszt a kezdéshez", + "delete_segment": "Szakasz törlése", + "desktop": "Asztali", + "devices": "Eszközök", + "edit_segment": "Szakasz szerkesztése", + "error_resetting_filters": "Hiba a szűrők visszaállításakor", + "error_saving_segment": "Hiba a szakasz mentéskor", + "ex_fully_activated_recurring_users": "Például: Teljesen aktivált visszatérő felhasználók", + "ex_power_users": "Például: Képzett felhasználók", + "filters_reset_successfully": "A szűrők sikeresen visszaállítva", + "here": "ide", + "hide_filters": "Szűrők elrejtése", + "identifying_users": "felhasználók azonosítása", + "invalid_segment": "Érvénytelen szakasz", + "invalid_segment_filters": "Érvénytelen szűrők. Ellenőrizze a szűrőket, és próbálja meg újra.", + "load_segment": "Szakasz betöltése", + "most_active_users_in_the_last_30_days": "Legaktívabb felhasználók az elmúlt 30 napban", + "no_attributes_yet": "Még nincsenek attribútumok!", + "no_filters_yet": "Még nincsenek szűrők!", + "no_segments_yet": "Jelenleg nincsenek mentett szakaszai.", + "operator_contains": "tartalmazza", + "operator_does_not_contain": "nem tartalmazza", + "operator_ends_with": "ezzel végződik", + "operator_is_after": "ez után", + "operator_is_before": "ez előtt", + "operator_is_between": "között", + "operator_is_newer_than": "újabb mint", + "operator_is_not_set": "nincs beállítva", + "operator_is_older_than": "régebbi mint", + "operator_is_same_day": "ugyanazon a napon", + "operator_is_set": "beállítva", + "operator_starts_with": "ezzel kezdődik", + "operator_title_contains": "Tartalmazza", + "operator_title_does_not_contain": "Nem tartalmazza", + "operator_title_ends_with": "Ezzel végződik", + "operator_title_equals": "Egyenlő", + "operator_title_greater_equal": "Nagyobb vagy egyenlő", + "operator_title_greater_than": "Nagyobb mint", + "operator_title_is_after": "Ez után", + "operator_title_is_before": "Ez előtt", + "operator_title_is_between": "Között", + "operator_title_is_newer_than": "Újabb mint", + "operator_title_is_not_set": "Nincs beállítva", + "operator_title_is_older_than": "Régebbi mint", + "operator_title_is_same_day": "Ugyanazon a napon", + "operator_title_is_set": "Beállítva", + "operator_title_less_equal": "Kisebb vagy egyenlő", + "operator_title_less_than": "Kisebb mint", + "operator_title_not_equals": "Nem egyenlő", + "operator_title_starts_with": "Ezzel kezdődik", + "operator_title_user_is_in": "A felhasználó benne van", + "operator_title_user_is_not_in": "A felhasználó nincs benne", + "operator_user_is_in": "A felhasználó benne van", + "operator_user_is_not_in": "A felhasználó nincs benne", + "person_and_attributes": "Személy és attribútumok", + "phone": "Telefon", + "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Távolítsa el a szakaszt ezekből a kérdőívekből, hogy törölhesse azt.", + "pre_segment_users": "A felhasználók előszakaszolása az attribútumok szűrőivel.", + "remove_all_filters": "Összes szűrő eltávolítása", + "reset_all_filters": "Összes szűrő visszaállítása", + "save_as_new_segment": "Mentés új szakaszként", + "save_your_filters_as_a_segment_to_use_it_in_other_surveys": "A szűrők mentése szakaszként más kérdőívekben való használathoz", + "segment_created_successfully": "A szakasz sikeresen létrehozva", + "segment_deleted_successfully": "A szakasz sikeresen törölve", + "segment_id": "Szakaszazonosító", + "segment_saved_successfully": "A szakasz sikeresen elmentve", + "segment_updated_successfully": "A szakasz sikeresen frissítve", + "segments_help_you_target_users_with_same_characteristics_easily": "A szakaszok segítik a hasonló jellemzőkkel rendelkező felhasználók könnyű megcélzását", + "target_audience": "Célközönség", + "this_action_resets_all_filters_in_this_survey": "Ez a művelet visszaállítja az összes szűrőt ebben a kérdőívben.", + "this_segment_is_used_in_other_surveys": "Ez a szakasz más kutatásokban is használva van. Változtatások elvégzése", + "title_is_required": "A cím megadása kötelező.", + "unknown_filter_type": "Ismeretlen szűrőtípus", + "unlock_segments_description": "Partnerek szakaszokba szervezése meghatározott felhasználói csoportok megcélzásához", + "unlock_segments_title": "Szakaszok feloldása egy magasabb csomaggal", + "user_targeting_is_currently_only_available_when": "A felhasználók megcélzása jelenleg csak akkor érhető el, ha", + "value_cannot_be_empty": "Az érték nem lehet üres.", + "value_must_be_a_number": "Az értékének számnak kell lennie.", + "value_must_be_positive": "Az értéknek pozitív számnak kell lennie.", + "view_filters": "Szűrők megtekintése", + "where": "Ahol", + "with_the_formbricks_sdk": "a Formbricks SDK-val" + }, + "settings": { + "api_keys": { + "add_api_key": "API-kulcs hozzáadása", + "add_permission": "Jogosultság hozzáadása", + "api_keys_description": "API-kulcsok kezelése a Formbricks kezelő API-jaihoz való hozzáféréshez" + }, + "billing": { + "1000_monthly_responses": "Havi 1000 válasz", + "1_workspace": "1 munkaterület", + "2000_contacts": "2000 partner", + "3_workspaces": "3 munkaterület", + "5000_monthly_responses": "Havi 5000 válasz", + "7500_contacts": "7500 partner", + "all_integrations": "Összes integráció", + "annually": "Évente", + "api_webhooks": "API és webhorgok", + "app_surveys": "Alkalmazás-kérdőívek", + "attribute_based_targeting": "Attribútumalapú célzás", + "current": "Jelenlegi", + "current_plan": "Jelenlegi csomag", + "current_tier_limit": "Jelenlegi szintkorlát", + "custom": "Egyéni és méretezés", + "custom_contacts_limit": "Egyéni partnerkorlát", + "custom_response_limit": "Egyéni válaszkorlát", + "custom_workspace_limit": "Egyéni munkaterület-korlát", + "email_embedded_surveys": "E-mailbe beágyazott kérdőívek", + "email_follow_ups": "E-mail követések", + "enterprise_description": "Prémium támogatás és egyéni korlátok.", + "everybody_has_the_free_plan_by_default": "Alapértelmezetten mindenki az ingyenes csomaggal rendelkezik!", + "everything_in_free": "Minden az Ingyenesben", + "everything_in_startup": "Minden a Kezdőben", + "free": "Ingyenes", + "free_description": "Korlátlan kérdőívek, csapattagok és egyebek.", + "get_2_months_free": "Szerezzen 2 hónapot ingyen", + "hosted_in_frankfurt": "Frankfurtból kiszolgálva", + "ios_android_sdks": "iOS és Android SDK a mobil kérdőívekhez", + "link_surveys": "Hivatkozás-kérdőívek (megosztható)", + "logic_jumps_hidden_fields_recurring_surveys": "Logikai ugrások, rejtett mezők, ismétlődő kérdőívek stb.", + "manage_card_details": "Kártya részleteinek kezelése", + "manage_subscription": "Feliratkozás kezelése", + "monthly": "Havonta", + "monthly_identified_users": "Havonta azonosított felhasználók", + "plan_upgraded_successfully": "A csomag sikeresen magasabbra váltva", + "premium_support_with_slas": "Prémium támogatás SLA-kkal", + "remove_branding": "Márkajel eltávolítása", + "startup": "Kezdő", + "startup_description": "Minden az Ingyenes csomagban további funkciókkal.", + "switch_plan": "Csomag váltása", + "team_access_roles": "Csapathozzáférés szerepei", + "unable_to_upgrade_plan": "Nem lehet magasabb csomagra váltani", + "unlimited_miu": "Korlátlan MIU", + "unlimited_responses": "Korlátlan válaszok", + "unlimited_surveys": "Korlátlan kérdőívek", + "unlimited_team_members": "Korlátlan csapattagok", + "unlimited_workspaces": "Korlátlan munkaterület", + "upgrade": "Frissítés", + "uptime_sla_99": "Működési idő SLA (99%)", + "website_surveys": "Weboldal-kérdőívek" + }, + "domain": { + "customize_favicon_description": "Egyéni böngészőikon feltöltése a hivatkozás-kérdőív élményének személyre szabásához és a márka jelenlétének erősítéséhez.", + "customize_favicon_with_higher_plan": "Böngészőikon személyre szabása egy magasabb csomaggal", + "description": "Csinos URL-eket használó összes kérdőív áttekintése a szervezeten belül", + "favicon_customization": "Böngészőikon", + "favicon_customization_description": "A hivatkozás-kérdőívek böngészőlapjain megjelenő böngészőikon személyre szabása. Ez nem érinti az adminisztrátori alkalmazást.", + "favicon_removed_successfully": "A böngészőikon sikeresen eltávolítva", + "favicon_saved_successfully": "A böngészőikon sikeresen elmentve", + "favicon_size_hint": "Legfeljebb 100 KB, a négyzetes képek működnek a legjobban", + "favicon_too_large": "A böngészőikonnak 100 KB alatt kell lennie", + "no_pretty_urls": "Még nincsenek csinos URL-ekkel rendelkező kérdőívek beállítva.", + "pretty_url": "Csinos URL", + "survey_name": "Kérdőív neve", + "title": "Csinos URL-ek", + "workspace": "Munkaterület" + }, + "enterprise": { + "audit_logs": "Auditálási naplók", + "coming_soon": "Hamarosan", + "contacts_and_segments": "Partnerkezelés és szakaszok", + "enterprise_features": "Vállalati funkciók", + "get_an_enterprise_license_to_get_access_to_all_features": "Vállalati licenc megszerzése az összes funkcióhoz való hozzáféréshez.", + "keep_full_control_over_your_data_privacy_and_security": "Az adatvédelem és biztonság fölötti rendelkezés teljes kézben tartása.", + "license_invalid_description": "Az ENTERPRISE_LICENSE_KEY környezeti változóban lévő licenckulcs nem érvényes. Ellenőrizze, hogy nem gépelte-e el, vagy kérjen új kulcsot.", + "license_status": "Licencállapot", + "license_status_active": "Aktív", + "license_status_description": "A vállalati licenc állapota.", + "license_status_expired": "Lejárt", + "license_status_invalid": "Érvénytelen licenc", + "license_status_unreachable": "Nem érhető el", + "license_unreachable_grace_period": "A licenckiszolgálót nem lehet elérni. A vállalati funkciók egy 3 napos türelmi időszak alatt aktívak maradnak, egészen eddig: {gracePeriodEnd}.", + "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Nincs szükség telefonálásra, nincs feltételekhez kötöttség: kérjen 30 napos ingyenes próbalicencet az összes funkció kipróbálásához az alábbi űrlap kitöltésével:", + "no_credit_card_no_sales_call_just_test_it": "Nem kell hitelkártya. Nincsenek értékesítési hívások. Egyszerűen csak próbálja ki :)", + "on_request": "Kérésre", + "organization_roles": "Szervezeti szerepek (adminisztrátor, szerkesztő, fejlesztő stb.)", + "questions_please_reach_out_to": "Kérdése van? Írjon nekünk erre az e-mail-címre:", + "recheck_license": "Licenc újraellenőrzése", + "recheck_license_failed": "A licencellenőrzés nem sikerült. Lehet, hogy a licenckiszolgáló nem érhető el.", + "recheck_license_invalid": "A licenckulcs érvénytelen. Ellenőrizze az ENTERPRISE_LICENSE_KEY értékét.", + "recheck_license_success": "A licencellenőrzés sikeres", + "recheck_license_unreachable": "A licenckiszolgáló nem érhető el. Próbálja meg később újra.", + "rechecking": "Újraellenőrzés…", + "request_30_day_trial_license": "30 napos ingyenes licenc kérése", + "saml_sso": "SAML SSO", + "service_level_agreement": "Szolgáltatási megállapodás", + "soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 megfelelőségi ellenőrzés", + "sso": "SSO (Google, Microsoft, OpenID-kapcsolat)", + "teams": "Csapatok és hozzáférési szerepek (olvasás, olvasás és írás, kezelés)", + "unlock_the_full_power_of_formbricks_free_for_30_days": "A Formbricks teljes erejének feloldása. 30 napig ingyen." + }, + "general": { + "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.", + "copy_invite_link_to_clipboard": "Meghívási hivatkozás másolása a vágólapra", + "create_new_organization": "Új szervezet létrehozása", + "create_new_organization_description": "Új szervezet létrehozása a különböző jellegű munkaterületek kezeléséhez.", + "customize_email_with_a_higher_plan": "E-mail személyre szabása egy magasabb csomaggal", + "delete_member_confirmation": "A törölt tagok elveszítik a hozzáférést a szervezete összes munkaterületéhez és kérdőívéhez.", + "delete_organization": "Szervezet törlése", + "delete_organization_description": "A szervezet törlése az összes munkaterületével együtt, beleértve az összes kérdőívet, választ, személyt, műveletet és attribútumot is", + "delete_organization_warning": "Mielőtt folytatná a szervezet törlését, legyen tisztában a következő következményekkel:", + "delete_organization_warning_1": "Ezen szervezethez kapcsolt összes munkaterület végleges eltávolítása.", + "delete_organization_warning_2": "Ezt a műveletet nem lehet visszavonni. Ha eltűnt, akkor eltűnt.", + "delete_organization_warning_3": "Adja meg a(z) {organizationName} szervezetnevet a következő mezőben a szervezet végleges törlésének megerősítéséhez:", + "eliminate_branding_with_whitelabel": "A Formbricks márkajel eltávolítása és további fehér címkés személyre szabási lehetőségek engedélyezése.", + "email_customization_preview_email_heading": "Helló {userName}", + "email_customization_preview_email_text": "Ez egy e-mail előnézet, amely azt mutatja meg, hogy melyik logó fog megjelenni az e-mailekben.", + "error_deleting_organization_please_try_again": "Hiba a szervezet törlésekor. Próbálja meg újra.", + "from_your_organization": "a szervezetétől", + "invitation_sent_once_more": "A meghívó még egyszer elküldve.", + "invite_deleted_successfully": "A meghívó sikeresen törölve", + "invite_expires_on": "A meghívó lejár ekkor: {date}", + "invites_failed": "A meghívás sikertelen", + "leave_organization": "Szervezet elhagyása", + "leave_organization_description": "Elhagyja ezt a szervezetet, és elveszíti az összes kérdőívhez és válaszhoz való hozzáférését. Csak akkor tud ismét csatlakozni, ha újra meghívják.", + "leave_organization_ok_btn_text": "Igen, szervezet elhagyása", + "leave_organization_title": "Biztos benne?", + "logo_in_email_header": "Logó az e-mail fejlécében", + "logo_removed_successfully": "A logó sikeresen eltávolítva", + "logo_saved_successfully": "A logó sikeresen elmentve", + "manage_members": "Tagok kezelése", + "manage_members_description": "Tagok hozzáadása vagy eltávolítása a szervezetben.", + "member_deleted_successfully": "A tag sikeresen törölve", + "member_invited_successfully": "A tag sikeresen meghívva", + "once_its_gone_its_gone": "Ha egyszer eltűnt, akkor eltűnt.", + "only_org_owner_can_perform_action": "Csak a szervezet tulajdonosai férhetnek hozzá ehhez a beállításhoz.", + "organization_created_successfully": "A szervezet sikeresen létrehozva", + "organization_deleted_successfully": "A szervezet sikeresen törölve", + "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_updated_successfully": "A szervezet neve sikeresen frissítve", + "organization_settings": "Szervezet beállításai", + "please_add_a_logo": "Adjon hozzá egy logót", + "please_check_csv_file": "Ellenőrizze a CSV-fájlt, és győződjön meg arról, hogy megfelel-e a formátumunknak", + "please_save_logo_before_sending_test_email": "Mentse el a logót, mielőtt elküld egy teszt e-mailt.", + "remove_logo": "Logó eltávolítása", + "replace_logo": "Logó cseréje", + "resend_invitation_email": "Meghívási e-mail újraküldése", + "security_list_tip": "Feliratkozott már a biztonsági listánkra? Maradjon naprakész, hogy biztonságban tartsa a példányát!", + "security_list_tip_link": "Regisztráljon itt.", + "share_invite_link": "Meghívási hivatkozás megosztása", + "share_this_link_to_let_your_organization_member_join_your_organization": "Ossza meg ezt a hivatkozást, hogy szervezetének tagja csatlakozhasson a szervezetéhez:", + "test_email_sent_successfully": "A teszt e-mail sikeresen elküldve", + "use_multi_language_surveys_with_a_higher_plan": "Többnyelvű kérdőívek használata egy magasabb csomaggal", + "use_multi_language_surveys_with_a_higher_plan_description": "Kérdezze meg felhasználóit különböző nyelveken." + }, + "notifications": { + "auto_subscribe_to_new_surveys": "Automatikus feliratkozás az új kérdőívekre", + "email_alerts_surveys": "Riasztások küldése e-mailben (kérdőívek)", + "every_response": "Minden válasz", + "every_response_tooltip": "Teljes válaszokat küld, nem részlegeseket.", + "need_slack_or_discord_notifications": "Slack vagy Discord értesítések szükségesek", + "notification_settings_updated": "Az értesítési beállítások frissítve", + "set_up_an_alert_to_get_an_email_on_new_responses": "Riasztás beállítása, hogy e-mailt kapjon az új válaszokról", + "use_the_integration": "Az integráció használata", + "want_to_loop_in_organization_mates": "Szeretné bevonni a szervezeti társakat?", + "you_will_not_be_auto_subscribed_to_this_organizations_surveys_anymore": "Többé nem lesz automatikusan feliratkoztatva ennek a szervezetnek a kérdőíveire!", + "you_will_not_receive_any_more_emails_for_responses_on_this_survey": "Többé nem fog e-maileket kapni ezen kérdőívre adott válaszokról!" + }, + "profile": { + "account_deletion_consequences_warning": "Fiók törlésének következményei", + "backup_code": "Visszaszerzési kód", + "confirm_delete_account": "A fiókja törlése az összes személyes információjával és adatával együtt", + "confirm_delete_my_account": "Saját fiók törlése", + "confirm_your_current_password_to_get_started": "Erősítse meg a jelenlegi jelszavát a kezdéshez.", + "delete_account": "Fiók törlése", + "disable_two_factor_authentication": "Kétfaktoros hitelesítés letiltása", + "disable_two_factor_authentication_description": "Ha le kell tiltania a kétfaktoros hitelesítést, akkor azt javasoljuk, hogy engedélyezze újra, amint lehetséges.", + "each_backup_code_can_be_used_exactly_once_to_grant_access_without_your_authenticator": "Minden visszaszerzési kód pontosan egyszer használható a hitelesítő nélküli hozzáférés megszerzéséhez.", + "email_change_initiated": "Az e-mail-címe megváltoztatása iránti kérelme kezdeményezve lett.", + "enable_two_factor_authentication": "Kétfaktoros hitelesítés engedélyezése", + "enter_the_code_from_your_authenticator_app_below": "Adja meg a hitelesítő alkalmazásból származó kódot lent.", + "lost_access": "Elvesztett hozzáférés", + "or_enter_the_following_code_manually": "Vagy adja meg a következő kódot kézileg:", + "organizations_delete_message": "Ön az egyetlen tulajdonosa ezeknek a szervezeteknek, ezért azok is törölve lesznek.", + "permanent_removal_of_all_of_your_personal_information_and_data": "Az összes személyes információinak és adatainak végleges törlése", + "personal_information": "Személyes információk", + "please_enter_email_to_confirm_account_deletion": "Adja meg a(z) {email} e-mail-címet a következő mezőben a fiókja végleges törlésének megerősítéséhez:", + "profile_updated_successfully": "A profilja sikeresen frissítve lett", + "save_the_following_backup_codes_in_a_safe_place": "Mentse el a következő visszaszerzési kódokat egy biztonságos helyre.", + "scan_the_qr_code_below_with_your_authenticator_app": "Olvassa be a lenti QR-kódot a hitelesítő alkalmazásával.", + "security_description": "A jelszava és egyéb biztonsági beállítások, például a kétfaktoros hitelesítés (2FA) kezelése.", + "two_factor_authentication": "Kétfaktoros hitelesítés", + "two_factor_authentication_description": "További biztonsági réteg hozzáadása a fiókjához arra az esetre, ha a jelszavát ellopnák.", + "two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "A kétfaktoros hitelesítés engedélyezve van. Adja a meg a 6 számjegyű kódot a hitelesítő alkalmazásából.", + "two_factor_code": "Kétfaktoros kód", + "unlock_two_factor_authentication": "A kétfaktoros hitelesítés feloldása egy magasabb csomaggal", + "update_personal_info": "Személyes információk frissítése", + "warning_cannot_delete_account": "Ön az egyetlen tulajdonosa ennek a szervezetnek. Először adja át a tulajdonjogot egy másik tagnak.", + "warning_cannot_undo": "Ezt nem lehet visszavonni" + }, + "teams": { + "add_members_description": "Tagok hozzáadása a csapathoz és a szerepük meghatározása.", + "add_workspaces_description": "Annak vezérlése, hogy mely munkaterületekhez férhetnek hozzá a csapattagok.", + "all_members_added": "Az összes tag hozzáadva ehhez a csapathoz.", + "all_workspaces_added": "Az összes munkaterület hozzáadva ehhez a csapathoz.", + "are_you_sure_you_want_to_delete_this_team": "Biztosan törölni szeretné ezt a csapatot? Ez eltávolítja a csapathoz hozzárendelt összes munkaterülethez és kérdőívhez való hozzáférést is.", + "billing_role_description": "Csak a számlázási információkhoz van hozzáférése.", + "bulk_invite": "Tömeges meghívás", + "contributor": "Közreműködő", + "create": "Létrehozás", + "create_first_team_message": "Először létre kell hoznia egy csapatot.", + "create_new_team": "Új csapat létrehozása", + "delete_team": "Csapat törlése", + "empty_teams_state": "Az első csapat létrehozása.", + "enter_team_name": "Csapatnév megadása", + "individual": "Egyéni", + "invite_member": "Tag meghívása", + "invite_member_description": "Munkatársak hozzáadása ehhez a szervezethez.", + "manage": "Kezelés", + "manage_team": "Csapat kezelése", + "manage_team_disabled": "Csak a szervezet tulajdonosai, kezelői és csapatadminisztrátorai kezelhetnek csapatokat.", + "manager_role_description": "A kezelők hozzáférhetnek az összes munkaterülethez, valamint tagokat adhatnak hozzá és távolíthatnak el.", + "member": "Tag", + "member_role_description": "A tagok a kiválasztott munkaterületeken dolgozhatnak.", + "member_role_info_message": "Ahhoz, hogy az új tagoknak hozzáférést adjon egy munkaterülethez, adja hozzá őket lent egy csapathoz. A csapatokkal kezelheti azt, hogy kinek melyik munkaterülethez van hozzáférése.", + "organization_role": "Szervezeti szerep", + "owner_role_description": "A tulajdonosoknak teljes irányításuk van a szervezet felett.", + "please_fill_all_member_fields": "Töltse ki az összes mezőt egy új tag hozzáadásához.", + "please_fill_all_workspace_fields": "Töltse ki az összes mezőt egy új munkaterület hozzáadásához.", + "read": "Olvasás", + "read_write": "Olvasás és írás", + "team_admin": "Csapatadminisztrátor", + "team_created_successfully": "A csapat sikeresen létrehozva", + "team_deleted_successfully": "A csapat sikeresen törölve", + "team_deletion_not_allowed": "Önnek nem engedélyezett ennek a csapatnak a törlése.", + "team_name": "Csapat neve", + "team_name_settings_title": "{teamName} beállításai", + "team_select_placeholder": "Csapatnév keresése…", + "team_settings_description": "Csapattagok, hozzáférési jogok és egyebek kezelése.", + "team_updated_successfully": "A csapat sikeresen frissítve", + "teams": "Csapatok", + "teams_description": "Tagok hozzárendelése csapatokhoz és a csapatok számára hozzáférés megadása a munkaterületekhez.", + "unlock_teams_description": "Annak kezelése, hogy mely szervezeti tagok rendelkeznek hozzáféréssel bizonyos munkaterületekhez és kérdőívekhez.", + "unlock_teams_title": "Csapatok feloldása egy magasabb csomaggal.", + "upgrade_plan_notice_message": "Szervezeti szerepek feloldása egy magasabb csomaggal.", + "you_are_a_member": "Ön tag" + } + }, + "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:", + "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", + "add_a_variable_to_calculate": "Változó hozzáadása a számításhoz", + "add_action_below": "Művelet hozzáadása lent", + "add_block": "Blokk hozzáadása", + "add_choice_below": "Választási lehetőség hozzáadása lent", + "add_color_coding": "Színkódolás hozzáadása", + "add_color_coding_description": "Piros, narancssárga és zöld színkódok hozzáadása a lehetőségekhez.", + "add_column": "Oszlop hozzáadása", + "add_condition_below": "Feltétel hozzáadása lent", + "add_custom_styles": "Egyéni stílusok hozzáadása", + "add_delay_before_showing_survey": "Kérdőív megjelenítése előtti késleltetés hozzáadása", + "add_description": "Leírás hozzáadása", + "add_ending": "Befejezés hozzáadása", + "add_ending_below": "Befejezés hozzáadása lent", + "add_fallback_placeholder": "Helykitöltő hozzáadása annak megjelenítéshez, hogy nincs visszahívandó érték.", + "add_hidden_field_id": "Rejtett mezőazonosító hozzáadása", + "add_highlight_border": "Kiemelési szegély hozzáadása", + "add_logic": "Logika hozzáadása", + "add_none_of_the_above": "„A fentiek közül egyik sem” hozzáadása", + "add_option": "Lehetőség hozzáadása", + "add_other": "„Egyéb” hozzáadása", + "add_photo_or_video": "Fénykép vagy videó hozzáadása", + "add_pin": "PIN-kód hozzáadása", + "add_question_below": "Kérdés hozzáadása lent", + "add_question_to_block": "Kérdés hozzáadása a blokkhoz", + "add_row": "Sor hozzáadása", + "add_variable": "Változó hozzáadása", + "address_fields": "Címmezők", + "address_line_1": "Cím 1. sora", + "address_line_2": "Cím 2. sora", + "adjust_survey_closed_message": "A „Kérdőív lezárva” üzenet módosítása", + "adjust_survey_closed_message_description": "Annak az üzenetnek a megváltoztatása, amelyet a látogatók akkor látnak, amikor a kérdőív lezárul.", + "adjust_the_theme_in_the": "A téma beállítása ebben:", + "all_other_answers_will_continue_to": "Az összes többi válasz továbbra is", + "allow_multi_select": "Több választás engedélyezése", + "allow_multiple_files": "Több fájl engedélyezése", + "allow_users_to_select_more_than_one_image": "Lehetővé tétel a felhasználóknak, hogy egynél több képet válasszanak ki", + "and_launch_surveys_in_your_website_or_app": "és kérdőívek indítása a webhelyén vagy az alkalmazásában.", + "animation": "Animáció", + "app_survey_description": "Egy kérdőív beágyazása a webalkalmazásába vagy webhelyére a válaszok gyűjtéséhez.", + "assign": "= hozzárendelése", + "audience": "Közönség", + "auto_close_on_inactivity": "Automatikus lezárás tétlenségnél", + "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", + "automatically_close_survey_after": "Kérdőív automatikus lezárása ezután:", + "automatically_close_the_survey_after_a_certain_number_of_responses": "A kérdőív automatikus lezárása egy bizonyos számú válasz után.", + "automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "A kérdőív automatikus lezárása, ha a felhasználó nem válaszol egy bizonyos másodpercnyi idő után.", + "automatically_mark_the_survey_as_complete_after": "A kérdőív automatikus megjelölése kitöltöttként ezután:", + "back_button_label": "A „Vissza” gomb címkéje", + "background_styling": "Háttér stílusának beállítása", + "block_duplicated": "A blokk kettőzve.", + "bold": "Félkövér", + "brand_color": "Márkajel színe", + "brand_color_description": "Gombokra, hivatkozásokra és kiemelésekre alkalmazva.", + "brightness": "Fényerő", + "bulk_edit": "Tömeges szerkesztés", + "bulk_edit_description": "Az összes lenti lehetőség szerkesztése, soronként egy. Az üres sorok kihagyásra kerülnek, az ismétlődések pedig el lesznek távolítva.", + "bulk_edit_options": "Tömeges szerkesztés beállításai", + "bulk_edit_options_for": "Tömeges szerkesztés beállításai {language} nyelvhez", + "button_external": "Külső hivatkozás engedélyezése", + "button_external_description": "Egy gomb hozzáadása, amely megnyit egy külső URL-t egy új lapon", + "button_label": "Gomb címkéje", + "button_url": "Gomb URL-e", + "cal_username": "Cal.com felhasználónév vagy felhasználónév/esemény", + "calculate": "Kiszámítás", + "capture_a_new_action_to_trigger_a_survey_on": "Új művelet rögzítése egy kérdőíven történő aktiváláshoz.", + "capture_ip_address": "IP-cím rögzítése", + "capture_ip_address_description": "A válaszadó IP-címének tárolása a válasz metaadataiban a kettőzés-felismeréshez és biztonsági célokból", + "capture_new_action": "Új művelet rögzítése", + "card_arrangement_for_survey_type_derived": "Kártyaelrendezés a(z) {surveyTypeDerived} kérdőíveknél", + "card_background_color": "Kártya hátterének színe", + "card_background_color_description": "Kitölti a kérdőívkártya területét.", + "card_border_color": "Kártya szegélyének színe", + "card_border_color_description": "Körberajzolja a kérdőívkártyát.", + "card_styling": "Kártya stílusának beállítása", + "casual": "Alkalmi", + "caution_edit_duplicate": "Kettőzés és szerkesztés", + "caution_edit_published_survey": "Szerkeszt egy közzétett kérdőívet?", + "caution_explanation_intro": "Megértjük, hogy esetleg még változtatásokat szeretne végrehajtani. Ez történik, ha megteszi: ", + "caution_explanation_new_responses_separated": "A változtatás előtti válaszok nem vagy csak részben kerülhetnek be a kérdőív összegzésébe.", + "caution_explanation_only_new_responses_in_summary": "Az összes adat, beleértve a korábbi válaszokat is, továbbra is elérhetők maradnak letölthető formában a kérdőív összegző oldalán.", + "caution_explanation_responses_are_safe": "A régebbi és az újabb válaszok összekeverednek, ami félrevezető adatösszegzésekhez vezethet.", + "caution_recommendation": "Ez adatellentmondásokat okozhat a kérdőív összegzésében. Azt javasoljuk, hogy inkább kettőzze meg a kérdőívet.", + "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_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.", + "change_the_placement_of_this_survey": "A kérdőív elhelyezésének megváltoztatása.", + "changes_saved": "Változtatások elmentve.", + "changing_survey_type_will_remove_existing_distribution_channels": "A kérdőív típusának megváltoztatása hatással lesz arra, hogy hogyan lehet megosztani azt. Ha a válaszadók már rendelkeznek a jelenlegi típushoz tartozó hozzáférési hivatkozásokkal, akkor elveszíthetik a hozzáférést a váltás után.", + "checkbox_label": "Jelölőnégyzet címkéje", + "choose_the_actions_which_trigger_the_survey": "Azon műveletek kiválasztása, amelyek aktiválják a kérdőívet.", + "choose_the_first_question_on_your_block": "Az első kérdés kiválasztása a blokkban", + "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", + "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", + "company": "Vállalat", + "company_logo": "Vállalat logója", + "completed_responses": "befejezett válasz.", + "concat": "Összefűzés +", + "conditional_logic": "Feltételes logika", + "confirm_default_language": "Alapértelmezett nyelv megerősítése", + "confirm_survey_changes": "Kérdőív változtatásainak megerősítése", + "contact_fields": "Kapcsolatfelvételi mezők", + "contains": "Tartalmazza", + "continue_to_settings": "Folytatás a beállításokhoz", + "convert_to_multiple_choice": "Átalakítás többválasztósra", + "convert_to_single_choice": "Átalakítás egyválasztósra", + "country": "Ország", + "create_group": "Csoport létrehozása", + "create_your_own_survey": "Saját kérdőív létrehozása", + "css_selector": "CSS-kiválasztó", + "cta_button_label": "A „CTA” gomb címkéje", + "custom_hostname": "Egyéni gépnév", + "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.", + "date_format": "Dátumformátum", + "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.", + "delete_anyways": "Törlés mindenképp", + "delete_block": "Blokk törlése", + "delete_choice": "Választási lehetőség törlése", + "disable_the_visibility_of_survey_progress": "A kérdőív előrehaladási folyamata láthatóságának letiltása.", + "display_an_estimate_of_completion_time_for_survey": "A kérdőív becsült kitöltési idejének megjelenítése", + "display_number_of_responses_for_survey": "A kérdőív válaszai számának megjelenítése", + "display_type": "Megjelenített típus", + "divide": "Osztás /", + "does_not_contain": "Nem tartalmazza", + "does_not_end_with": "Nem ezzel végződik", + "does_not_equal": "Nem egyenlő", + "does_not_include_all_of": "Nem tartalmazza ezekből az összeset", + "does_not_include_one_of": "Nem tartalmazza ezek egyikét", + "does_not_start_with": "Nem ezzel kezdődik", + "dropdown": "Legördülő", + "duplicate_block": "Blokk kettőzése", + "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.", + "enable_spam_protection": "Szemét elleni védekezés", + "end_screen_card": "Befejező képernyő kártyája", + "ending_card": "Befejező kártya", + "ending_card_used_in_logic": "Ez a befejező kártya használatban van a(z) {questionIndex}. kérdés logikájában.", + "ending_used_in_quota": "Ez a befejezés használatban van a(z) „{quotaName}” kvótában", + "ends_with": "Ezzel végződik", + "enter_fallback_value": "Tartalékérték megadása", + "equals": "Egyenlő", + "equals_one_of": "Egyenlő ezek egyikével", + "error_publishing_survey": "Hiba történt a kérdőív közzététele során.", + "error_saving_changes": "Hiba a változtatások mentésekor", + "even_after_they_submitted_a_response_e_g_feedback_box": "Több válasz lehetővé tétele. Még válasz után is látható marad (például visszajelző doboz).", + "everyone": "Mindenki", + "external_urls_paywall_tooltip": "Váltson a magasabb Kezdő csomagra a külső URL-ek személyre szabásához. Ez segít nekünk megelőzni az adathalászatot.", + "fallback_missing": "Tartalék hiányzik", + "fieldId_is_used_in_logic_of_question_please_remove_it_from_logic_first": "A(z) {fieldId} használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.", + "fieldId_is_used_in_quota_please_remove_it_from_quota_first": "A(z) „{fieldId}” rejtett mező használatban van a(z) „{quotaName}” kvótában", + "field_name_eg_score_price": "Mező neve, például pontszám, ár", + "first_name": "Keresztnév", + "five_points_recommended": "5 pont (ajánlott)", + "follow_ups": "Követések", + "follow_ups_delete_modal_text": "Biztosan törölni szeretné ezt a követést?", + "follow_ups_delete_modal_title": "Törli a követést?", + "follow_ups_empty_description": "Üzenetek küldése a válaszadóknak, önmagának vagy csapattársaknak.", + "follow_ups_empty_heading": "Automatikus követések küldése", + "follow_ups_ending_card_delete_modal_text": "Ez a befejező kártya használatban van a követésekben. A törlése eltávolítja az összes követésből. Biztosan törölni szeretné?", + "follow_ups_ending_card_delete_modal_title": "Törli a befejező kártyát?", + "follow_ups_hidden_field_error": "A rejtett mező használatban van egy követésben. Először távolítsa el a követésből.", + "follow_ups_include_hidden_fields": "Rejtett mezők értékeinek felvétele", + "follow_ups_include_variables": "Változó értékeinek felvétele", + "follow_ups_item_ending_tag": "Befejezések", + "follow_ups_item_issue_detected_tag": "Probléma észlelve", + "follow_ups_item_response_tag": "Bármilyen válasz", + "follow_ups_item_send_email_tag": "E-mail küldése", + "follow_ups_modal_action_attach_response_data_description": "Csak azokat a kérdéseket csatolja, amelyek megválaszolásra kerültek a kérdőív válaszában", + "follow_ups_modal_action_attach_response_data_label": "Válasz adatainak csatolása", + "follow_ups_modal_action_body_label": "Törzs", + "follow_ups_modal_action_body_placeholder": "Az e-mail törzse", + "follow_ups_modal_action_email_content": "E-mail tartalma", + "follow_ups_modal_action_email_settings": "E-mail beállításai", + "follow_ups_modal_action_from_description": "Az az e-mail-cím, ahonnan az e-mail elküldésre kerül", + "follow_ups_modal_action_from_label": "Feladó", + "follow_ups_modal_action_label": "Művelet", + "follow_ups_modal_action_replyTo_description": "Ha a címzett megnyomja a válasz gombot, a következő e-mail-címre érkezik a válasz", + "follow_ups_modal_action_replyTo_label": "Válasz", + "follow_ups_modal_action_subject": "Köszönjük a válaszait!", + "follow_ups_modal_action_subject_label": "Tárgy", + "follow_ups_modal_action_subject_placeholder": "Az e-mail tárgya", + "follow_ups_modal_action_to_description": "Az az e-mail-cím, ahova az e-mail elküldésre kerül", + "follow_ups_modal_action_to_label": "Címzett", + "follow_ups_modal_action_to_warning": "Nem találhatók érvényes beállítások az e-mailek küldéséhez, adjon hozzá néhány szabad szöveges vagy kapcsolatfelvételi információkat tartalmazó kérdést vagy rejtett mezőt", + "follow_ups_modal_create_heading": "Új követés létrehozása", + "follow_ups_modal_created_successfull_toast": "A követés létrehozva, és akkor lesz elmentve, ha elmenti a kérdőívet.", + "follow_ups_modal_edit_heading": "A követés szerkesztése", + "follow_ups_modal_edit_no_id": "Nincs kérdőívkövetési azonosító megadva, nem lehet frissíteni a kérdőívkövetést", + "follow_ups_modal_name_label": "Követés neve", + "follow_ups_modal_name_placeholder": "A követés elnevezése", + "follow_ups_modal_subheading": "Üzenetek küldése a válaszadóknak, önmagának vagy csapattársaknak", + "follow_ups_modal_trigger_description": "Mikor kell ezt a követést aktiválni?", + "follow_ups_modal_trigger_label": "Aktiváló", + "follow_ups_modal_trigger_type_ending": "A válaszadó egy adott befejezést lát", + "follow_ups_modal_trigger_type_ending_select": "Befejezések kiválasztása: ", + "follow_ups_modal_trigger_type_ending_warning": "Válasszon legalább egy befejezést, vagy változtassa meg az aktiváló típusát", + "follow_ups_modal_trigger_type_response": "A válaszadó kitölti a kérdőívet", + "follow_ups_modal_updated_successfull_toast": "A követés frissítve, és akkor lesz elmentve, ha elmenti a kérdőívet.", + "follow_ups_new": "Új követés", + "follow_ups_upgrade_button_text": "Magasabb csomagra váltás a követések engedélyezéséhez", + "form_styling": "Űrlap stílusának beállítása", + "formbricks_sdk_is_not_connected": "A Formbricks SDK nincs csatlakoztatva", + "four_points": "4 pont", + "heading": "Címsor", + "hidden_field_added_successfully": "A rejtett mező sikeresen hozzáadva", + "hidden_field_used_in_recall": "A(z) „{hiddenField}” rejtett mező visszahívásra kerül a(z) {questionIndex}. kérdésben.", + "hidden_field_used_in_recall_ending_card": "A(z) „{hiddenField}” rejtett mező visszahívásra kerül a befejező kártyában", + "hidden_field_used_in_recall_welcome": "A(z) „{hiddenField}” rejtett mező visszahívásra kerül az üdvözlő kártyában.", + "hide_back_button": "A „Vissza” gomb elrejtése", + "hide_back_button_description": "Ne jelenjen meg a vissza gomb a kérdőívben", + "hide_block_settings": "Blokkbeállítások elrejtése", + "hide_logo": "Logó elrejtése", + "hide_logo_from_survey": "Logó elrejtése ebből a kérdőívből", + "hide_progress_bar": "Folyamatjelző elrejtése", + "hide_question_settings": "Kérdésbeállítások elrejtése", + "hostname": "Gépnév", + "if_you_need_more_please": "Ha többre van szüksége, akkor", + "if_you_really_want_that_answer_ask_until_you_get_it": "Maradjon megjelenítve bármikor is aktiválódott, amíg egy választ el nem küldenek.", + "ignore_global_waiting_time": "Várakozási időszak figyelmen kívül hagyása", + "ignore_global_waiting_time_description": "Ez a kérdőív akkor jelenhet meg, ha a feltételei teljesülnek, még akkor is, ha egy másik kérdőív jelent meg nemrég.", + "image": "Kép", + "includes_all_of": "Tartalmazza ezekből az összeset", + "includes_one_of": "Tartalmazza ezek egyikét", + "initial_value": "Kezdeti érték", + "inner_text": "Belső szöveg", + "input_border_color": "Beviteli mező szegélyének színe", + "input_border_color_description": "Körberajzolja a szöveges beviteli mezőket és a szövegdobozokat.", + "input_color": "Beviteli mező színe", + "input_color_description": "Kitölti a szöveges beviteli mezők belsejét.", + "insert_link": "Hivatkozás beszúrása", + "invalid_targeting": "Érvénytelen célzás: ellenőrizze a közönség szűrőit", + "invalid_video_url_warning": "Adjon meg egy érvényes YouTube, Vimeo vagy Loom URL-t. Jelenleg nem támogatunk más videomegosztó szolgáltatókat.", + "invalid_youtube_url": "Érvénytelen YouTube-URL", + "is_accepted": "Elfogadva", + "is_after": "Ez után", + "is_any_of": "Ezek egyike", + "is_before": "Ez előtt", + "is_booked": "Foglalva van", + "is_clicked": "Kattintva", + "is_completely_submitted": "Teljesen elküldve", + "is_empty": "Üres", + "is_not_clicked": "Nincs kattintva", + "is_not_empty": "Nem üres", + "is_not_set": "Nincs beállítva", + "is_partially_submitted": "Részben elküldve", + "is_set": "Beállítva", + "is_skipped": "Kihagyva", + "is_submitted": "Elküldve", + "italic": "Dőlt", + "jump_to_block": "Ugrás a blokkhoz", + "keep_current_order": "Jelenlegi sorrend megtartása", + "keep_showing_while_conditions_match": "Addig jelenjen meg, amíg a feltételek teljesülnek", + "key": "Kulcs", + "last_name": "Vezetéknév", + "let_people_upload_up_to_25_files_at_the_same_time": "Lehetővé tétel a személyek számára, hogy egyszerre legfeljebb 25 fájlt töltsenek fel.", + "limit_the_maximum_file_size": "A legnagyobb fájlméret korlátozása a feltöltéseknél.", + "limit_upload_file_size_to": "Feltöltési fájlméret korlátozása erre:", + "link_survey_description": "Egy kérdőív oldalára mutató hivatkozás megosztása vagy a kérdőív beágyazása egy weboldalba vagy e-mailbe.", + "list": "Lista", + "load_segment": "Szakasz betöltése", + "logic_error_warning": "A változtatás logikai hibákat fog okozni", + "logic_error_warning_text": "A kérdés típusának megváltoztatása eltávolítja a logikai feltételeket ebből a kérdésből.", + "logo_settings": "Logó beállításai", + "long_answer": "Hosszú válasz", + "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", + "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", + "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", + "next_block": "Következő blokk", + "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_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.", + "number": "Szám", + "once_set_the_default_language_for_this_survey_can_only_be_changed_by_disabling_the_multi_language_option_and_deleting_all_translations": "A beállítás után a kérdőív alapértelmezett nyelve csak a többnyelvű beállítás letiltásával és az összes fordítás törlésével változtatható meg.", + "only_display_the_survey_to_a_subset_of_the_users": "A kérdőív megjelenítése csak a felhasználók egy részének", + "only_lower_case_letters_numbers_and_underscores_are_allowed": "Csak kisbetűk, számok és aláhúzásjelek engedélyezettek.", + "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ó", + "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.", + "overwrite_global_waiting_time": "Egyéni várakozási időszak beállítása", + "overwrite_global_waiting_time_description": "A munkaterület beállításainak felülbírálása csak ennél a kérdőívnél.", + "overwrite_placement": "Elhelyezés felülírása", + "overwrite_survey_logo": "Egyéni kérdőívlogó beállítása", + "overwrite_the_global_placement_of_the_survey": "A kérdőív globális elhelyezésének felülírása", + "pick_a_background_from_our_library_or_upload_your_own": "Válasszon hátteret a könyvtárunkból, vagy töltsön fel sajátot.", + "picture_idx": "Kép {idx}", + "pin_can_only_contain_numbers": "A PIN-kód csak számokat tartalmazhat.", + "pin_must_be_a_four_digit_number": "A PIN-kód csak négyjegyű szám lehet.", + "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", + "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", + "protect_survey_with_pin": "Kérdőív megvédése PIN-kóddal", + "protect_survey_with_pin_description": "Csak a PIN-kóddal rendelkező felhasználók férhetnek hozzá a kérdőívhez.", + "publish": "Közzététel", + "question": "Kérdés", + "question_deleted": "Kérdés törölve.", + "question_duplicated": "Kérdés megkettőzve.", + "question_id_updated": "Kérdésazonosító frissítve", + "question_used_in_logic_warning_text": "Ezen blokkból származó elemek egy logikai szabályban vannak használva, biztosan törölni szeretné?", + "question_used_in_logic_warning_title": "Logikai következetlenség", + "question_used_in_quota": "Ez a kérdés használatban van a(z) „{quotaName}” kvótában", + "question_used_in_recall": "Ez a kérdés visszahívásra kerül a(z) {questionIndex}. kérdésben.", + "question_used_in_recall_ending_card": "Ez a kérdés visszahívásra kerül a befejező kártyában", + "quotas": { + "add_quota": "Kvóta hozzáadása", + "change_quota_for_public_survey": "Megváltoztatja a kvótát a nyilvános kérdőívnél?", + "confirm_quota_changes": "Kvótaváltoztatások megerősítése", + "confirm_quota_changes_body": "Elmentetlen változtatások vannak a kvótájában. Szeretné elmenteni azokat a kilépés előtt?", + "continue_survey_normally": "Kérdőív folytatása normál módon", + "count_partial_submissions": "Részleges beküldések számlálása", + "count_partial_submissions_description": "Azon válaszadók beleszámolása, akik megfelelnek a kvóta feltételeinek, de nem töltötték ki a kérdőívet", + "create_quota_for_public_survey": "Létrehoz kvótát a nyilvános kérdőívhez?", + "create_quota_for_public_survey_description": "Csak a jövőbeli válaszok számítanak bele a kvótába.", + "create_quota_for_public_survey_text": "Ez a kérdőív már nyilvános. A meglévő válaszok nem lesznek figyelembe véve az új kvótánál.", + "delete_quota_confirmation_text": "Ez véglegesen törölni fogja a(z) {quotaName} kvótát.", + "duplicate_quota": "Kvóta kettőzése", + "edit_quota": "Kvóta szerkesztése", + "end_survey_for_matching_participants": "Kérdőív befejezése a résztvevők összeillesztéséhez", + "inclusion_criteria": "Tartalmazási feltételek", + "limit_must_be_greater_than_or_equal_to_the_number_of_responses": "{value, plural, one {Már {value} válasza van erre a kvótára, ezért a korlátnak nagyobbnak kell lennie mint {value}.} other {Már {value} válasza van erre a kvótára, ezért a korlátnak nagyobbnak kell lennie mint {value}.}}", + "limited_to_x_responses": "{limit} értékre korlátozva", + "new_quota": "Új kvóta", + "quota_created_successfull_toast": "A kvóta sikeresen létrehozva", + "quota_deleted_successfull_toast": "A kvóta sikeresen törölve", + "quota_duplicated_successfull_toast": "A kvóta sikeresen megkettőzve", + "quota_name_placeholder": "például: 18-25 éves résztvevők", + "quota_updated_successfull_toast": "A kvóta sikeresen frissítve", + "response_limit": "Korlátok", + "save_changes_confirmation_body": "A tartalmazási feltételek bármely megváltoztatása csak a jövőbeli válaszokra vonatkozik. Javasoljuk, hogy vagy kettőzzön egy meglévőt, vagy hozzon létre új kvótát.", + "save_changes_confirmation_text": "A meglévő válaszok a kvótában maradnak", + "select_ending_card": "Befejező kártya kiválasztása", + "upgrade_prompt_title": "Kvóták használata egy magasabb csomaggal", + "when_quota_has_been_reached": "Amikor a kvóta elérésre került" + }, + "randomize_all": "Összes véletlenszerűsítése", + "randomize_all_except_last": "Összes véletlenszerűsítése az utolsó kivételével", + "range": "Tartomány", + "recall_data": "Adatok visszahívása", + "recall_information_from": "Információk visszahívása innen…", + "recontact_options_section": "Újbóli kapcsolatfelvételi beállítások", + "recontact_options_section_description": "Ha a várakozási időszak megengedi, annak kiválasztása, hogy ez a kérdőív milyen gyakran jelenhet meg egy személynek.", + "redirect_thank_you_card": "Köszönetnyilvánító kártya átirányítása", + "redirect_to_url": "Átirányítás URL-re", + "remove_description": "Leírás eltávolítása", + "remove_translations": "Fordítások eltávolítása", + "require_answer": "Válasz szükséges", + "required": "Kötelező", + "reset_to_theme_styles": "Visszaállítás a téma stílusaira", + "reset_to_theme_styles_main_text": "Biztosan vissza szeretné állítani a stílust a téma stílusaira? Ez eltávolítja az összes egyéni stílust.", + "respect_global_waiting_time": "Várakozási időszak használata", + "respect_global_waiting_time_description": "Ez a kérdőív a munkaterület beállításában megadott várakozási időszakot követi. Csak akkor jelenik meg, ha nem jelent meg más kérdőív abban az időszakban.", + "response_limit_can_t_be_set_to_0": "A válaszkorlátot nem lehet 0 értékre állítani", + "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", + "roundness": "Kerekesség", + "roundness_description": "Annak vezérlése, hogy a kártya sarkai 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.", + "rows": "Sorok", + "save_and_close": "Mentés és bezárás", + "scale": "Méretezés", + "search_for_images": "Képek keresése", + "seconds_after_trigger_the_survey_will_be_closed_if_no_response": "másodperccel az aktiváló után a kérdőív le lesz zárva, ha nincs válasz", + "seconds_before_showing_the_survey": "másodperc a kérdőív megjelenítése előtt.", + "select_field": "Mező kiválasztása", + "select_or_type_value": "Érték kiválasztása vagy beírása", + "select_ordering": "Sorrend kiválasztása", + "select_saved_action": "Mentett művelet kiválasztása", + "select_type": "Típus kiválasztása", + "send_survey_to_audience_who_match": "Kérdőív küldése az erre illeszkedő közönségnek…", + "send_your_respondents_to_a_page_of_your_choice": "A válaszadók küldése a választási lehetőség oldalára.", + "set_the_global_placement_in_the_look_feel_settings": "A globális elhelyezés beállítása a megjelenítési beállításokban.", + "settings_saved_successfully": "A beállítások sikeresen elmentve", + "seven_points": "7 pont", + "show_block_settings": "Blokkbeállítások megjelenítése", + "show_button": "Gomb megjelenítése", + "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", + "show_question_settings": "Kérdésbeállítások megjelenítése", + "show_survey_maximum_of": "Kérdőív megjelenítése legfeljebb:", + "show_survey_to_users": "Kérdőív megjelenítése a felhasználók ennyi százalékának", + "show_to_x_percentage_of_targeted_users": "Megjelenítés a célzott felhasználók {percentage}%-ának", + "simple": "Egyszerű", + "six_points": "6 pont", + "smiley": "Hangulatjel", + "spam_protection_note": "A szemét elleni védekezés nem működik az iOS, React Native és Android SDK-kkal megjelenített kérdőíveknél. El fogja rontani a kérdőívet.", + "spam_protection_threshold_description": "Állítsa az értéket 0 és 1 közé, az ezen érték alatt lévő válaszok elutasításra kerülnek.", + "spam_protection_threshold_heading": "Válasz küszöbszintje", + "star": "Csillag", + "starts_with": "Ezzel kezdődik", + "state": "Állapot", + "straight": "Egyenesen", + "style_the_question_texts_descriptions_and_input_fields": "A kérdés szövegeinek, leírásainak és beviteli mezőinek stílusszerkesztése.", + "style_the_survey_card": "A kérdőívkártya stílusszerkesztése.", + "styling_set_to_theme_styles": "A stílus a téma stílusaira állítva", + "subheading": "Alcím", + "subtract": "Kivonás -", + "survey_completed_heading": "A kérdőív kitöltve", + "survey_completed_subheading": "Ez a szabad és nyílt forráskódú kérdőív le lett zárva", + "survey_display_settings": "Kérdőív megjelenítésének beállításai", + "survey_placement": "Kérdőív elhelyezése", + "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", + "the_survey_will_be_shown_multiple_times_until_they_respond": "Megjelenítés legfeljebb a megadott számú alkalommal vagy amíg válaszolnak (amelyik előbb bekövetkezik).", + "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.", + "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:", + "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", + "underline": "Aláhúzott", + "unlock_targeting_description": "Bizonyos felhasználói csoportok megcélzása attribútumok vagy eszközinformációk alapján", + "unlock_targeting_title": "Célzás feloldása egy magasabb csomaggal", + "unsaved_changes_warning": "Elmentetlen változtatások vannak a kérdőívében. Szeretné elmenteni azokat a kilépés előtt?", + "until_they_submit_a_response": "Kérdezzen, amíg nem küldenek választ", + "untitled_block": "Névtelen blokk", + "update_options": "Lehetőségek frissítése", + "upgrade_notice_description": "Többnyelvű kérdőívek létrehozása és számos további funkció feloldása", + "upgrade_notice_title": "Többnyelvű kérdőívek feloldása egy magasabb csomaggal", + "upload": "Feltöltés", + "upload_at_least_2_images": "Töltsön fel legalább 2 képet", + "upper_label": "Felső címke", + "url_filters": "URL szűrők", + "url_not_supported": "Az URL nem támogatott", + "validation": { + "add_validation_rule": "Ellenőrzési szabály hozzáadása", + "answer_all_rows": "Válaszoljon az összes sorra", + "characters": "Karakterek", + "contains": "Tartalmazza", + "delete_validation_rule": "Ellenőrzési szabály törlése", + "does_not_contain": "Nem tartalmazza", + "email": "Érvényes e-mail-cím", + "end_date": "Befejezési dátum", + "file_extension_is": "A fájlkiterjesztés ez", + "file_extension_is_not": "A fájlkiterjesztés nem ez", + "is": "Ez", + "is_between": "Között", + "is_earlier_than": "Korábban mint", + "is_greater_than": "Nagyobb mint", + "is_later_than": "Később mint", + "is_less_than": "Kisebb mint", + "is_not": "Nem ez", + "is_not_between": "Nem ezek között", + "kb": "KB", + "max_length": "Legfeljebb", + "max_selections": "Legfeljebb", + "max_value": "Legfeljebb", + "mb": "MB", + "min_length": "Legalább", + "min_selections": "Legalább", + "min_value": "Legalább", + "minimum_options_ranked": "Legkevesebb rangsorolt lehetőség", + "minimum_rows_answered": "Legkevesebb megválaszolt sor", + "options_selected": "Lehetőségek kiválasztva", + "pattern": "Illeszkedő reguláris kifejezés", + "phone": "Érvényes telefon", + "rank_all_options": "Összes lehetőség rangsorolása", + "select_file_extensions": "Fájlkiterjesztések kiválasztása…", + "select_option": "Lehetőség kiválasztása", + "start_date": "Kezdési dátum", + "url": "Érvényes URL" + }, + "validation_logic_and": "Az összes igaz", + "validation_logic_or": "bármelyik igaz", + "validation_rules": "Ellenőrzési szabályok", + "validation_rules_description": "Csak olyan válaszok elfogadása, amelyek teljesítik a következő feltételeket", + "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "A(z) {variable} változó használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "A(z) „{variableName}” változó használatban van a(z) „{quotaName}” kvótában", + "variable_name_conflicts_with_hidden_field": "A változónév ütközik egy meglévő rejtett mező azonosítójával.", + "variable_name_is_already_taken_please_choose_another": "A változónév már használatban van. Válasszon egy másikat.", + "variable_name_must_start_with_a_letter": "A változónév csak ékezet nélküli betűvel kezdődhet.", + "variable_used_in_recall": "A(z) „{variable}” változó visszahívásra kerül a(z) {questionIndex}. kérdésben.", + "variable_used_in_recall_ending_card": "A(z) {variable} változó visszahívásra kerül a befejező kártyában", + "variable_used_in_recall_welcome": "A(z) „{variable}” változó visszahívásra kerül az üdvözlő kártyában.", + "verify_email_before_submission": "E-mail-cím ellenőrzése a beküldés előtt", + "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.", + "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)", + "waiting_time_across_surveys_description": "A kérdőívekbe való belefáradás megakadályozásához válassza ki, hogy ez a kérdőív hogyan lép kölcsönhatásba a munkaterület-szintű várakozási időszakkal.", + "welcome_message": "Üdvözlő üzenet", + "without_a_filter_all_of_your_users_can_be_surveyed": "Szűrő nélkül az összes felhasználója megkérdezhető.", + "you_have_not_created_a_segment_yet": "Még nem hozott létre szakaszt", + "you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Be kell állítania kettő vagy több nyelvet a munkaterületen a fordításokkal való munkához.", + "your_description_here_recall_information_with": "Ide jön a leírás. Információk visszahívása a @ karakterrel.", + "your_question_here_recall_information_with": "Ide jön a kérdés. Információk visszahívása a @ karakterrel.", + "your_web_app": "Saját webalkalmazás", + "zip": "Irányítószám" + }, + "error_deleting_survey": "Hiba történt a kérdőív törlése során", + "filter": { + "complete_and_partial_responses": "Teljes és részleges válaszok", + "complete_responses": "Teljes válaszok", + "partial_responses": "Részleges válaszok" + }, + "new_survey": "Új kérdőív", + "no_surveys_created_yet": "Még nincsenek kérdőívek létrehozva", + "open_options": "Beállítások megnyitása", + "preview_survey_in_a_new_tab": "Kérdőív előnézete új lapon", + "read_only_user_not_allowed_to_create_survey_warning": "Csak olvasási jogosultságú felhasználóként nem hozhat létre kérdőíveket. Kérjen meg egy írási jogosultsággal rendelkező felhasználót, hogy hozzon létre egy kérdőívet, vagy egy vezetőt, hogy frissítse az Ön szerepét.", + "relevance": "Fontosság", + "responses": { + "address_line_1": "Cím 1. sora", + "address_line_2": "Cím 2. sora", + "an_error_occurred_adding_the_tag": "Hiba történt a címke hozzáadásakor", + "an_error_occurred_creating_the_tag": "Hiba történt a címke létrehozásakor", + "an_error_occurred_deleting_the_tag": "Hiba történt a címke törlésekor", + "browser": "Böngésző", + "bulk_delete_response_quotas": "A válaszok a kérdőív kvótáinak részét képezik. Hogyan szeretné kezelni a kvótákat?", + "city": "Város", + "company": "Vállalat", + "completed": "Befejezve ✅", + "country": "Ország", + "decrement_quotas": "A kvóták összes korlátjának csökkentése, beleértve ezt a választ is", + "delete_response_confirmation": "Ez törölni fogja a kérdőívre adott választ, beleértve az összes választ, címkét, csatolt dokumentumot és a válasz metaadatait.", + "delete_response_quotas": "A válasz a kérdőív kvótáinak részét képezik. Hogyan szeretné kezelni a kvótákat?", + "device": "Eszköz", + "device_info": "Eszközinformációk", + "email": "E-mail", + "error_downloading_responses": "Hiba történt a válaszok letöltése során", + "first_name": "Keresztnév", + "how_to_identify_users": "Hogyan kell azonosítani a felhasználókat", + "ip_address": "IP-cím", + "last_name": "Vezetéknév", + "not_completed": "Nincs befejezve ⏳", + "os": "Operációs rendszer", + "person_attributes": "A személy jellemzői a beküldés időpontjában", + "phone": "Telefon", + "respondent_skipped_questions": "A válaszadó kihagyta ezeket a kérdéseket.", + "response_deleted_successfully": "A válasz sikeresen törölve", + "single_use_id": "Egyszer használatos azonosító", + "source": "Forrás", + "state_region": "Állam vagy régió", + "survey_closed": "Kérdőív lezárva", + "tag_already_exists": "A címke már létezik", + "this_response_is_in_progress": "Ez a válasz folyamatban van.", + "zip_post_code": "Irányítószám" + }, + "search_by_survey_name": "Keresés kérőívnév alapján", + "share": { + "anonymous_links": { + "custom_single_use_id_description": "Ha nem titkosítja az egyszer használatos azonosítókat, akkor a „suid=…” bármilyen értéke működik egy válasznál.", + "custom_single_use_id_title": "Bármilyen értéket beállíthat egyszer használatos azonosítóként az URL-ben.", + "custom_start_point": "Egyéni kezdési pont", + "data_prefilling": "Adatok előre kitöltése", + "description": "Az ezekről a hivatkozásokról érkező válaszok névtelenek lesznek", + "disable_multi_use_link_modal_button": "Többször használatos hivatkozás letiltása", + "disable_multi_use_link_modal_description": "A többször használatos hivatkozás letiltása megakadályozza, hogy bárki beküldjön egy választ a hivatkozáson keresztül.", + "disable_multi_use_link_modal_description_subtext": "Ez el fogja rontani az összes aktív beágyazást az olyan webhelyeken, e-mailekben, közösségi médiában és QR-kódokban, amelyek ezt a többször használatos hivatkozást használják.", + "disable_multi_use_link_modal_title": "Biztos benne? Ez elronthatja az aktív beágyazásokat.", + "disable_single_use_link_modal_button": "Egyszer használatos hivatkozások letiltása", + "disable_single_use_link_modal_description": "Ha egyszer használatos hivatkozásokat osztott meg, akkor a résztvevők többé nem fognak tudni válaszolni a kérdőívre.", + "generate_and_download_links": "Hivatkozások előállítása és letöltése", + "generate_links_error": "Az egyszer használatos hivatkozásokat nem sikerült előállítani. Dolgozzon közvetlenül az API-val.", + "multi_use_link": "Többször használatos hivatkozás", + "multi_use_link_description": "Több válasz összegyűjtése névtelen válaszadóktól egyetlen hivatkozással.", + "multi_use_powers_other_channels_description": "Ha ezt letiltja, akkor ezek a többi terjesztési csatornák is letiltásra kerülnek.", + "multi_use_powers_other_channels_title": "Ez a hivatkozás működteti a weboldalakba való beágyazást, az e-mailekbe való beágyazást, a közösségi médiában való megosztást és a QR-kódokat.", + "nav_title": "Névtelen hivatkozások", + "number_of_links_label": "Hivatkozások száma (1-5000)", + "single_use_link": "Egyszer használatos hivatkozások", + "single_use_link_description": "Kérdőív-hivatkozásonként csak egy válasz engedélyezése.", + "single_use_links": "Egyszer használatos hivatkozások", + "source_tracking": "Forrás követése", + "url_encryption_description": "Csak akkor tiltsa le, ha egyéni egyszer használatos azonosítót kell beállítania.", + "url_encryption_label": "Egyszer használatos azonosító URL-titkosítása" + }, + "custom_html": { + "add_mode_description": "A kérdőív-parancsfájlok a munkaterület-szintű parancsfájlok mellett futnak.", + "add_to_workspace": "Hozzáadás a munkaterület-parancsfájlokhoz", + "description": "Követő parancsfájlok és képpontok hozzáadása ehhez kérdőívhez", + "nav_title": "Egyéni HTML", + "no_workspace_scripts": "Nincsenek munkaterület-szintű parancsfájlok beállítva. A Munkaterület → Általános menüpontban adhatja hozzá azokat.", + "placeholder": "\n", + "replace_mode_description": "Csak a kérdőív-parancsfájlok fognak lefutni. A munkaterület-parancsfájlok figyelmen kívül lesznek hagyva. Tartsa üresen, hogy ne töltsön be semmilyen parancsfájlt.", + "replace_workspace": "Munkaterület-parancsfájlok cseréje", + "saved_successfully": "Az egyéni parancsfájlok sikeres elmentve", + "script_mode": "Parancsfájl módja", + "security_warning": "A parancsfájlok teljes böngésző-hozzáféréssel kerülnek végrehajtásra. Csak megbízható forrásokból származó parancsfájlokat adjon hozzá.", + "survey_scripts_description": "Egyéni HTML hozzáadása ezen kérdőívoldal részébe való beszúrásához.", + "survey_scripts_label": "Kérdőívre jellemző parancsfájlok", + "workspace_scripts_label": "Munkaterület-parancsfájlok (örökölt)" + }, + "dynamic_popup": { + "alert_button": "Kérdőív szerkesztése", + "alert_description": "Ez a kérdőív jelenleg olyan hivatkozás-kérdőívként van beállítva, amely nem támogatja a dinamikus felugró ablakokat. Ezt a kérdőívszerkesztő beállítások lapján változtathatja meg.", + "alert_title": "Kérdőív típusának megváltoztatása alkalmazáson belülire", + "attribute_based_targeting": "Attribútumalapú célzás", + "code_no_code_triggers": "Kód és kód nélküli aktiválók", + "description": "A Formbricks kérdőívei beágyazhatók felugró ablakként a felhasználói interakció alapján.", + "nav_title": "Dinamikus (felugró)", + "recontact_options": "Újbóli kapcsolatfelvételi beállítások" + }, + "embed_on_website": { + "description": "A Formbricks kérdőívei beágyazhatók statikus elemként.", + "embed_code_copied_to_clipboard": "A beágyazási kód a vágólapra másolva!", + "embed_mode": "Beágyazási mód", + "embed_mode_description": "A kérdőív beágyazása minimalista kialakítással, térközök és háttér elhagyásával.", + "nav_title": "Webhelybe beágyazott" + }, + "link_settings": { + "description": "Egyéni cím, leírás és kép adása a hivatkozásokhoz a nyilvános megosztáshoz.", + "language_help_text": "A metaadatok az URL-ben lévő „lang” értéke alapján kerülnek betöltésre.", + "link_description": "Hivatkozás leírása", + "link_description_description": "Az 55-200 karakter közötti leírások működnek a legjobban.", + "link_title": "Hivatkozás címe", + "link_title_description": "A rövid címek működnek a legjobban metacímekként.", + "preview_image": "Előnézeti kép", + "preview_image_description": "A kis fájlméretű (4 MB-nál kisebb) fekvő képek működnek a legjobban.", + "title": "Hivatkozás beállításai" + }, + "personal_links": { + "create_and_manage_segments": "Szakaszok létrehozása és kezelése a Partnerek > Szakaszok menüpontban", + "description": "Személyes hivatkozások előállítása egy szakaszhoz és a kérdőívre adott válaszok párosítása az egyes partnerekkel.", + "error_generating_links": "Nem sikerült előállítani a hivatkozásokat, próbálja meg újra", + "expiry_date_description": "Ha a hivatkozás lejár, a címzett többé nem tud válaszolni a kérdőívre.", + "expiry_date_optional": "Lejárati dátum (elhagyható)", + "generate_and_download_links": "Hivatkozások előállítása és letöltése", + "generating_links": "Hivatkozások előállítása", + "generating_links_toast": "Hivatkozások előállítása, a letöltés hamarosan elindul…", + "links_generated_success_toast": "A hivatkozások sikeresen előállítva, a letöltés hamarosan elindul.", + "nav_title": "Személyes hivatkozások", + "no_segments_available": "Nem érhetők el szakaszok", + "select_segment": "Szakasz kiválasztása", + "upgrade_prompt_description": "Személyes hivatkozások előállítása egy szakaszhoz és a kérdőívre adott válaszok hozzákapcsolása az egyes partnerekhez.", + "upgrade_prompt_title": "Személyes hivatkozások használata egy magasabb csomaggal", + "work_with_segments": "A személyes hivatkozások szakaszokkal működnek." + }, + "pretty_url": { + "description": "Egyéni, megjegyezhető URL létrehozása a kérdőívhez", + "remove_description": "A kérdőív továbbra is elérhető lesz az eredeti URL-en keresztül.", + "remove_success": "A csinos URL sikeresen eltávolítva", + "save_success": "A csinos URL sikeresen elmentve", + "slug_help": "Csak ékezet nélküli kisbetűket, számokat és kötőjeleket használjon.", + "slug_label": "Egyéni keresőbarát URL", + "slug_placeholder": "ugyfel-visszajelzes", + "slug_required": "Adjon meg egy érvényes keresőbarát URL-t", + "title": "Csinos URL" + }, + "send_email": { + "copy_embed_code": "Beágyazási kód másolása", + "description": "A kérdőív beágyazása egy e-mailbe, hogy válaszokat kapjon a közönségétől.", + "email_preview_tab": "E-mail előnézete", + "email_sent": "E-mail elküldve!", + "email_subject_label": "Tárgy", + "email_to_label": "Címzett", + "embed_code_copied_to_clipboard": "A beágyazási kód a vágólapra másolva!", + "embed_code_copied_to_clipboard_failed": "A másolás nem sikerült, próbálja meg újra", + "embed_code_tab": "Beágyazási kód", + "formbricks_email_survey_preview": "Formbricks e-mail-kérdőív előnézet", + "nav_title": "Beágyazott e-mail", + "send_preview": "Előnézet küldése", + "send_preview_email": "Előnézeti e-mail küldése" + }, + "share_settings_title": "Megosztási beállítások", + "share_view_title": "Megosztás ezzel", + "social_media": { + "description": "Válaszok beszerzése a partnerektől különböző közösségi média hálózatokon.", + "source_tracking_enabled": "Forráskövetés engedélyezve", + "source_tracking_enabled_alert_description": "Ha ebből a párbeszédablakból osztja meg, akkor a közösségi média hálózata hozzá lesz fűzve a kérdőív hivatkozásához, így tudni fogja, hogy melyik válasz melyik hálózaton keresztül érkezett.", + "title": "Közösségi média" + } + }, + "summary": { + "added_filter_for_responses_where_answer_to_question": "Szűrő hozzáadva azokhoz a válaszokhoz, ahol a(z) {questionIdx}. kérdésre adott válasz {filterComboBoxValue} - {filterValue} ", + "added_filter_for_responses_where_answer_to_question_is_skipped": "Szűrő hozzáadva azokhoz a válaszokhoz, ahol a(z) {questionIdx}. kérdésre adott válasz kihagyva", + "aggregated": "Összesített", + "all_responses_csv": "Összes válasz (CSV)", + "all_responses_excel": "Összes válasz (Excel)", + "all_time": "Összes idő", + "almost_there": "Már majdnem kész! Telepítse a felületi elemet a válaszok fogadásának megkezdéséhez.", + "average": "Átlag", + "completed": "Befejezve", + "completed_tooltip": "A kérdőív kitöltési alkalmainak száma.", + "configure_alerts": "Riasztások beállítása", + "congrats": "Gratulálunk! A kérdőíve élő.", + "connect_your_website_or_app_with_formbricks_to_get_started": "A webhelye vagy alkalmazása csatlakoztatása a Formbrickshez a kezdéshez.", + "current_count": "Jelenlegi darabszám", + "custom_range": "Egyéni tartomány…", + "delete_all_existing_responses_and_displays": "Az összes meglévő válasz és megjelenítés törlése", + "download_qr_code": "QR-kód letöltése", + "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)", + "filtered_responses_excel": "Szűrt válaszok (Excel)", + "generating_qr_code": "QR-kód előállítása", + "impressions": "Benyomások", + "impressions_tooltip": "A kérdőív megtekintési alkalmainak száma.", + "in_app": { + "connection_description": "A kérdőív a webhelye azon felhasználóinak lesz megjelenítve, akik megfelelnek az alább felsorolt feltételeknek", + "connection_title": "A Formbricks SDK csatlakoztatva van", + "description": "A Formbricks kérdőívei beágyazhatók felugró ablakként a felhasználói interakció alapján.", + "display_criteria": "Megjelenítési feltételek", + "display_criteria.audience_description": "Célközönség", + "display_criteria.code_trigger": "Kódművelet", + "display_criteria.everyone": "Mindenki", + "display_criteria.no_code_trigger": "Kód nélkül", + "display_criteria.overwritten": "Felülírva", + "display_criteria.randomizer": "{percentage}% véletlenszerűsítő", + "display_criteria.randomizer_description": "Csak a műveletet végrehajtó személyek {percentage}%-a kaphatja meg a kérdőívet.", + "display_criteria.recontact_description": "Újbóli kapcsolatfelvételi beállítások", + "display_criteria.targeted": "Célzott", + "display_criteria.time_based_always": "Mindig jelenjen meg a kérdőív", + "display_criteria.time_based_day": "Nap", + "display_criteria.time_based_days": "Napok", + "display_criteria.time_based_description": "Globális várakozási idő", + "display_criteria.trigger_description": "Kérdőív aktiválója", + "documentation_title": "Elfogási kérdőívek terjesztése az összes platformon", + "html_embed": "HTML-beágyazás a szakaszban", + "ios_sdk": "iOS SDK az Apple-alkalmazásokhoz", + "javascript_sdk": "JavaScript SDK", + "kotlin_sdk": "Kotlin SDK az Android-alkalmazásokhoz", + "no_connection_description": "A webhelye vagy alkalmazása csatlakoztatása a Formbrickshez az elfogási kérdőívek közzétételéhez.", + "no_connection_title": "Még nincs csatlakoztatva!", + "react_native_sdk": "React Native SDK az RN-alkalmazásokhoz.", + "title": "Elfogási kérdőív beállításai" + }, + "includes_all": "Tartalmazza az összeset", + "includes_either": "Tartalmazza vagy", + "individual": "Egyéni", + "is_equal_to": "Egyenlő ezzel", + "is_less_than": "Kisebb mint", + "last_30_days": "Elmúlt 30 nap", + "last_6_months": "Elmúlt 6 hónap", + "last_7_days": "Elmúlt 7 nap", + "last_month": "Elmúlt hónap", + "last_quarter": "Elmúlt negyedév", + "last_year": "Elmúlt év", + "limit": "Korlát", + "no_responses_found": "Nem találhatók válaszok", + "other_values_found": "Más értékek találhatók", + "overall": "Összesen", + "promoters": "Népszerűsítők", + "qr_code": "QR-kód", + "qr_code_description": "A QR-kódon keresztül összegyűjtött válaszok névtelenek.", + "qr_code_download_failed": "A QR-kód letöltése nem sikerült", + "qr_code_download_with_start_soon": "A QR-kód letöltése hamarosan elindul", + "qr_code_generation_failed": "Probléma történt a kérdőív QR-kódjának betöltésekor. Próbálja meg újra.", + "quotas_completed": "Teljesített kvóták", + "quotas_completed_tooltip": "A válaszadók által teljesített kvóták száma.", + "reset_survey": "Kérdőív visszaállítása", + "reset_survey_warning": "Egy kérdőív visszaállítása eltávolítja a kérdőívhez hozzárendelt összes választ és megjelenítést. Ezt nem lehet visszavonni.", + "satisfied": "Elégedett", + "selected_responses_csv": "Kijelölt válaszok (CSV)", + "selected_responses_excel": "Kijelölt válaszok (Excel)", + "setup_integrations": "Integrációk beállítása", + "share_survey": "Kérdőív megosztása", + "show_all_responses_that_match": "Az összes illeszkedő válasz megtekintése", + "starts": "Elkezdések", + "starts_tooltip": "A kérdőív elkezdési alkalmainak száma.", + "survey_reset_successfully": "A kérdőív sikeresen visszaállítva. {responseCount} válasz és {displayCount} megjelenítés lett törölve.", + "this_month": "Ez a hónap", + "this_quarter": "Ez a negyedév", + "this_year": "Ez az év", + "time_to_complete": "Kitöltéshez szükséges idő", + "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", + "whats_next": "Mi következik?", + "your_survey_is_public": "A kérdőíve nyilvános", + "youre_not_plugged_in_yet": "Még nincs csatlakoztatva!" + }, + "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", + "all_roles": "Összes szerep", + "create_a_new_survey": "Új kérdőív létrehozása", + "multiple_industries": "Több iparág", + "use_this_template": "A sablon használata", + "uses_branching_logic": "Ez a kérdőív elágazási logikát használ." + } + }, + "workspace": { + "api_keys": { + "add_api_key": "API-kulcs hozzáadása", + "api_key": "API-kulcs", + "api_key_copied_to_clipboard": "Az API-kulcs a vágólapra másolva", + "api_key_created": "API-kulcs létrehozva", + "api_key_deleted": "API-kulcs törölve", + "api_key_label": "API-kulcs címkéje", + "api_key_security_warning": "Biztonsági okokból az API-kulcs csak egyszer jelenik meg a létrehozás után. Másolja át azonnal a kívánt helyre.", + "api_key_updated": "API-kulcs frissítve", + "delete_api_key_confirmation": "Az ezt a kulcsot használó bármely alkalmazás többé nem fog tudni hozzáférni a Formbricks adataihoz.", + "duplicate_access": "A kettőzött munkaterület-hozzáférés nem engedélyezett", + "no_api_keys_yet": "Még nincs semmilyen API-kulcsa", + "no_env_permissions_found": "Nem találhatók környezetjogosultságok", + "organization_access": "Szervezeti hozzáférés", + "organization_access_description": "Olvasási vagy írási jogosultságok kiválasztása a teljes szervezetre vonatkozó erőforrásokhoz.", + "permissions": "Jogosultságok", + "secret": "Titok", + "unable_to_delete_api_key": "Nem lehet törölni az API-kulcsot", + "workspace_access": "Munkaterület-hozzáférés" + }, + "app-connection": { + "app_connection": "Alkalmazáskapcsolat", + "app_connection_description": "Alkalmazás vagy webhely csatlakoztatása a Formbrickshez.", + "cache_update_delay_description": "Ha frissítéseket hajt végre a kérdőíveken, partnereken, műveleteken vagy egyéb adatokon, akkor akár 1 percet is igénybe vehet, mire azok a változtatások megjelennek a Formbricks SDK-t futtató helyi alkalmazásban.", + "cache_update_delay_title": "A változtatások körülbelül 1 perc múlva jelennek meg a gyorsítótárazás miatt", + "environment_id": "Az Ön környezeti azonosítója", + "environment_id_description": "Ez az azonosító egyedileg azonosítja ezt a Formbricks környezetet.", + "formbricks_sdk_connected": "A Formbricks SDK csatlakoztatva van", + "formbricks_sdk_not_connected": "A Formbricks SDK még nincs csatlakoztatva.", + "formbricks_sdk_not_connected_description": "Adja hozzá a Formbricks SDK-t a webhelyéhez vagy az alkalmazásához, hogy összekapcsolja azt a Formbricks platformmal", + "how_to_setup": "Hogyan kell beállítani", + "how_to_setup_description": "Kövesse ezeket a lépéseket a Formbricks felületi elemének az alkalmazásán belüli beállításához.", + "receiving_data": "Adatok fogadása 💃🕺", + "recheck": "Újraellenőrzés", + "sdk_connection_details": "SDK-kapcsolat részletei", + "sdk_connection_details_description": "Az Ön egyedi környezeti azonosítója és SDK-kapcsolati URL-e a Formbricks alkalmazás és az Ön alkalmazásának integrálásához.", + "setup_alert_description": "Kövesse ezt a léptékenkénti oktatóanyagot, hogy 5 perc alatt csatlakoztassa az alkalmazását vagy a webhelyét.", + "setup_alert_title": "Hogyan kell kapcsolódni", + "webapp_url": "SDK-kapcsolati URL" + }, + "general": { + "cannot_delete_only_workspace": "Ez az egyetlen munkaterülete, nem lehet törölni. Először hozzon létre egy új munkaterületet.", + "custom_scripts": "Egyéni parancsfájlok", + "custom_scripts_card_description": "Követő parancsfájlok és képpontok hozzáadása a munkaterületen lévő összes hivatkozás-kérdőívhez.", + "custom_scripts_description": "A parancsfájlok az összes hivatkozáskérdőív-oldal részébe beszúrásra kerülnek.", + "custom_scripts_label": "HTML parancsfájlok", + "custom_scripts_placeholder": "\n", + "custom_scripts_updated_successfully": "Az egyéni parancsfájlok sikeres frissítve", + "custom_scripts_warning": "A parancsfájlok teljes böngésző-hozzáféréssel kerülnek végrehajtásra. Csak megbízható forrásokból származó parancsfájlokat adjon hozzá.", + "delete_workspace": "Munkaterület törlése", + "delete_workspace_confirmation": "Biztosan törölni szeretné a(z) {projectName} munkaterületet? Ezt a műveletet nem lehet visszavonni.", + "delete_workspace_name_includes_surveys_responses_people_and_more": "A(z) {projectName} munkaterület törlése, beleértve az összes kérdőívet, választ, személyt, műveletet és attribútumot is.", + "delete_workspace_settings_description": "A munkaterület törlése az összes kérdőívvel, válasszal, személlyel, művelettel és attribútummal együtt. Ezt nem lehet visszavonni.", + "error_saving_workspace_information": "Hiba a munkaterület-információk mentésekor", + "only_owners_or_managers_can_delete_workspaces": "Csak tulajdonosok vagy kezelők törölhetnek munkaterületeket", + "recontact_waiting_time": "Várakozási időszak (kérdőívek között)", + "recontact_waiting_time_settings_description": "Annak vezérlése, hogy a felhasználók milyen gyakran kérdezhetők meg a munkaterületen lévő összes weboldal- és alkalmazás-kérdőívben.", + "this_action_cannot_be_undone": "Ezt a műveletet nem lehet visszavonni.", + "wait_x_days_before_showing_next_survey": "Várakozás X napot a következő kérdőív megjelenése előtt:", + "waiting_period_updated_successfully": "A várakozási időszak sikeresen frissítve", + "whats_your_workspace_called": "Hogy hívják a munkaterületét?", + "workspace_deleted_successfully": "A munkaterület sikeresen törölve", + "workspace_name_settings_description": "A munkaterület nevének megváltoztatása.", + "workspace_name_updated_successfully": "A munkaterületnév sikeresen frissítve" + }, + "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ó)", + "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.", + "delete_language_confirmation": "Biztosan törölni szeretné ezt a nyelvet? Ezt a műveletet nem lehet visszavonni.", + "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", + "multi_language_surveys": "Többnyelvű kérdőívek", + "multi_language_surveys_description": "Nyelvek hozzáadása többnyelvű kérdőívek létrehozásához.", + "no_language_found": "Nem található nyelv. Adja hozzá az első nyelvet lent.", + "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" + }, + "look": { + "add_background_color": "Háttérszín hozzáadása", + "add_background_color_description": "Hátérszín hozzáadása a logó tárolódobozához.", + "advanced_styling_field_border_radius": "Szegély sugara", + "advanced_styling_field_button_bg": "Gomb háttere", + "advanced_styling_field_button_bg_description": "Kitölti a „Következő” és az „Elküldés” gombokat.", + "advanced_styling_field_button_border_radius_description": "Lekerekíti a gomb sarkait.", + "advanced_styling_field_button_font_size_description": "Átméretezi a gomb címkéjének szövegét.", + "advanced_styling_field_button_font_weight_description": "Vékonyabbá vagy vastagabbá teszi a gomb szövegét.", + "advanced_styling_field_button_height_description": "A gomb magasságát vezérli.", + "advanced_styling_field_button_padding_x_description": "Térközt ad hozzá balra és jobbra.", + "advanced_styling_field_button_padding_y_description": "Térközt ad hozzá fent és lent.", + "advanced_styling_field_button_text": "Gomb szövege", + "advanced_styling_field_button_text_description": "Kiszínezi a gombokon belüli címkét.", + "advanced_styling_field_description_color": "Leírás színe", + "advanced_styling_field_description_color_description": "Kiszínezi az egyes címsorok alatti szöveget.", + "advanced_styling_field_description_size": "Leírás betűmérete", + "advanced_styling_field_description_size_description": "Átméretezi a leírás szövegét.", + "advanced_styling_field_description_weight": "Leírás betűvastagsága", + "advanced_styling_field_description_weight_description": "Vékonyabbá vagy vastagabbá teszi a leírás szövegét.", + "advanced_styling_field_font_size": "Betűméret", + "advanced_styling_field_font_weight": "Betűvastagság", + "advanced_styling_field_headline_color": "Címsor színe", + "advanced_styling_field_headline_color_description": "Kiszínezi a fő kérdés szövegét.", + "advanced_styling_field_headline_size": "Címsor betűmérete", + "advanced_styling_field_headline_size_description": "Átméretezi a címsor szövegét.", + "advanced_styling_field_headline_weight": "Címsor betűvastagsága", + "advanced_styling_field_headline_weight_description": "Vékonyabbá vagy vastagabbá teszi a címsor szövegét.", + "advanced_styling_field_height": "Minimális magasság", + "advanced_styling_field_indicator_bg": "Jelző háttere", + "advanced_styling_field_indicator_bg_description": "Kiszínezi a sáv kitöltött részét.", + "advanced_styling_field_input_border_radius_description": "Lekerekíti a beviteli mező sarkait.", + "advanced_styling_field_input_font_size_description": "Átméretezi a beviteli mezőkbe beírt szöveget.", + "advanced_styling_field_input_height_description": "A beviteli mező minimális magasságát szabályozza.", + "advanced_styling_field_input_padding_x_description": "Térközt ad hozzá balra és jobbra.", + "advanced_styling_field_input_padding_y_description": "Térközt ad hozzá fent és lent.", + "advanced_styling_field_input_placeholder_opacity_description": "Elhalványítja a helykitöltő súgószöveget.", + "advanced_styling_field_input_shadow_description": "Vetett árnyékot ad hozzá a beviteli mezők köré.", + "advanced_styling_field_input_text": "Beviteli mező szövege", + "advanced_styling_field_input_text_description": "Kiszínezi a beviteli mezőkbe beírt szöveget.", + "advanced_styling_field_option_bg": "Háttér", + "advanced_styling_field_option_bg_description": "Kitölti a választási lehetőség elemeit.", + "advanced_styling_field_option_border_radius_description": "Lekerekíti a választási lehetőség sarkait.", + "advanced_styling_field_option_font_size_description": "Átméretezi a választási lehetőség címkéjének szövegét.", + "advanced_styling_field_option_label": "Címke színe", + "advanced_styling_field_option_label_description": "Kiszínezi a választási lehetőség címkéjének szövegét.", + "advanced_styling_field_option_padding_x_description": "Térközt ad hozzá balra és jobbra.", + "advanced_styling_field_option_padding_y_description": "Térközt ad hozzá fent és lent.", + "advanced_styling_field_padding_x": "X kitöltés", + "advanced_styling_field_padding_y": "Y kitöltés", + "advanced_styling_field_placeholder_opacity": "Helykitöltő átlátszatlansága", + "advanced_styling_field_shadow": "Árnyék", + "advanced_styling_field_track_bg": "Követés háttere", + "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_section_buttons": "Gombok", + "advanced_styling_section_headlines": "Címsorok és leírások", + "advanced_styling_section_inputs": "Beviteli mezők", + "advanced_styling_section_options": "Lehetőségek (rádiógomb vagy jelölőnégyzet)", + "app_survey_placement": "Alkalmazás-kérdőív elhelyezése", + "app_survey_placement_settings_description": "Annak megváltoztatása, hogy a kérdőívek hol jelennek meg a webalkalmazásban vagy a webhelyen.", + "email_customization": "E-mail személyre szabás", + "email_customization_description": "Azon e-mailek megjelenésének megváltoztatása, amelyeket a Formbricks az Ön nevében küld ki.", + "enable_custom_styling": "Egyéni stílus engedélyezése", + "enable_custom_styling_description": "Lehetővé tétel a felhasználóknak, hogy felülírják ezt a témát a kérdőív szerkesztőjében.", + "failed_to_remove_logo": "Nem sikerült eltávolítani a logót", + "failed_to_update_logo": "Nem sikerült frissíteni a logót", + "formbricks_branding": "Formbricks márkajel", + "formbricks_branding_hidden": "A Formbricks márkajel rejtve van.", + "formbricks_branding_settings_description": "Nagyra értékeljük a támogatását, de megértjük, ha kikapcsolja.", + "formbricks_branding_shown": "A Formbricks márkajel megjelenik.", + "generate_theme_btn": "Előállítás", + "generate_theme_confirmation": "Szeretne hozzáillő színtémát létrehozni a márkajel színei alapján? Ez felülírja a jelenlegi színbeállításokat.", + "generate_theme_header": "Előállítja a színtémát?", + "logo_removed_successfully": "A logó sikeresen eltávolítva", + "logo_settings_description": "Vállalati logo feltöltése a kérdőívek és hivatkozások előnézeteinek márkaépítéséhez.", + "logo_updated_successfully": "A logó sikeresen frissítve", + "logo_upload_failed": "A logó feltöltése nem sikerült. Próbálja meg újra.", + "placement_updated_successfully": "Az elhelyezés sikeresen frissítve", + "remove_branding_with_a_higher_plan": "Márkajel eltávolítása egy magasabb csomaggal", + "remove_logo": "Logó eltávolítása", + "remove_logo_confirmation": "Biztosan el szeretné távolítani a logót?", + "replace_logo": "Logó cseréje", + "reset_styling": "Stílus visszaállítása", + "reset_styling_confirmation": "Biztosan vissza szeretné állítani a stílust az alapbeállításra?", + "show_formbricks_branding_in": "Formbricks márkajel megjelenítése a(z) {type} kérdőívekben", + "show_powered_by_formbricks": "Az „A gépházban: Formbricks” aláírás megjelenítése", + "styling_updated_successfully": "A stílus sikeresen frissítve", + "suggest_colors": "Színek ajánlása", + "suggested_colors_applied_please_save": "A javasolt színek sikeresen generálva. Nyomd meg a \"Mentés\" gombot a változtatások véglegesítéséhez.", + "theme": "Téma", + "theme_settings_description": "Stílustéma létrehozása az összes kérdőívhez. Egyéni stílust engedélyezhet minden egyes kérdőívhez." + }, + "tags": { + "add": "Hozzáadás", + "add_tag": "Címke hozzáadása", + "count": "Szám", + "delete_tag_confirmation": "Biztosan törölni szeretné ezt a címkét?", + "manage_tags": "Címkék kezelése", + "manage_tags_description": "Válaszcímkék egyesítése és eltávolítása.", + "merge": "Egyesítés", + "no_tag_found": "Nem található címke", + "search_tags": "Címkék keresése…", + "tag": "Címke", + "tag_already_exists": "A címke már létezik", + "tag_deleted": "A címke sikeresen törölve", + "tag_updated": "A címke sikeresen frissítve", + "tags_merged": "A címkék sikeresen egyesítve" + }, + "teams": { + "manage_teams": "Csapatok kezelése", + "no_teams_found": "Nem találhatók csapatok", + "permission": "Jogosultság", + "team_name": "Csapat neve", + "team_settings_description": "Annak megtekintése, hogy mely csapatok férhetnek hozzá ehhez a munkaterülethez." + } + }, + "xm-templates": { + "ces": "CES", + "ces_description": "Minden érintkezési pont kihasználása az ügyfelekkel való interakció egyszerűségének megértéséhez.", + "csat": "CSAT", + "csat_description": "Legjobb gyakorlatok megvalósítása az ügyfél-elégedettség méréséhez.", + "enps": "eNPS", + "enps_description": "Általános visszajelzés a munkavállalók elkötelezettségének és elégedettségének megértéséhez.", + "five_star_rating": "5 csillagos értékelés", + "five_star_rating_description": "Általános visszajelzési megoldás az általános elégedettség méréséhez.", + "headline": "Milyen jellegű visszajelzést szeretne kapni?", + "nps": "NPS", + "nps_description": "Bizonyított legjobb gyakorlatok megvalósítása annak megértéséhez, hogy a személyek MIÉRT vásárolnak.", + "smileys": "Hangulatjelek", + "smileys_description": "Látható jelzők használata a visszajelzések rögzítéséhez az ügyfelek érintkezési pontjain keresztül." + } + }, + "organizations": { + "landing": { + "no_workspaces_warning_subtitle": "Vegye fel a kapcsolatot a szervezet tulajdonosával, hogy hozzáférést kapjon a munkaterületekhez. Vagy hozzon létre saját szervezetet a kezdéshez.", + "no_workspaces_warning_title": "A fiókja még nem rendelkezik hozzáféréssel egyetlen munkaterülethez sem." + }, + "workspaces": { + "new": { + "channel": { + "channel_select_subtitle": "Hivatkozás megosztása vagy a kérdőív megjelenítése az alkalmazásokban vagy webhelyeken.", + "channel_select_title": "Milyen típusú kérdőívekre van szüksége?", + "in_product_surveys": "Terméken belüli kérdőívek", + "in_product_surveys_description": "Beágyazva alkalmazásokba vagy webhelyekre.", + "link_and_email_surveys": "Hivatkozás- és e-mail-kérdőívek", + "link_and_email_surveys_description": "Személyek elérése bárhol az interneten." + }, + "mode": { + "formbricks_cx": "Formbricks CX", + "formbricks_cx_description": "Kérdőívek és jelentések annak megértéséhez, hogy mire van szükségük az ügyfeleknek.", + "formbricks_surveys": "Formbricks kérőívek", + "formbricks_surveys_description": "Többcélú kérdőíves platform web-, alkalmazás- és e-mail-kérdőívekhez.", + "what_are_you_here_for": "Miért van itt?" + }, + "settings": { + "brand_color": "Márkajel színe", + "brand_color_description": "A kérdőívek fő színének hozzáigazítása a márkajelhez.", + "create_new_team": "Új csapat létrehozása", + "team_description": "Kik férhetnek hozzá ehhez a munkaterülethez?", + "workspace_creation_failed": "A munkaterület létrehozása nem sikerült", + "workspace_name": "Termék neve", + "workspace_name_description": "Hogy hívják a termékét?", + "workspace_settings_subtitle": "Ha a személyek felismerik a márkáját, akkor sokkal inkább hajlandóak lesznek elkezdeni és befejezni a válaszokat.", + "workspace_settings_title": "Tudassa a válaszadókkal, hogy Ön az" + } + } + } + }, + "s": { + "check_inbox_or_spam": "Nézze meg a levélszemét mappát is, ha nem találja az e-mailt a beérkező levelek között.", + "completed": "Ez a kérdőív le van zárva.", + "create_your_own": "Saját nyílt forráskódú kérdőív létrehozása", + "enter_pin": "Ez a kérdőív védett. Adja meg a PIN-kódot lent.", + "just_curious": "Csak kíváncsi?", + "link_invalid": "Ez a kérdőív csak meghívás útján tölthető ki.", + "paused": "Ez a kérdőív átmenetileg szüneteltetve van.", + "please_try_again_with_the_original_link": "Próbálja meg újra az eredeti hivatkozással", + "preview_survey_questions": "Kérdőív kérdéseinek előnézete.", + "question_preview": "Kérdés előnézete", + "response_already_received": "Már kaptunk választ erről az e-mail-címről.", + "response_submitted": "Ehhez a kérdőívhez és partnerhez kapcsolódó válasz már létezik", + "survey_already_answered_heading": "A kérdőív már meg lett válaszolva.", + "survey_already_answered_subheading": "Ezt a hivatkozást csak egyszer használhatja.", + "survey_sent_to": "A kérdőív elküldve a(z) {email} e-mail-címre", + "this_looks_fishy": "Ez gyanúsnak tűnik.", + "verify_email": "E-mail-cím ellenőrzése.", + "verify_email_before_submission": "Ellenőrizze az e-mail-címét a válaszadáshoz", + "verify_email_before_submission_button": "Ellenőrzés", + "verify_email_before_submission_description": "A kérdőívre való válaszadáshoz ellenőrizze az e-mail-címét", + "want_to_respond": "Szeretne válaszolni?" + }, + "setup": { + "intro": { + "get_started": "Kezdjen hozzá", + "made_with_love_in_kiel": "Sok 🤍 felhasználásával készült Németországban", + "paragraph_1": "A Formbricks egy élménykezelő alkalmazáscsomag, amely a világ leggyorsabban növekvő, nyílt forráskódú kérdőívplatformjára épül.", + "paragraph_2": "Futtasson célzott kérdőíveket a webhelyeken, alkalmazásokban vagy bárhol az interneten. Gyűjtsön értékes tapasztalatokat, hogy ellenállhatatlan élményeket teremtsen ügyfelei, felhasználói és munkavállalói számára.", + "paragraph_3": "Elkötelezettek vagyunk az adatvédelem legmagasabb foka iránt. Üzemeltesse saját kiszolgálóján, hogy teljes ellenőrzéssel rendelkezzen az adatai felett.", + "welcome_to_formbricks": "Üdvözli a Formbricks!" + }, + "invite": { + "add_another_member": "Másik tag hozzáadása", + "continue": "Folytatás", + "failed_to_invite": "Nem sikerült meghívni", + "invitation_sent_to": "A meghívó elküldve ide:", + "invite_your_organization_members": "Szervezeti tagok meghívása", + "life_s_no_fun_alone": "Az élet nem szórakoztató egyedül.", + "skip": "Kihagyás", + "smtp_not_configured": "Az SMTP nincs beállítva", + "smtp_not_configured_description": "Jelenleg nem lehet meghívókat küldeni, mert az e-mail-szolgáltatás nincs beállítva. A meghívási hivatkozást később lemásolhatja a szervezet beállításaiból." + }, + "organization": { + "create": { + "continue": "Folytatás", + "delete_account": "Fiók törlése", + "delete_account_description": "Ha törölni szeretné a fiókját, akkor azt a lenti gombra kattintva teheti meg.", + "description": "Tegye a sajátjává.", + "no_membership_found": "Nem található tagság!", + "no_membership_found_description": "Ön jelenleg nem tagja egyetlen szervezetnek sem. Ha úgy gondolja, hogy ez tévedés, akkor vegye fel a kapcsolatot a szervezet tulajdonosával.", + "title": "A szervezet beállítása" + } + }, + "signup": { + "create_administrator": "Adminisztrátor létrehozása", + "this_user_has_all_the_power": "Ez a felhasználó rendelkezik az összes jogosultsággal." + } + }, + "templates": { + "address": "Cím", + "address_description": "Levelezési cím kérése", + "alignment_and_engagement_survey_description": "A munkavállalók összhangjának mérése a vállalat jövőképével, stratégiájával és kommunikációjával, valamint a csapatmunkával kapcsolatban.", + "alignment_and_engagement_survey_name": "A vállalat jövőképével való összhang és elkötelezettség", + "alignment_and_engagement_survey_question_1_headline": "Megértem, hogy a szerepem hogyan járul hozzá a vállalat általános stratégiájához.", + "alignment_and_engagement_survey_question_1_lower_label": "Nincs megértés", + "alignment_and_engagement_survey_question_1_upper_label": "Teljes megértés", + "alignment_and_engagement_survey_question_2_headline": "Úgy érzem, hogy az értékeim összhangban vannak a vállalat küldetésével és kultúrájával.", + "alignment_and_engagement_survey_question_2_lower_label": "Nincs összhang", + "alignment_and_engagement_survey_question_3_headline": "Hatékonyan együttműködök a csapatommal a céljaink elérése érdekében.", + "alignment_and_engagement_survey_question_3_lower_label": "Gyenge együttműködés", + "alignment_and_engagement_survey_question_3_upper_label": "Kitűnő együttműködés", + "alignment_and_engagement_survey_question_4_headline": "Hogyan tudná javítani a vállalat a jövőképe és stratégiája összehangolását?", + "alignment_and_engagement_survey_question_4_placeholder": "Írja be ide a válaszát…", + "back": "Vissza", + "book_interview": "Interjú foglalása", + "build_product_roadmap_description": "A felhasználók által leginkább igényelt EGY dolog azonosítása és összeállítása.", + "build_product_roadmap_name": "Termékútiterv összeállítása", + "build_product_roadmap_question_1_headline": "Mennyire elégedett a(z) $[projectName] funkcióival és működésével?", + "build_product_roadmap_question_1_lower_label": "Egyáltalán nem elégedett", + "build_product_roadmap_question_1_upper_label": "Rendkívül elégedett", + "build_product_roadmap_question_2_headline": "Mi az az EGY változtatás, amellyel leginkább javíthatnánk a(z) $[projectName] élményét?", + "build_product_roadmap_question_2_placeholder": "Írja be ide a válaszát…", + "card_abandonment_survey": "Kosárelhagyási kérdőív", + "card_abandonment_survey_description": "A webáruházában való kosárelhagyás mögötti okok megértése.", + "card_abandonment_survey_question_1_button_label": "Persze!", + "card_abandonment_survey_question_1_headline": "Van 2 perce, hogy segítsen nekünk fejlődni?", + "card_abandonment_survey_question_1_html": "

    Észrevettük, hogy néhány terméket a kosárban hagyott. Szeretnénk megérteni, hogy miért.

    ", + "card_abandonment_survey_question_2_choice_1": "Magas szállítási költségek", + "card_abandonment_survey_question_2_choice_2": "Jobb árat találtam máshol", + "card_abandonment_survey_question_2_choice_3": "Csak böngészek", + "card_abandonment_survey_question_2_choice_4": "Úgy döntöttem, hogy nem vásárolok", + "card_abandonment_survey_question_2_choice_5": "Fizetési problémák", + "card_abandonment_survey_question_2_choice_6": "Egyéb", + "card_abandonment_survey_question_2_headline": "Mi volt az elsődleges oka annak, hogy nem fejezte be a vásárlást?", + "card_abandonment_survey_question_2_subheader": "Válassza ki a következő lehetőségek egyikét:", + "card_abandonment_survey_question_3_headline": "Fejtse ki bővebben az okát, hogy miért nem fejezte be a vásárlást:", + "card_abandonment_survey_question_4_headline": "Hogyan értékelné az összesített vásárlási élményét?", + "card_abandonment_survey_question_4_lower_label": "Nagyon elégedetlen", + "card_abandonment_survey_question_4_upper_label": "Nagyon elégedett", + "card_abandonment_survey_question_5_choice_1": "Alacsonyabb szállítási költségek", + "card_abandonment_survey_question_5_choice_2": "Kedvezmények vagy akciók", + "card_abandonment_survey_question_5_choice_3": "További fizetési lehetőségek", + "card_abandonment_survey_question_5_choice_4": "Jobb termékleírások", + "card_abandonment_survey_question_5_choice_5": "Továbbfejlesztett webhely-navigáció", + "card_abandonment_survey_question_5_choice_6": "Egyéb", + "card_abandonment_survey_question_5_headline": "Milyen tényezők ösztönöznék arra, hogy befejezze a vásárlást a jövőben?", + "card_abandonment_survey_question_5_subheader": "Válassza ki az összes megfelelőt:", + "card_abandonment_survey_question_6_headline": "Szeretne kedvezménykódot kapni e-mailben?", + "card_abandonment_survey_question_6_label": "Igen, vegyék fel velem a kapcsolatot.", + "card_abandonment_survey_question_7_headline": "Adja meg az e-mail-címét:", + "card_abandonment_survey_question_8_headline": "Van további megjegyzése vagy javaslata?", + "career_development_survey_description": "A munkavállalói elégedettség értékelése a karrierépítési és fejlődési lehetőségekkel kapcsolatban.", + "career_development_survey_name": "Karrierépítési kérdőív", + "career_development_survey_question_1_headline": "Elégedett vagyok a személyes és szakmai fejlődés lehetőségeivel a(z) $[projectName] projektnél.", + "career_development_survey_question_1_lower_label": "Egyáltalán nem értek egyet", + "career_development_survey_question_1_upper_label": "Teljesen egyetértek", + "career_development_survey_question_2_headline": "Elégedett vagyok a számomra elérhető karrierépítési lehetőségekkel a(z) $[projectName] projektnél.", + "career_development_survey_question_2_lower_label": "Egyáltalán nem értek egyet", + "career_development_survey_question_2_upper_label": "Teljesen egyetértek", + "career_development_survey_question_3_headline": "Elégedett vagyok a szervezetem által kínált munkához kapcsolódó képzésekkel.", + "career_development_survey_question_3_lower_label": "Egyáltalán nem értek egyet", + "career_development_survey_question_3_upper_label": "Teljesen egyetértek", + "career_development_survey_question_4_headline": "Elégedett vagyok a szervezetem által a képzésbe és oktatásba fektetett beruházásokkal.", + "career_development_survey_question_4_lower_label": "Egyáltalán nem értek egyet", + "career_development_survey_question_4_upper_label": "Teljesen egyetértek", + "career_development_survey_question_5_choice_1": "Termékfejlesztés", + "career_development_survey_question_5_choice_2": "Marketing", + "career_development_survey_question_5_choice_3": "Közönségkapcsolatok", + "career_development_survey_question_5_choice_4": "Számvitel", + "career_development_survey_question_5_choice_5": "Üzemeltetés", + "career_development_survey_question_5_choice_6": "Egyéb", + "career_development_survey_question_5_headline": "Milyen funkcióban dolgozik?", + "career_development_survey_question_5_subheader": "Válassza ki a következő lehetőségek egyikét:", + "career_development_survey_question_6_choice_1": "Egyéni közreműködő", + "career_development_survey_question_6_choice_2": "Igazgató", + "career_development_survey_question_6_choice_3": "Vezető igazgató", + "career_development_survey_question_6_choice_4": "Alelnök", + "career_development_survey_question_6_choice_5": "Igazgató", + "career_development_survey_question_6_choice_6": "Egyéb", + "career_development_survey_question_6_headline": "Az alábbiak közül melyik írja le legjobban a jelenlegi munkája szintjét?", + "career_development_survey_question_6_subheader": "Válassza ki a következő lehetőségek egyikét:", + "cess_survey_name": "Ügyfél-erőfeszítési pontszám kérdőív", + "cess_survey_question_1_headline": "A(z) $[projectName] megkönnyíti számomra a [CÉL HOZZÁADÁSA] tevékenységet", + "cess_survey_question_1_lower_label": "Egyáltalán nem értek egyet", + "cess_survey_question_1_upper_label": "Teljesen egyetértek", + "cess_survey_question_2_headline": "Köszönjük! Hogyan tudnánk megkönnyíteni Önnek a [CÉL HOZZÁADÁSA] tevékenységet?", + "cess_survey_question_2_placeholder": "Írja be ide a válaszát…", + "changing_subscription_experience_description": "Annak kiderítése, hogy mi jár az emberek fejében, amikor megváltoztatják az előfizetésüket.", + "changing_subscription_experience_name": "Előfizetés-megváltoztatási élmény", + "changing_subscription_experience_question_1_choice_1": "Rendkívül nehéz", + "changing_subscription_experience_question_1_choice_2": "Eltartott egy ideig, de végül sikerült", + "changing_subscription_experience_question_1_choice_3": "Minden rendben volt", + "changing_subscription_experience_question_1_choice_4": "Elég egyszerű", + "changing_subscription_experience_question_1_choice_5": "Nagyon egyszerű, imádom!", + "changing_subscription_experience_question_1_headline": "Mennyire volt egyszerű megváltoztatni a csomagot?", + "changing_subscription_experience_question_2_choice_1": "Igen, nagyon világos.", + "changing_subscription_experience_question_2_choice_2": "Először összezavarodtam, de megtaláltam, amire szükségem volt.", + "changing_subscription_experience_question_2_choice_3": "Elég bonyolult.", + "changing_subscription_experience_question_2_headline": "Az árazási információk könnyen érthetőek?", + "churn_survey": "Lemorzsolódási kérdőív", + "churn_survey_description": "Annak kiderítése, hogy az emberek miért mondják le az előfizetésüket. Ezek a tapasztalatok aranyat érnek!", + "churn_survey_question_1_choice_1": "Nehéz használni", + "churn_survey_question_1_choice_2": "Túl drága", + "churn_survey_question_1_choice_3": "Hiányolok funkciókat", + "churn_survey_question_1_choice_4": "Szegényes ügyfélszolgálat", + "churn_survey_question_1_choice_5": "Egyszerűen már nem volt rá szükségem", + "churn_survey_question_1_headline": "Miért mondta le az előfizetését?", + "churn_survey_question_1_subheader": "Sajnáljuk, hogy elhagy minket. Segítsen nekünk jobbá válni:", + "churn_survey_question_2_button_label": "Küldés", + "churn_survey_question_2_headline": "Mi tette volna a(z) $[projectName] projektet könnyebben használhatóvá?", + "churn_survey_question_3_button_label": "30% kedvezmény", + "churn_survey_question_3_headline": "30% kedvezmény a következő évre!", + "churn_survey_question_3_html": "

    Szeretnénk megtartani Önt ügyfelünknek. Boldogan felajánlunk 30% kedvezményt a következő évre.

    ", + "churn_survey_question_4_headline": "Milyen funkciókat hiányol?", + "churn_survey_question_5_button_label": "E-mail küldése a vezérigazgatónak", + "churn_survey_question_5_headline": "Sajnálattal halljuk 😔. Beszéljen közvetlenül a vezérigazgatónkkal!", + "churn_survey_question_5_html": "

    Célunk, hogy a lehető legjobb ügyfélszolgálatot nyújtsuk. Küldjön e-mailt a vezérigazgatónknak, aki majd személyesen foglalkozik a problémájával.

    ", + "collect_feedback_description": "Átfogó visszajelzések gyűjtése a termékről vagy szolgáltatásról.", + "collect_feedback_name": "Visszajelzés gyűjtése", + "collect_feedback_question_1_headline": "Hogyan értékelné az összesített élményét?", + "collect_feedback_question_1_lower_label": "Nem jó", + "collect_feedback_question_1_subheader": "Ne aggódjon, legyen őszinte.", + "collect_feedback_question_1_upper_label": "Nagyon jó", + "collect_feedback_question_2_headline": "Csodálatos! Mi tetszett benne?", + "collect_feedback_question_2_placeholder": "Írja be ide a válaszát…", + "collect_feedback_question_3_headline": "Köszönjük, hogy megosztotta velünk! Mi nem tetszett?", + "collect_feedback_question_3_placeholder": "Írja be ide a válaszát…", + "collect_feedback_question_4_headline": "Hogyan értékelné a kommunikációnkat?", + "collect_feedback_question_4_lower_label": "Nem jó", + "collect_feedback_question_4_upper_label": "Nagyon jó", + "collect_feedback_question_5_headline": "Bármi egyéb, amit meg szeretne osztani a csapatunkkal?", + "collect_feedback_question_5_placeholder": "Írja be ide a válaszát…", + "collect_feedback_question_6_choice_1": "Google", + "collect_feedback_question_6_choice_2": "Közösségi média", + "collect_feedback_question_6_choice_3": "Ismerősök", + "collect_feedback_question_6_choice_4": "Podcast", + "collect_feedback_question_6_choice_5": "Egyéb", + "collect_feedback_question_6_headline": "Honnan hallott rólunk?", + "collect_feedback_question_7_headline": "Végezetül pedig szívesen válaszolnánk a visszajelzésére. Adja meg az e-mail-címét:", + "collect_feedback_question_7_placeholder": "pelda@email.hu", + "consent": "Hozzájárulás", + "consent_description": "Felhasználási feltételek vagy adatfelhasználás elfogadásának kérése", + "contact_info": "Kapcsolatfelvételi információk", + "contact_info_description": "Név, vezetéknév, e-mail-cím, telefonszám és vállalat együttes megadásának kérése", + "csat_description": "A termék vagy szolgáltatás ügyfél-elégedettségi pontszámának mérése.", + "csat_name": "Ügyfél-elégedettségi pontszám (CSAT)", + "csat_question_10_headline": "Van még egyéb megjegyzése, kérdése vagy aggálya?", + "csat_question_10_placeholder": "Írja be ide a válaszát…", + "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": "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", + "csat_question_2_choice_5": "Nagyon elégedetlen", + "csat_question_2_headline": "Összességében mennyire elégedett vagy elégedetlen a(z) $[projectName] projektünkkel?", + "csat_question_2_subheader": "Válasszon egyet:", + "csat_question_3_choice_1": "Hatástalan", + "csat_question_3_choice_10": "Egyedi", + "csat_question_3_choice_2": "Hasznos", + "csat_question_3_choice_3": "Praktikátlan", + "csat_question_3_choice_4": "Túlárazott", + "csat_question_3_choice_5": "Magas minőség", + "csat_question_3_choice_6": "Megbízható", + "csat_question_3_choice_7": "Jó ár-érték arány", + "csat_question_3_choice_8": "Gyenge minőség", + "csat_question_3_choice_9": "Megbízhatatlan", + "csat_question_3_headline": "A következő szavak közül melyiket használná a(z) $[projectName] projektünk leírására?", + "csat_question_3_subheader": "Válassza ki az összes megfelelőt:", + "csat_question_4_choice_1": "Rendkívül jól", + "csat_question_4_choice_2": "Nagyon jól", + "csat_question_4_choice_3": "Valamelyest jól", + "csat_question_4_choice_4": "Nem túl jól", + "csat_question_4_choice_5": "Egyáltalán nem jól", + "csat_question_4_headline": "Mennyire jól felel meg a(z) $[projectName] projektünk az Ön igényeinek?", + "csat_question_4_subheader": "Válasszon egy lehetőséget:", + "csat_question_5_choice_1": "Nagyon magas minőség", + "csat_question_5_choice_2": "Magas minőség", + "csat_question_5_choice_3": "Alacsony minőség", + "csat_question_5_choice_4": "Nagyon alacsony minőség", + "csat_question_5_choice_5": "Sem magas, sem alacsony", + "csat_question_5_headline": "Hogyan értékelné a(z) $[projectName] projekt minőségét?", + "csat_question_5_subheader": "Válasszon egy lehetőséget:", + "csat_question_6_choice_1": "Kitűnő", + "csat_question_6_choice_2": "Átlag fölötti", + "csat_question_6_choice_3": "Átlagos", + "csat_question_6_choice_4": "Átlag alatti", + "csat_question_6_choice_5": "Gyenge", + "csat_question_6_headline": "Hogyan értékelné a(z) $[projectName] projekt ár-érték arányát?", + "csat_question_6_subheader": "Válasszon egyet:", + "csat_question_7_choice_1": "Rendkívül alkalmazkodóképes", + "csat_question_7_choice_2": "Nagyon alkalmazkodóképes", + "csat_question_7_choice_3": "Valamelyest alkalmazkodóképes", + "csat_question_7_choice_4": "Nem túl alkalmazkodóképes", + "csat_question_7_choice_5": "Egyáltalán nem alkalmazkodóképes", + "csat_question_7_headline": "Mennyire voltunk alkalmazkodóképesek a szolgáltatásainkkal kapcsolatos kérdéseinél?", + "csat_question_7_subheader": "Válasszon egyet:", + "csat_question_8_choice_1": "Ez az első vásárlásom", + "csat_question_8_choice_2": "Kevesebb mint hat hónap", + "csat_question_8_choice_3": "Hat hónap és egy év között", + "csat_question_8_choice_4": "1 és 2 év között", + "csat_question_8_choice_5": "3 vagy több év", + "csat_question_8_headline": "Mióta ügyfele a(z) $[projectName] projektnek?", + "csat_question_8_subheader": "Válasszon egyet:", + "csat_question_9_choice_1": "Rendkívül valószínű", + "csat_question_9_choice_2": "Nagyon valószínű", + "csat_question_9_choice_3": "Valamelyest valószínű", + "csat_question_9_choice_4": "Nem túl valószínű", + "csat_question_9_choice_5": "Egyáltalán nem valószínű", + "csat_question_9_headline": "Mennyire valószínű, hogy újra megvásárolná a(z) $[projectName] projektünk valamelyikét?", + "csat_question_9_subheader": "Válasszon egy lehetőséget:", + "csat_survey_name": "A(z) $[projectName] projekt ügyfél-elégedettségi pontszáma", + "csat_survey_question_1_headline": "Mennyire elégedett a(z) $[projectName] projekt élményével?", + "csat_survey_question_1_lower_label": "Rendkívül elégedetlen", + "csat_survey_question_1_upper_label": "Rendkívül elégedett", + "csat_survey_question_2_headline": "Csodálatos! Tehetünk valamit, amivel javíthatnánk az élményén?", + "csat_survey_question_2_placeholder": "Írja be ide a válaszát…", + "csat_survey_question_3_headline": "Jaj, bocsánat! Tehetünk valamit, amivel javíthatnánk az élményén?", + "csat_survey_question_3_placeholder": "Írja be ide a válaszát…", + "cta_description": "Információk megjelenítése és a felhasználók felkérése egy bizonyos művelet elvégzésére", + "custom_survey_block_1_name": "1. blokk", + "custom_survey_description": "Kérdőív létrehozása sablon nélkül.", + "custom_survey_name": "Kezdés a semmiből", + "custom_survey_question_1_headline": "Mit szeretne tudni?", + "custom_survey_question_1_placeholder": "Írja be ide a válaszát…", + "customer_effort_score_description": "Annak meghatározása, hogy mennyire egyszerű egy funkció használata.", + "customer_effort_score_name": "Ügyfél-erőfeszítési pontszám (CES)", + "customer_effort_score_question_1_headline": "A(z) $[projectName] megkönnyíti számomra a [CÉL HOZZÁADÁSA] tevékenységet", + "customer_effort_score_question_1_lower_label": "Egyáltalán nem értek egyet", + "customer_effort_score_question_1_upper_label": "Teljesen egyetértek", + "customer_effort_score_question_2_headline": "Köszönjük! Hogyan tudnánk megkönnyíteni Önnek a [CÉL HOZZÁADÁSA] tevékenységet?", + "customer_effort_score_question_2_placeholder": "Írja be ide a válaszát…", + "date": "Dátum", + "date_description": "Dátum kiválasztásának kérése", + "default_ending_card_button_label": "Saját kérdőív létrehozása", + "default_ending_card_headline": "Köszönjük!", + "default_ending_card_subheader": "Nagyra értékeljük a visszajelzését.", + "default_welcome_card_button_label": "Következő", + "default_welcome_card_headline": "Üdvözöljük!", + "default_welcome_card_html": "Köszönjük a visszajelzését – induljunk!", + "docs_feedback_description": "Annak mérése, hogy a fejlesztői dokumentáció egyes oldalai mennyire érthetőek.", + "docs_feedback_name": "Dokumentációs visszajelzés", + "docs_feedback_question_1_choice_1": "Igen 👍", + "docs_feedback_question_1_choice_2": "Nem 👎", + "docs_feedback_question_1_headline": "Hasznos volt ez az oldal?", + "docs_feedback_question_2_headline": "Fejtse ki:", + "docs_feedback_question_3_headline": "Oldal URL-e", + "earned_advocacy_score_description": "Az EAS az NPS-ből merít ihletet, de a tényleges múltbeli viselkedésre kérdez a nemes szándék helyett.", + "earned_advocacy_score_name": "Megszerzett támogatási pontszám (EAS)", + "earned_advocacy_score_question_1_choice_1": "Igen", + "earned_advocacy_score_question_1_choice_2": "Nem", + "earned_advocacy_score_question_1_headline": "Aktívan ajánlotta másoknak a(z) $[projectName] projektet?", + "earned_advocacy_score_question_2_headline": "Miért ajánlott minket?", + "earned_advocacy_score_question_2_placeholder": "Írja be ide a válaszát…", + "earned_advocacy_score_question_3_headline": "Olyan szomorú. Miért nem?", + "earned_advocacy_score_question_3_placeholder": "Írja be ide a válaszát…", + "earned_advocacy_score_question_4_choice_1": "Igen", + "earned_advocacy_score_question_4_choice_2": "Nem", + "earned_advocacy_score_question_4_headline": "Aktívan lebeszélt másokat arról, hogy ezt a(z) $[projectName] projektet válasszák?", + "earned_advocacy_score_question_5_headline": "Mi késztette arra, hogy lebeszélje őket?", + "earned_advocacy_score_question_5_placeholder": "Írja be ide a válaszát…", + "employee_satisfaction_description": "Munkavállalói elégedettség mérése és a fejlesztésre szoruló területek azonosítása.", + "employee_satisfaction_name": "Munkavállalói elégedettség", + "employee_satisfaction_question_1_headline": "Mennyire elégedett a jelenlegi munkakörével?", + "employee_satisfaction_question_1_lower_label": "Nem elégedett", + "employee_satisfaction_question_1_upper_label": "Nagyon elégedett", + "employee_satisfaction_question_2_choice_1": "Rendkívül jelentőségteljes", + "employee_satisfaction_question_2_choice_2": "Nagyon jelentőségteljes", + "employee_satisfaction_question_2_choice_3": "Közepesen jelentőségteljes", + "employee_satisfaction_question_2_choice_4": "Enyhén jelentőségteljes", + "employee_satisfaction_question_2_choice_5": "Egyáltalán nem jelentőségteljes", + "employee_satisfaction_question_2_headline": "Mennyire tartja jelentőségteljesnek a munkáját?", + "employee_satisfaction_question_3_headline": "Mi az, amit legjobban élvez az itteni munkában?", + "employee_satisfaction_question_3_placeholder": "Írja be ide a válaszát…", + "employee_satisfaction_question_5_headline": "Értékelje a vezetőjétől kapott támogatást.", + "employee_satisfaction_question_5_lower_label": "Gyenge", + "employee_satisfaction_question_5_upper_label": "Kitűnő", + "employee_satisfaction_question_6_headline": "Milyen fejlesztéseket javasolna a munkahelyünkön?", + "employee_satisfaction_question_6_placeholder": "Írja be ide a válaszát…", + "employee_satisfaction_question_7_choice_1": "Rendkívül valószínű", + "employee_satisfaction_question_7_choice_2": "Nagyon valószínű", + "employee_satisfaction_question_7_choice_3": "Közepesen valószínű", + "employee_satisfaction_question_7_choice_4": "Enyhén valószínű", + "employee_satisfaction_question_7_choice_5": "Egyáltalán nem valószínű", + "employee_satisfaction_question_7_headline": "Mennyire valószínű, hogy ajánlaná a vállalatunkat egy ismerősének?", + "employee_well_being_description": "A munkavállalói jóllét értékelése a munka és a magánélet egyensúlyán, a munkaterhelésen és a környezeten keresztül.", + "employee_well_being_name": "Munkavállalói jóllét", + "employee_well_being_question_1_headline": "Úgy érzem, hogy jó egyensúly van a munkám és a magánéletem között.", + "employee_well_being_question_1_lower_label": "Nagyon gyenge egyensúly", + "employee_well_being_question_1_upper_label": "Kitűnő egyensúly", + "employee_well_being_question_2_headline": "A munkaterhelésem kezelhető, lehetővé téve hogy produktív maradjak túlterheltségi érzés nélkül.", + "employee_well_being_question_2_lower_label": "Túlterhelő munkaterhelés", + "employee_well_being_question_2_upper_label": "Tökéletesen kezelhető", + "employee_well_being_question_3_headline": "A munkakörnyezet támogatja a fizikai és mentális jóllétemet.", + "employee_well_being_question_3_lower_label": "Nem támogató", + "employee_well_being_question_3_upper_label": "Erősen támogató", + "employee_well_being_question_4_headline": "Milyen változtatások (ha vannak ilyenek) javítanák az összesített jóllétét a munkahelyén?", + "employee_well_being_question_4_placeholder": "Írja be ide a válaszát…", + "enps_survey_name": "eNPS kérdőív", + "enps_survey_question_1_headline": "Mennyire valószínű, hogy ajánlaná az ennél a vállalatnál való munkavállalást egy ismerősének vagy kollégájának?", + "enps_survey_question_1_lower_label": "Egyáltalán nem valószínű", + "enps_survey_question_1_upper_label": "Rendkívül valószínű", + "enps_survey_question_2_headline": "Hogy segítsen nekünk fejlődni, leírná az értékelése indokait?", + "enps_survey_question_3_headline": "Bármilyen egyéb megjegyzés, visszajelzés vagy aggály?", + "evaluate_a_product_idea_description": "A felhasználók megkérdezése termékkel vagy funkcióval kapcsolatos ötletekről. Gyors visszajelzést kaphat.", + "evaluate_a_product_idea_name": "Termékötlet kiértékelése", + "evaluate_a_product_idea_question_1_button_label": "Csináljuk!", + "evaluate_a_product_idea_question_1_headline": "Imádjuk, ahogy a(z) $[projectName] projektet használja! Szeretnénk kikérni a véleményét egy funkcióval kapcsolatos ötletről. Van egy perce?", + "evaluate_a_product_idea_question_1_html": "

    Tiszteletben tartjuk az idejét, ezért rövidre fogtuk 🤸

    ", + "evaluate_a_product_idea_question_2_headline": "Köszönjük! Mennyire nehéz vagy könnyű Önnek a [PROBLÉMATERÜLET] ma?", + "evaluate_a_product_idea_question_2_lower_label": "Nagyon nehéz", + "evaluate_a_product_idea_question_2_upper_label": "Nagyon könnyű", + "evaluate_a_product_idea_question_3_headline": "Mi a legnehezebb Önnek, ha [PROBLÉMATERÜLET] jön szóba?", + "evaluate_a_product_idea_question_3_placeholder": "Írja be ide a válaszát…", + "evaluate_a_product_idea_question_4_button_label": "Következő", + "evaluate_a_product_idea_question_4_headline": "Egy olyan ötleten dolgozunk, amely segíthet a [PROBLÉMATERÜLET] megoldásában.", + "evaluate_a_product_idea_question_4_html": "

    Ide illessze be az elgondolás rövid leírását. Adja meg a szükséges részleteket, de legyen tömör és könnyen érthető.

    ", + "evaluate_a_product_idea_question_5_headline": "Mennyire lenne értékes ez a funkció az Ön számára?", + "evaluate_a_product_idea_question_5_lower_label": "Nem értékes", + "evaluate_a_product_idea_question_5_upper_label": "Nagyon értékes", + "evaluate_a_product_idea_question_6_headline": "Értjük. Miért nem lenne ez a funkció értékes az Ön számára?", + "evaluate_a_product_idea_question_6_placeholder": "Írja be ide a válaszát…", + "evaluate_a_product_idea_question_7_headline": "Mi lenne a legértékesebb az Ön számára ebben a funkcióban?", + "evaluate_a_product_idea_question_7_placeholder": "Írja be ide a válaszát…", + "evaluate_a_product_idea_question_8_headline": "Bármi egyéb, amit észben kell tartanunk?", + "evaluate_a_product_idea_question_8_placeholder": "Írja be ide a válaszát…", + "evaluate_content_quality_description": "Annak mérése, hogy a tartalom marketingdarabjai elérték-e a céljukat.", + "evaluate_content_quality_name": "Tartalom minőségének kiértékelése", + "evaluate_content_quality_question_1_headline": "Mennyire jól tárgyalta ez a cikk azt, amit meg akart tanulni?", + "evaluate_content_quality_question_1_lower_label": "Egyáltalán nem jól", + "evaluate_content_quality_question_1_upper_label": "Rendkívül jól", + "evaluate_content_quality_question_2_headline": "Hm! Mire számított?", + "evaluate_content_quality_question_2_placeholder": "Írja be ide a válaszát…", + "evaluate_content_quality_question_3_headline": "Csodálatos! Van még valami, amit szeretne, hogy kitárgyaljunk?", + "evaluate_content_quality_question_3_placeholder": "Témák, trendek, oktatóanyagok…", + "fake_door_follow_up_description": "Követés olyan felhasználókkal, akik belefutottak az egyik „fake door” kísérletébe.", + "fake_door_follow_up_name": "„Fake door” követés", + "fake_door_follow_up_question_1_headline": "Mennyire fontos ez a funkció az Ön számára?", + "fake_door_follow_up_question_1_lower_label": "Nem fontos", + "fake_door_follow_up_question_1_upper_label": "Nagyon fontos", + "fake_door_follow_up_question_2_choice_1": "1. szempont", + "fake_door_follow_up_question_2_choice_2": "2. szempont", + "fake_door_follow_up_question_2_choice_3": "3. szempont", + "fake_door_follow_up_question_2_choice_4": "4. szempont", + "fake_door_follow_up_question_2_headline": "Mit kell feltétlenül tartalmaznia ennek összeállításakor?", + "feature_chaser_description": "Követés olyan felhasználókkal, akik épp most használtak egy bizonyos funkciót.", + "feature_chaser_name": "Funkcióvadász", + "feature_chaser_question_1_headline": "Mennyire fontos a [FUNKCIÓ HOZZÁADÁSA] az Ön számára?", + "feature_chaser_question_1_lower_label": "Nem fontos", + "feature_chaser_question_1_upper_label": "Nagyon fontos", + "feature_chaser_question_2_choice_1": "1. szempont", + "feature_chaser_question_2_choice_2": "2. szempont", + "feature_chaser_question_2_choice_3": "3. szempont", + "feature_chaser_question_2_choice_4": "4. szempont", + "feature_chaser_question_2_headline": "Melyik szempont a legfontosabb?", + "feedback_box_description": "Lehetőség adása a felhasználóknak, hogy problémamentesen megosszák, mi jár a fejükben.", + "feedback_box_name": "Visszajelzés doboz", + "feedback_box_question_1_choice_1": "Hibajelentés 🐞", + "feedback_box_question_1_choice_2": "Funkciókérés 💡", + "feedback_box_question_1_headline": "Mi jár a fejében, főnök?", + "feedback_box_question_1_subheader": "Köszönjük, hogy megosztotta velünk. A lehető leghamarabb visszajelzünk Önnek.", + "feedback_box_question_2_headline": "Mi romlott el?", + "feedback_box_question_2_subheader": "Minél több részletet ad meg, annál jobb :)", + "feedback_box_question_3_button_label": "Igen, értesítsenek", + "feedback_box_question_3_headline": "Szeretne naprakész maradni?", + "feedback_box_question_3_html": "

    A lehető leghamarabb kijavítjuk. Szeretne értesítést kapni, amikor elkészültünk?

    ", + "feedback_box_question_4_button_label": "Funkció kérése", + "feedback_box_question_4_headline": "Csodálatos, mondjon nekünk többet!", + "feedback_box_question_4_placeholder": "Írja be ide a válaszát…", + "feedback_box_question_4_subheader": "Milyen problémát szeretne megoldatni velünk?", + "file_upload": "Fájlfeltöltés", + "file_upload_description": "Dokumentumok, képek vagy egyéb fájlok feltöltésének engedélyezése a válaszadóknak", + "finish": "Befejezés", + "follow_ups_modal_action_body": "

    Helló 👋

    Köszönjük, hogy időt szánt a válaszadásra, hamarosan felvesszük Önnel a kapcsolatot.

    Legyen szép napja!

    ", + "free_text": "Szabad szöveg", + "free_text_description": "Szabad szavas visszajelzések gyűjtése", + "free_text_placeholder": "Írja be ide a válaszát…", + "gauge_feature_satisfaction_description": "A termék bizonyos funkcióival való elégedettségének kiértékelése.", + "gauge_feature_satisfaction_name": "Funkció-elégedettségi mérés", + "gauge_feature_satisfaction_question_1_headline": "Mennyire volt könnyű elérni … ?", + "gauge_feature_satisfaction_question_1_lower_label": "Nem könnyű", + "gauge_feature_satisfaction_question_1_upper_label": "Nagyon könnyű", + "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_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", + "identify_sign_up_barriers_question_1_headline": "Válaszoljon erre a rövid kérdőívre, és 10% kedvezményt kap!", + "identify_sign_up_barriers_question_1_html": "Úgy tűnik, hogy fontolgatja a regisztrációt. Válaszoljon négy kérdésre, és 10% kedvezményt kap bármelyik csomagra.", + "identify_sign_up_barriers_question_2_headline": "Mennyire valószínű, hogy regisztrál a(z) $[projectName] projektre?", + "identify_sign_up_barriers_question_2_lower_label": "Egyáltalán nem valószínű", + "identify_sign_up_barriers_question_2_upper_label": "Nagyon valószínű", + "identify_sign_up_barriers_question_3_choice_1_label": "Lehet, hogy nem találom meg, amit keresek", + "identify_sign_up_barriers_question_3_choice_2_label": "Továbbra is összehasonlítom a lehetőségeket", + "identify_sign_up_barriers_question_3_choice_3_label": "Bonyolultnak tűnik", + "identify_sign_up_barriers_question_3_choice_4_label": "Az árazás aggodalmat kelt", + "identify_sign_up_barriers_question_3_choice_5_label": "Valami más", + "identify_sign_up_barriers_question_3_headline": "Mi tartja vissza attól, hogy kipróbálja a(z) $[projectName] projektet?", + "identify_sign_up_barriers_question_4_headline": "Mire van szüksége, amit a(z) $[projectName] projekt nem kínál?", + "identify_sign_up_barriers_question_4_placeholder": "Írja be ide a válaszát…", + "identify_sign_up_barriers_question_5_headline": "Milyen lehetőségeket mérlegel?", + "identify_sign_up_barriers_question_5_placeholder": "Írja be ide a válaszát…", + "identify_sign_up_barriers_question_6_headline": "Mi tűnik bonyolultnak az Ön számára?", + "identify_sign_up_barriers_question_6_placeholder": "Írja be ide a válaszát…", + "identify_sign_up_barriers_question_7_headline": "Mi aggasztja Önt az árazással kapcsolatban?", + "identify_sign_up_barriers_question_7_placeholder": "Írja be ide a válaszát…", + "identify_sign_up_barriers_question_8_headline": "Magyarázza el:", + "identify_sign_up_barriers_question_8_placeholder": "Írja be ide a válaszát…", + "identify_sign_up_barriers_question_9_button_label": "Regisztráció", + "identify_sign_up_barriers_question_9_headline": "Köszönjük! Itt van a kódja: SIGNUPNOW10", + "identify_sign_up_barriers_question_9_html": "

    Nagyon köszönjük, hogy időt szánt a visszajelzés megosztására 🙏

    ", + "identify_upsell_opportunities_description": "Annak kiderítése, hogy a termék mennyi időt takarít meg a felhasználónak. Használja ezt a felülértékesítésre.", + "identify_upsell_opportunities_name": "Felülértékesítési lehetőségek azonosítása", + "identify_upsell_opportunities_question_1_choice_1": "Kevesebb mint 1 óra", + "identify_upsell_opportunities_question_1_choice_2": "1 és 2 óra között", + "identify_upsell_opportunities_question_1_choice_3": "3 és 5 óra között", + "identify_upsell_opportunities_question_1_choice_4": "Több mint 5 óra", + "identify_upsell_opportunities_question_1_headline": "Hány órát takarít meg a csapata hetente a(z) $[projectName] projekt használatával?", + "improve_activation_rate_description": "A beléptetési folyamat gyengeségeinek azonosítása a felhasználói aktiválás növeléséhez.", + "improve_activation_rate_name": "Aktiválási arány javítása", + "improve_activation_rate_question_1_choice_1": "Nem tűnt hasznosnak számomra", + "improve_activation_rate_question_1_choice_2": "Nehéz beállítani vagy használni", + "improve_activation_rate_question_1_choice_3": "Hiányzó funkciók vagy működés", + "improve_activation_rate_question_1_choice_4": "Csak nem volt rá időm", + "improve_activation_rate_question_1_choice_5": "Valami más", + "improve_activation_rate_question_1_headline": "Mi a fő oka annak, amiért nem fejezte be a(z) $[projectName] projekt beállítását?", + "improve_activation_rate_question_2_headline": "Mi alapján gondolta, hogy a(z) $[projectName] projekt nem lenne hasznos?", + "improve_activation_rate_question_2_placeholder": "Írja be ide a válaszát…", + "improve_activation_rate_question_3_headline": "Mi volt nehéz a(z) $[projectName] projekt beállításával vagy használatával kapcsolatban?", + "improve_activation_rate_question_3_placeholder": "Írja be ide a válaszát…", + "improve_activation_rate_question_4_headline": "Milyen funkciók vagy működés hiányzott?", + "improve_activation_rate_question_4_placeholder": "Írja be ide a válaszát…", + "improve_activation_rate_question_5_headline": "Hogyan tudnánk megkönnyíteni Önnek a kezdést?", + "improve_activation_rate_question_5_placeholder": "Írja be ide a válaszát…", + "improve_activation_rate_question_6_headline": "Mi volt az? Magyarázza el:", + "improve_activation_rate_question_6_placeholder": "Írja be ide a válaszát…", + "improve_activation_rate_question_6_subheader": "A lehető leghamarabb szeretnénk kijavítani.", + "improve_newsletter_content_description": "Annak kiderítése, hogy a feliratkozók mennyire kedvelik a hírlevél tartalmát.", + "improve_newsletter_content_name": "Hírlevéltartalom továbbfejlesztése", + "improve_newsletter_content_question_1_headline": "Hogy értékelné az e heti hírlevelet?", + "improve_newsletter_content_question_1_lower_label": "Nem az igazi", + "improve_newsletter_content_question_1_upper_label": "Nagyszerű", + "improve_newsletter_content_question_2_headline": "Mi tette volna hasznosabbá az e heti hírlevelet?", + "improve_newsletter_content_question_2_placeholder": "Írja be ide a válaszát…", + "improve_newsletter_content_question_3_button_label": "Örömmel segítek!", + "improve_newsletter_content_question_3_headline": "Köszönjük! ❤️ Ossza meg a szeretetét EGY ismerősével.", + "improve_newsletter_content_question_3_html": "

    Van olyan ismerőse, akivel egy rugóra jár az agyuk? Hatalmas szívességet tenne nekünk, ha megosztaná vele az e heti epizódot!

    ", + "improve_trial_conversion_description": "Annak kiderítése, hogy miért szakították meg az emberek a próbaidőszakot. Ezek a tapasztalatok segítenek Önnek a konverziós folyamat javításában.", + "improve_trial_conversion_name": "Próbaidőszakos konverzió javítása", + "improve_trial_conversion_question_1_choice_1": "Nem sok hasznot húztam belőle", + "improve_trial_conversion_question_1_choice_2": "Valami másra számítottam", + "improve_trial_conversion_question_1_choice_3": "Túl drága ahhoz képest, amit nyújt", + "improve_trial_conversion_question_1_choice_4": "Hiányolok egy funkciót", + "improve_trial_conversion_question_1_choice_5": "Csak nézelődtem", + "improve_trial_conversion_question_1_headline": "Miért szakította meg a próbaidőszakot?", + "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_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": "

    Boldogan felajánlunk 20% kedvezményt az éves csomagra.

    ", + "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_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.", + "integration_setup_survey_name": "Integrációhasználati kérdőív", + "integration_setup_survey_question_1_headline": "Mennyire volt könnyű beállítani ezt az integrációt?", + "integration_setup_survey_question_1_lower_label": "Nem könnyű", + "integration_setup_survey_question_1_upper_label": "Nagyon könnyű", + "integration_setup_survey_question_2_headline": "Miért volt nehéz?", + "integration_setup_survey_question_2_placeholder": "Írja be ide a válaszát…", + "integration_setup_survey_question_3_headline": "Milyen egyéb eszközöket szeretne használni a(z) $[projectName] projekttel?", + "integration_setup_survey_question_3_subheader": "Folyamatosan állítunk össze integrációkat, az Öné lehet a következő:", + "interview_prompt_description": "A felhasználók egy bizonyos részének meghívása, hogy interjút szervezzenek a termékcsapattal.", + "interview_prompt_name": "Interjúkérés", + "interview_prompt_question_1_button_label": "Időpont foglalása", + "interview_prompt_question_1_headline": "Van 15 perce, hogy beszéljen velünk? 🙏", + "interview_prompt_question_1_html": "Ön a tapasztalt felhasználóink egyike. Szeretnénk egy rövid interjút készíteni Önnel!", + "long_term_retention_check_in_description": "Hosszú távú felhasználói elégedettség, lojalitás és fejlesztésre szoruló területek mérése a lojális felhasználók megtartásához.", + "long_term_retention_check_in_name": "Hosszú távú megtartás ellenőrzése", + "long_term_retention_check_in_question_10_headline": "Bármilyen további visszajelzés vagy megjegyzés?", + "long_term_retention_check_in_question_10_placeholder": "Osszon meg velünk bármilyen gondolatot vagy visszajelzést, amely segíthet nekünk a fejlődésben…", + "long_term_retention_check_in_question_1_headline": "Mennyire elégedett a(z) $[projectName] projekttel összességében?", + "long_term_retention_check_in_question_1_lower_label": "Nem elégedett", + "long_term_retention_check_in_question_1_upper_label": "Nagyon elégedett", + "long_term_retention_check_in_question_2_headline": "Mit tart a legértékesebbnek a(z) $[projectName] projekttel kapcsolatban?", + "long_term_retention_check_in_question_2_placeholder": "Írja le a legértékesebbnek tartott funkciót vagy előnyt…", + "long_term_retention_check_in_question_3_choice_1": "Funkciók", + "long_term_retention_check_in_question_3_choice_2": "Ügyfélszolgálat", + "long_term_retention_check_in_question_3_choice_3": "Felhasználói élmény", + "long_term_retention_check_in_question_3_choice_4": "Árazás", + "long_term_retention_check_in_question_3_choice_5": "Megbízhatóság és üzemidő", + "long_term_retention_check_in_question_3_headline": "A(z) $[projectName] projekt melyik szempontját tartja a legfontosabbnak a tapasztalatai alapján?", + "long_term_retention_check_in_question_4_headline": "Mennyire felel meg a(z) $[projectName] projekt az elvárásainak?", + "long_term_retention_check_in_question_4_lower_label": "Elmarad a várakozásoktól", + "long_term_retention_check_in_question_4_upper_label": "Meghaladja az elvárásokat", + "long_term_retention_check_in_question_5_headline": "Milyen kihívásokkal vagy bosszúságokkal szembesült a(z) $[projectName] projekt használata során?", + "long_term_retention_check_in_question_5_placeholder": "Írjon le bármilyen kihívást vagy fejlesztést, amelyet szívesen látna…", + "long_term_retention_check_in_question_6_headline": "Mennyire valószínű, hogy ajánlaná a(z) $[projectName] projektet egy ismerősének vagy kollégájának?", + "long_term_retention_check_in_question_6_lower_label": "Nem valószínű", + "long_term_retention_check_in_question_6_upper_label": "Nagyon valószínű", + "long_term_retention_check_in_question_7_choice_1": "Új funkciók és továbbfejlesztések", + "long_term_retention_check_in_question_7_choice_2": "Továbbfejlesztett ügyfélszolgálat", + "long_term_retention_check_in_question_7_choice_3": "Jobb árazási lehetőségek", + "long_term_retention_check_in_question_7_choice_4": "További integrációk", + "long_term_retention_check_in_question_7_choice_5": "Felhasználói élmény finomítások", + "long_term_retention_check_in_question_7_headline": "Mi tenné még valószínűbbé, hogy hosszú távú felhasználó maradjon?", + "long_term_retention_check_in_question_8_headline": "Ha megváltoztathatna egy dolgot a(z) $[projectName] projekttel kapcsolatban, mi lenne az?", + "long_term_retention_check_in_question_8_placeholder": "Osszon meg bármilyen változtatást vagy funkciót, amelyet szeretne, ha figyelembe vennénk…", + "long_term_retention_check_in_question_9_headline": "Mennyire elégedett a termékfrissítéseinkkel és azok gyakoriságával?", + "long_term_retention_check_in_question_9_lower_label": "Nem elégedett", + "long_term_retention_check_in_question_9_upper_label": "Nagyon elégedett", + "market_attribution_description": "Annak megismerése, hogy a felhasználók honnan hallottak először a termékéről.", + "market_attribution_name": "Marketing forrásmegjelölés", + "market_attribution_question_1_choice_1": "Ajánlás", + "market_attribution_question_1_choice_2": "Közösségi média", + "market_attribution_question_1_choice_3": "Hirdetések", + "market_attribution_question_1_choice_4": "Google keresés", + "market_attribution_question_1_choice_5": "Podcastban", + "market_attribution_question_1_headline": "Honnan hallott rólunk először?", + "market_attribution_question_1_subheader": "Válassza ki a következő lehetőségek egyikét:", + "market_site_clarity_description": "A marketingoldalt elhagyó felhasználók azonosítása. Az üzenetküldés javítása.", + "market_site_clarity_name": "Marketingoldal egyértelműsége", + "market_site_clarity_question_1_choice_1": "Igen, teljesen", + "market_site_clarity_question_1_choice_2": "Nagyjából…", + "market_site_clarity_question_1_choice_3": "Nem, egyáltalán nem", + "market_site_clarity_question_1_headline": "Minden szükséges információval rendelkezik ahhoz, hogy kipróbálja a(z) $[projectName] projektet?", + "market_site_clarity_question_2_headline": "Mi hiányzik vagy nem világos Önnek a(z) $[projectName] projekttel kapcsolatban?", + "market_site_clarity_question_3_button_label": "Kedvezmény kérése", + "market_site_clarity_question_3_headline": "Köszönjük a válaszát! 25% kedvezményt kap az első 6 hónapra:", + "matrix": "Mátrix", + "matrix_description": "Rács létrehozása több elem ugyanazon feltételrendszer alapján való értékeléséhez", + "measure_search_experience_description": "Annak mérése, hogy a keresési találatok mennyire helytállóak.", + "measure_search_experience_name": "Keresési élmény mérése", + "measure_search_experience_question_1_headline": "Mennyire helytállóak ezek a keresési találatok?", + "measure_search_experience_question_1_lower_label": "Egyáltalán nem helytálló", + "measure_search_experience_question_1_upper_label": "Nagyon helytálló", + "measure_search_experience_question_2_headline": "Jaj! Mi teszi a találatokat lényegtelenné az Ön számára?", + "measure_search_experience_question_2_placeholder": "Írja be ide a válaszát…", + "measure_search_experience_question_3_headline": "Csodálatos! Tehetünk valamit, amivel javíthatnánk az élményén?", + "measure_search_experience_question_3_placeholder": "Írja be ide a válaszát…", + "measure_task_accomplishment_description": "Annak megállapítása, hogy az emberek elvégzik-e a „teljesítendő feladatukat”. A sikeres emberek jobb ügyfelek.", + "measure_task_accomplishment_name": "Feladat teljesítésének mérése", + "measure_task_accomplishment_question_1_headline": "Képes volt teljesíteni azt, amiért ma idejött, hogy megcsinálja?", + "measure_task_accomplishment_question_1_option_1_label": "Igen", + "measure_task_accomplishment_question_1_option_2_label": "Dolgozom rajta, főnök", + "measure_task_accomplishment_question_1_option_3_label": "Nem", + "measure_task_accomplishment_question_2_headline": "Mennyire volt könnyű elérni a célját?", + "measure_task_accomplishment_question_2_lower_label": "Nagyon nehéz", + "measure_task_accomplishment_question_2_upper_label": "Nagyon könnyű", + "measure_task_accomplishment_question_3_headline": "Mi tette nehézzé?", + "measure_task_accomplishment_question_3_placeholder": "Írja be ide a válaszát…", + "measure_task_accomplishment_question_4_button_label": "Küldés", + "measure_task_accomplishment_question_4_headline": "Nagyszerű! Mit jött ma ide elvégezni?", + "measure_task_accomplishment_question_5_button_label": "Küldés", + "measure_task_accomplishment_question_5_headline": "Mi állította meg?", + "measure_task_accomplishment_question_5_placeholder": "Írja be ide a válaszát…", + "multi_select": "Többválasztós", + "multi_select_description": "A válaszadók megkérése, hogy egy vagy több lehetőséget válasszanak", + "new_integration_survey_description": "Annak kiderítése, hogy a felhasználók milyen integrációkat szeretnének legközelebb látni.", + "new_integration_survey_name": "Új integráció kérdőív", + "new_integration_survey_question_1_choice_1": "PostHog", + "new_integration_survey_question_1_choice_2": "Segment", + "new_integration_survey_question_1_choice_3": "Hubspot", + "new_integration_survey_question_1_choice_4": "Twilio", + "new_integration_survey_question_1_choice_5": "Egyéb", + "new_integration_survey_question_1_headline": "Milyen egyéb eszközöket használ?", + "next": "Következő", + "nps": "Valós ügyfél-támogatottsági érték (NPS)", + "nps_description": "Valós ügyfél-támogatottsági érték mérése (0-10)", + "nps_lower_label": "Egyáltalán nem valószínű", + "nps_name": "Valós ügyfél-támogatottsági érték (NPS)", + "nps_question_1_headline": "Mennyire valószínű, hogy ajánlaná a(z) $[projectName] projektet egy ismerősének vagy kollégájának?", + "nps_question_1_lower_label": "Nem valószínű", + "nps_question_1_upper_label": "Nagyon valószínű", + "nps_question_2_headline": "Mi alapján adta ezt az értékelést?", + "nps_survey_name": "NPS kérdőív", + "nps_survey_question_1_headline": "Mennyire valószínű, hogy ajánlaná a(z) $[projectName] projektet egy ismerősének vagy kollégájának?", + "nps_survey_question_1_lower_label": "Egyáltalán nem valószínű", + "nps_survey_question_1_upper_label": "Rendkívül valószínű", + "nps_survey_question_2_headline": "Hogy segítsen nekünk fejlődni, leírná az értékelése indokait?", + "nps_survey_question_3_headline": "Bármilyen egyéb megjegyzés, visszajelzés vagy aggály?", + "nps_upper_label": "Rendkívül valószínű", + "onboarding_segmentation": "Beléptetés szakaszolása", + "onboarding_segmentation_description": "További információk azzal kapcsolatban, hogy kik regisztráltak a termékére és miért.", + "onboarding_segmentation_question_1_choice_1": "Alapító", + "onboarding_segmentation_question_1_choice_2": "Igazgató", + "onboarding_segmentation_question_1_choice_3": "Termékmenedzser", + "onboarding_segmentation_question_1_choice_4": "Terméktulajdonos", + "onboarding_segmentation_question_1_choice_5": "Szoftvermérnök", + "onboarding_segmentation_question_1_headline": "Mi a munkaköre?", + "onboarding_segmentation_question_1_subheader": "Válassza ki a következő lehetőségek egyikét:", + "onboarding_segmentation_question_2_choice_1": "csak én", + "onboarding_segmentation_question_2_choice_2": "1-5 munkavállaló", + "onboarding_segmentation_question_2_choice_3": "6-10 munkavállaló", + "onboarding_segmentation_question_2_choice_4": "11-100 munkavállaló", + "onboarding_segmentation_question_2_choice_5": "több mint 100 munkavállaló", + "onboarding_segmentation_question_2_headline": "Mekkora méretű a vállalata?", + "onboarding_segmentation_question_2_subheader": "Válassza ki a következő lehetőségek egyikét:", + "onboarding_segmentation_question_3_choice_1": "Ajánlás", + "onboarding_segmentation_question_3_choice_2": "Közösségi média", + "onboarding_segmentation_question_3_choice_3": "Hirdetések", + "onboarding_segmentation_question_3_choice_4": "Google keresés", + "onboarding_segmentation_question_3_choice_5": "Podcastban", + "onboarding_segmentation_question_3_headline": "Honnan hallott rólunk először?", + "onboarding_segmentation_question_3_subheader": "Válassza ki a következő lehetőségek egyikét:", + "picture_selection": "Képkiválasztás", + "picture_selection_description": "A válaszadók megkérése, hogy egy vagy több képet válasszanak", + "preview_survey_ending_card_description": "Folytassa a beléptetését.", + "preview_survey_ending_card_headline": "Megcsinálta!", + "preview_survey_name": "Új kérdőív", + "preview_survey_question_1_headline": "Hogyan értékelné a(z) {projectName} projektet?", + "preview_survey_question_1_lower_label": "Nem jó", + "preview_survey_question_1_subheader": "Ez egy kérdőív előnézete.", + "preview_survey_question_1_upper_label": "Nagyon jó", + "preview_survey_question_2_back_button_label": "Vissza", + "preview_survey_question_2_choice_1_label": "Igen, folyamatosan tájékoztassanak.", + "preview_survey_question_2_choice_2_label": "Nem, köszönöm!", + "preview_survey_question_2_headline": "Szeretne naprakész maradni?", + "preview_survey_question_2_subheader": "Ez egy példa a leírásra.", + "preview_survey_welcome_card_headline": "Üdvözöljük!", + "prioritize_features_description": "A felhasználóknak leginkább és legkevésbé szükséges funkciók azonosítása.", + "prioritize_features_name": "Funkciók rangsorolása", + "prioritize_features_question_1_choice_1": "1. funkció", + "prioritize_features_question_1_choice_2": "2. funkció", + "prioritize_features_question_1_choice_3": "3. funkció", + "prioritize_features_question_1_choice_4": "Egyéb", + "prioritize_features_question_1_headline": "Ezen funkciók közül melyik lenne a legértékesebb az Ön számára?", + "prioritize_features_question_2_choice_1": "1 funkció", + "prioritize_features_question_2_choice_2": "2. funkció", + "prioritize_features_question_2_choice_3": "3. funkció", + "prioritize_features_question_2_headline": "Ezen funkciók közül melyik lenne a legkevésbé értékes az Ön számára?", + "prioritize_features_question_3_headline": "Hogyan tudnánk még javítani a(z) $[projectName] projekttel kapcsolatos élményét?", + "prioritize_features_question_3_placeholder": "Írja be ide a válaszát…", + "product_market_fit_short_description": "A termékpiaci illeszkedés mérése annak értékelésével, hogy a felhasználók mennyire lennének csalódottak, ha a termék eltűnne.", + "product_market_fit_short_name": "Termékpiaci illeszkedés kérdőív (rövid)", + "product_market_fit_short_question_1_choice_1": "Egyáltalán nem csalódott", + "product_market_fit_short_question_1_choice_2": "Valamelyest csalódott", + "product_market_fit_short_question_1_choice_3": "Nagyon csalódott", + "product_market_fit_short_question_1_headline": "Mennyire lenne csalódott, ha többé nem használhatná a(z) $[projectName] projektet?", + "product_market_fit_short_question_1_subheader": "Válassza ki a következő lehetőségek egyikét:", + "product_market_fit_short_question_2_headline": "Hogyan tudjuk továbbfejleszteni a(z) $[projectName] projektet az Ön számára?", + "product_market_fit_short_question_2_subheader": "Legyen a lehető legkonkrétabb.", + "product_market_fit_superhuman": "Termékpiaci illeszkedés kérdőív (szuperember)", + "product_market_fit_superhuman_description": "A termékpiaci illeszkedés mérése annak értékelésével, hogy a felhasználók mennyire lennének csalódottak, ha a termék eltűnne.", + "product_market_fit_superhuman_question_1_button_label": "Örömmel segítek!", + "product_market_fit_superhuman_question_1_headline": "Ön a tapasztalt felhasználóink egyike. Van 5 perce?", + "product_market_fit_superhuman_question_1_html": "

    Szeretnénk jobban megérteni a felhasználói élményét. A tapasztalatai megosztása sokat segít nekünk.

    ", + "product_market_fit_superhuman_question_2_choice_1": "Egyáltalán nem csalódott", + "product_market_fit_superhuman_question_2_choice_2": "Valamelyest csalódott", + "product_market_fit_superhuman_question_2_choice_3": "Nagyon csalódott", + "product_market_fit_superhuman_question_2_headline": "Mennyire lenne csalódott, ha többé nem használhatná a(z) $[projectName] projektet?", + "product_market_fit_superhuman_question_2_subheader": "Válassza ki a következő lehetőségek egyikét:", + "product_market_fit_superhuman_question_3_choice_1": "Alapító", + "product_market_fit_superhuman_question_3_choice_2": "Igazgató", + "product_market_fit_superhuman_question_3_choice_3": "Termékmenedzser", + "product_market_fit_superhuman_question_3_choice_4": "Terméktulajdonos", + "product_market_fit_superhuman_question_3_choice_5": "Szoftvermérnök", + "product_market_fit_superhuman_question_3_headline": "Mi a munkaköre?", + "product_market_fit_superhuman_question_3_subheader": "Válassza ki a következő lehetőségek egyikét:", + "product_market_fit_superhuman_question_4_headline": "Mit gondol, milyen típusú emberek profitálnának a legtöbbet a(z) $[projectName] projektből?", + "product_market_fit_superhuman_question_5_headline": "Mi a legnagyobb előny, amit a(z) $[projectName] projekttől kap?", + "product_market_fit_superhuman_question_6_headline": "Hogyan tudjuk továbbfejleszteni a(z) $[projectName] projektet az Ön számára?", + "product_market_fit_superhuman_question_6_subheader": "Legyen a lehető legkonkrétabb.", + "professional_development_growth_survey_description": "A munkavállalói elégedettség értékelése a szakmai növekedési és fejlődési lehetőségekkel kapcsolatban.", + "professional_development_growth_survey_name": "Szakmai fejlődési növekedés kérdőív", + "professional_development_growth_survey_question_1_headline": "Úgy érzem, hogy van lehetőségem fejlődni és fejleszteni a képességeimet a munkahelyemen.", + "professional_development_growth_survey_question_1_lower_label": "Nincsenek fejlődési lehetőségek", + "professional_development_growth_survey_question_1_upper_label": "Sok fejlődési lehetőség", + "professional_development_growth_survey_question_2_headline": "Elég önállóságom van ahhoz, hogy döntéseket hozzak a munkavégzésemmel kapcsolatban.", + "professional_development_growth_survey_question_2_lower_label": "Nincs önállóság", + "professional_development_growth_survey_question_2_upper_label": "Teljes önállóság", + "professional_development_growth_survey_question_3_headline": "A munkahelyemen kitűzött céljaim egyértelműek és összhangban állnak a fejlődésemmel.", + "professional_development_growth_survey_question_3_lower_label": "Nem egyértelmű célok", + "professional_development_growth_survey_question_3_upper_label": "Világos és összehangolt célok", + "professional_development_growth_survey_question_4_headline": "Mit lehetne javítani a szakmai fejlődése támogatásához?", + "professional_development_growth_survey_question_4_placeholder": "Írja be ide a válaszát…", + "professional_development_survey_description": "A munkavállalói elégedettség értékelése a szakmai növekedési és fejlődési lehetőségekkel kapcsolatban.", + "professional_development_survey_name": "Szakmai fejlődési kérdőív", + "professional_development_survey_question_1_choice_1": "Igen", + "professional_development_survey_question_1_choice_2": "Nem", + "professional_development_survey_question_1_headline": "Érdeklik a szakmai fejlődési tevékenységek?", + "professional_development_survey_question_2_choice_1": "Kapcsolatépítő események", + "professional_development_survey_question_2_choice_2": "Konferenciák vagy szemináriumok", + "professional_development_survey_question_2_choice_3": "Tanfolyamok vagy gyakorlati képzések", + "professional_development_survey_question_2_choice_4": "Mentorálás", + "professional_development_survey_question_2_choice_5": "Egyéni kutatás", + "professional_development_survey_question_2_choice_6": "Egyéb", + "professional_development_survey_question_2_headline": "Mit gondol, milyen típusú szakmai fejlődési tevékenységek lennének a legértékesebbek a fejlődéséhez?", + "professional_development_survey_question_2_subheader": "Válassza ki az összes megfelelőt", + "professional_development_survey_question_3_choice_1": "Igen", + "professional_development_survey_question_3_choice_2": "Nem", + "professional_development_survey_question_3_headline": "Fordított időt a szakmai fejlődésére a múltban?", + "professional_development_survey_question_4_headline": "Mennyire érzi támogatottnak a munkahelyén, ha szakmai továbbképzésről van szó?", + "professional_development_survey_question_4_lower_label": "Egyáltalán nem támogatott", + "professional_development_survey_question_4_upper_label": "Rendkívül támogatott", + "professional_development_survey_question_5_choice_1": "Saját ismereteimhez", + "professional_development_survey_question_5_choice_2": "Több felelősséget szerezni", + "professional_development_survey_question_5_choice_3": "Készségeim fejlesztése", + "professional_development_survey_question_5_choice_4": "Előrelépés a jelenlegi karrierutamon", + "professional_development_survey_question_5_choice_5": "Új munkahely keresése", + "professional_development_survey_question_5_choice_6": "Egyéb", + "professional_development_survey_question_5_headline": "Mik a fő okai annak, hogy időt szeretne fordítani a szakmai fejlődésre?", + "ranking": "Rangsorolás", + "ranking_description": "A válaszadók megkérése, hogy rendezzék sorba az elemeket előnyben részesítés vagy fontosság szerint", + "rate_checkout_experience_description": "Lehetővé tétel az ügyfeleknek, hogy értékeljék a fizetési folyamat élményét a konverzió finomhangolásához.", + "rate_checkout_experience_name": "Fizetési folyamat élményének értékelése", + "rate_checkout_experience_question_1_headline": "Mennyire volt könnyű vagy nehéz a fizetési folyamat befejezése?", + "rate_checkout_experience_question_1_lower_label": "Nagyon nehéz", + "rate_checkout_experience_question_1_upper_label": "Nagyon könnyű", + "rate_checkout_experience_question_2_headline": "Sajnáljuk! Mi tette volna könnyebbé az Ön számára?", + "rate_checkout_experience_question_2_placeholder": "Írja be ide a válaszát…", + "rate_checkout_experience_question_3_headline": "Csodálatos! Tehetünk valamit, amivel javíthatnánk az élményén?", + "rate_checkout_experience_question_3_placeholder": "Írja be ide a válaszát…", + "rating": "Értékelés", + "rating_description": "A válaszadók megkérése értékelésre (csillagok, hangulatjelek, számok)", + "rating_lower_label": "Nem jó", + "rating_upper_label": "Nagyon jó", + "recognition_and_reward_survey_description": "A munkavállalói elégedettség kiértékelése az elismerés, a jutalmazás, a vezetői támogatás és a véleménynyilvánítás szabadsága tekintetében.", + "recognition_and_reward_survey_name": "Elismerés és jutalom", + "recognition_and_reward_survey_question_1_headline": "Ha jól teljesítek, a szervezet elismeri a közreműködéseimet.", + "recognition_and_reward_survey_question_1_lower_label": "Egyáltalán nincs elismerve", + "recognition_and_reward_survey_question_1_upper_label": "Magasan elismerve", + "recognition_and_reward_survey_question_2_headline": "Úgy érzem, hogy méltóan jutalmaznak az elvégzett munkámért.", + "recognition_and_reward_survey_question_2_lower_label": "Nem méltányosan jutalmazva", + "recognition_and_reward_survey_question_2_upper_label": "Nagyon méltányosan jutalmazva", + "recognition_and_reward_survey_question_3_headline": "Kényelmesnek érzem, hogy nyíltan megoszthatom a véleményeimet a munkahelyemen.", + "recognition_and_reward_survey_question_3_lower_label": "Nem kényelmes", + "recognition_and_reward_survey_question_3_upper_label": "Nagyon kényelmes", + "recognition_and_reward_survey_question_4_headline": "Hogyan javíthatná a szervezet az elismerést és a jutalmazást?", + "recognition_and_reward_survey_question_4_placeholder": "Írja be ide a válaszát…", + "review_prompt_description": "A termékért rajongó felhasználók maghívása, hogy nyilvánosan véleményezzék azt.", + "review_prompt_name": "Véleményezési kérés", + "review_prompt_question_1_headline": "Hogy tetszik a(z) $[projectName] projekt?", + "review_prompt_question_1_lower_label": "Nem jó", + "review_prompt_question_1_upper_label": "Nagyon elégedett", + "review_prompt_question_2_button_label": "Értékelés írása", + "review_prompt_question_2_headline": "Örülünk, hogy ezt halljuk 🙏. Írjon nekünk értékelést!", + "review_prompt_question_2_html": "

    Ez sokat segít nekünk.

    ", + "review_prompt_question_3_button_label": "Küldés", + "review_prompt_question_3_headline": "Sajnálattal halljuk! Mi az az EGY dolog, amit jobban csinálhatnánk?", + "review_prompt_question_3_placeholder": "Írja be ide a válaszát…", + "review_prompt_question_3_subheader": "Segítsen nekünk javítani az élményét!", + "schedule_a_meeting": "Megbeszélés ütemezése", + "schedule_a_meeting_description": "A válaszadók megkérése, hogy foglaljanak időpontot találkozókra vagy hívásokra", + "single_select": "Egyválasztós", + "single_select_description": "Lehetőségek listájának felkínálása (egy választása)", + "site_abandonment_survey": "Webhelyelhagyási kérdőív", + "site_abandonment_survey_description": "A webáruházában való webhelyelhagyás mögötti okok megértése.", + "site_abandonment_survey_question_1_html": "

    Észrevettük, hogy vásárlás nélkül távozik a webhelyünkről. Szeretnénk megérteni, hogy miért.

    ", + "site_abandonment_survey_question_2_button_label": "Persze!", + "site_abandonment_survey_question_2_headline": "Van egy perce?", + "site_abandonment_survey_question_3_choice_1": "Nem találom, amit keresek", + "site_abandonment_survey_question_3_choice_2": "Találtam egy jobb webhelyet", + "site_abandonment_survey_question_3_choice_3": "A webhely túl lassú", + "site_abandonment_survey_question_3_choice_4": "Csak böngészek", + "site_abandonment_survey_question_3_choice_5": "Jobb árat találtam máshol", + "site_abandonment_survey_question_3_choice_6": "Egyéb", + "site_abandonment_survey_question_3_headline": "Mi az elsődleges oka annak, hogy elhagyja a webhelyünket?", + "site_abandonment_survey_question_3_subheader": "Válassza ki a következő lehetőségek egyikét:", + "site_abandonment_survey_question_4_headline": "Fejtse ki bővebben az okát, hogy miért távozik a webhelyről:", + "site_abandonment_survey_question_5_headline": "Hogyan értékelné a webhelyünkön szerzett összesített élményét?", + "site_abandonment_survey_question_5_lower_label": "Nagyon elégedetlen", + "site_abandonment_survey_question_5_upper_label": "Nagyon elégedett", + "site_abandonment_survey_question_6_choice_1": "Gyorsabb betöltési idők", + "site_abandonment_survey_question_6_choice_2": "Jobb termékkeresési funkció", + "site_abandonment_survey_question_6_choice_3": "Nagyobb termékválaszték", + "site_abandonment_survey_question_6_choice_4": "Továbbfejlesztett webhely-megjelenítés", + "site_abandonment_survey_question_6_choice_5": "Több vásárlói értékelés", + "site_abandonment_survey_question_6_headline": "Milyen fejlesztések ösztönöznének arra, hogy tovább maradjon a webhelyünkön?", + "site_abandonment_survey_question_6_subheader": "Válassza ki az összes megfelelőt:", + "site_abandonment_survey_question_7_headline": "Szeretne értesülni az új termékekről és ajánlatokról?", + "site_abandonment_survey_question_7_label": "Igen, vegyék fel velem a kapcsolatot.", + "site_abandonment_survey_question_8_headline": "Adja meg az e-mail-címét:", + "site_abandonment_survey_question_9_headline": "Van további megjegyzése vagy javaslata?", + "smileys_survey_name": "Hangulatjelek kérdőív", + "smileys_survey_question_1_headline": "Hogy tetszik a(z) $[projectName] projekt?", + "smileys_survey_question_1_lower_label": "Nem jó", + "smileys_survey_question_1_upper_label": "Nagyon elégedett", + "smileys_survey_question_2_button_label": "Értékelés írása", + "smileys_survey_question_2_headline": "Örülünk, hogy ezt halljuk 🙏. Írjon nekünk értékelést!", + "smileys_survey_question_2_html": "

    Ez sokat segít nekünk.

    ", + "smileys_survey_question_3_button_label": "Küldés", + "smileys_survey_question_3_headline": "Sajnálattal halljuk! Mi az az EGY dolog, amit jobban csinálhatnánk?", + "smileys_survey_question_3_placeholder": "Írja be ide a válaszát…", + "smileys_survey_question_3_subheader": "Segítsen nekünk javítani az élményét!", + "star_rating_survey_name": "A(z) $[projectName] értékelése kérdőív", + "star_rating_survey_question_1_headline": "Hogy tetszik a(z) $[projectName] projekt?", + "star_rating_survey_question_1_lower_label": "Rendkívül elégedetlen", + "star_rating_survey_question_1_upper_label": "Rendkívül elégedett", + "star_rating_survey_question_2_button_label": "Értékelés írása", + "star_rating_survey_question_2_headline": "Örülünk, hogy ezt halljuk 🙏. Írjon nekünk értékelést!", + "star_rating_survey_question_2_html": "

    Ez sokat segít nekünk.

    ", + "star_rating_survey_question_3_button_label": "Küldés", + "star_rating_survey_question_3_headline": "Sajnálattal halljuk! Mi az az EGY dolog, amit jobban csinálhatnánk?", + "star_rating_survey_question_3_placeholder": "Írja be ide a válaszát…", + "star_rating_survey_question_3_subheader": "Segítsen nekünk javítani az élményét!", + "statement_call_to_action": "Kijelentés (cselekvésre való felhívás)", + "strongly_agree": "Teljesen egyetértek", + "strongly_disagree": "Egyáltalán nem értek egyet", + "supportive_work_culture_survey_description": "A vezetői támogatás, a kommunikáció és az általános munkakörnyezet munkavállalói megítélésének értékelése.", + "supportive_work_culture_survey_name": "Támogató munkakultúra", + "supportive_work_culture_survey_question_1_headline": "A felettesem megadja nekem a munkám elvégzéséhez szükséges támogatást.", + "supportive_work_culture_survey_question_1_lower_label": "Nem támogatott", + "supportive_work_culture_survey_question_1_upper_label": "Erősen támogatott", + "supportive_work_culture_survey_question_2_headline": "A szervezeten belüli kommunikáció nyitott és hatékony.", + "supportive_work_culture_survey_question_2_lower_label": "Gyenge kommunikáció", + "supportive_work_culture_survey_question_2_upper_label": "Kitűnő kommunikáció", + "supportive_work_culture_survey_question_3_headline": "A munkakörnyezet pozitív és támogatja a jóllétemet.", + "supportive_work_culture_survey_question_3_lower_label": "Nem támogató", + "supportive_work_culture_survey_question_3_upper_label": "Nagyon támogató", + "supportive_work_culture_survey_question_4_headline": "Hogyan lehetne javítani a munkakultúrán, hogy jobban támogassa Önt?", + "supportive_work_culture_survey_question_4_placeholder": "Írja be ide a válaszát…", + "uncover_strengths_and_weaknesses_description": "Annak kiderítése, hogy a felhasználók mit szeretnek és mit nem szeretnek a termékével vagy szolgáltatásával kapcsolatban.", + "uncover_strengths_and_weaknesses_name": "Erősségek és gyengeségek feltárása", + "uncover_strengths_and_weaknesses_question_1_choice_1": "Könnyű használat", + "uncover_strengths_and_weaknesses_question_1_choice_2": "Jó ár-érték arány", + "uncover_strengths_and_weaknesses_question_1_choice_3": "Nyílt forráskódú", + "uncover_strengths_and_weaknesses_question_1_choice_4": "Az alapítók aranyosak", + "uncover_strengths_and_weaknesses_question_1_choice_5": "Egyéb", + "uncover_strengths_and_weaknesses_question_1_headline": "Mit értékel leginkább a(z) $[projectName] projektben?", + "uncover_strengths_and_weaknesses_question_2_choice_1": "Dokumentáció", + "uncover_strengths_and_weaknesses_question_2_choice_2": "Személyre szabhatóság", + "uncover_strengths_and_weaknesses_question_2_choice_3": "Árazás", + "uncover_strengths_and_weaknesses_question_2_choice_4": "Egyéb", + "uncover_strengths_and_weaknesses_question_2_headline": "Min kellene javítanunk?", + "uncover_strengths_and_weaknesses_question_2_subheader": "Válassza ki a következő lehetőségek egyikét:", + "uncover_strengths_and_weaknesses_question_3_headline": "Szeretne valamit hozzáfűzni?", + "uncover_strengths_and_weaknesses_question_3_subheader": "Nyugodtan mondja el a véleményét, mi is azt tesszük.", + "understand_low_engagement_description": "Az alacsony elkötelezettség okainak azonosítása a felhasználói elfogadottság javításához.", + "understand_low_engagement_name": "Alacsony elkötelezettség megértése", + "understand_low_engagement_question_1_choice_1": "Nehéz használni", + "understand_low_engagement_question_1_choice_2": "Találtam egy jobb alternatívát", + "understand_low_engagement_question_1_choice_3": "Csak nem volt rá időm", + "understand_low_engagement_question_1_choice_4": "Hiányoztak a számomra szükséges funkciók", + "understand_low_engagement_question_1_choice_5": "Egyéb", + "understand_low_engagement_question_1_headline": "Mi a fő oka annak, hogy nem tért vissza a(z) $[projectName] projekthez az utóbbi időben?", + "understand_low_engagement_question_2_headline": "Mi nehéz a(z) $[projectName] projekt használatával kapcsolatban?", + "understand_low_engagement_question_2_placeholder": "Írja be ide a válaszát…", + "understand_low_engagement_question_3_headline": "Értjük. Melyik alternatívát használja inkább?", + "understand_low_engagement_question_3_placeholder": "Írja be ide a válaszát…", + "understand_low_engagement_question_4_headline": "Értjük. Hogyan tudnánk megkönnyíteni Önnek a kezdést?", + "understand_low_engagement_question_4_placeholder": "Írja be ide a válaszát…", + "understand_low_engagement_question_5_headline": "Értjük. Milyen funkciók vagy működés hiányzott?", + "understand_low_engagement_question_5_placeholder": "Írja be ide a válaszát…", + "understand_low_engagement_question_6_headline": "Adjon meg további részleteket:", + "understand_low_engagement_question_6_placeholder": "Írja be ide a válaszát…", + "understand_purchase_intention_description": "Annak kiderítése, hogy a látogatók mennyire vannak közel a vásárláshoz vagy előfizetéshez.", + "understand_purchase_intention_name": "Vásárlási szándék megértése", + "understand_purchase_intention_question_1_headline": "Mennyire valószínű, hogy ma vásárol tőlünk?", + "understand_purchase_intention_question_1_lower_label": "Egyáltalán nem valószínű", + "understand_purchase_intention_question_1_upper_label": "Rendkívül valószínű", + "understand_purchase_intention_question_2_headline": "Értjük. Mi az elsődleges oka a mai látogatásának?", + "understand_purchase_intention_question_2_placeholder": "Írja be ide a válaszát…", + "understand_purchase_intention_question_3_headline": "Mi tartja vissza attól, hogy ma vásároljon (ha van ilyen indok)?", + "understand_purchase_intention_question_3_placeholder": "Írja be ide a válaszát…", + "usability_question_10_headline": "Sokat kellett tanulnom, mielőtt megfelelően tudtam használni a rendszert.", + "usability_question_1_headline": "Valószínűleg gyakran használnám ezt a rendszert.", + "usability_question_2_headline": "A rendszer bonyolultabbnak tűnt annál, mint amilyennek lennie kellett volna.", + "usability_question_3_headline": "A rendszer könnyen megérthető volt.", + "usability_question_4_headline": "Azt hiszem, egy műszaki szakértő segítségére lenne szükségem ennek a rendszernek a használatához.", + "usability_question_5_headline": "Úgy tűnt, hogy a rendszerben minden jól működik együtt.", + "usability_question_6_headline": "A rendszer következetlennek érződött, ahogy a dolgok működtek.", + "usability_question_7_headline": "Szerintem a legtöbb ember gyorsan meg tudta tanulni ennek a rendszernek a használatát.", + "usability_question_8_headline": "A rendszer használata körülményesnek tűnt.", + "usability_question_9_headline": "Magabiztosnak éreztem magam a rendszer használata során.", + "usability_rating_description": "Az érzékelt használhatóság mérése arra kérve a felhasználókat, hogy értékeljék a termékkel kapcsolatos tapasztalataikat egy szabványosított, 10 kérdésből álló kérdőív használatával.", + "usability_score_name": "Rendszer-használhatósági pontszám (SUS)" + } +} diff --git a/apps/web/locales/ja-JP.json b/apps/web/locales/ja-JP.json index 2e3fe3c08f..5cc9cb6d7b 100644 --- a/apps/web/locales/ja-JP.json +++ b/apps/web/locales/ja-JP.json @@ -187,6 +187,7 @@ "customer_success": "カスタマーサクセス", "dark_overlay": "暗いオーバーレイ", "date": "日付", + "days": "日", "default": "デフォルト", "delete": "削除", "description": "説明", @@ -253,6 +254,7 @@ "label": "ラベル", "language": "言語", "learn_more": "詳細を見る", + "license_expired": "License Expired", "light_overlay": "明るいオーバーレイ", "limits_reached": "上限に達しました", "link": "リンク", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks は より 大きな 画面 で最適に 作動します。 フォーム を 管理または 構築する には、 別の デバイス に 切り替える 必要が あります。", "mobile_overlay_surveys_look_good": "ご安心ください - お使い の デバイス や 画面 サイズ に 関係なく、 フォーム は 素晴らしく 見えます!", "mobile_overlay_title": "おっと、 小さな 画面 が 検出されました!", + "months": "ヶ月", "move_down": "下に移動", "move_up": "上に移動", "multiple_languages": "多言語", @@ -283,6 +286,7 @@ "no_background_image_found": "背景画像が見つかりません。", "no_code": "ノーコード", "no_files_uploaded": "ファイルがアップロードされていません", + "no_overlay": "オーバーレイなし", "no_quotas_found": "クォータが見つかりません", "no_result_found": "結果が見つかりません", "no_results": "結果なし", @@ -309,6 +313,7 @@ "organization_teams_not_found": "組織のチームが見つかりません", "other": "その他", "others": "その他", + "overlay_color": "オーバーレイの色", "overview": "概要", "password": "パスワード", "paused": "一時停止", @@ -348,6 +353,7 @@ "request_trial_license": "トライアルライセンスをリクエスト", "reset_to_default": "デフォルトにリセット", "response": "回答", + "response_id": "回答ID", "responses": "回答", "restart": "再開", "role": "役割", @@ -388,6 +394,7 @@ "status": "ステータス", "step_by_step_manual": "ステップバイステップマニュアル", "storage_not_configured": "ファイルストレージが設定されていないため、アップロードは失敗する可能性があります", + "string": "テキスト", "styling": "スタイル", "submit": "送信", "summary": "概要", @@ -443,6 +450,7 @@ "website_and_app_connection": "ウェブサイト&アプリ接続", "website_app_survey": "ウェブサイト&アプリフォーム", "website_survey": "ウェブサイトフォーム", + "weeks": "週間", "welcome_card": "ウェルカムカード", "workspace_configuration": "ワークスペース設定", "workspace_created_successfully": "ワークスペースが正常に作成されました", @@ -453,13 +461,15 @@ "workspace_not_found": "ワークスペースが見つかりません", "workspace_permission_not_found": "ワークスペースの権限が見つかりません", "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}件に達しました。", "you_have_reached_your_monthly_miu_limit_of": "月間MIU(月間アクティブユーザー)の上限に達しました", "you_have_reached_your_monthly_response_limit_of": "月間回答数の上限に達しました", - "you_will_be_downgraded_to_the_community_edition_on_date": "コミュニティ版へのダウングレードは {date} に行われます。" + "you_will_be_downgraded_to_the_community_edition_on_date": "コミュニティ版へのダウングレードは {date} に行われます。", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "承認", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "属性を更新しました", "attribute_value": "値", "attribute_value_placeholder": "属性値", + "attributes_msg_attribute_limit_exceeded": "最大制限の{limit}個の属性クラスを超えるため、{count}個の新しい属性を作成できませんでした。既存の属性は正常に更新されました。", + "attributes_msg_attribute_type_validation_error": "{error}(属性'{key}'のデータ型: {dataType})", + "attributes_msg_email_already_exists": "このメールアドレスはこの環境に既に存在するため、更新されませんでした。", + "attributes_msg_email_or_userid_required": "メールアドレスまたはユーザーIDのいずれかが必要です。既存の値は保持されました。", + "attributes_msg_new_attribute_created": "新しい属性'{key}'をタイプ'{dataType}'で作成しました", + "attributes_msg_userid_already_exists": "このユーザーIDはこの環境に既に存在するため、更新されませんでした。", "contact_deleted_successfully": "連絡先を正常に削除しました", "contact_not_found": "そのような連絡先は見つかりません", "contacts_table_refresh": "連絡先を更新", @@ -631,6 +647,11 @@ "create_key": "キーを作成", "create_new_attribute": "新しい属性を作成", "create_new_attribute_description": "セグメンテーション用の新しい属性を作成します。", + "custom_attributes": "カスタム属性", + "data_type": "データ型", + "data_type_cannot_be_changed": "データ型は作成後に変更できません", + "data_type_description": "この属性の保存方法とフィルタリング方法を選択してください", + "date_value_required": "日付の値が必要です。日付を設定したくない場合は、削除ボタンを使用してこの属性を削除してください。", "delete_attribute_confirmation": "{value, plural, one {選択した属性を削除します。この属性に関連付けられたすべてのコンタクトデータは失われます。} other {選択した属性を削除します。これらの属性に関連付けられたすべてのコンタクトデータは失われます。}}", "delete_contact_confirmation": "これにより、この連絡先に関連付けられているすべてのフォーム回答と連絡先属性が削除されます。この連絡先のデータに基づいたターゲティングとパーソナライゼーションはすべて失われます。", "delete_contact_confirmation_with_quotas": "{value, plural, one {これにより この連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。この連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。この連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。} other {これにより これらの連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。これらの連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。これらの連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "この属性のラベルと説明を更新します。", "edit_attribute_values": "属性を編集", "edit_attribute_values_description": "この連絡先の特定の属性の値を変更します。", + "edit_attributes": "属性を編集", "edit_attributes_success": "連絡先属性が正常に更新されました", "generate_personal_link": "個人リンクを生成", "generate_personal_link_description": "公開されたフォームを選択して、この連絡先用のパーソナライズされたリンクを生成します。", + "invalid_csv_column_names": "無効なCSV列名: {columns}。新しい属性となる列名は、小文字、数字、アンダースコアのみを含み、文字で始まる必要があります。", + "invalid_date_format": "無効な日付形式です。有効な日付を使用してください。", + "invalid_number_format": "無効な数値形式です。有効な数値を入力してください。", "no_published_link_surveys_available": "公開されたリンクフォームはありません。まずリンクフォームを公開してください。", "no_published_surveys": "公開されたフォームはありません", "no_responses_found": "回答が見つかりません", "not_provided": "提供されていません", + "number_value_required": "数値が必要です。この属性を削除するには削除ボタンを使用してください。", "personal_link_generated": "個人リンクが正常に生成されました", "personal_link_generated_but_clipboard_failed": "個人用リンクは生成されましたが、クリップボードへのコピーに失敗しました: {url}", "personal_survey_link": "個人調査リンク", @@ -653,13 +679,22 @@ "search_contact": "連絡先を検索", "select_a_survey": "フォームを選択", "select_attribute": "属性を選択", + "select_attribute_key": "属性キーを選択", + "system_attributes": "システム属性", "unlock_contacts_description": "連絡先を管理し、特定のフォームを送信します", "unlock_contacts_title": "上位プランで連絡先をアンロック", + "upload_contacts_error_attribute_type_mismatch": "属性「{key}」は「{dataType}」として型付けされていますが、CSVに無効な値が含まれています:{values}", + "upload_contacts_error_duplicate_mappings": "次の属性に重複したマッピングが見つかりました:{attributes}", + "upload_contacts_error_file_too_large": "ファイルサイズが最大制限の800KBを超えています", + "upload_contacts_error_generic": "連絡先のアップロード中にエラーが発生しました。後でもう一度お試しください。", + "upload_contacts_error_invalid_file_type": "CSVファイルをアップロードしてください", + "upload_contacts_error_no_valid_contacts": "アップロードされたCSVファイルには有効な連絡先が含まれていません。正しい形式についてはサンプルCSVファイルをご確認ください。", + "upload_contacts_modal_attribute_header": "Formbricks属性", "upload_contacts_modal_attributes_description": "CSVの列をFormbricksの属性にマッピングします。", "upload_contacts_modal_attributes_new": "新しい属性", "upload_contacts_modal_attributes_search_or_add": "属性を検索または追加", - "upload_contacts_modal_attributes_should_be_mapped_to": "は以下にマッピングする必要があります", "upload_contacts_modal_attributes_title": "属性", + "upload_contacts_modal_csv_column_header": "CSV列", "upload_contacts_modal_description": "CSVをアップロードして、属性を持つ連絡先をすばやくインポート", "upload_contacts_modal_download_example_csv": "CSVの例をダウンロード", "upload_contacts_modal_duplicates_description": "連絡先がすでに存在する場合、どのように処理しますか?", @@ -840,6 +875,40 @@ "no_attributes_yet": "属性がまだありません!", "no_filters_yet": "フィルターはまだありません!", "no_segments_yet": "保存されたセグメントはまだありません。", + "operator_contains": "を含む", + "operator_does_not_contain": "を含まない", + "operator_ends_with": "で終わる", + "operator_is_after": "より後", + "operator_is_before": "より前", + "operator_is_between": "の間である", + "operator_is_newer_than": "より新しい", + "operator_is_not_set": "設定されていない", + "operator_is_older_than": "より古い", + "operator_is_same_day": "同じ日である", + "operator_is_set": "設定されている", + "operator_starts_with": "で始まる", + "operator_title_contains": "を含む", + "operator_title_does_not_contain": "を含まない", + "operator_title_ends_with": "で終わる", + "operator_title_equals": "と等しい", + "operator_title_greater_equal": "以上", + "operator_title_greater_than": "より大きい", + "operator_title_is_after": "より後", + "operator_title_is_before": "より前", + "operator_title_is_between": "の間である", + "operator_title_is_newer_than": "より新しい", + "operator_title_is_not_set": "設定されていない", + "operator_title_is_older_than": "より古い", + "operator_title_is_same_day": "同じ日である", + "operator_title_is_set": "設定されている", + "operator_title_less_equal": "以下", + "operator_title_less_than": "より小さい", + "operator_title_not_equals": "等しくない", + "operator_title_starts_with": "で始まる", + "operator_title_user_is_in": "ユーザーが含まれる", + "operator_title_user_is_not_in": "ユーザーが含まれない", + "operator_user_is_in": "ユーザーが含まれる", + "operator_user_is_not_in": "ユーザーが含まれない", "person_and_attributes": "人物と属性", "phone": "電話", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "このセグメントを削除するには、まず以下のフォームから外してください。", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "ユーザーターゲティングは現在、利用条件を満たす場合のみ利用可能です", "value_cannot_be_empty": "値は空にできません。", "value_must_be_a_number": "値は数値である必要があります。", + "value_must_be_positive": "値は正の数である必要があります。", "view_filters": "フィルターを表示", "where": "条件", "with_the_formbricks_sdk": "Formbricks SDK を利用して" @@ -950,19 +1020,32 @@ "enterprise_features": "エンタープライズ機能", "get_an_enterprise_license_to_get_access_to_all_features": "すべての機能にアクセスするには、エンタープライズライセンスを取得してください。", "keep_full_control_over_your_data_privacy_and_security": "データのプライバシーとセキュリティを完全に制御できます。", + "license_invalid_description": "ENTERPRISE_LICENSE_KEY環境変数のライセンスキーが無効です。入力ミスがないか確認するか、新しいキーをリクエストしてください。", + "license_status": "ライセンスステータス", + "license_status_active": "有効", + "license_status_description": "エンタープライズライセンスのステータス。", + "license_status_expired": "期限切れ", + "license_status_invalid": "無効なライセンス", + "license_status_unreachable": "接続不可", + "license_unreachable_grace_period": "ライセンスサーバーに接続できません。エンタープライズ機能は{gracePeriodEnd}までの3日間の猶予期間中は引き続き利用できます。", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "電話不要、制約なし: このフォームに記入して、すべての機能をテストするための無料の30日間トライアルライセンスをリクエストしてください:", "no_credit_card_no_sales_call_just_test_it": "クレジットカード不要。営業電話もありません。ただテストしてください :)", "on_request": "リクエストに応じて", "organization_roles": "組織ロール(管理者、編集者、開発者など)", "questions_please_reach_out_to": "質問はありますか?こちらまでお問い合わせください", + "recheck_license": "ライセンスを再確認", + "recheck_license_failed": "ライセンスの確認に失敗しました。ライセンスサーバーに接続できない可能性があります。", + "recheck_license_invalid": "ライセンスキーが無効です。ENTERPRISE_LICENSE_KEYを確認してください。", + "recheck_license_success": "ライセンスの確認に成功しました", + "recheck_license_unreachable": "ライセンスサーバーに接続できません。後ほど再度お試しください。", + "rechecking": "再確認中...", "request_30_day_trial_license": "30日間トライアルライセンスをリクエスト", "saml_sso": "SAML SSO", "service_level_agreement": "サービスレベル契約", "soc2_hipaa_iso_27001_compliance_check": "SOC2、HIPAA、ISO 27001準拠チェック", "sso": "SSO(Google、Microsoft、OpenID Connect)", "teams": "チーム&アクセスロール(読み取り、読み書き、管理)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。", - "your_enterprise_license_is_active_all_features_unlocked": "あなたのエンタープライズライセンスは有効です。すべての機能がアンロックされました。" + "unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。" }, "general": { "bulk_invite_warning_description": "無料プランでは、すべての組織メンバーに常に「オーナー」ロールが割り当てられます。", @@ -986,7 +1069,7 @@ "from_your_organization": "あなたの組織から", "invitation_sent_once_more": "招待状を再度送信しました。", "invite_deleted_successfully": "招待を正常に削除しました", - "invited_on": "{date}に招待", + "invite_expires_on": "招待は{date}に期限切れ", "invites_failed": "招待に失敗しました", "leave_organization": "組織を離れる", "leave_organization_description": "この組織を離れ、すべてのフォームと回答へのアクセス権を失います。再度招待された場合にのみ再参加できます。", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "新しいワークスペースを追加するには、すべてのフィールドを入力してください。", "read": "読み取り", "read_write": "読み書き", - "select_member": "メンバーを選択", - "select_workspace": "ワークスペースを選択", "team_admin": "チーム管理者", "team_created_successfully": "チームを正常に作成しました。", "team_deleted_successfully": "チームを正常に削除しました。", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "質問がスキップされた場合に表示するプレースホルダーを追加:", "add_hidden_field_id": "非表示フィールドIDを追加", "add_highlight_border": "ハイライトボーダーを追加", - "add_highlight_border_description": "フォームカードに外側のボーダーを追加します。", "add_logic": "ロジックを追加", "add_none_of_the_above": "\"いずれも該当しません\" を追加", "add_option": "オプションを追加", @@ -1189,6 +1269,7 @@ "block_duplicated": "ブロックが複製されました。", "bold": "太字", "brand_color": "ブランドカラー", + "brand_color_description": "ボタン、リンク、ハイライトに適用されます。", "brightness": "明るさ", "bulk_edit": "一括編集", "bulk_edit_description": "以下のオプションを1行ずつ編集してください。空の行はスキップされ、重複は削除されます。", @@ -1206,7 +1287,9 @@ "capture_new_action": "新しいアクションをキャプチャ", "card_arrangement_for_survey_type_derived": "{surveyTypeDerived} フォームのカード配置", "card_background_color": "カードの背景色", + "card_background_color_description": "フォームカードエリアを塗りつぶします。", "card_border_color": "カードの枠線の色", + "card_border_color_description": "フォームカードの輪郭を描きます。", "card_styling": "カードのスタイル設定", "casual": "カジュアル", "caution_edit_duplicate": "複製して編集", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "古い回答と新しい回答が混ざり、データの概要が誤解を招く可能性があります。", "caution_recommendation": "これにより、フォームの概要にデータの不整合が生じる可能性があります。代わりにフォームを複製することをお勧めします。", "caution_text": "変更は不整合を引き起こします", - "centered_modal_overlay_color": "中央モーダルのオーバーレイ色", "change_anyway": "とにかく変更", "change_background": "背景を変更", "change_question_type": "質問の種類を変更", "change_survey_type": "フォームの種類を変更すると、既存のアクセスに影響します", - "change_the_background_color_of_the_card": "カードの背景色を変更します。", - "change_the_background_color_of_the_input_fields": "入力フィールドの背景色を変更します。", "change_the_background_to_a_color_image_or_animation": "背景を色、画像、またはアニメーションに変更します。", - "change_the_border_color_of_the_card": "カードの枠線の色を変更します。", - "change_the_border_color_of_the_input_fields": "入力フィールドの枠線の色を変更します。", - "change_the_border_radius_of_the_card_and_the_inputs": "カードと入力の角丸を変更します。", - "change_the_brand_color_of_the_survey": "フォームのブランドカラーを変更します。", "change_the_placement_of_this_survey": "このフォームの配置を変更します。", - "change_the_question_color_of_the_survey": "フォームの質問の色を変更します。", "changes_saved": "変更を保存しました。", "changing_survey_type_will_remove_existing_distribution_channels": "フォームの種類を変更すると、共有方法に影響します。回答者が現在のタイプのアクセスリンクをすでに持っている場合、切り替え後にアクセスを失う可能性があります。", "checkbox_label": "チェックボックスのラベル", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "フォームの進捗状況の表示を無効にする。", "display_an_estimate_of_completion_time_for_survey": "フォームの完了時間の目安を表示", "display_number_of_responses_for_survey": "フォームの回答数を表示", + "display_type": "表示タイプ", "divide": "除算 /", "does_not_contain": "を含まない", "does_not_end_with": "で終わらない", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "のすべてを含まない", "does_not_include_one_of": "のいずれも含まない", "does_not_start_with": "で始まらない", + "dropdown": "ドロップダウン", "duplicate_block": "ブロックを複製", "duplicate_question": "質問を複製", "edit_link": "編集 リンク", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "プログレスバーを非表示", "hide_question_settings": "質問設定を非表示", "hostname": "ホスト名", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "{surveyTypeDerived} フォームのカードをどれくらいユニークにしますか", "if_you_need_more_please": "さらに必要な場合は、", "if_you_really_want_that_answer_ask_until_you_get_it": "回答が提出されるまで、トリガーされるたびに表示し続けます。", "ignore_global_waiting_time": "クールダウン期間を無視", @@ -1379,7 +1455,9 @@ "initial_value": "初期値", "inner_text": "内部テキスト", "input_border_color": "入力の枠線の色", + "input_border_color_description": "テキスト入力とテキストエリアの輪郭を描きます。", "input_color": "入力の色", + "input_color_description": "テキスト入力の内側を塗りつぶします。", "insert_link": "リンク を 挿入", "invalid_targeting": "無効なターゲティング: オーディエンスフィルターを確認してください", "invalid_video_url_warning": "有効なYouTube、Vimeo、またはLoomのURLを入力してください。現在、他の動画ホスティングプロバイダーはサポートしていません。", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "アップロードの最大ファイルサイズを制限します。", "limit_upload_file_size_to": "アップロードファイルサイズの上限", "link_survey_description": "フォームページへのリンクを共有するか、ウェブページやメールに埋め込みます。", + "list": "リスト", "load_segment": "セグメントを読み込み", "logic_error_warning": "変更するとロジックエラーが発生します", "logic_error_warning_text": "質問の種類を変更すると、この質問のロジック条件が削除されます", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "PINを持つユーザーのみがフォームにアクセスできます。", "publish": "公開", "question": "質問", - "question_color": "質問の色", "question_deleted": "質問を削除しました。", "question_duplicated": "質問を複製しました。", "question_id_updated": "質問IDを更新しました", "question_used_in_logic_warning_text": "このブロックの要素はロジックルールで使用されていますが、本当に削除しますか?", "question_used_in_logic_warning_title": "ロジックの不整合", - "question_used_in_quota": "この 質問 は \"{quotaName}\" の クオータ に使用されています", + "question_used_in_quota": "この質問は“{quotaName}”クォータで使用されています", "question_used_in_recall": "この 質問 は 質問 {questionIndex} で 呼び出され て います 。", "question_used_in_recall_ending_card": "この 質問 は エンディング カード で 呼び出され て います。", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "回答数の上限、リダイレクトなど。", "response_options": "回答オプション", "roundness": "丸み", + "roundness_description": "カードの角の丸みを調整します。", "row_used_in_logic_error": "この行は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。", "rows": "行", "save_and_close": "保存して閉じる", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "スタイルをテーマのスタイルに設定しました", "subheading": "サブ見出し", "subtract": "減算 -", - "suggest_colors": "色を提案", "survey_completed_heading": "フォームが完了しました", "survey_completed_subheading": "この無料のオープンソースフォームは閉鎖されました", "survey_display_settings": "フォーム表示設定", @@ -1642,7 +1720,7 @@ "validation_rules": "検証ルール", "validation_rules_description": "次の条件を満たす回答のみを受け付ける", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "変数 \"{variableName}\" は \"{quotaName}\" クォータ で使用されています", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "変数“{variableName}”は“{quotaName}”クォータで使用されています", "variable_name_conflicts_with_hidden_field": "変数名が既存の非表示フィールドIDと競合しています。", "variable_name_is_already_taken_please_choose_another": "変数名はすでに使用されています。別の名前を選択してください。", "variable_name_must_start_with_a_letter": "変数名はアルファベットで始まらなければなりません。", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "背景色を追加", "add_background_color_description": "ロゴコンテナに背景色を追加します。", + "advanced_styling_field_border_radius": "境界線の丸み", + "advanced_styling_field_button_bg": "ボタンの背景", + "advanced_styling_field_button_bg_description": "次へ/送信ボタンを塗りつぶします。", + "advanced_styling_field_button_border_radius_description": "ボタンの角を丸めます。", + "advanced_styling_field_button_font_size_description": "ボタンラベルのテキストサイズを調整します。", + "advanced_styling_field_button_font_weight_description": "ボタンテキストを細くまたは太くします。", + "advanced_styling_field_button_height_description": "ボタンの高さを調整します。", + "advanced_styling_field_button_padding_x_description": "左右にスペースを追加します。", + "advanced_styling_field_button_padding_y_description": "上下にスペースを追加します。", + "advanced_styling_field_button_text": "ボタンのテキスト", + "advanced_styling_field_button_text_description": "ボタン内のラベルに色を付けます。", + "advanced_styling_field_description_color": "説明文の色", + "advanced_styling_field_description_color_description": "各見出しの下のテキストに色を付けます。", + "advanced_styling_field_description_size": "説明文のフォントサイズ", + "advanced_styling_field_description_size_description": "説明テキストのサイズを調整します。", + "advanced_styling_field_description_weight": "説明文のフォントの太さ", + "advanced_styling_field_description_weight_description": "説明テキストを細くまたは太くします。", + "advanced_styling_field_font_size": "フォントサイズ", + "advanced_styling_field_font_weight": "フォントの太さ", + "advanced_styling_field_headline_color": "見出しの色", + "advanced_styling_field_headline_color_description": "メインの質問テキストに色を付けます。", + "advanced_styling_field_headline_size": "見出しのフォントサイズ", + "advanced_styling_field_headline_size_description": "見出しテキストのサイズを調整します。", + "advanced_styling_field_headline_weight": "見出しのフォントの太さ", + "advanced_styling_field_headline_weight_description": "見出しテキストを細くまたは太くします。", + "advanced_styling_field_height": "最小の高さ", + "advanced_styling_field_indicator_bg": "インジケーターの背景", + "advanced_styling_field_indicator_bg_description": "バーの塗りつぶし部分に色を付けます。", + "advanced_styling_field_input_border_radius_description": "入力フィールドの角を丸めます。", + "advanced_styling_field_input_font_size_description": "入力フィールド内の入力テキストのサイズを調整します。", + "advanced_styling_field_input_height_description": "入力フィールドの最小の高さを制御します。", + "advanced_styling_field_input_padding_x_description": "左右にスペースを追加します。", + "advanced_styling_field_input_padding_y_description": "上下にスペースを追加します。", + "advanced_styling_field_input_placeholder_opacity_description": "プレースホルダーのヒントテキストを薄くします。", + "advanced_styling_field_input_shadow_description": "入力フィールドの周囲にドロップシャドウを追加します。", + "advanced_styling_field_input_text": "入力テキスト", + "advanced_styling_field_input_text_description": "入力フィールドに入力されたテキストの色を設定します。", + "advanced_styling_field_option_bg": "背景", + "advanced_styling_field_option_bg_description": "オプション項目を塗りつぶします。", + "advanced_styling_field_option_border_radius_description": "オプションの角を丸くします。", + "advanced_styling_field_option_font_size_description": "オプションラベルのテキストサイズを調整します。", + "advanced_styling_field_option_label": "ラベルの色", + "advanced_styling_field_option_label_description": "オプションラベルのテキストの色を設定します。", + "advanced_styling_field_option_padding_x_description": "左右にスペースを追加します。", + "advanced_styling_field_option_padding_y_description": "上下にスペースを追加します。", + "advanced_styling_field_padding_x": "パディングX", + "advanced_styling_field_padding_y": "パディングY", + "advanced_styling_field_placeholder_opacity": "プレースホルダーの不透明度", + "advanced_styling_field_shadow": "影", + "advanced_styling_field_track_bg": "トラックの背景", + "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_section_buttons": "ボタン", + "advanced_styling_section_headlines": "見出しと説明", + "advanced_styling_section_inputs": "入力フィールド", + "advanced_styling_section_options": "選択肢(ラジオボタン/チェックボックス)", "app_survey_placement": "アプリ内フォームの配置", "app_survey_placement_settings_description": "Webアプリまたはウェブサイトでフォームを表示する場所を変更します。", - "centered_modal_overlay_color": "中央モーダルのオーバーレイ色", "email_customization": "メールのカスタマイズ", "email_customization_description": "Formbricksがあなたに代わって送信するメールの外観を変更します。", "enable_custom_styling": "カスタムスタイルを有効化", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricksブランディングは非表示です。", "formbricks_branding_settings_description": "あなたのサポートに感謝していますが、オフにすることもご理解いただけます。", "formbricks_branding_shown": "Formbricksブランディングは表示されています。", + "generate_theme_btn": "生成", + "generate_theme_confirmation": "ブランドカラーに基づいて、マッチするカラーテーマを生成しますか?現在のカラー設定は上書きされます。", + "generate_theme_header": "カラーテーマを生成しますか?", "logo_removed_successfully": "ロゴを正常に削除しました", "logo_settings_description": "会社のロゴをアップロードして、アンケートとリンクプレビューにブランディングを適用します。", "logo_updated_successfully": "ロゴを正常に更新しました", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "{type}アンケートにFormbricksブランディングを表示", "show_powered_by_formbricks": "「Powered by Formbricks」署名を表示", "styling_updated_successfully": "スタイルを正常に更新しました", + "suggest_colors": "カラーを提案", + "suggested_colors_applied_please_save": "推奨カラーが正常に生成されました。変更を保存するには「保存」を押してください。", "theme": "テーマ", "theme_settings_description": "すべてのアンケート用のスタイルテーマを作成します。各アンケートでカスタムスタイルを有効にできます。" }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "はい、最新情報を知りたいです。", "preview_survey_question_2_choice_2_label": "いいえ、結構です!", "preview_survey_question_2_headline": "最新情報を知りたいですか?", + "preview_survey_question_2_subheader": "これは説明の例です。", "preview_survey_welcome_card_headline": "ようこそ!", "prioritize_features_description": "ユーザーが最も必要とする機能と最も必要としない機能を特定する。", "prioritize_features_name": "機能の優先順位付け", diff --git a/apps/web/locales/nl-NL.json b/apps/web/locales/nl-NL.json index d67344a575..4437b6435b 100644 --- a/apps/web/locales/nl-NL.json +++ b/apps/web/locales/nl-NL.json @@ -187,6 +187,7 @@ "customer_success": "Klant succes", "dark_overlay": "Donkere overlay", "date": "Datum", + "days": "dagen", "default": "Standaard", "delete": "Verwijderen", "description": "Beschrijving", @@ -253,6 +254,7 @@ "label": "Label", "language": "Taal", "learn_more": "Meer informatie", + "license_expired": "License Expired", "light_overlay": "Lichte overlay", "limits_reached": "Grenzen bereikt", "link": "Link", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks werkt het beste op een groter scherm. Schakel over naar een ander apparaat om enquêtes te beheren of samen te stellen.", "mobile_overlay_surveys_look_good": "Maakt u zich geen zorgen: uw enquêtes zien er geweldig uit op elk apparaat en schermformaat!", "mobile_overlay_title": "Oeps, klein scherm gedetecteerd!", + "months": "maanden", "move_down": "Ga naar beneden", "move_up": "Ga omhoog", "multiple_languages": "Meerdere talen", @@ -283,6 +286,7 @@ "no_background_image_found": "Geen achtergrondafbeelding gevonden.", "no_code": "Geen code", "no_files_uploaded": "Er zijn geen bestanden geüpload", + "no_overlay": "Geen overlay", "no_quotas_found": "Geen quota gevonden", "no_result_found": "Geen resultaat gevonden", "no_results": "Geen resultaten", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Organisatieteams niet gevonden", "other": "Ander", "others": "Anderen", + "overlay_color": "Overlaykleur", "overview": "Overzicht", "password": "Wachtwoord", "paused": "Gepauzeerd", @@ -348,6 +353,7 @@ "request_trial_license": "Proeflicentie aanvragen", "reset_to_default": "Resetten naar standaard", "response": "Antwoord", + "response_id": "Antwoord-ID", "responses": "Reacties", "restart": "Opnieuw opstarten", "role": "Rol", @@ -388,6 +394,7 @@ "status": "Status", "step_by_step_manual": "Stap voor stap handleiding", "storage_not_configured": "Bestandsopslag is niet ingesteld, uploads zullen waarschijnlijk mislukken", + "string": "Tekst", "styling": "Styling", "submit": "Indienen", "summary": "Samenvatting", @@ -443,6 +450,7 @@ "website_and_app_connection": "Website- en app-verbinding", "website_app_survey": "Website- en app-enquête", "website_survey": "Website-enquête", + "weeks": "weken", "welcome_card": "Welkomstkaart", "workspace_configuration": "Werkruimte-configuratie", "workspace_created_successfully": "Project succesvol aangemaakt", @@ -453,13 +461,15 @@ "workspace_not_found": "Werkruimte niet gevonden", "workspace_permission_not_found": "Werkruimte-machtiging niet gevonden", "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.", "you_have_reached_your_monthly_miu_limit_of": "U heeft uw maandelijkse MIU-limiet van bereikt", "you_have_reached_your_monthly_response_limit_of": "U heeft uw maandelijkse responslimiet bereikt van", - "you_will_be_downgraded_to_the_community_edition_on_date": "Je wordt gedowngraded naar de Community-editie op {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Je wordt gedowngraded naar de Community-editie op {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Accepteren", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Attribuut succesvol bijgewerkt", "attribute_value": "Waarde", "attribute_value_placeholder": "Attribuutwaarde", + "attributes_msg_attribute_limit_exceeded": "Kon {count} nieuwe attribu(u)t(en) niet aanmaken omdat dit de maximale limiet van {limit} attribuutklassen zou overschrijden. Bestaande attributen zijn succesvol bijgewerkt.", + "attributes_msg_attribute_type_validation_error": "{error} (attribuut '{key}' heeft dataType: {dataType})", + "attributes_msg_email_already_exists": "Het e-mailadres bestaat al voor deze omgeving en is niet bijgewerkt.", + "attributes_msg_email_or_userid_required": "E-mailadres of userId is vereist. De bestaande waarden zijn behouden.", + "attributes_msg_new_attribute_created": "Nieuw attribuut '{key}' aangemaakt met type '{dataType}'", + "attributes_msg_userid_already_exists": "De userId bestaat al voor deze omgeving en is niet bijgewerkt.", "contact_deleted_successfully": "Contact succesvol verwijderd", "contact_not_found": "Er is geen dergelijk contact gevonden", "contacts_table_refresh": "Vernieuw contacten", @@ -631,6 +647,11 @@ "create_key": "Sleutel aanmaken", "create_new_attribute": "Nieuw attribuut aanmaken", "create_new_attribute_description": "Maak een nieuw attribuut aan voor segmentatiedoeleinden.", + "custom_attributes": "Aangepaste kenmerken", + "data_type": "Gegevenstype", + "data_type_cannot_be_changed": "Gegevenstype kan niet worden gewijzigd na aanmaak", + "data_type_description": "Kies hoe dit attribuut moet worden opgeslagen en gefilterd", + "date_value_required": "Datumwaarde is vereist. Gebruik de verwijderknop om dit attribuut te verwijderen als je geen datum wilt instellen.", "delete_attribute_confirmation": "{value, plural, one {Dit verwijdert het geselecteerde attribuut. Alle contactgegevens die aan dit attribuut zijn gekoppeld, gaan verloren.} other {Dit verwijdert de geselecteerde attributen. Alle contactgegevens die aan deze attributen zijn gekoppeld, gaan verloren.}}", "delete_contact_confirmation": "Hierdoor worden alle enquêtereacties en contactkenmerken verwijderd die aan dit contact zijn gekoppeld. Elke targeting en personalisatie op basis van de gegevens van dit contact gaat verloren.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Dit verwijdert alle enquêteresultaten en contactattributen die aan dit contact zijn gekoppeld. Alle targeting en personalisatie op basis van de gegevens van dit contact gaan verloren. Als dit contact reacties heeft die meetellen voor enquêtekvota, worden de quotawaarden verlaagd maar blijven de limieten ongewijzigd.} other {Dit verwijdert alle enquêteresultaten en contactattributen die aan deze contacten zijn gekoppeld. Alle targeting en personalisatie op basis van de gegevens van deze contacten gaan verloren. Als deze contacten reacties hebben die meetellen voor enquêtekvota, worden de quotawaarden verlaagd maar blijven de limieten ongewijzigd.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Werk het label en de beschrijving voor dit attribuut bij.", "edit_attribute_values": "Attributen bewerken", "edit_attribute_values_description": "Wijzig de waarden voor specifieke attributen voor dit contact.", + "edit_attributes": "Attributen bewerken", "edit_attributes_success": "Contactattributen succesvol bijgewerkt", "generate_personal_link": "Persoonlijke link genereren", "generate_personal_link_description": "Selecteer een gepubliceerde enquête om een gepersonaliseerde link voor dit contact te genereren.", + "invalid_csv_column_names": "Ongeldige CSV-kolomna(a)m(en): {columns}. Kolomnamen die nieuwe kenmerken worden, mogen alleen kleine letters, cijfers en underscores bevatten en moeten beginnen met een letter.", + "invalid_date_format": "Ongeldig datumformaat. Gebruik een geldige datum.", + "invalid_number_format": "Ongeldig getalformaat. Voer een geldig getal in.", "no_published_link_surveys_available": "Geen gepubliceerde link-enquêtes beschikbaar. Publiceer eerst een link-enquête.", "no_published_surveys": "Geen gepubliceerde enquêtes", "no_responses_found": "Geen reacties gevonden", "not_provided": "Niet voorzien", + "number_value_required": "Getalwaarde is verplicht. Gebruik de verwijderknop om dit attribuut te verwijderen.", "personal_link_generated": "Persoonlijke link succesvol gegenereerd", "personal_link_generated_but_clipboard_failed": "Persoonlijke link gegenereerd maar kopiëren naar klembord mislukt: {url}", "personal_survey_link": "Persoonlijke enquêtelink", @@ -653,13 +679,22 @@ "search_contact": "Zoek contactpersoon", "select_a_survey": "Selecteer een enquête", "select_attribute": "Selecteer Kenmerk", + "select_attribute_key": "Selecteer kenmerksleutel", + "system_attributes": "Systeemkenmerken", "unlock_contacts_description": "Beheer contacten en verstuur gerichte enquêtes", "unlock_contacts_title": "Ontgrendel contacten met een hoger abonnement", + "upload_contacts_error_attribute_type_mismatch": "Attribuut \"{key}\" is getypeerd als \"{dataType}\" maar CSV bevat ongeldige waarden: {values}", + "upload_contacts_error_duplicate_mappings": "Dubbele koppelingen gevonden voor de volgende attributen: {attributes}", + "upload_contacts_error_file_too_large": "Bestandsgrootte overschrijdt de maximale limiet van 800KB", + "upload_contacts_error_generic": "Er is een fout opgetreden bij het uploaden van de contacten. Probeer het later opnieuw.", + "upload_contacts_error_invalid_file_type": "Upload een CSV-bestand", + "upload_contacts_error_no_valid_contacts": "Het geüploade CSV-bestand bevat geen geldige contacten, zie het voorbeeld CSV-bestand voor het juiste formaat.", + "upload_contacts_modal_attribute_header": "Formbricks attribuut", "upload_contacts_modal_attributes_description": "Wijs de kolommen in uw CSV toe aan de attributen in Formbricks.", "upload_contacts_modal_attributes_new": "Nieuw attribuut", "upload_contacts_modal_attributes_search_or_add": "Kenmerk zoeken of toevoegen", - "upload_contacts_modal_attributes_should_be_mapped_to": "in kaart moeten worden gebracht", "upload_contacts_modal_attributes_title": "Kenmerken", + "upload_contacts_modal_csv_column_header": "CSV kolom", "upload_contacts_modal_description": "Upload een CSV om snel contacten met attributen te importeren", "upload_contacts_modal_download_example_csv": "Voorbeeld-CSV downloaden", "upload_contacts_modal_duplicates_description": "Hoe moeten we omgaan als er al een contact bestaat in uw contacten?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Nog geen attributen!", "no_filters_yet": "Er zijn nog geen filters!", "no_segments_yet": "Je hebt momenteel geen opgeslagen segmenten.", + "operator_contains": "bevat", + "operator_does_not_contain": "bevat niet", + "operator_ends_with": "eindigt met", + "operator_is_after": "is na", + "operator_is_before": "is eerder", + "operator_is_between": "is tussen", + "operator_is_newer_than": "is nieuwer dan", + "operator_is_not_set": "is niet ingesteld", + "operator_is_older_than": "is ouder dan", + "operator_is_same_day": "is dezelfde dag", + "operator_is_set": "is ingesteld", + "operator_starts_with": "begint met", + "operator_title_contains": "Bevat", + "operator_title_does_not_contain": "Bevat niet", + "operator_title_ends_with": "Eindigt met", + "operator_title_equals": "Gelijk aan", + "operator_title_greater_equal": "Groter dan of gelijk aan", + "operator_title_greater_than": "Groter dan", + "operator_title_is_after": "Is na", + "operator_title_is_before": "Is eerder", + "operator_title_is_between": "Is tussen", + "operator_title_is_newer_than": "Is nieuwer dan", + "operator_title_is_not_set": "Is niet ingesteld", + "operator_title_is_older_than": "Is ouder dan", + "operator_title_is_same_day": "Is dezelfde dag", + "operator_title_is_set": "Is ingesteld", + "operator_title_less_equal": "Kleiner dan of gelijk aan", + "operator_title_less_than": "Kleiner dan", + "operator_title_not_equals": "Is niet gelijk aan", + "operator_title_starts_with": "Begint met", + "operator_title_user_is_in": "Gebruiker zit in", + "operator_title_user_is_not_in": "Gebruiker zit niet in", + "operator_user_is_in": "Gebruiker zit in", + "operator_user_is_not_in": "Gebruiker zit niet in", "person_and_attributes": "Persoon & attributen", "phone": "Telefoon", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Verwijder het segment uit deze enquêtes om het te kunnen verwijderen.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "Gebruikerstargeting is momenteel alleen beschikbaar wanneer", "value_cannot_be_empty": "Waarde kan niet leeg zijn.", "value_must_be_a_number": "Waarde moet een getal zijn.", + "value_must_be_positive": "Waarde moet een positief getal zijn.", "view_filters": "Bekijk filters", "where": "Waar", "with_the_formbricks_sdk": "met de Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "Enterprise-functies", "get_an_enterprise_license_to_get_access_to_all_features": "Ontvang een Enterprise-licentie om toegang te krijgen tot alle functies.", "keep_full_control_over_your_data_privacy_and_security": "Houd de volledige controle over de privacy en beveiliging van uw gegevens.", + "license_invalid_description": "De licentiesleutel in je ENTERPRISE_LICENSE_KEY omgevingsvariabele is niet geldig. Controleer op typefouten of vraag een nieuwe sleutel aan.", + "license_status": "Licentiestatus", + "license_status_active": "Actief", + "license_status_description": "Status van je enterprise-licentie.", + "license_status_expired": "Verlopen", + "license_status_invalid": "Ongeldige licentie", + "license_status_unreachable": "Niet bereikbaar", + "license_unreachable_grace_period": "Licentieserver is niet bereikbaar. Je enterprise functies blijven actief tijdens een respijtperiode van 3 dagen die eindigt op {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Geen telefoontje nodig, geen verplichtingen: vraag een gratis proeflicentie van 30 dagen aan om alle functies te testen door dit formulier in te vullen:", "no_credit_card_no_sales_call_just_test_it": "Geen creditcard. Geen verkoopgesprek. Gewoon testen :)", "on_request": "Op aanvraag", "organization_roles": "Organisatierollen (beheerder, redacteur, ontwikkelaar, etc.)", "questions_please_reach_out_to": "Vragen? Neem contact op met", + "recheck_license": "Licentie opnieuw controleren", + "recheck_license_failed": "Licentiecontrole mislukt. De licentieserver is mogelijk niet bereikbaar.", + "recheck_license_invalid": "De licentiesleutel is ongeldig. Controleer je ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Licentiecontrole geslaagd", + "recheck_license_unreachable": "Licentieserver is niet bereikbaar. Probeer het later opnieuw.", + "rechecking": "Opnieuw controleren...", "request_30_day_trial_license": "Vraag een proeflicentie van 30 dagen aan", "saml_sso": "SAML-SSO", "service_level_agreement": "Service Level Overeenkomst", "soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 Conformiteitscontrole", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Teams en toegangsrollen (lezen, lezen en schrijven, beheren)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis.", - "your_enterprise_license_is_active_all_features_unlocked": "Uw Enterprise-licentie is actief. Alle functies ontgrendeld." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis." }, "general": { "bulk_invite_warning_description": "Bij het gratis abonnement krijgen alle organisatieleden altijd de rol 'Eigenaar' toegewezen.", @@ -986,7 +1069,7 @@ "from_your_organization": "vanuit uw organisatie", "invitation_sent_once_more": "Uitnodiging nogmaals verzonden.", "invite_deleted_successfully": "Uitnodiging succesvol verwijderd", - "invited_on": "Uitgenodigd op {date}", + "invite_expires_on": "Uitnodiging verloopt op {date}", "invites_failed": "Uitnodigingen zijn mislukt", "leave_organization": "Verlaat de organisatie", "leave_organization_description": "U verlaat deze organisatie en verliest de toegang tot alle enquêtes en reacties. Je kunt alleen weer meedoen als je opnieuw wordt uitgenodigd.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Vul alle velden in om een nieuwe werkruimte toe te voegen.", "read": "Lezen", "read_write": "Lezen en schrijven", - "select_member": "Selecteer lid", - "select_workspace": "Selecteer werkruimte", "team_admin": "Teambeheerder", "team_created_successfully": "Team succesvol aangemaakt.", "team_deleted_successfully": "Team succesvol verwijderd.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Voeg een tijdelijke aanduiding toe om aan te geven of er geen waarde is om te onthouden.", "add_hidden_field_id": "Voeg een verborgen veld-ID toe", "add_highlight_border": "Markeerrand toevoegen", - "add_highlight_border_description": "Voeg een buitenrand toe aan uw enquêtekaart.", "add_logic": "Voeg logica toe", "add_none_of_the_above": "Voeg 'Geen van bovenstaande' toe", "add_option": "Optie toevoegen", @@ -1189,6 +1269,7 @@ "block_duplicated": "Blok gedupliceerd.", "bold": "Vetgedrukt", "brand_color": "Merk kleur", + "brand_color_description": "Toegepast op knoppen, links en highlights.", "brightness": "Helderheid", "bulk_edit": "Bulkbewerking", "bulk_edit_description": "Bewerk alle opties hieronder, één per regel. Lege regels worden overgeslagen en duplicaten verwijderd.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Leg nieuwe actie vast", "card_arrangement_for_survey_type_derived": "Kaartarrangement voor {surveyTypeDerived} enquêtes", "card_background_color": "Achtergrondkleur van de kaart", + "card_background_color_description": "Vult het enquêtekaartgebied.", "card_border_color": "Randkleur kaart", + "card_border_color_description": "Omlijnt de enquêtekaart.", "card_styling": "Kaartstijl", "casual": "Casual", "caution_edit_duplicate": "Dupliceren en bewerken", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Oudere en nieuwere antwoorden lopen door elkaar heen, wat kan leiden tot misleidende gegevenssamenvattingen.", "caution_recommendation": "Dit kan inconsistenties in de gegevens in de onderzoekssamenvatting veroorzaken. Wij raden u aan de enquête te dupliceren.", "caution_text": "Veranderingen zullen tot inconsistenties leiden", - "centered_modal_overlay_color": "Gecentreerde modale overlaykleur", "change_anyway": "Hoe dan ook veranderen", "change_background": "Achtergrond 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_color_of_the_card": "Verander de achtergrondkleur van de kaart.", - "change_the_background_color_of_the_input_fields": "Verander de achtergrondkleur van de invoervelden.", "change_the_background_to_a_color_image_or_animation": "Verander de achtergrond in een kleur, afbeelding of animatie.", - "change_the_border_color_of_the_card": "Verander de randkleur van de kaart.", - "change_the_border_color_of_the_input_fields": "Wijzig de randkleur van de invoervelden.", - "change_the_border_radius_of_the_card_and_the_inputs": "Wijzig de randradius van de kaart en de ingangen.", - "change_the_brand_color_of_the_survey": "Wijzig de merkkleur van de enquête.", "change_the_placement_of_this_survey": "Wijzig de plaatsing van deze enquête.", - "change_the_question_color_of_the_survey": "Verander de vraagkleur van de enquête.", "changes_saved": "Wijzigingen opgeslagen.", "changing_survey_type_will_remove_existing_distribution_channels": "Het wijzigen van het enquêtetype heeft invloed op de manier waarop deze kan worden gedeeld. Als respondenten al toegangslinks hebben voor het huidige type, verliezen ze mogelijk de toegang na de overstap.", "checkbox_label": "Selectievakje-label", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Schakel de zichtbaarheid van de voortgang van het onderzoek uit.", "display_an_estimate_of_completion_time_for_survey": "Geef een schatting weer van de voltooiingstijd voor het onderzoek", "display_number_of_responses_for_survey": "Weergave aantal reacties voor enquête", + "display_type": "Weergavetype", "divide": "Verdeling /", "does_not_contain": "Bevat niet", "does_not_end_with": "Eindigt niet met", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Omvat niet alles", "does_not_include_one_of": "Bevat niet een van", "does_not_start_with": "Begint niet met", + "dropdown": "Dropdown", "duplicate_block": "Blok dupliceren", "duplicate_question": "Vraag dupliceren", "edit_link": "Link bewerken", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Voortgangsbalk verbergen", "hide_question_settings": "Vraaginstellingen verbergen", "hostname": "Hostnaam", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Hoe funky wil je je kaarten hebben in {surveyTypeDerived} Enquêtes", "if_you_need_more_please": "Als je meer nodig hebt,", "if_you_really_want_that_answer_ask_until_you_get_it": "Blijf tonen wanneer geactiveerd totdat een reactie is ingediend.", "ignore_global_waiting_time": "Afkoelperiode negeren", @@ -1379,7 +1455,9 @@ "initial_value": "Initiële waarde", "inner_text": "Innerlijke tekst", "input_border_color": "Randkleur invoeren", + "input_border_color_description": "Omlijnt tekstvelden en tekstgebieden.", "input_color": "Kleur invoeren", + "input_color_description": "Vult de binnenkant van tekstvelden.", "insert_link": "Link invoegen", "invalid_targeting": "Ongeldige targeting: controleer uw doelgroepfilters", "invalid_video_url_warning": "Voer een geldige YouTube-, Vimeo- of Loom-URL in. We ondersteunen momenteel geen andere videohostingproviders.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Beperk de maximale bestandsgrootte voor uploads.", "limit_upload_file_size_to": "Beperk uploadbestandsgrootte tot", "link_survey_description": "Deel een link naar een enquêtepagina of sluit deze in op een webpagina of e-mail.", + "list": "Lijst", "load_segment": "Laadsegment", "logic_error_warning": "Wijzigen zal logische fouten veroorzaken", "logic_error_warning_text": "Als u het vraagtype wijzigt, worden de logische voorwaarden van deze vraag verwijderd", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Alleen gebruikers die de pincode hebben, hebben toegang tot de enquête.", "publish": "Publiceren", "question": "Vraag", - "question_color": "Vraag kleur", "question_deleted": "Vraag verwijderd.", "question_duplicated": "Vraag dubbel gesteld.", "question_id_updated": "Vraag-ID bijgewerkt", "question_used_in_logic_warning_text": "Elementen uit dit blok worden gebruikt in een logische regel, weet je zeker dat je het wilt verwijderen?", "question_used_in_logic_warning_title": "Logica-inconsistentie", - "question_used_in_quota": "Deze vraag wordt gebruikt in het quotum '{quotaName}'", + "question_used_in_quota": "Deze vraag wordt gebruikt in het quotum “{quotaName}”", "question_used_in_recall": "Deze vraag wordt teruggehaald in vraag {questionIndex}.", "question_used_in_recall_ending_card": "Deze vraag wordt teruggeroepen in de Eindkaart", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Reactielimieten, omleidingen en meer.", "response_options": "Reactieopties", "roundness": "Rondheid", + "roundness_description": "Bepaalt hoe afgerond de kaarthoeken zijn.", "row_used_in_logic_error": "Deze rij wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.", "rows": "Rijen", "save_and_close": "Opslaan en sluiten", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Styling ingesteld op themastijlen", "subheading": "Ondertitel", "subtract": "Aftrekken -", - "suggest_colors": "Stel kleuren voor", "survey_completed_heading": "Enquête voltooid", "survey_completed_subheading": "Deze gratis en open source-enquête is gesloten", "survey_display_settings": "Enquêteweergave-instellingen", @@ -1642,7 +1720,7 @@ "validation_rules": "Validatieregels", "validation_rules_description": "Accepteer alleen antwoorden die voldoen aan de volgende criteria", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabele \"{variableName}\" wordt gebruikt in het \"{quotaName}\" quotum", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabele “{variableName}” wordt gebruikt in het quotum “{quotaName}”", "variable_name_conflicts_with_hidden_field": "Variabelenaam conflicteert met een bestaande verborgen veld-ID.", "variable_name_is_already_taken_please_choose_another": "Variabelenaam is al in gebruik, kies een andere.", "variable_name_must_start_with_a_letter": "Variabelenaam moet beginnen met een letter.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Achtergrondkleur toevoegen", "add_background_color_description": "Voeg een achtergrondkleur toe aan de logocontainer.", + "advanced_styling_field_border_radius": "Hoekradius", + "advanced_styling_field_button_bg": "Knopachtergrond", + "advanced_styling_field_button_bg_description": "Vult de volgende/verzend-knop.", + "advanced_styling_field_button_border_radius_description": "Rondt de knophoeken af.", + "advanced_styling_field_button_font_size_description": "Schaalt de tekst van het knoplabel.", + "advanced_styling_field_button_font_weight_description": "Maakt knoptekst lichter of vetter.", + "advanced_styling_field_button_height_description": "Bepaalt de knophoogte.", + "advanced_styling_field_button_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.", + "advanced_styling_field_button_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.", + "advanced_styling_field_button_text": "Knoptekst", + "advanced_styling_field_button_text_description": "Kleurt het label binnen knoppen.", + "advanced_styling_field_description_color": "Beschrijvingskleur", + "advanced_styling_field_description_color_description": "Kleurt de tekst onder elke kop.", + "advanced_styling_field_description_size": "Lettergrootte beschrijving", + "advanced_styling_field_description_size_description": "Schaalt de beschrijvingstekst.", + "advanced_styling_field_description_weight": "Letterdikte beschrijving", + "advanced_styling_field_description_weight_description": "Maakt beschrijvingstekst lichter of vetter.", + "advanced_styling_field_font_size": "Lettergrootte", + "advanced_styling_field_font_weight": "Letterdikte", + "advanced_styling_field_headline_color": "Kopkleur", + "advanced_styling_field_headline_color_description": "Kleurt de hoofdvraagtekst.", + "advanced_styling_field_headline_size": "Lettergrootte kop", + "advanced_styling_field_headline_size_description": "Schaalt de koptekst.", + "advanced_styling_field_headline_weight": "Letterdikte kop", + "advanced_styling_field_headline_weight_description": "Maakt koptekst lichter of vetter.", + "advanced_styling_field_height": "Minimale hoogte", + "advanced_styling_field_indicator_bg": "Indicatorachtergrond", + "advanced_styling_field_indicator_bg_description": "Kleurt het gevulde deel van de balk.", + "advanced_styling_field_input_border_radius_description": "Rondt de invoerhoeken af.", + "advanced_styling_field_input_font_size_description": "Schaalt de getypte tekst in invoervelden.", + "advanced_styling_field_input_height_description": "Bepaalt de minimale hoogte van het invoerveld.", + "advanced_styling_field_input_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.", + "advanced_styling_field_input_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.", + "advanced_styling_field_input_placeholder_opacity_description": "Vervaagt de tijdelijke aanwijzingstekst.", + "advanced_styling_field_input_shadow_description": "Voegt een slagschaduw toe rond invoervelden.", + "advanced_styling_field_input_text": "Invoertekst", + "advanced_styling_field_input_text_description": "Kleurt de getypte tekst in invoervelden.", + "advanced_styling_field_option_bg": "Achtergrond", + "advanced_styling_field_option_bg_description": "Vult de optie-items.", + "advanced_styling_field_option_border_radius_description": "Rondt de hoeken van opties af.", + "advanced_styling_field_option_font_size_description": "Schaalt de tekst van optielabels.", + "advanced_styling_field_option_label": "Labelkleur", + "advanced_styling_field_option_label_description": "Kleurt de tekst van optielabels.", + "advanced_styling_field_option_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.", + "advanced_styling_field_option_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.", + "advanced_styling_field_padding_x": "Opvulling X", + "advanced_styling_field_padding_y": "Opvulling Y", + "advanced_styling_field_placeholder_opacity": "Plaatshouderopaciteit", + "advanced_styling_field_shadow": "Schaduw", + "advanced_styling_field_track_bg": "Sporachtergrond", + "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_section_buttons": "Knoppen", + "advanced_styling_section_headlines": "Koppen & beschrijvingen", + "advanced_styling_section_inputs": "Invoervelden", + "advanced_styling_section_options": "Opties (radio/checkbox)", "app_survey_placement": "App-enquête plaatsing", "app_survey_placement_settings_description": "Wijzig waar enquêtes worden weergegeven in uw web-app of website.", - "centered_modal_overlay_color": "Gecentreerde modale overlaykleur", "email_customization": "E-mail aanpassing", "email_customization_description": "Wijzig het uiterlijk van e-mails die Formbricks namens u verstuurt.", "enable_custom_styling": "Aangepaste styling inschakelen", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks-branding is verborgen.", "formbricks_branding_settings_description": "We waarderen uw steun, maar begrijpen het als u dit uitschakelt.", "formbricks_branding_shown": "Formbricks-branding wordt weergegeven.", + "generate_theme_btn": "Genereren", + "generate_theme_confirmation": "Wil je een passend kleurthema genereren op basis van je merkkleur? Dit overschrijft je huidige kleurinstellingen.", + "generate_theme_header": "Kleurthema genereren?", "logo_removed_successfully": "Logo succesvol verwijderd", "logo_settings_description": "Upload uw bedrijfslogo om enquêtes en linkvoorbeelden te voorzien van uw huisstijl.", "logo_updated_successfully": "Logo succesvol bijgewerkt", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Toon Formbricks-branding in {type} enquêtes", "show_powered_by_formbricks": "Toon 'Powered by Formbricks' handtekening", "styling_updated_successfully": "Styling succesvol bijgewerkt", + "suggest_colors": "Kleuren voorstellen", + "suggested_colors_applied_please_save": "Voorgestelde kleuren succesvol gegenereerd. Druk op \"Opslaan\" om de wijzigingen te behouden.", "theme": "Thema", "theme_settings_description": "Maak een stijlthema voor alle enquêtes. Je kunt aangepaste styling inschakelen voor elke enquête." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Ja, houd mij op de hoogte.", "preview_survey_question_2_choice_2_label": "Nee, dank je!", "preview_survey_question_2_headline": "Wil je op de hoogte blijven?", + "preview_survey_question_2_subheader": "Dit is een voorbeeldbeschrijving.", "preview_survey_welcome_card_headline": "Welkom!", "prioritize_features_description": "Identificeer functies die uw gebruikers het meest en het minst nodig hebben.", "prioritize_features_name": "Geef prioriteit aan functies", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index 3ce781ef1d..f5edea9cf2 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -187,6 +187,7 @@ "customer_success": "Sucesso do Cliente", "dark_overlay": "sobreposição escura", "date": "Encontro", + "days": "dias", "default": "Padrão", "delete": "Apagar", "description": "Descrição", @@ -253,6 +254,7 @@ "label": "Etiqueta", "language": "Língua", "learn_more": "Saiba mais", + "license_expired": "License Expired", "light_overlay": "sobreposição leve", "limits_reached": "Limites Atingidos", "link": "link", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks funciona melhor em uma tela maior. Para gerenciar ou criar pesquisas, mude para outro dispositivo.", "mobile_overlay_surveys_look_good": "Não se preocupe – suas pesquisas ficam ótimas em qualquer dispositivo e tamanho de tela!", "mobile_overlay_title": "Eita, tela pequena detectada!", + "months": "meses", "move_down": "Descer", "move_up": "Subir", "multiple_languages": "Vários idiomas", @@ -283,6 +286,7 @@ "no_background_image_found": "Imagem de fundo não encontrada.", "no_code": "Sem código", "no_files_uploaded": "Nenhum arquivo foi enviado", + "no_overlay": "Sem sobreposição", "no_quotas_found": "Nenhuma cota encontrada", "no_result_found": "Nenhum resultado encontrado", "no_results": "Nenhum resultado", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Equipes da organização não encontradas", "other": "outro", "others": "Outros", + "overlay_color": "Cor da sobreposição", "overview": "Visão Geral", "password": "Senha", "paused": "Pausado", @@ -348,6 +353,7 @@ "request_trial_license": "Pedir licença de teste", "reset_to_default": "Restaurar para o padrão", "response": "Resposta", + "response_id": "ID da resposta", "responses": "Respostas", "restart": "Reiniciar", "role": "Rolê", @@ -388,6 +394,7 @@ "status": "status", "step_by_step_manual": "Manual passo a passo", "storage_not_configured": "Armazenamento de arquivos não configurado, uploads provavelmente falharão", + "string": "Texto", "styling": "Estilização", "submit": "Enviar", "summary": "Resumo", @@ -443,6 +450,7 @@ "website_and_app_connection": "Conexão de Site e App", "website_app_survey": "Pesquisa de Site e App", "website_survey": "Pesquisa de Site", + "weeks": "semanas", "welcome_card": "Cartão de boas-vindas", "workspace_configuration": "Configuração do projeto", "workspace_created_successfully": "Projeto criado com sucesso", @@ -453,13 +461,15 @@ "workspace_not_found": "Projeto não encontrado", "workspace_permission_not_found": "Permissão do projeto não encontrada", "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.", "you_have_reached_your_monthly_miu_limit_of": "Você atingiu o seu limite mensal de MIU de", "you_have_reached_your_monthly_response_limit_of": "Você atingiu o limite mensal de respostas de", - "you_will_be_downgraded_to_the_community_edition_on_date": "Você será rebaixado para a Edição Comunitária em {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Você será rebaixado para a Edição Comunitária em {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Aceitar", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Atributo atualizado com sucesso", "attribute_value": "Valor", "attribute_value_placeholder": "Valor do atributo", + "attributes_msg_attribute_limit_exceeded": "Não foi possível criar {count} novo(s) atributo(s), pois excederia o limite máximo de {limit} classes de atributos. Os atributos existentes foram atualizados com sucesso.", + "attributes_msg_attribute_type_validation_error": "{error} (atributo '{key}' tem dataType: {dataType})", + "attributes_msg_email_already_exists": "O e-mail já existe para este ambiente e não foi atualizado.", + "attributes_msg_email_or_userid_required": "E-mail ou userId é obrigatório. Os valores existentes foram preservados.", + "attributes_msg_new_attribute_created": "Novo atributo '{key}' criado com tipo '{dataType}'", + "attributes_msg_userid_already_exists": "O userId já existe para este ambiente e não foi atualizado.", "contact_deleted_successfully": "Contato excluído com sucesso", "contact_not_found": "Nenhum contato encontrado", "contacts_table_refresh": "Atualizar contatos", @@ -631,6 +647,11 @@ "create_key": "Criar chave", "create_new_attribute": "Criar novo atributo", "create_new_attribute_description": "Crie um novo atributo para fins de segmentação.", + "custom_attributes": "Atributos personalizados", + "data_type": "Tipo de dados", + "data_type_cannot_be_changed": "O tipo de dados não pode ser alterado após a criação", + "data_type_description": "Escolha como este atributo deve ser armazenado e filtrado", + "date_value_required": "O valor da data é obrigatório. Use o botão excluir para remover este atributo se você não quiser definir uma data.", "delete_attribute_confirmation": "{value, plural, one {Isso excluirá o atributo selecionado. Todos os dados de contato associados a este atributo serão perdidos.} other {Isso excluirá os atributos selecionados. Todos os dados de contato associados a estes atributos serão perdidos.}}", "delete_contact_confirmation": "Isso irá apagar todas as respostas da pesquisa e atributos de contato associados a este contato. Qualquer direcionamento e personalização baseados nos dados deste contato serão perdidos.", "delete_contact_confirmation_with_quotas": "{value, plural, other {Isso irá apagar todas as respostas da pesquisa e atributos de contato associados a este contato. Qualquer direcionamento e personalização baseados nos dados deste contato serão perdidos. Se este contato tiver respostas que contam para cotas da pesquisa, as contagens das cotas serão reduzidas, mas os limites das cotas permanecerão inalterados.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.", "edit_attribute_values": "Editar atributos", "edit_attribute_values_description": "Altere os valores de atributos específicos para este contato.", + "edit_attributes": "Editar atributos", "edit_attributes_success": "Atributos do contato atualizados com sucesso", "generate_personal_link": "Gerar link pessoal", "generate_personal_link_description": "Selecione uma pesquisa publicada para gerar um link personalizado para este contato.", + "invalid_csv_column_names": "Nome(s) de coluna CSV inválido(s): {columns}. Os nomes de colunas que se tornarão novos atributos devem conter apenas letras minúsculas, números e sublinhados, e devem começar com uma letra.", + "invalid_date_format": "Formato de data inválido. Por favor, use uma data válida.", + "invalid_number_format": "Formato de número inválido. Por favor, insira um número válido.", "no_published_link_surveys_available": "Não há pesquisas de link publicadas disponíveis. Por favor, publique uma pesquisa de link primeiro.", "no_published_surveys": "Sem pesquisas publicadas", "no_responses_found": "Nenhuma resposta encontrada", "not_provided": "Não fornecido", + "number_value_required": "O valor numérico é obrigatório. Use o botão excluir para remover este atributo.", "personal_link_generated": "Link pessoal gerado com sucesso", "personal_link_generated_but_clipboard_failed": "Link pessoal gerado, mas falha ao copiar para a área de transferência: {url}", "personal_survey_link": "Link da pesquisa pessoal", @@ -653,13 +679,22 @@ "search_contact": "Buscar contato", "select_a_survey": "Selecione uma pesquisa", "select_attribute": "Selecionar Atributo", + "select_attribute_key": "Selecionar chave de atributo", + "system_attributes": "Atributos do sistema", "unlock_contacts_description": "Gerencie contatos e envie pesquisas direcionadas", "unlock_contacts_title": "Desbloqueie contatos com um plano superior", + "upload_contacts_error_attribute_type_mismatch": "O atributo \"{key}\" está tipado como \"{dataType}\", mas o CSV contém valores inválidos: {values}", + "upload_contacts_error_duplicate_mappings": "Mapeamentos duplicados encontrados para os seguintes atributos: {attributes}", + "upload_contacts_error_file_too_large": "O tamanho do arquivo excede o limite máximo de 800KB", + "upload_contacts_error_generic": "Ocorreu um erro ao fazer upload dos contatos. Por favor, tente novamente mais tarde.", + "upload_contacts_error_invalid_file_type": "Por favor, faça upload de um arquivo CSV", + "upload_contacts_error_no_valid_contacts": "O arquivo CSV enviado não contém nenhum contato válido, por favor veja o arquivo CSV de exemplo para o formato correto.", + "upload_contacts_modal_attribute_header": "Atributo do Formbricks", "upload_contacts_modal_attributes_description": "Mapeie as colunas do seu CSV para os atributos no Formbricks.", "upload_contacts_modal_attributes_new": "Novo atributo", "upload_contacts_modal_attributes_search_or_add": "Buscar ou adicionar atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Coluna CSV", "upload_contacts_modal_description": "Faça upload de um CSV para importar contatos com atributos rapidamente", "upload_contacts_modal_download_example_csv": "Baixar exemplo de CSV", "upload_contacts_modal_duplicates_description": "O que devemos fazer se um contato já existir nos seus contatos?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Ainda não tem atributos!", "no_filters_yet": "Ainda não tem filtros!", "no_segments_yet": "Você não tem segmentos salvos no momento.", + "operator_contains": "contém", + "operator_does_not_contain": "não contém", + "operator_ends_with": "termina com", + "operator_is_after": "é depois", + "operator_is_before": "é antes", + "operator_is_between": "está entre", + "operator_is_newer_than": "é mais recente que", + "operator_is_not_set": "não está definido", + "operator_is_older_than": "é mais antigo que", + "operator_is_same_day": "é no mesmo dia", + "operator_is_set": "está definido", + "operator_starts_with": "começa com", + "operator_title_contains": "Contém", + "operator_title_does_not_contain": "Não contém", + "operator_title_ends_with": "Termina com", + "operator_title_equals": "Igual", + "operator_title_greater_equal": "Maior ou igual a", + "operator_title_greater_than": "Maior que", + "operator_title_is_after": "É depois", + "operator_title_is_before": "É antes", + "operator_title_is_between": "Está entre", + "operator_title_is_newer_than": "É mais recente que", + "operator_title_is_not_set": "Não está definido", + "operator_title_is_older_than": "É mais antigo que", + "operator_title_is_same_day": "É no mesmo dia", + "operator_title_is_set": "Está definido", + "operator_title_less_equal": "Menor ou igual a", + "operator_title_less_than": "Menor que", + "operator_title_not_equals": "Diferente de", + "operator_title_starts_with": "Começa com", + "operator_title_user_is_in": "Usuário está em", + "operator_title_user_is_not_in": "Usuário não está em", + "operator_user_is_in": "Usuário está em", + "operator_user_is_not_in": "Usuário não está em", "person_and_attributes": "Pessoa & Atributos", "phone": "Celular", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Por favor, remova o segmento dessas pesquisas para deletá-lo.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "A segmentação de usuários está disponível apenas quando", "value_cannot_be_empty": "O valor não pode estar vazio.", "value_must_be_a_number": "O valor deve ser um número.", + "value_must_be_positive": "O valor deve ser um número positivo.", "view_filters": "Ver filtros", "where": "Onde", "with_the_formbricks_sdk": "com o SDK do Formbricks." @@ -950,19 +1020,32 @@ "enterprise_features": "Recursos Empresariais", "get_an_enterprise_license_to_get_access_to_all_features": "Adquira uma licença Enterprise para ter acesso a todos os recursos.", "keep_full_control_over_your_data_privacy_and_security": "Mantenha controle total sobre a privacidade e segurança dos seus dados.", + "license_invalid_description": "A chave de licença na sua variável de ambiente ENTERPRISE_LICENSE_KEY não é válida. Verifique se há erros de digitação ou solicite uma nova chave.", + "license_status": "Status da licença", + "license_status_active": "Ativa", + "license_status_description": "Status da sua licença enterprise.", + "license_status_expired": "Expirada", + "license_status_invalid": "Licença inválida", + "license_status_unreachable": "Inacessível", + "license_unreachable_grace_period": "O servidor de licenças não pode ser alcançado. Seus recursos empresariais permanecem ativos durante um período de carência de 3 dias que termina em {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sem necessidade de ligação, sem compromisso: Solicite uma licença de teste gratuita de 30 dias para testar todas as funcionalidades preenchendo este formulário:", "no_credit_card_no_sales_call_just_test_it": "Sem cartão de crédito. Sem ligação de vendas. Só teste :)", "on_request": "Quando solicitado", "organization_roles": "Funções na Organização (Admin, Editor, Desenvolvedor, etc.)", "questions_please_reach_out_to": "Perguntas? Entre em contato com", + "recheck_license": "Verificar licença novamente", + "recheck_license_failed": "Falha na verificação da licença. O servidor de licenças pode estar inacessível.", + "recheck_license_invalid": "A chave de licença é inválida. Verifique sua ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Verificação da licença bem-sucedida", + "recheck_license_unreachable": "Servidor de licenças inacessível. Por favor, tente novamente mais tarde.", + "rechecking": "Verificando novamente...", "request_30_day_trial_license": "Pedir Licença de Teste de 30 Dias", "saml_sso": "SSO SAML", "service_level_agreement": "Acordo de Nível de Serviço", "soc2_hipaa_iso_27001_compliance_check": "Verificação de conformidade SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Equipes e Funções de Acesso (Ler, Ler e Escrever, Gerenciar)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias.", - "your_enterprise_license_is_active_all_features_unlocked": "Sua licença empresarial está ativa. Todos os recursos estão desbloqueados." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias." }, "general": { "bulk_invite_warning_description": "Por favor, note que no Plano Gratuito, todos os membros da organização são automaticamente atribuídos ao papel de 'Owner', independentemente do papel especificado no arquivo CSV.", @@ -986,7 +1069,7 @@ "from_your_organization": "da sua organização", "invitation_sent_once_more": "Convite enviado de novo.", "invite_deleted_successfully": "Convite deletado com sucesso", - "invited_on": "Convidado em {date}", + "invite_expires_on": "O convite expira em {date}", "invites_failed": "Convites falharam", "leave_organization": "Sair da organização", "leave_organization_description": "Você vai sair dessa organização e perder acesso a todas as pesquisas e respostas. Você só pode voltar se for convidado de novo.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Preencha todos os campos para adicionar um novo espaço de trabalho.", "read": "Leitura", "read_write": "Leitura & Escrita", - "select_member": "Selecionar membro", - "select_workspace": "Selecionar espaço de trabalho", "team_admin": "Administrador da equipe", "team_created_successfully": "Equipe criada com sucesso.", "team_deleted_successfully": "Equipe excluída com sucesso.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Adicionar um texto padrão para mostrar se a pergunta for ignorada:", "add_hidden_field_id": "Adicionar campo oculto ID", "add_highlight_border": "Adicionar borda de destaque", - "add_highlight_border_description": "Adicione uma borda externa ao seu cartão de pesquisa.", "add_logic": "Adicionar lógica", "add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"", "add_option": "Adicionar opção", @@ -1189,6 +1269,7 @@ "block_duplicated": "Bloco duplicado.", "bold": "Negrito", "brand_color": "Cor da marca", + "brand_color_description": "Aplicado a botões, links e destaques.", "brightness": "brilho", "bulk_edit": "Edição em massa", "bulk_edit_description": "Edite todas as opções abaixo, uma por linha. Linhas vazias serão ignoradas e duplicatas removidas.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Capturar nova ação", "card_arrangement_for_survey_type_derived": "Arranjo de Cartões para Pesquisas {surveyTypeDerived}", "card_background_color": "Cor de fundo do cartão", + "card_background_color_description": "Preenche a área do cartão da pesquisa.", "card_border_color": "Cor da borda do cartão", + "card_border_color_description": "Contorna o cartão da pesquisa.", "card_styling": "Estilo do cartão", "casual": "Casual", "caution_edit_duplicate": "Duplicar e editar", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Respostas antigas e novas são misturadas, o que pode levar a resumos de dados enganosos.", "caution_recommendation": "Isso pode causar inconsistências de dados no resumo da pesquisa. Recomendamos duplicar a pesquisa em vez disso.", "caution_text": "Mudanças vão levar a inconsistências", - "centered_modal_overlay_color": "cor de sobreposição modal centralizada", "change_anyway": "Mudar mesmo assim", "change_background": "Mudar fundo", "change_question_type": "Mudar tipo de pergunta", "change_survey_type": "Alterar o tipo de pesquisa afeta o acesso existente", - "change_the_background_color_of_the_card": "Muda a cor de fundo do cartão.", - "change_the_background_color_of_the_input_fields": "Mude a cor de fundo dos campos de entrada.", "change_the_background_to_a_color_image_or_animation": "Mude o fundo para uma cor, imagem ou animação.", - "change_the_border_color_of_the_card": "Muda a cor da borda do cartão.", - "change_the_border_color_of_the_input_fields": "Mude a cor da borda dos campos de entrada.", - "change_the_border_radius_of_the_card_and_the_inputs": "Muda o raio da borda do card e dos inputs.", - "change_the_brand_color_of_the_survey": "Muda a cor da marca da pesquisa.", "change_the_placement_of_this_survey": "Muda a posição dessa pesquisa.", - "change_the_question_color_of_the_survey": "Muda a cor da pergunta da pesquisa.", "changes_saved": "Mudanças salvas.", "changing_survey_type_will_remove_existing_distribution_channels": "Alterar o tipo de pesquisa afetará a forma como ela pode ser compartilhada. Se os respondentes já tiverem links de acesso para o tipo atual, podem perder o acesso após a mudança.", "checkbox_label": "Rótulo da Caixa de Seleção", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Desativar a visibilidade do progresso da pesquisa.", "display_an_estimate_of_completion_time_for_survey": "Mostrar uma estimativa de tempo de conclusão da pesquisa", "display_number_of_responses_for_survey": "Mostrar número de respostas da pesquisa", + "display_type": "Tipo de exibição", "divide": "Divida /", "does_not_contain": "não contém", "does_not_end_with": "Não termina com", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Não inclui todos de", "does_not_include_one_of": "Não inclui um de", "does_not_start_with": "Não começa com", + "dropdown": "Menu suspenso", "duplicate_block": "Duplicar bloco", "duplicate_question": "Duplicar pergunta", "edit_link": "Editar link", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Esconder barra de progresso", "hide_question_settings": "Ocultar configurações da pergunta", "hostname": "nome do host", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Quão descoladas você quer suas cartas em Pesquisas {surveyTypeDerived}", "if_you_need_more_please": "Se você precisar de mais, por favor", "if_you_really_want_that_answer_ask_until_you_get_it": "Continuar mostrando sempre que acionada até que uma resposta seja enviada.", "ignore_global_waiting_time": "Ignorar período de espera", @@ -1379,7 +1455,9 @@ "initial_value": "Valor inicial", "inner_text": "Texto Interno", "input_border_color": "Cor da borda de entrada", + "input_border_color_description": "Contorna campos de texto e áreas de texto.", "input_color": "Cor de entrada", + "input_color_description": "Preenche o interior dos campos de texto.", "insert_link": "Inserir link", "invalid_targeting": "Segmentação inválida: Por favor, verifique os filtros do seu público", "invalid_video_url_warning": "Por favor, insira uma URL válida do YouTube, Vimeo ou Loom. No momento, não suportamos outros provedores de vídeo.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limitar o tamanho máximo de arquivo para uploads.", "limit_upload_file_size_to": "Limitar tamanho de arquivo de upload para", "link_survey_description": "Compartilhe um link para a página da pesquisa ou incorpore-a em uma página da web ou e-mail.", + "list": "Lista", "load_segment": "segmento de carga", "logic_error_warning": "Mudar vai causar erros de lógica", "logic_error_warning_text": "Mudar o tipo de pergunta vai remover as condições lógicas dessa pergunta", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Somente usuários que têm o PIN podem acessar a pesquisa.", "publish": "Publicar", "question": "Pergunta", - "question_color": "Cor da pergunta", "question_deleted": "Pergunta deletada.", "question_duplicated": "Pergunta duplicada.", "question_id_updated": "ID da pergunta atualizado", "question_used_in_logic_warning_text": "Elementos deste bloco são usados em uma regra de lógica, tem certeza de que deseja excluí-lo?", "question_used_in_logic_warning_title": "Inconsistência de lógica", - "question_used_in_quota": "Esta questão está sendo usada na cota \"{quotaName}\"", + "question_used_in_quota": "Esta pergunta está sendo usada na cota \"{quotaName}\"", "question_used_in_recall": "Esta pergunta está sendo recordada na pergunta {questionIndex}.", "question_used_in_recall_ending_card": "Esta pergunta está sendo recordada no card de Encerramento", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.", "response_options": "Opções de Resposta", "roundness": "Circularidade", + "roundness_description": "Controla o arredondamento dos cantos do cartão.", "row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "rows": "linhas", "save_and_close": "Salvar e Fechar", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Estilo definido para os estilos do tema", "subheading": "Subtítulo", "subtract": "Subtrair -", - "suggest_colors": "Sugerir cores", "survey_completed_heading": "Pesquisa Concluída", "survey_completed_subheading": "Essa pesquisa gratuita e de código aberto foi encerrada", "survey_display_settings": "Configurações de Exibição da Pesquisa", @@ -1642,7 +1720,7 @@ "validation_rules": "Regras de validação", "validation_rules_description": "Aceitar apenas respostas que atendam aos seguintes critérios", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} está sendo usado na lógica da pergunta {questionIndex}. Por favor, remova-o da lógica primeiro.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variável \"{variableName}\" está sendo usada na cota \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "A variável \"{variableName}\" está sendo usada na cota \"{quotaName}\"", "variable_name_conflicts_with_hidden_field": "O nome da variável está em conflito com um ID de campo oculto existente.", "variable_name_is_already_taken_please_choose_another": "O nome da variável já está em uso, por favor escolha outro.", "variable_name_must_start_with_a_letter": "O nome da variável deve começar com uma letra.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Adicionar cor de fundo", "add_background_color_description": "Adicione uma cor de fundo ao container do logo.", + "advanced_styling_field_border_radius": "Raio da borda", + "advanced_styling_field_button_bg": "Fundo do botão", + "advanced_styling_field_button_bg_description": "Preenche o botão Próximo / Enviar.", + "advanced_styling_field_button_border_radius_description": "Arredonda os cantos do botão.", + "advanced_styling_field_button_font_size_description": "Ajusta o tamanho do texto do rótulo do botão.", + "advanced_styling_field_button_font_weight_description": "Torna o texto do botão mais leve ou mais negrito.", + "advanced_styling_field_button_height_description": "Controla a altura do botão.", + "advanced_styling_field_button_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_button_padding_y_description": "Adiciona espaço no topo e na base.", + "advanced_styling_field_button_text": "Texto do botão", + "advanced_styling_field_button_text_description": "Colore o rótulo dentro dos botões.", + "advanced_styling_field_description_color": "Cor da descrição", + "advanced_styling_field_description_color_description": "Colore o texto abaixo de cada título.", + "advanced_styling_field_description_size": "Tamanho da fonte da descrição", + "advanced_styling_field_description_size_description": "Ajusta o tamanho do texto da descrição.", + "advanced_styling_field_description_weight": "Peso da fonte da descrição", + "advanced_styling_field_description_weight_description": "Torna o texto da descrição mais leve ou mais negrito.", + "advanced_styling_field_font_size": "Tamanho da fonte", + "advanced_styling_field_font_weight": "Peso da fonte", + "advanced_styling_field_headline_color": "Cor do título", + "advanced_styling_field_headline_color_description": "Colore o texto principal da pergunta.", + "advanced_styling_field_headline_size": "Tamanho da fonte do título", + "advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.", + "advanced_styling_field_headline_weight": "Peso da fonte do título", + "advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.", + "advanced_styling_field_height": "Altura mínima", + "advanced_styling_field_indicator_bg": "Fundo do indicador", + "advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.", + "advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.", + "advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.", + "advanced_styling_field_input_height_description": "Controla a altura mínima do campo de entrada.", + "advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_input_padding_y_description": "Adiciona espaço na parte superior e inferior.", + "advanced_styling_field_input_placeholder_opacity_description": "Esmaece o texto de dica do placeholder.", + "advanced_styling_field_input_shadow_description": "Adiciona uma sombra ao redor dos campos de entrada.", + "advanced_styling_field_input_text": "Texto de entrada", + "advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.", + "advanced_styling_field_option_bg": "Fundo", + "advanced_styling_field_option_bg_description": "Preenche os itens de opção.", + "advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.", + "advanced_styling_field_option_font_size_description": "Ajusta o tamanho do texto do rótulo da opção.", + "advanced_styling_field_option_label": "Cor do rótulo", + "advanced_styling_field_option_label_description": "Colore o texto do rótulo da opção.", + "advanced_styling_field_option_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_option_padding_y_description": "Adiciona espaço na parte superior e inferior.", + "advanced_styling_field_padding_x": "Espaçamento X", + "advanced_styling_field_padding_y": "Espaçamento Y", + "advanced_styling_field_placeholder_opacity": "Opacidade do placeholder", + "advanced_styling_field_shadow": "Sombra", + "advanced_styling_field_track_bg": "Fundo da trilha", + "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_section_buttons": "Botões", + "advanced_styling_section_headlines": "Títulos e descrições", + "advanced_styling_section_inputs": "Campos de entrada", + "advanced_styling_section_options": "Opções (rádio/caixa de seleção)", "app_survey_placement": "Posicionamento da pesquisa de app", "app_survey_placement_settings_description": "Altere onde as pesquisas serão exibidas em seu aplicativo web ou site.", - "centered_modal_overlay_color": "Cor de sobreposição modal centralizada", "email_customization": "Personalização de e-mail", "email_customization_description": "Altere a aparência dos e-mails que o Formbricks envia em seu nome.", "enable_custom_styling": "Habilitar estilização personalizada", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "A marca Formbricks está oculta.", "formbricks_branding_settings_description": "Adoramos seu apoio, mas entendemos se você desativar.", "formbricks_branding_shown": "A marca Formbricks está visível.", + "generate_theme_btn": "Gerar", + "generate_theme_confirmation": "Gostaria de gerar um tema de cores correspondente baseado na cor da sua marca? Isso substituirá suas configurações de cores atuais.", + "generate_theme_header": "Gerar tema de cores?", "logo_removed_successfully": "Logo removido com sucesso", "logo_settings_description": "Faça upload do logo da sua empresa para personalizar pesquisas e pré-visualizações de links.", "logo_updated_successfully": "Logo atualizado com sucesso", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Mostrar marca Formbricks em pesquisas {type}", "show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'", "styling_updated_successfully": "Estilo atualizado com sucesso", + "suggest_colors": "Sugerir cores", + "suggested_colors_applied_please_save": "Cores sugeridas geradas com sucesso. Pressione \"Salvar\" para manter as alterações.", "theme": "Tema", "theme_settings_description": "Crie um tema de estilo para todas as pesquisas. Você pode ativar estilo personalizado para cada pesquisa." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Sim, me mantenha informado.", "preview_survey_question_2_choice_2_label": "Não, obrigado!", "preview_survey_question_2_headline": "Quer ficar por dentro?", + "preview_survey_question_2_subheader": "Este é um exemplo de descrição.", "preview_survey_welcome_card_headline": "Bem-vindo!", "prioritize_features_description": "Identifique os recursos que seus usuários mais e menos precisam.", "prioritize_features_name": "Priorizar Funcionalidades", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index 67f96c78c2..fa86471a28 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -187,6 +187,7 @@ "customer_success": "Sucesso do Cliente", "dark_overlay": "Sobreposição escura", "date": "Data", + "days": "dias", "default": "Padrão", "delete": "Eliminar", "description": "Descrição", @@ -253,6 +254,7 @@ "label": "Etiqueta", "language": "Idioma", "learn_more": "Saiba mais", + "license_expired": "License Expired", "light_overlay": "Sobreposição leve", "limits_reached": "Limites Atingidos", "link": "Link", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks funciona melhor num ecrã maior. Para gerir ou criar inquéritos, mude de dispositivo.", "mobile_overlay_surveys_look_good": "Não se preocupe – os seus inquéritos têm uma ótima aparência em todos os dispositivos e tamanhos de ecrã!", "mobile_overlay_title": "Oops, ecrã pequeno detectado!", + "months": "meses", "move_down": "Mover para baixo", "move_up": "Mover para cima", "multiple_languages": "Várias línguas", @@ -283,6 +286,7 @@ "no_background_image_found": "Nenhuma imagem de fundo encontrada.", "no_code": "Sem código", "no_files_uploaded": "Nenhum ficheiro foi carregado", + "no_overlay": "Sem sobreposição", "no_quotas_found": "Nenhum quota encontrado", "no_result_found": "Nenhum resultado encontrado", "no_results": "Nenhum resultado", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Equipas da organização não encontradas", "other": "Outro", "others": "Outros", + "overlay_color": "Cor da sobreposição", "overview": "Visão geral", "password": "Palavra-passe", "paused": "Em pausa", @@ -348,6 +353,7 @@ "request_trial_license": "Solicitar licença de teste", "reset_to_default": "Repor para o padrão", "response": "Resposta", + "response_id": "ID de resposta", "responses": "Respostas", "restart": "Reiniciar", "role": "Função", @@ -388,6 +394,7 @@ "status": "Estado", "step_by_step_manual": "Manual passo a passo", "storage_not_configured": "Armazenamento de ficheiros não configurado, uploads provavelmente falharão", + "string": "Texto", "styling": "Estilo", "submit": "Submeter", "summary": "Resumo", @@ -443,6 +450,7 @@ "website_and_app_connection": "Ligação de Website e Aplicação", "website_app_survey": "Inquérito do Website e da Aplicação", "website_survey": "Inquérito do Website", + "weeks": "semanas", "welcome_card": "Cartão de boas-vindas", "workspace_configuration": "Configuração do projeto", "workspace_created_successfully": "Projeto criado com sucesso", @@ -453,13 +461,15 @@ "workspace_not_found": "Projeto não encontrado", "workspace_permission_not_found": "Permissão do projeto não encontrada", "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.", "you_have_reached_your_monthly_miu_limit_of": "Atingiu o seu limite mensal de MIU de", "you_have_reached_your_monthly_response_limit_of": "Atingiu o seu limite mensal de respostas de", - "you_will_be_downgraded_to_the_community_edition_on_date": "Será rebaixado para a Edição Comunitária em {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Será rebaixado para a Edição Comunitária em {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Aceitar", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Atributo atualizado com sucesso", "attribute_value": "Valor", "attribute_value_placeholder": "Valor do atributo", + "attributes_msg_attribute_limit_exceeded": "Não foi possível criar {count} novo(s) atributo(s), pois excederia o limite máximo de {limit} classes de atributos. Os atributos existentes foram atualizados com sucesso.", + "attributes_msg_attribute_type_validation_error": "{error} (o atributo '{key}' tem dataType: {dataType})", + "attributes_msg_email_already_exists": "O email já existe para este ambiente e não foi atualizado.", + "attributes_msg_email_or_userid_required": "É necessário email ou userId. Os valores existentes foram preservados.", + "attributes_msg_new_attribute_created": "Criado novo atributo '{key}' com tipo '{dataType}'", + "attributes_msg_userid_already_exists": "O userId já existe para este ambiente e não foi atualizado.", "contact_deleted_successfully": "Contacto eliminado com sucesso", "contact_not_found": "Nenhum contacto encontrado", "contacts_table_refresh": "Atualizar contactos", @@ -631,6 +647,11 @@ "create_key": "Criar chave", "create_new_attribute": "Criar novo atributo", "create_new_attribute_description": "Crie um novo atributo para fins de segmentação.", + "custom_attributes": "Atributos personalizados", + "data_type": "Tipo de dados", + "data_type_cannot_be_changed": "O tipo de dados não pode ser alterado após a criação", + "data_type_description": "Escolhe como este atributo deve ser armazenado e filtrado", + "date_value_required": "O valor da data é obrigatório. Usa o botão eliminar para remover este atributo se não quiseres definir uma data.", "delete_attribute_confirmation": "{value, plural, one {Isto irá eliminar o atributo selecionado. Todos os dados de contacto associados a este atributo serão perdidos.} other {Isto irá eliminar os atributos selecionados. Todos os dados de contacto associados a estes atributos serão perdidos.}}", "delete_contact_confirmation": "Isto irá eliminar todas as respostas das pesquisas e os atributos de contato associados a este contato. Qualquer direcionamento e personalização baseados nos dados deste contato serão perdidos.", "delete_contact_confirmation_with_quotas": "{value, plural, other {Isto irá eliminar todas as respostas das pesquisas e os atributos de contacto associados a este contacto. Qualquer segmentação e personalização baseados nos dados deste contacto serão perdidos. Se este contacto tiver respostas que contribuam para as quotas das pesquisas, as contagens de quotas serão reduzidas, mas os limites das quotas permanecerão inalterados.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.", "edit_attribute_values": "Editar atributos", "edit_attribute_values_description": "Altere os valores de atributos específicos para este contacto.", + "edit_attributes": "Editar atributos", "edit_attributes_success": "Atributos do contacto atualizados com sucesso", "generate_personal_link": "Gerar Link Pessoal", "generate_personal_link_description": "Selecione um inquérito publicado para gerar um link personalizado para este contacto.", + "invalid_csv_column_names": "Nome(s) de coluna CSV inválido(s): {columns}. Os nomes de colunas que se tornarão novos atributos devem conter apenas letras minúsculas, números e underscores, e devem começar com uma letra.", + "invalid_date_format": "Formato de data inválido. Por favor, usa uma data válida.", + "invalid_number_format": "Formato de número inválido. Por favor, introduz um número válido.", "no_published_link_surveys_available": "Não existem inquéritos de link publicados disponíveis. Por favor, publique primeiro um inquérito de link.", "no_published_surveys": "Sem inquéritos publicados", "no_responses_found": "Nenhuma resposta encontrada", "not_provided": "Não fornecido", + "number_value_required": "O valor numérico é obrigatório. Usa o botão eliminar para remover este atributo.", "personal_link_generated": "Link pessoal gerado com sucesso", "personal_link_generated_but_clipboard_failed": "Link pessoal gerado mas falha ao copiar para a área de transferência: {url}", "personal_survey_link": "Link do inquérito pessoal", @@ -653,13 +679,22 @@ "search_contact": "Procurar contacto", "select_a_survey": "Selecione um inquérito", "select_attribute": "Selecionar Atributo", + "select_attribute_key": "Selecionar chave de atributo", + "system_attributes": "Atributos do sistema", "unlock_contacts_description": "Gerir contactos e enviar inquéritos direcionados", "unlock_contacts_title": "Desbloqueie os contactos com um plano superior", + "upload_contacts_error_attribute_type_mismatch": "O atributo \"{key}\" está definido como \"{dataType}\", mas o CSV contém valores inválidos: {values}", + "upload_contacts_error_duplicate_mappings": "Foram encontrados mapeamentos duplicados para os seguintes atributos: {attributes}", + "upload_contacts_error_file_too_large": "O tamanho do ficheiro excede o limite máximo de 800KB", + "upload_contacts_error_generic": "Ocorreu um erro ao carregar os contactos. Por favor, tenta novamente mais tarde.", + "upload_contacts_error_invalid_file_type": "Por favor, carrega um ficheiro CSV", + "upload_contacts_error_no_valid_contacts": "O ficheiro CSV carregado não contém contactos válidos, por favor consulta o ficheiro CSV de exemplo para o formato correto.", + "upload_contacts_modal_attribute_header": "Atributo Formbricks", "upload_contacts_modal_attributes_description": "Mapeie as colunas no seu CSV para os atributos no Formbricks.", "upload_contacts_modal_attributes_new": "Novo atributo", "upload_contacts_modal_attributes_search_or_add": "Pesquisar ou adicionar atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Coluna CSV", "upload_contacts_modal_description": "Carregue um ficheiro CSV para importar rapidamente contactos com atributos", "upload_contacts_modal_download_example_csv": "Descarregar exemplo de CSV", "upload_contacts_modal_duplicates_description": "Como devemos proceder se um contacto já existir nos seus contactos?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Ainda não há atributos!", "no_filters_yet": "Ainda não há filtros!", "no_segments_yet": "Atualmente, não tem segmentos guardados.", + "operator_contains": "contém", + "operator_does_not_contain": "não contém", + "operator_ends_with": "termina com", + "operator_is_after": "é depois", + "operator_is_before": "é antes", + "operator_is_between": "está entre", + "operator_is_newer_than": "é mais recente que", + "operator_is_not_set": "não está definido", + "operator_is_older_than": "é mais antigo que", + "operator_is_same_day": "é no mesmo dia", + "operator_is_set": "está definido", + "operator_starts_with": "começa com", + "operator_title_contains": "Contém", + "operator_title_does_not_contain": "Não contém", + "operator_title_ends_with": "Termina com", + "operator_title_equals": "Igual", + "operator_title_greater_equal": "Maior ou igual a", + "operator_title_greater_than": "Maior que", + "operator_title_is_after": "É depois", + "operator_title_is_before": "É antes", + "operator_title_is_between": "Está entre", + "operator_title_is_newer_than": "É mais recente que", + "operator_title_is_not_set": "Não está definido", + "operator_title_is_older_than": "É mais antigo que", + "operator_title_is_same_day": "É no mesmo dia", + "operator_title_is_set": "Está definido", + "operator_title_less_equal": "Menor ou igual a", + "operator_title_less_than": "Menor que", + "operator_title_not_equals": "Diferente de", + "operator_title_starts_with": "Começa com", + "operator_title_user_is_in": "O utilizador está em", + "operator_title_user_is_not_in": "O utilizador não está em", + "operator_user_is_in": "O utilizador está em", + "operator_user_is_not_in": "O utilizador não está em", "person_and_attributes": "Pessoa e Atributos", "phone": "Telefone", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Por favor, remova o segmento destes questionários para o eliminar.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "A segmentação de utilizadores está atualmente disponível apenas quando", "value_cannot_be_empty": "O valor não pode estar vazio.", "value_must_be_a_number": "O valor deve ser um número.", + "value_must_be_positive": "O valor deve ser um número positivo.", "view_filters": "Ver filtros", "where": "Onde", "with_the_formbricks_sdk": "com o SDK Formbricks" @@ -950,19 +1020,32 @@ "enterprise_features": "Funcionalidades da Empresa", "get_an_enterprise_license_to_get_access_to_all_features": "Obtenha uma licença Enterprise para ter acesso a todas as funcionalidades.", "keep_full_control_over_your_data_privacy_and_security": "Mantenha controlo total sobre a privacidade e segurança dos seus dados.", + "license_invalid_description": "A chave de licença na sua variável de ambiente ENTERPRISE_LICENSE_KEY não é válida. Por favor, verifique se existem erros de digitação ou solicite uma nova chave.", + "license_status": "Estado da licença", + "license_status_active": "Ativa", + "license_status_description": "Estado da sua licença empresarial.", + "license_status_expired": "Expirada", + "license_status_invalid": "Licença inválida", + "license_status_unreachable": "Inacessível", + "license_unreachable_grace_period": "Não é possível contactar o servidor de licenças. As suas funcionalidades empresariais permanecem ativas durante um período de tolerância de 3 dias que termina a {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sem necessidade de chamada, sem compromissos: Solicite uma licença de teste gratuita de 30 dias para testar todas as funcionalidades preenchendo este formulário:", "no_credit_card_no_sales_call_just_test_it": "Sem cartão de crédito. Sem chamada de vendas. Apenas teste :)", "on_request": "A pedido", "organization_roles": "Funções da Organização (Administrador, Editor, Programador, etc.)", "questions_please_reach_out_to": "Questões? Por favor entre em contacto com", + "recheck_license": "Verificar licença novamente", + "recheck_license_failed": "A verificação da licença falhou. O servidor de licenças pode estar inacessível.", + "recheck_license_invalid": "A chave de licença é inválida. Por favor, verifique a sua ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Verificação da licença bem-sucedida", + "recheck_license_unreachable": "O servidor de licenças está inacessível. Por favor, tenta novamente mais tarde.", + "rechecking": "A verificar novamente...", "request_30_day_trial_license": "Solicitar Licença de Teste de 30 Dias", "saml_sso": "SSO SAML", "service_level_agreement": "Acordo de Nível de Serviço", "soc2_hipaa_iso_27001_compliance_check": "Verificação de conformidade SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Equipas e Funções de Acesso (Ler, Ler e Escrever, Gerir)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias.", - "your_enterprise_license_is_active_all_features_unlocked": "A sua licença Enterprise está ativa. Todas as funcionalidades desbloqueadas." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias." }, "general": { "bulk_invite_warning_description": "No plano gratuito, todos os membros da organização são sempre atribuídos ao papel de \"Proprietário\".", @@ -986,7 +1069,7 @@ "from_your_organization": "da sua organização", "invitation_sent_once_more": "Convite enviado mais uma vez.", "invite_deleted_successfully": "Convite eliminado com sucesso", - "invited_on": "Convidado em {date}", + "invite_expires_on": "O convite expira em {date}", "invites_failed": "Convites falharam", "leave_organization": "Sair da organização", "leave_organization_description": "Vai sair desta organização e perder o acesso a todos os inquéritos e respostas. Só pode voltar a juntar-se se for convidado novamente.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Preencha todos os campos para adicionar um novo espaço de trabalho.", "read": "Ler", "read_write": "Ler e Escrever", - "select_member": "Selecionar membro", - "select_workspace": "Selecionar espaço de trabalho", "team_admin": "Administrador da Equipa", "team_created_successfully": "Equipa criada com sucesso.", "team_deleted_successfully": "Equipa eliminada com sucesso.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Adicionar um espaço reservado para mostrar se não houver valor para recordar.", "add_hidden_field_id": "Adicionar ID do campo oculto", "add_highlight_border": "Adicionar borda de destaque", - "add_highlight_border_description": "Adicione uma borda externa ao seu cartão de inquérito.", "add_logic": "Adicionar lógica", "add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"", "add_option": "Adicionar opção", @@ -1189,6 +1269,7 @@ "block_duplicated": "Bloco duplicado.", "bold": "Negrito", "brand_color": "Cor da marca", + "brand_color_description": "Aplicado a botões, links e destaques.", "brightness": "Brilho", "bulk_edit": "Edição em massa", "bulk_edit_description": "Edite todas as opções abaixo, uma por linha. Linhas vazias serão ignoradas e duplicados removidos.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Capturar nova ação", "card_arrangement_for_survey_type_derived": "Arranjo de Cartões para Inquéritos {surveyTypeDerived}", "card_background_color": "Cor de fundo do cartão", + "card_background_color_description": "Preenche a área do cartão do inquérito.", "card_border_color": "Cor da borda do cartão", + "card_border_color_description": "Contorna o cartão do inquérito.", "card_styling": "Estilo de cartão", "casual": "Casual", "caution_edit_duplicate": "Duplicar e editar", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "As respostas mais antigas e mais recentes se misturam, o que pode levar a resumos de dados enganosos.", "caution_recommendation": "Isso pode causar inconsistências de dados no resumo do inquérito. Recomendamos duplicar o inquérito em vez disso.", "caution_text": "As alterações levarão a inconsistências", - "centered_modal_overlay_color": "Cor da sobreposição modal centralizada", "change_anyway": "Alterar mesmo assim", "change_background": "Alterar fundo", "change_question_type": "Alterar tipo de pergunta", "change_survey_type": "Alterar o tipo de inquérito afeta o acesso existente", - "change_the_background_color_of_the_card": "Alterar a cor de fundo do cartão", - "change_the_background_color_of_the_input_fields": "Alterar a cor de fundo dos campos de entrada", "change_the_background_to_a_color_image_or_animation": "Altere o fundo para uma cor, imagem ou animação", - "change_the_border_color_of_the_card": "Alterar a cor da borda do cartão.", - "change_the_border_color_of_the_input_fields": "Alterar a cor da borda dos campos de entrada", - "change_the_border_radius_of_the_card_and_the_inputs": "Alterar o raio da borda do cartão e dos campos de entrada", - "change_the_brand_color_of_the_survey": "Alterar a cor da marca do inquérito", "change_the_placement_of_this_survey": "Alterar a colocação deste inquérito.", - "change_the_question_color_of_the_survey": "Alterar a cor da pergunta do inquérito", "changes_saved": "Alterações guardadas.", "changing_survey_type_will_remove_existing_distribution_channels": "Alterar o tipo de inquérito afetará como ele pode ser partilhado. Se os respondentes já tiverem links de acesso para o tipo atual, podem perder o acesso após a mudança.", "checkbox_label": "Rótulo da Caixa de Seleção", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Desativar a visibilidade do progresso da pesquisa.", "display_an_estimate_of_completion_time_for_survey": "Mostrar uma estimativa do tempo de conclusão do inquérito", "display_number_of_responses_for_survey": "Mostrar número de respostas do inquérito", + "display_type": "Tipo de exibição", "divide": "Dividir /", "does_not_contain": "Não contém", "does_not_end_with": "Não termina com", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Não inclui todos de", "does_not_include_one_of": "Não inclui um de", "does_not_start_with": "Não começa com", + "dropdown": "Menu suspenso", "duplicate_block": "Duplicar bloco", "duplicate_question": "Duplicar pergunta", "edit_link": "Editar link", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Ocultar barra de progresso", "hide_question_settings": "Ocultar definições da pergunta", "hostname": "Nome do host", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Quão extravagantes quer os seus cartões em Inquéritos {surveyTypeDerived}", "if_you_need_more_please": "Se precisar de mais, por favor", "if_you_really_want_that_answer_ask_until_you_get_it": "Continuar a mostrar sempre que acionado até que uma resposta seja submetida.", "ignore_global_waiting_time": "Ignorar período de espera", @@ -1379,7 +1455,9 @@ "initial_value": "Valor inicial", "inner_text": "Texto Interno", "input_border_color": "Cor da borda do campo de entrada", + "input_border_color_description": "Contorna campos de texto e áreas de texto.", "input_color": "Cor do campo de entrada", + "input_color_description": "Preenche o interior dos campos de texto.", "insert_link": "Inserir ligação", "invalid_targeting": "Segmentação inválida: Por favor, verifique os seus filtros de audiência", "invalid_video_url_warning": "Por favor, insira um URL válido do YouTube, Vimeo ou Loom. Atualmente, não suportamos outros fornecedores de hospedagem de vídeo.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limitar o tamanho máximo de ficheiro para carregamentos.", "limit_upload_file_size_to": "Limitar o tamanho de ficheiro de carregamento para", "link_survey_description": "Partilhe um link para uma página de inquérito ou incorpore-o numa página web ou email.", + "list": "Lista", "load_segment": "Carregar segmento", "logic_error_warning": "A alteração causará erros de lógica", "logic_error_warning_text": "Alterar o tipo de pergunta irá remover as condições lógicas desta pergunta", @@ -1462,7 +1541,6 @@ "protect_survey_with_pin_description": "Apenas utilizadores com o PIN podem aceder ao inquérito.", "publish": "Publicar", "question": "Pergunta", - "question_color": "Cor da pergunta", "question_deleted": "Pergunta eliminada.", "question_duplicated": "Pergunta duplicada.", "question_id_updated": "ID da pergunta atualizado", @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.", "response_options": "Opções de Resposta", "roundness": "Arredondamento", + "roundness_description": "Controla o arredondamento dos cantos do cartão.", "row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "rows": "Linhas", "save_and_close": "Guardar e Fechar", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Estilo definido para estilos do tema", "subheading": "Subtítulo", "subtract": "Subtrair -", - "suggest_colors": "Sugerir cores", "survey_completed_heading": "Inquérito Concluído", "survey_completed_subheading": "Este inquérito gratuito e de código aberto foi encerrado", "survey_display_settings": "Configurações de Exibição do Inquérito", @@ -1642,7 +1720,7 @@ "validation_rules": "Regras de validação", "validation_rules_description": "Aceitar apenas respostas que cumpram os seguintes critérios", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variável \"{variableName}\" está a ser utilizada na quota \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "A variável \"{variableName}\" está a ser usada na quota \"{quotaName}\"", "variable_name_conflicts_with_hidden_field": "O nome da variável está em conflito com um ID de campo oculto existente.", "variable_name_is_already_taken_please_choose_another": "O nome da variável já está em uso, por favor escolha outro.", "variable_name_must_start_with_a_letter": "O nome da variável deve começar com uma letra.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Adicionar cor de fundo", "add_background_color_description": "Adicione uma cor de fundo ao contentor do logótipo.", + "advanced_styling_field_border_radius": "Raio da borda", + "advanced_styling_field_button_bg": "Fundo do botão", + "advanced_styling_field_button_bg_description": "Preenche o botão Seguinte / Submeter.", + "advanced_styling_field_button_border_radius_description": "Arredonda os cantos do botão.", + "advanced_styling_field_button_font_size_description": "Ajusta o tamanho do texto da etiqueta do botão.", + "advanced_styling_field_button_font_weight_description": "Torna o texto do botão mais leve ou mais negrito.", + "advanced_styling_field_button_height_description": "Controla a altura do botão.", + "advanced_styling_field_button_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_button_padding_y_description": "Adiciona espaço no topo e na base.", + "advanced_styling_field_button_text": "Texto do botão", + "advanced_styling_field_button_text_description": "Colore a etiqueta dentro dos botões.", + "advanced_styling_field_description_color": "Cor da descrição", + "advanced_styling_field_description_color_description": "Colore o texto abaixo de cada título.", + "advanced_styling_field_description_size": "Tamanho da fonte da descrição", + "advanced_styling_field_description_size_description": "Ajusta o tamanho do texto da descrição.", + "advanced_styling_field_description_weight": "Peso da fonte da descrição", + "advanced_styling_field_description_weight_description": "Torna o texto da descrição mais leve ou mais negrito.", + "advanced_styling_field_font_size": "Tamanho da fonte", + "advanced_styling_field_font_weight": "Peso da fonte", + "advanced_styling_field_headline_color": "Cor do título", + "advanced_styling_field_headline_color_description": "Colore o texto principal da pergunta.", + "advanced_styling_field_headline_size": "Tamanho da fonte do título", + "advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.", + "advanced_styling_field_headline_weight": "Peso da fonte do título", + "advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.", + "advanced_styling_field_height": "Altura mínima", + "advanced_styling_field_indicator_bg": "Fundo do indicador", + "advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.", + "advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.", + "advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.", + "advanced_styling_field_input_height_description": "Controla a altura mínima do campo de entrada.", + "advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_input_padding_y_description": "Adiciona espaço no topo e na base.", + "advanced_styling_field_input_placeholder_opacity_description": "Atenua o texto de sugestão do placeholder.", + "advanced_styling_field_input_shadow_description": "Adiciona uma sombra ao redor dos campos de entrada.", + "advanced_styling_field_input_text": "Texto de entrada", + "advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.", + "advanced_styling_field_option_bg": "Fundo", + "advanced_styling_field_option_bg_description": "Preenche os itens de opção.", + "advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.", + "advanced_styling_field_option_font_size_description": "Ajusta o tamanho do texto da etiqueta da opção.", + "advanced_styling_field_option_label": "Cor da etiqueta", + "advanced_styling_field_option_label_description": "Colore o texto da etiqueta da opção.", + "advanced_styling_field_option_padding_x_description": "Adiciona espaço à esquerda e à direita.", + "advanced_styling_field_option_padding_y_description": "Adiciona espaço no topo e na base.", + "advanced_styling_field_padding_x": "Espaçamento X", + "advanced_styling_field_padding_y": "Espaçamento Y", + "advanced_styling_field_placeholder_opacity": "Opacidade do marcador de posição", + "advanced_styling_field_shadow": "Sombra", + "advanced_styling_field_track_bg": "Fundo da faixa", + "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_section_buttons": "Botões", + "advanced_styling_section_headlines": "Títulos e descrições", + "advanced_styling_section_inputs": "Campos de entrada", + "advanced_styling_section_options": "Opções (rádio/caixa de seleção)", "app_survey_placement": "Colocação do inquérito (app)", "app_survey_placement_settings_description": "Altere onde os inquéritos serão apresentados na sua aplicação web ou website.", - "centered_modal_overlay_color": "Cor da sobreposição modal centralizada", "email_customization": "Personalização de e-mail", "email_customization_description": "Altere a aparência dos e-mails que a Formbricks envia em seu nome.", "enable_custom_styling": "Ativar estilização personalizada", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "A marca Formbricks está oculta.", "formbricks_branding_settings_description": "Adoramos o seu apoio, mas compreendemos se preferir desativar.", "formbricks_branding_shown": "A marca Formbricks está visível.", + "generate_theme_btn": "Gerar", + "generate_theme_confirmation": "Gostarias de gerar um tema de cores correspondente com base na cor da tua marca? Isto irá substituir as tuas definições de cor atuais.", + "generate_theme_header": "Gerar tema de cores?", "logo_removed_successfully": "Logótipo removido com sucesso", "logo_settings_description": "Carregue o logótipo da sua empresa para personalizar inquéritos e pré-visualizações de links.", "logo_updated_successfully": "Logótipo atualizado com sucesso", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Mostrar marca Formbricks em inquéritos {type}", "show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'", "styling_updated_successfully": "Estilo atualizado com sucesso", + "suggest_colors": "Sugerir cores", + "suggested_colors_applied_please_save": "Cores sugeridas geradas com sucesso. Pressiona \"Guardar\" para manter as alterações.", "theme": "Tema", "theme_settings_description": "Crie um tema de estilo para todos os inquéritos. Pode ativar estilos personalizados para cada inquérito." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Sim, mantenha-me informado.", "preview_survey_question_2_choice_2_label": "Não, obrigado!", "preview_survey_question_2_headline": "Quer manter-se atualizado?", + "preview_survey_question_2_subheader": "Este é um exemplo de descrição.", "preview_survey_welcome_card_headline": "Bem-vindo!", "prioritize_features_description": "Identifique as funcionalidades que os seus utilizadores precisam mais e menos.", "prioritize_features_name": "Priorizar Funcionalidades", diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json index a869b1ec9d..9052e374ff 100644 --- a/apps/web/locales/ro-RO.json +++ b/apps/web/locales/ro-RO.json @@ -187,6 +187,7 @@ "customer_success": "Succesul Clientului", "dark_overlay": "Suprapunere întunecată", "date": "Dată", + "days": "zile", "default": "Implicit", "delete": "Șterge", "description": "Descriere", @@ -253,6 +254,7 @@ "label": "Etichetă", "language": "Limba", "learn_more": "Află mai multe", + "license_expired": "License Expired", "light_overlay": "Suprapunere ușoară", "limits_reached": "Limite atinse", "link": "Legătura", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks funcționează cel mai bine pe un ecran mai mare. Pentru a gestiona sau crea chestionare, treceți la un alt dispozitiv.", "mobile_overlay_surveys_look_good": "Nu vă faceți griji – chestionarele dumneavoastră arată grozav pe orice dispozitiv și dimensiune a ecranului!", "mobile_overlay_title": "Ups, ecran mic detectat!", + "months": "luni", "move_down": "Mută în jos", "move_up": "Mută sus", "multiple_languages": "Mai multe limbi", @@ -283,6 +286,7 @@ "no_background_image_found": "Nu a fost găsită nicio imagine de fundal.", "no_code": "Fără Cod", "no_files_uploaded": "Nu au fost încărcate fișiere", + "no_overlay": "Fără overlay", "no_quotas_found": "Nicio cotă găsită", "no_result_found": "Niciun rezultat găsit", "no_results": "Nicio rezultat", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Echipele organizației nu au fost găsite", "other": "Altele", "others": "Altele", + "overlay_color": "Culoare overlay", "overview": "Prezentare generală", "password": "Parolă", "paused": "Pauză", @@ -348,6 +353,7 @@ "request_trial_license": "Solicitați o licență de încercare", "reset_to_default": "Revino la implicit", "response": "Răspuns", + "response_id": "ID răspuns", "responses": "Răspunsuri", "restart": "Repornește", "role": "Rolul", @@ -388,6 +394,7 @@ "status": "Stare", "step_by_step_manual": "Manual pas cu pas", "storage_not_configured": "Stocarea fișierelor neconfigurată, upload-urile vor eșua probabil", + "string": "Text", "styling": "Stilizare", "submit": "Trimite", "summary": "Sumar", @@ -443,6 +450,7 @@ "website_and_app_connection": "Conectare site web și aplicație", "website_app_survey": "Chestionar pentru site și aplicație", "website_survey": "Chestionar despre site", + "weeks": "săptămâni", "welcome_card": "Card de bun venit", "workspace_configuration": "Configurare workspace", "workspace_created_successfully": "Spațiul de lucru a fost creat cu succes", @@ -453,13 +461,15 @@ "workspace_not_found": "Workspace-ul nu a fost găsit", "workspace_permission_not_found": "Permisiunea pentru workspace nu a fost găsită", "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.", "you_have_reached_your_monthly_miu_limit_of": "Ați atins limita lunară MIU de", "you_have_reached_your_monthly_response_limit_of": "Ați atins limita lunară de răspunsuri de", - "you_will_be_downgraded_to_the_community_edition_on_date": "Vei fi retrogradat la ediția Community pe {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Vei fi retrogradat la ediția Community pe {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Acceptă", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Atribut actualizat cu succes", "attribute_value": "Valoare", "attribute_value_placeholder": "Valoare atribut", + "attributes_msg_attribute_limit_exceeded": "Nu s-au putut crea {count, plural, one {1 atribut nou} few {# atribute noi} other {# de atribute noi}} deoarece s-ar depăși limita maximă de {limit, plural, one {1 clasă de atribute} few {# clase de atribute} other {# de clase de atribute}}. Atributele existente au fost actualizate cu succes.", + "attributes_msg_attribute_type_validation_error": "{error} (atributul „{key}” are dataType: {dataType})", + "attributes_msg_email_already_exists": "Emailul există deja pentru acest mediu și nu a fost actualizat.", + "attributes_msg_email_or_userid_required": "Este necesar fie un email, fie un userId. Valorile existente au fost păstrate.", + "attributes_msg_new_attribute_created": "A fost creat atributul nou „{key}” cu tipul „{dataType}”", + "attributes_msg_userid_already_exists": "UserId-ul există deja pentru acest mediu și nu a fost actualizat.", "contact_deleted_successfully": "Contact șters cu succes", "contact_not_found": "Nu a fost găsit niciun contact", "contacts_table_refresh": "Reîmprospătare contacte", @@ -631,6 +647,11 @@ "create_key": "Creează cheie", "create_new_attribute": "Creează atribut nou", "create_new_attribute_description": "Creează un atribut nou pentru segmentare.", + "custom_attributes": "Atribute personalizate", + "data_type": "Tip de date", + "data_type_cannot_be_changed": "Tipul de date nu poate fi schimbat după creare", + "data_type_description": "Alege cum să fie stocat și filtrat acest atribut", + "date_value_required": "Valoarea pentru dată este obligatorie. Folosește butonul de ștergere dacă nu vrei să setezi o dată.", "delete_attribute_confirmation": "{value, plural, one {Acest lucru va șterge atributul selectat. Orice date de contact asociate cu acest atribut vor fi pierdute.} few {Acest lucru va șterge atributele selectate. Orice date de contact asociate cu aceste atribute vor fi pierdute.} other {Acest lucru va șterge atributele selectate. Orice date de contact asociate cu aceste atribute vor fi pierdute.}}", "delete_contact_confirmation": "Acest lucru va șterge toate răspunsurile la sondaj și atributele de contact asociate cu acest contact. Orice țintire și personalizare bazată pe datele acestui contact vor fi pierdute.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Această acțiune va șterge toate răspunsurile chestionarului și atributele de contact asociate cu acest contact. Orice țintire și personalizare bazată pe datele acestui contact vor fi pierdute. Dacă acest contact are răspunsuri care contează pentru cotele chestionarului, numărul cotelor va fi redus, dar limitele cotelor vor rămâne neschimbate.} other {Aceste acțiuni vor șterge toate răspunsurile chestionarului și atributele de contact asociate cu acești contacți. Orice țintire și personalizare bazată pe datele acestor contacți vor fi pierdute. Dacă acești contacți au răspunsuri care contează pentru cotele chestionarului, numărul cotelor va fi redus, dar limitele cotelor vor rămâne neschimbate.} }", @@ -638,13 +659,18 @@ "edit_attribute_description": "Actualizează eticheta și descrierea acestui atribut.", "edit_attribute_values": "Editează atributele", "edit_attribute_values_description": "Modifică valorile anumitor atribute pentru acest contact.", + "edit_attributes": "Editează atributele", "edit_attributes_success": "Atributele contactului au fost actualizate cu succes", "generate_personal_link": "Generează link personal", "generate_personal_link_description": "Selectați un sondaj publicat pentru a genera un link personalizat pentru acest contact.", + "invalid_csv_column_names": "Nume de coloană CSV nevalide: {columns}. Numele coloanelor care vor deveni atribute noi trebuie să conțină doar litere mici, cifre și caractere de subliniere și trebuie să înceapă cu o literă.", + "invalid_date_format": "Format de dată invalid. Te rugăm să folosești o dată validă.", + "invalid_number_format": "Format de număr invalid. Te rugăm să introduci un număr valid.", "no_published_link_surveys_available": "Nu există sondaje publicate pentru linkuri disponibile. Vă rugăm să publicați mai întâi un sondaj pentru linkuri.", "no_published_surveys": "Nu există sondaje publicate", "no_responses_found": "Nu s-au găsit răspunsuri", "not_provided": "Nu a fost furnizat", + "number_value_required": "Valoarea numerică este obligatorie. Folosește butonul de ștergere pentru a elimina acest atribut.", "personal_link_generated": "Linkul personal a fost generat cu succes", "personal_link_generated_but_clipboard_failed": "Linkul personal a fost generat, dar nu s-a reușit copierea în clipboard: {url}", "personal_survey_link": "Link către sondajul personal", @@ -653,13 +679,22 @@ "search_contact": "Căutați contact", "select_a_survey": "Selectați un sondaj", "select_attribute": "Selectează atributul", + "select_attribute_key": "Selectează cheia atributului", + "system_attributes": "Atribute de sistem", "unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite", "unlock_contacts_title": "Deblocați contactele cu un plan superior.", + "upload_contacts_error_attribute_type_mismatch": "Atributul „{key}” este de tipul „{dataType}”, dar CSV-ul conține valori invalide: {values}", + "upload_contacts_error_duplicate_mappings": "Au fost găsite mapări duplicate pentru următoarele atribute: {attributes}", + "upload_contacts_error_file_too_large": "Dimensiunea fișierului depășește limita maximă de 800KB", + "upload_contacts_error_generic": "A apărut o eroare la încărcarea contactelor. Te rugăm să încerci din nou mai târziu.", + "upload_contacts_error_invalid_file_type": "Te rugăm să încarci un fișier CSV", + "upload_contacts_error_no_valid_contacts": "Fișierul CSV încărcat nu conține contacte valide. Consultă fișierul CSV de exemplu pentru formatul corect.", + "upload_contacts_modal_attribute_header": "Atribut Formbricks", "upload_contacts_modal_attributes_description": "Mapează coloanele din CSV-ul tău la atributele din Formbricks.", "upload_contacts_modal_attributes_new": "Atribut nou", "upload_contacts_modal_attributes_search_or_add": "Căutați sau adăugați atribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "ar trebui să fie mapat către", "upload_contacts_modal_attributes_title": "Atribute", + "upload_contacts_modal_csv_column_header": "Coloană CSV", "upload_contacts_modal_description": "Încărcați un fișier CSV pentru a importa rapid contactele cu atribute.", "upload_contacts_modal_download_example_csv": "Descărcați exemplul CSV", "upload_contacts_modal_duplicates_description": "Cum ar trebui să procedăm dacă un contact există deja în agenda dumneavoastră?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Niciun atribut încă!", "no_filters_yet": "Nu există filtre încă!", "no_segments_yet": "În prezent nu aveți segmente salvate.", + "operator_contains": "conține", + "operator_does_not_contain": "nu conține", + "operator_ends_with": "se termină cu", + "operator_is_after": "este după", + "operator_is_before": "este înainte", + "operator_is_between": "este între", + "operator_is_newer_than": "este mai nou decât", + "operator_is_not_set": "nu este setat", + "operator_is_older_than": "este mai vechi decât", + "operator_is_same_day": "este în aceeași zi", + "operator_is_set": "este setat", + "operator_starts_with": "începe cu", + "operator_title_contains": "Conține", + "operator_title_does_not_contain": "Nu conține", + "operator_title_ends_with": "Se termină cu", + "operator_title_equals": "Egal", + "operator_title_greater_equal": "Mai mare sau egal cu", + "operator_title_greater_than": "Mai mare decât", + "operator_title_is_after": "Este după", + "operator_title_is_before": "Este înainte", + "operator_title_is_between": "Este între", + "operator_title_is_newer_than": "Este mai nou decât", + "operator_title_is_not_set": "Nu este setat", + "operator_title_is_older_than": "Este mai vechi decât", + "operator_title_is_same_day": "Este în aceeași zi", + "operator_title_is_set": "Este setat", + "operator_title_less_equal": "Mai mic sau egal cu", + "operator_title_less_than": "Mai mic decât", + "operator_title_not_equals": "Nu este egal cu", + "operator_title_starts_with": "Începe cu", + "operator_title_user_is_in": "Utilizatorul este în", + "operator_title_user_is_not_in": "Utilizatorul nu este în", + "operator_user_is_in": "Utilizatorul este în", + "operator_user_is_not_in": "Utilizatorul nu este în", "person_and_attributes": "Persoană & Atribute", "phone": "Telefon", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Vă rugăm să eliminați segmentul din aceste chestionare pentru a-l șterge.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "Targetarea utilizatorilor este disponibilă în prezent doar atunci când", "value_cannot_be_empty": "Valoarea nu poate fi goală.", "value_must_be_a_number": "Valoarea trebuie să fie un număr.", + "value_must_be_positive": "Valoarea trebuie să fie un număr pozitiv.", "view_filters": "Vizualizați filtrele", "where": "Unde", "with_the_formbricks_sdk": "cu SDK Formbricks" @@ -950,19 +1020,32 @@ "enterprise_features": "Funcții Enterprise", "get_an_enterprise_license_to_get_access_to_all_features": "Obțineți o licență Enterprise pentru a avea acces la toate funcționalitățile.", "keep_full_control_over_your_data_privacy_and_security": "Mențineți controlul complet asupra confidențialității și securității datelor dumneavoastră.", + "license_invalid_description": "Cheia de licență din variabila de mediu ENTERPRISE_LICENSE_KEY nu este validă. Te rugăm să verifici dacă există greșeli de scriere sau să soliciți o cheie nouă.", + "license_status": "Stare licență", + "license_status_active": "Activă", + "license_status_description": "Starea licenței tale enterprise.", + "license_status_expired": "Expirată", + "license_status_invalid": "Licență invalidă", + "license_status_unreachable": "Indisponibilă", + "license_unreachable_grace_period": "Serverul de licențe nu poate fi contactat. Funcționalitățile enterprise rămân active timp de 3 zile, până la data de {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Nicio apel necesar, fără obligații: Solicitați o licență de probă gratuită de 30 de zile pentru a testa toate funcțiile prin completarea acestui formular:", "no_credit_card_no_sales_call_just_test_it": "Nu este nevoie de card de credit. Fără apeluri de vânzări. Doar testează-l :)", "on_request": "La cerere", "organization_roles": "Roluri organizaționale (Administrator, Editor, Dezvoltator, etc.)", "questions_please_reach_out_to": "Întrebări? Vă rugăm să trimiteți mesaj către", + "recheck_license": "Verifică din nou licența", + "recheck_license_failed": "Verificarea licenței a eșuat. Serverul de licențe poate fi indisponibil.", + "recheck_license_invalid": "Cheia de licență este invalidă. Te rugăm să verifici variabila ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Licența a fost verificată cu succes", + "recheck_license_unreachable": "Serverul de licențe este indisponibil. Te rugăm să încerci din nou mai târziu.", + "rechecking": "Se verifică din nou...", "request_30_day_trial_license": "Solicitați o licență de încercare de 30 de zile", "saml_sso": "SAML SSO", "service_level_agreement": "Acord privind nivelul de servicii", "soc2_hipaa_iso_27001_compliance_check": "Verificare conformitate SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Echipe & Roluri de Acces (Citiți, Citiți și Scrieți, Gestionați)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile.", - "your_enterprise_license_is_active_all_features_unlocked": "Licența dvs. Enterprise este activă. Toate funcțiile sunt deblocate." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile." }, "general": { "bulk_invite_warning_description": "În planul gratuit, toți membrii organizației sunt întotdeauna alocați rolului „Proprietar”.", @@ -986,7 +1069,7 @@ "from_your_organization": "din organizația ta", "invitation_sent_once_more": "Invitație trimisă din nou.", "invite_deleted_successfully": "Invitație ștearsă cu succes", - "invited_on": "Invitat pe {date}", + "invite_expires_on": "Invitația expiră pe {date}", "invites_failed": "Invitații eșuate", "leave_organization": "Părăsește organizația", "leave_organization_description": "Vei părăsi această organizație și vei pierde accesul la toate sondajele și răspunsurile. Poți să te alături din nou doar dacă ești invitat.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Vă rugăm să completați toate câmpurile pentru a adăuga un nou spațiu de lucru.", "read": "Citește", "read_write": "Citire & Scriere", - "select_member": "Selectează membrul", - "select_workspace": "Selectați spațiul de lucru", "team_admin": "Administrator Echipe", "team_created_successfully": "Echipă creată cu succes", "team_deleted_successfully": "Echipă ștearsă cu succes.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Adaugă un placeholder pentru a afișa dacă nu există valoare de reamintit", "add_hidden_field_id": "Adăugați ID câmp ascuns", "add_highlight_border": "Adaugă bordură evidențiată", - "add_highlight_border_description": "Adaugă o margine exterioară cardului tău de sondaj.", "add_logic": "Adaugă logică", "add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"", "add_option": "Adăugați opțiune", @@ -1189,6 +1269,7 @@ "block_duplicated": "Bloc duplicat.", "bold": "Îngroșat", "brand_color": "Culoarea brandului", + "brand_color_description": "Se aplică pe butoane, linkuri și evidențieri.", "brightness": "Luminozitate", "bulk_edit": "Editare în bloc", "bulk_edit_description": "Editați toate opțiunile de mai jos, câte una pe linie. Liniile goale vor fi omise, iar duplicatele vor fi eliminate.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Capturați acțiune nouă", "card_arrangement_for_survey_type_derived": "Aranjament de carduri pentru sondaje de tip {surveyTypeDerived}", "card_background_color": "Culoarea de fundal a cardului", + "card_background_color_description": "Umple zona cardului de sondaj.", "card_border_color": "Culoarea bordurii cardului", + "card_border_color_description": "Conturează cardul sondajului.", "card_styling": "Stilizare card", "casual": "Casual", "caution_edit_duplicate": "Duplică & editează", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Răspunsurile mai vechi și mai noi se amestecă, ceea ce poate duce la rezumate de date înșelătoare.", "caution_recommendation": "Aceasta poate cauza inconsistențe de date în rezultatul sondajului. Vă recomandăm să duplicați sondajul în schimb.", "caution_text": "Schimbările vor duce la inconsecvențe", - "centered_modal_overlay_color": "Culoare suprapunere modală centralizată", "change_anyway": "Schimbă oricum", "change_background": "Schimbați fundalul", "change_question_type": "Schimbă tipul întrebării", "change_survey_type": "Schimbarea tipului chestionarului afectează accesul existent", - "change_the_background_color_of_the_card": "Schimbați culoarea de fundal a cardului.", - "change_the_background_color_of_the_input_fields": "Schimbați culoarea de fundal a câmpurilor de introducere.", "change_the_background_to_a_color_image_or_animation": "Schimbați fundalul cu o culoare, imagine sau animație.", - "change_the_border_color_of_the_card": "Schimbați culoarea bordurii cardului.", - "change_the_border_color_of_the_input_fields": "Schimbați culoarea bordurii câmpurilor de introducere.", - "change_the_border_radius_of_the_card_and_the_inputs": "Schimbați raza de rotunjire a cardului și a câmpurilor de introducere.", - "change_the_brand_color_of_the_survey": "Schimbați culoarea brandului chestionarului", "change_the_placement_of_this_survey": "Schimbă amplasarea acestui sondaj.", - "change_the_question_color_of_the_survey": "Schimbați culoarea întrebării chestionarului.", "changes_saved": "Modificările au fost salvate", "changing_survey_type_will_remove_existing_distribution_channels": "Schimbarea tipului chestionarului va afecta modul în care acesta poate fi distribuit. Dacă respondenții au deja linkuri de acces pentru tipul curent, aceștia ar putea pierde accesul după schimbare.", "checkbox_label": "Etichetă casetă de selectare", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Dezactivați vizibilitatea progresului sondajului", "display_an_estimate_of_completion_time_for_survey": "Afișează o estimare a timpului de finalizare pentru sondaj", "display_number_of_responses_for_survey": "Afișează numărul de răspunsuri pentru sondaj", + "display_type": "Tip de afișare", "divide": "Împarte /", "does_not_contain": "Nu conține", "does_not_end_with": "Nu se termină cu", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Nu include toate", "does_not_include_one_of": "Nu include una dintre", "does_not_start_with": "Nu începe cu", + "dropdown": "Dropdown", "duplicate_block": "Duplicați blocul", "duplicate_question": "Duplică întrebarea", "edit_link": "Editare legătură", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Ascunde bara de progres", "hide_question_settings": "Ascunde setările întrebării", "hostname": "Nume gazdă", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Cât de funky doriți să fie cardurile dumneavoastră în sondajele de tip {surveyTypeDerived}", "if_you_need_more_please": "Dacă aveți nevoie de mai mult, vă rugăm", "if_you_really_want_that_answer_ask_until_you_get_it": "Continuă afișarea ori de câte ori este declanșat până când se trimite un răspuns.", "ignore_global_waiting_time": "Ignoră perioada de răcire", @@ -1379,7 +1455,9 @@ "initial_value": "Valoare inițială", "inner_text": "Text Interior", "input_border_color": "Culoarea graniței câmpului de introducere", + "input_border_color_description": "Conturează câmpurile de text și zonele de text.", "input_color": "Culoarea câmpului de introducere", + "input_color_description": "Umple interiorul câmpurilor de text.", "insert_link": "Inserează link", "invalid_targeting": "\"Targetare nevalidă: Vă rugăm să verificați filtrele pentru audiență\"", "invalid_video_url_warning": "Vă rugăm să introduceți un URL valid de YouTube, Vimeo sau Loom. În prezent nu susținem alți furnizori de găzduire video.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Limitați dimensiunea maximă a fișierului pentru încărcări.", "limit_upload_file_size_to": "Limitați dimensiunea fișierului încărcat la", "link_survey_description": "Partajați un link către o pagină de chestionar sau încorporați-l într-o pagină web sau email.", + "list": "Listă", "load_segment": "Încarcă segment", "logic_error_warning": "Schimbarea va provoca erori de logică", "logic_error_warning_text": "Schimbarea tipului de întrebare va elimina condițiile de logică din această întrebare", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Doar utilizatorii care cunosc PIN-ul pot accesa sondajul.", "publish": "Publică", "question": "Întrebare", - "question_color": "Culoarea întrebării", "question_deleted": "Întrebare ștearsă.", "question_duplicated": "Întrebare duplicată.", "question_id_updated": "ID întrebare actualizat", "question_used_in_logic_warning_text": "Elemente din acest bloc sunt folosite într-o regulă de logică. Sigur doriți să îl ștergeți?", "question_used_in_logic_warning_title": "Inconsistență logică", - "question_used_in_quota": "Întrebarea aceasta este folosită în cota \"{quotaName}\"", + "question_used_in_quota": "Întrebarea aceasta este folosită în cota „{quotaName}”", "question_used_in_recall": "Această întrebare este reamintită în întrebarea {questionIndex}.", "question_used_in_recall_ending_card": "Această întrebare este reamintită în Cardul de Încheiere.", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.", "response_options": "Opțiuni răspuns", "roundness": "Rotunjire", + "roundness_description": "Controlează cât de rotunjite sunt colțurile cardului.", "row_used_in_logic_error": "Această linie este folosită în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.", "rows": "Rânduri", "save_and_close": "Salvează & Închide", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Stilizare setată la stilurile temei", "subheading": "Subtitlu", "subtract": "Scade -", - "suggest_colors": "Sugerați culori", "survey_completed_heading": "Sondaj Completat", "survey_completed_subheading": "Acest sondaj gratuit și open-source a fost închis", "survey_display_settings": "Setări de afișare a sondajului", @@ -1642,7 +1720,7 @@ "validation_rules": "Reguli de validare", "validation_rules_description": "Acceptă doar răspunsurile care îndeplinesc următoarele criterii", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} este folosit în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabila \"{variableName}\" este folosită în cota \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabila „{variableName}” este folosită în cota „{quotaName}”. Vă rugăm să o eliminați mai întâi din cotă", "variable_name_conflicts_with_hidden_field": "Numele variabilei intră în conflict cu un ID de câmp ascuns existent.", "variable_name_is_already_taken_please_choose_another": "Numele variabilei este deja utilizat, vă rugăm să alegeți altul.", "variable_name_must_start_with_a_letter": "Numele variabilei trebuie să înceapă cu o literă.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Adăugați culoare de fundal", "add_background_color_description": "Adăugați o culoare de fundal la containerul siglei.", + "advanced_styling_field_border_radius": "Raza colțurilor", + "advanced_styling_field_button_bg": "Fundal buton", + "advanced_styling_field_button_bg_description": "Umple butonul Următor / Trimite.", + "advanced_styling_field_button_border_radius_description": "Rotunjește colțurile butonului.", + "advanced_styling_field_button_font_size_description": "Scalează textul etichetei butonului.", + "advanced_styling_field_button_font_weight_description": "Face textul butonului mai subțire sau mai îngroșat.", + "advanced_styling_field_button_height_description": "Controlează înălțimea butonului.", + "advanced_styling_field_button_padding_x_description": "Adaugă spațiu la stânga și la dreapta.", + "advanced_styling_field_button_padding_y_description": "Adaugă spațiu sus și jos.", + "advanced_styling_field_button_text": "Text buton", + "advanced_styling_field_button_text_description": "Colorează eticheta din interiorul butoanelor.", + "advanced_styling_field_description_color": "Culoare descriere", + "advanced_styling_field_description_color_description": "Colorează textul de sub fiecare titlu.", + "advanced_styling_field_description_size": "Mărime font descriere", + "advanced_styling_field_description_size_description": "Scalează textul descrierii.", + "advanced_styling_field_description_weight": "Grosime font descriere", + "advanced_styling_field_description_weight_description": "Face textul descrierii mai subțire sau mai îngroșat.", + "advanced_styling_field_font_size": "Mărime font", + "advanced_styling_field_font_weight": "Grosime font", + "advanced_styling_field_headline_color": "Culoare titlu", + "advanced_styling_field_headline_color_description": "Colorează textul principal al întrebării.", + "advanced_styling_field_headline_size": "Mărime font titlu", + "advanced_styling_field_headline_size_description": "Scalează textul titlului.", + "advanced_styling_field_headline_weight": "Grosime font titlu", + "advanced_styling_field_headline_weight_description": "Face textul titlului mai subțire sau mai îngroșat.", + "advanced_styling_field_height": "Înălțime minimă", + "advanced_styling_field_indicator_bg": "Fundal indicator", + "advanced_styling_field_indicator_bg_description": "Colorează partea umplută a barei.", + "advanced_styling_field_input_border_radius_description": "Rotunjește colțurile câmpurilor de introducere.", + "advanced_styling_field_input_font_size_description": "Scalează textul introdus în câmpuri.", + "advanced_styling_field_input_height_description": "Controlează înălțimea minimă a câmpului de introducere.", + "advanced_styling_field_input_padding_x_description": "Adaugă spațiu la stânga și la dreapta.", + "advanced_styling_field_input_padding_y_description": "Adaugă spațiu deasupra și dedesubt.", + "advanced_styling_field_input_placeholder_opacity_description": "Estompează textul de sugestie din placeholder.", + "advanced_styling_field_input_shadow_description": "Adaugă o umbră în jurul câmpurilor de introducere.", + "advanced_styling_field_input_text": "Text câmp", + "advanced_styling_field_input_text_description": "Colorează textul introdus în câmpuri.", + "advanced_styling_field_option_bg": "Fundal", + "advanced_styling_field_option_bg_description": "Umple elementele de opțiune.", + "advanced_styling_field_option_border_radius_description": "Rotunjește colțurile opțiunilor.", + "advanced_styling_field_option_font_size_description": "Redimensionează textul etichetei opțiunii.", + "advanced_styling_field_option_label": "Culoare etichetă", + "advanced_styling_field_option_label_description": "Colorează textul etichetei opțiunii.", + "advanced_styling_field_option_padding_x_description": "Adaugă spațiu în stânga și în dreapta.", + "advanced_styling_field_option_padding_y_description": "Adaugă spațiu deasupra și dedesubt.", + "advanced_styling_field_padding_x": "Spațiere X", + "advanced_styling_field_padding_y": "Spațiere Y", + "advanced_styling_field_placeholder_opacity": "Opacitate placeholder", + "advanced_styling_field_shadow": "Umbră", + "advanced_styling_field_track_bg": "Fundal track", + "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_section_buttons": "Butoane", + "advanced_styling_section_headlines": "Titluri și descrieri", + "advanced_styling_section_inputs": "Inputuri", + "advanced_styling_section_options": "Opțiuni (Radio/Checkbox)", "app_survey_placement": "Amplasarea sondajului în aplicație", "app_survey_placement_settings_description": "Schimbați unde vor fi afișate sondajele în aplicația sau site-ul dvs. web.", - "centered_modal_overlay_color": "Culoare suprapunere modală centralizată", "email_customization": "Personalizare email", "email_customization_description": "Schimbați aspectul și stilul emailurilor trimise de Formbricks în numele dvs.", "enable_custom_styling": "Activați stilizarea personalizată", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Brandingul Formbricks este ascuns.", "formbricks_branding_settings_description": "Ne bucurăm de susținerea ta, dar înțelegem dacă vrei să dezactivezi această opțiune.", "formbricks_branding_shown": "Brandingul Formbricks este afișat.", + "generate_theme_btn": "Generează", + "generate_theme_confirmation": "Vrei să generezi o temă de culori potrivită pe baza culorii brandului tău? Aceasta va suprascrie setările actuale de culoare.", + "generate_theme_header": "Generezi temă de culori?", "logo_removed_successfully": "Sigla a fost eliminată cu succes", "logo_settings_description": "Încarcă sigla companiei pentru a personaliza sondajele și previzualizările de linkuri.", "logo_updated_successfully": "Sigla a fost actualizată cu succes", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Afișează brandingul Formbricks în sondajele de tip {type}", "show_powered_by_formbricks": "Afișează semnătura „Powered by Formbricks”", "styling_updated_successfully": "Stilizarea a fost actualizată cu succes", + "suggest_colors": "Sugerează culori", + "suggested_colors_applied_please_save": "Culorile sugerate au fost generate cu succes. Apasă pe „Salvează” pentru a păstra modificările.", "theme": "Temă", "theme_settings_description": "Creează o temă de stil pentru toate sondajele. Poți activa stilizare personalizată pentru fiecare sondaj." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Da, ține-mă informat.", "preview_survey_question_2_choice_2_label": "Nu, mulţumesc!", "preview_survey_question_2_headline": "Vrei să fii în temă?", + "preview_survey_question_2_subheader": "Aceasta este o descriere exemplu.", "preview_survey_welcome_card_headline": "Bun venit!", "prioritize_features_description": "Identificați caracteristicile de care utilizatorii dumneavoastră au cel mai mult și cel mai puțin nevoie.", "prioritize_features_name": "Prioritizați caracteristicile", diff --git a/apps/web/locales/ru-RU.json b/apps/web/locales/ru-RU.json index 744a1247f1..cca23e647b 100644 --- a/apps/web/locales/ru-RU.json +++ b/apps/web/locales/ru-RU.json @@ -187,6 +187,7 @@ "customer_success": "Customer Success", "dark_overlay": "Тёмный оверлей", "date": "Дата", + "days": "дни", "default": "По умолчанию", "delete": "Удалить", "description": "Описание", @@ -253,6 +254,7 @@ "label": "Метка", "language": "Язык", "learn_more": "Подробнее", + "license_expired": "License Expired", "light_overlay": "Светлый оверлей", "limits_reached": "Достигнуты лимиты", "link": "Ссылка", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks лучше всего работает на большом экране. Для управления или создания опросов перейдите на другое устройство.", "mobile_overlay_surveys_look_good": "Не волнуйтесь — ваши опросы отлично выглядят на любом устройстве и экране!", "mobile_overlay_title": "Ой, обнаружен маленький экран!", + "months": "месяцы", "move_down": "Переместить вниз", "move_up": "Переместить вверх", "multiple_languages": "Несколько языков", @@ -283,6 +286,7 @@ "no_background_image_found": "Фоновое изображение не найдено.", "no_code": "Нет кода", "no_files_uploaded": "Файлы не были загружены", + "no_overlay": "Без наложения", "no_quotas_found": "Квоты не найдены", "no_result_found": "Результат не найден", "no_results": "Нет результатов", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Команды организации не найдены", "other": "Другое", "others": "Другие", + "overlay_color": "Цвет наложения", "overview": "Обзор", "password": "Пароль", "paused": "Приостановлено", @@ -348,6 +353,7 @@ "request_trial_license": "Запросить пробную лицензию", "reset_to_default": "Сбросить по умолчанию", "response": "Ответ", + "response_id": "ID ответа", "responses": "Ответы", "restart": "Перезапустить", "role": "Роль", @@ -388,6 +394,7 @@ "status": "Статус", "step_by_step_manual": "Пошаговая инструкция", "storage_not_configured": "Хранилище файлов не настроено, загрузка, скорее всего, не удастся", + "string": "Текст", "styling": "Стилизация", "submit": "Отправить", "summary": "Сводка", @@ -443,6 +450,7 @@ "website_and_app_connection": "Связь сайта и приложения", "website_app_survey": "Опрос сайта и приложения", "website_survey": "Опрос сайта", + "weeks": "недели", "welcome_card": "Приветственная карточка", "workspace_configuration": "Настройка рабочего пространства", "workspace_created_successfully": "Рабочий проект успешно создан", @@ -453,13 +461,15 @@ "workspace_not_found": "Рабочее пространство не найдено", "workspace_permission_not_found": "Разрешение на рабочее пространство не найдено", "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} рабочих пространств.", "you_have_reached_your_monthly_miu_limit_of": "Вы достигли месячного лимита MIU:", "you_have_reached_your_monthly_response_limit_of": "Вы достигли месячного лимита ответов:", - "you_will_be_downgraded_to_the_community_edition_on_date": "Ваша версия будет понижена до Community Edition {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Ваша версия будет понижена до Community Edition {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Принять", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Атрибут успешно обновлён", "attribute_value": "Значение", "attribute_value_placeholder": "Значение атрибута", + "attributes_msg_attribute_limit_exceeded": "Не удалось создать {count} новых атрибута, так как это превысит максимальное количество классов атрибутов: {limit}. Существующие атрибуты были успешно обновлены.", + "attributes_msg_attribute_type_validation_error": "{error} (атрибут «{key}» имеет тип данных: {dataType})", + "attributes_msg_email_already_exists": "Этот email уже существует в данной среде и не был обновлён.", + "attributes_msg_email_or_userid_required": "Требуется указать либо email, либо userId. Существующие значения были сохранены.", + "attributes_msg_new_attribute_created": "Создан новый атрибут «{key}» с типом «{dataType}»", + "attributes_msg_userid_already_exists": "Этот userId уже существует в данной среде и не был обновлён.", "contact_deleted_successfully": "Контакт успешно удалён", "contact_not_found": "Такой контакт не найден", "contacts_table_refresh": "Обновить контакты", @@ -631,6 +647,11 @@ "create_key": "Создать ключ", "create_new_attribute": "Создать новый атрибут", "create_new_attribute_description": "Создайте новый атрибут для целей сегментации.", + "custom_attributes": "Пользовательские атрибуты", + "data_type": "Тип данных", + "data_type_cannot_be_changed": "Тип данных нельзя изменить после создания", + "data_type_description": "Выберите, как этот атрибут будет храниться и фильтроваться", + "date_value_required": "Требуется значение даты. Используйте кнопку удаления, если не хотите указывать дату.", "delete_attribute_confirmation": "{value, plural, one {Будет удалён выбранный атрибут. Все данные контактов, связанные с этим атрибутом, будут потеряны.} few {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} many {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} other {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.}}", "delete_contact_confirmation": "Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны. Если у этого контакта есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} few {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} many {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} other {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Обновите метку и описание для этого атрибута.", "edit_attribute_values": "Редактировать атрибуты", "edit_attribute_values_description": "Измените значения определённых атрибутов для этого контакта.", + "edit_attributes": "Редактировать атрибуты", "edit_attributes_success": "Атрибуты контакта успешно обновлены", "generate_personal_link": "Сгенерировать персональную ссылку", "generate_personal_link_description": "Выберите опубликованный опрос, чтобы сгенерировать персональную ссылку для этого контакта.", + "invalid_csv_column_names": "Недопустимые имена столбцов в CSV: {columns}. Имена столбцов, которые станут новыми атрибутами, должны содержать только строчные буквы, цифры и подчёркивания, а также начинаться с буквы.", + "invalid_date_format": "Неверный формат даты. Пожалуйста, используйте корректную дату.", + "invalid_number_format": "Неверный формат числа. Пожалуйста, введите корректное число.", "no_published_link_surveys_available": "Нет доступных опубликованных опросов-ссылок. Пожалуйста, сначала опубликуйте опрос-ссылку.", "no_published_surveys": "Нет опубликованных опросов", "no_responses_found": "Ответы не найдены", "not_provided": "Не указано", + "number_value_required": "Требуется числовое значение. Используй кнопку удаления, чтобы убрать этот атрибут.", "personal_link_generated": "Персональная ссылка успешно сгенерирована", "personal_link_generated_but_clipboard_failed": "Персональная ссылка сгенерирована, но не удалось скопировать в буфер обмена: {url}", "personal_survey_link": "Персональная ссылка на опрос", @@ -653,13 +679,22 @@ "search_contact": "Поиск контакта", "select_a_survey": "Выберите опрос", "select_attribute": "Выберите атрибут", + "select_attribute_key": "Выберите ключ атрибута", + "system_attributes": "Системные атрибуты", "unlock_contacts_description": "Управляйте контактами и отправляйте целевые опросы", "unlock_contacts_title": "Откройте доступ к контактам с более высоким тарифом", + "upload_contacts_error_attribute_type_mismatch": "Атрибут «{key}» имеет тип «{dataType}», но в CSV обнаружены некорректные значения: {values}", + "upload_contacts_error_duplicate_mappings": "Обнаружены дублирующиеся сопоставления для следующих атрибутов: {attributes}", + "upload_contacts_error_file_too_large": "Размер файла превышает максимальный лимит 800 КБ", + "upload_contacts_error_generic": "Произошла ошибка при загрузке контактов. Пожалуйста, попробуй ещё раз позже.", + "upload_contacts_error_invalid_file_type": "Пожалуйста, загрузи файл в формате CSV", + "upload_contacts_error_no_valid_contacts": "Загруженный CSV-файл не содержит ни одного корректного контакта. Ознакомься с примером CSV-файла для правильного формата.", + "upload_contacts_modal_attribute_header": "Атрибут Formbricks", "upload_contacts_modal_attributes_description": "Сопоставьте столбцы в вашем CSV с атрибутами в Formbricks.", "upload_contacts_modal_attributes_new": "Новый атрибут", "upload_contacts_modal_attributes_search_or_add": "Найти или добавить атрибут", - "upload_contacts_modal_attributes_should_be_mapped_to": "должен быть сопоставлен с", "upload_contacts_modal_attributes_title": "Атрибуты", + "upload_contacts_modal_csv_column_header": "Столбец CSV", "upload_contacts_modal_description": "Загрузите CSV, чтобы быстро импортировать контакты с атрибутами", "upload_contacts_modal_download_example_csv": "Скачать пример CSV", "upload_contacts_modal_duplicates_description": "Как поступить, если контакт уже существует в вашей базе?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Пока нет атрибутов!", "no_filters_yet": "Пока нет фильтров!", "no_segments_yet": "У вас пока нет сохранённых сегментов.", + "operator_contains": "содержит", + "operator_does_not_contain": "не содержит", + "operator_ends_with": "оканчивается на", + "operator_is_after": "после", + "operator_is_before": "до", + "operator_is_between": "находится между", + "operator_is_newer_than": "новее чем", + "operator_is_not_set": "не задано", + "operator_is_older_than": "старше чем", + "operator_is_same_day": "в тот же день", + "operator_is_set": "задано", + "operator_starts_with": "начинается с", + "operator_title_contains": "Содержит", + "operator_title_does_not_contain": "Не содержит", + "operator_title_ends_with": "Оканчивается на", + "operator_title_equals": "Равно", + "operator_title_greater_equal": "Больше или равно", + "operator_title_greater_than": "Больше чем", + "operator_title_is_after": "После", + "operator_title_is_before": "До", + "operator_title_is_between": "Находится между", + "operator_title_is_newer_than": "Новее чем", + "operator_title_is_not_set": "Не задано", + "operator_title_is_older_than": "Старше чем", + "operator_title_is_same_day": "В тот же день", + "operator_title_is_set": "Задано", + "operator_title_less_equal": "Меньше или равно", + "operator_title_less_than": "Меньше чем", + "operator_title_not_equals": "Не равно", + "operator_title_starts_with": "Начинается с", + "operator_title_user_is_in": "Пользователь входит в", + "operator_title_user_is_not_in": "Пользователь не входит в", + "operator_user_is_in": "Пользователь входит в", + "operator_user_is_not_in": "Пользователь не входит в", "person_and_attributes": "Пользователь и атрибуты", "phone": "Телефон", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Пожалуйста, удалите этот сегмент из указанных опросов, чтобы его удалить.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "Таргетинг пользователей сейчас доступен только когда", "value_cannot_be_empty": "Значение не может быть пустым.", "value_must_be_a_number": "Значение должно быть числом.", + "value_must_be_positive": "Значение должно быть положительным числом.", "view_filters": "Просмотреть фильтры", "where": "Где", "with_the_formbricks_sdk": "с помощью Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "Функции для предприятий", "get_an_enterprise_license_to_get_access_to_all_features": "Получите корпоративную лицензию для доступа ко всем функциям.", "keep_full_control_over_your_data_privacy_and_security": "Полный контроль над конфиденциальностью и безопасностью ваших данных.", + "license_invalid_description": "Ключ лицензии в переменной окружения ENTERPRISE_LICENSE_KEY недействителен. Проверь, нет ли опечаток, или запроси новый ключ.", + "license_status": "Статус лицензии", + "license_status_active": "Активна", + "license_status_description": "Статус вашей корпоративной лицензии.", + "license_status_expired": "Срок действия истёк", + "license_status_invalid": "Недействительная лицензия", + "license_status_unreachable": "Недоступна", + "license_unreachable_grace_period": "Не удаётся подключиться к серверу лицензий. Корпоративные функции останутся активными в течение 3-дневного льготного периода, который закончится {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Без звонков и обязательств: запросите бесплатную 30-дневную пробную лицензию для тестирования всех функций, заполнив эту форму:", "no_credit_card_no_sales_call_just_test_it": "Без кредитной карты. Без звонков от отдела продаж. Просто попробуйте :)", "on_request": "По запросу", "organization_roles": "Роли в организации (администратор, редактор, разработчик и др.)", "questions_please_reach_out_to": "Вопросы? Свяжитесь с", + "recheck_license": "Проверить лицензию ещё раз", + "recheck_license_failed": "Не удалось проверить лицензию. Сервер лицензий может быть недоступен.", + "recheck_license_invalid": "Ключ лицензии недействителен. Пожалуйста, проверь свою переменную ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Проверка лицензии прошла успешно", + "recheck_license_unreachable": "Сервер лицензий недоступен. Пожалуйста, попробуй позже.", + "rechecking": "Проверка...", "request_30_day_trial_license": "Запросить 30-дневную пробную лицензию", "saml_sso": "SAML SSO", "service_level_agreement": "Соглашение об уровне обслуживания (SLA)", "soc2_hipaa_iso_27001_compliance_check": "Проверка соответствия SOC2, HIPAA, ISO 27001", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Команды и роли доступа (чтение, чтение и запись, управление)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней.", - "your_enterprise_license_is_active_all_features_unlocked": "Ваша корпоративная лицензия активна. Все функции разблокированы." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней." }, "general": { "bulk_invite_warning_description": "В бесплатном тарифе всем участникам организации всегда назначается роль \"Владелец\".", @@ -986,7 +1069,7 @@ "from_your_organization": "из вашей организации", "invitation_sent_once_more": "Приглашение отправлено ещё раз.", "invite_deleted_successfully": "Приглашение успешно удалено", - "invited_on": "Приглашён {date}", + "invite_expires_on": "Приглашение истекает {date}", "invites_failed": "Не удалось отправить приглашения", "leave_organization": "Покинуть организацию", "leave_organization_description": "Вы покинете эту организацию и потеряете доступ ко всем опросам и ответам. Вы сможете вернуться только по новому приглашению.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Пожалуйста, заполните все поля для добавления нового рабочего пространства.", "read": "Чтение", "read_write": "Чтение и запись", - "select_member": "Выберите участника", - "select_workspace": "Выберите рабочее пространство", "team_admin": "Администратор команды", "team_created_successfully": "Команда успешно создана.", "team_deleted_successfully": "Команда успешно удалена.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Добавить плейсхолдер, который будет показан, если нет значения для отображения.", "add_hidden_field_id": "Добавить скрытый ID поля", "add_highlight_border": "Добавить выделяющую рамку", - "add_highlight_border_description": "Добавьте внешнюю рамку к карточке опроса.", "add_logic": "Добавить логику", "add_none_of_the_above": "Добавить вариант «Ничего из вышеперечисленного»", "add_option": "Добавить вариант", @@ -1189,6 +1269,7 @@ "block_duplicated": "Блокировать дубликаты.", "bold": "Жирный", "brand_color": "Фирменный цвет", + "brand_color_description": "Применяется к кнопкам, ссылкам и выделениям.", "brightness": "Яркость", "bulk_edit": "Массовое редактирование", "bulk_edit_description": "Отредактируйте все варианты ниже, по одному на строку. Пустые строки будут пропущены, а дубликаты удалены.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Захватить новое действие", "card_arrangement_for_survey_type_derived": "Расположение карточек для опросов типа {surveyTypeDerived}", "card_background_color": "Цвет фона карточки", + "card_background_color_description": "Заполняет область карточки опроса.", "card_border_color": "Цвет рамки карточки", + "card_border_color_description": "Обводит карточку опроса.", "card_styling": "Оформление карточки", "casual": "Неформальный", "caution_edit_duplicate": "Дублировать и редактировать", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Старые и новые ответы смешиваются, что может привести к искажённым итоговым данным.", "caution_recommendation": "Это может привести к несоответствиям в итогах опроса. Рекомендуем вместо этого дублировать опрос.", "caution_text": "Изменения приведут к несоответствиям", - "centered_modal_overlay_color": "Цвет оверлея центрированного модального окна", "change_anyway": "Всё равно изменить", "change_background": "Изменить фон", "change_question_type": "Изменить тип вопроса", "change_survey_type": "Смена типа опроса влияет на существующий доступ", - "change_the_background_color_of_the_card": "Изменить цвет фона карточки.", - "change_the_background_color_of_the_input_fields": "Изменить цвет фона полей ввода.", "change_the_background_to_a_color_image_or_animation": "Изменить фон на цвет, изображение или анимацию.", - "change_the_border_color_of_the_card": "Изменить цвет рамки карточки.", - "change_the_border_color_of_the_input_fields": "Изменить цвет рамки полей ввода.", - "change_the_border_radius_of_the_card_and_the_inputs": "Изменить скругление углов карточки и полей ввода.", - "change_the_brand_color_of_the_survey": "Изменить фирменный цвет опроса.", "change_the_placement_of_this_survey": "Изменить размещение этого опроса.", - "change_the_question_color_of_the_survey": "Изменить цвет вопросов в опросе.", "changes_saved": "Изменения сохранены.", "changing_survey_type_will_remove_existing_distribution_channels": "Изменение типа опроса повлияет на способы его распространения. Если у респондентов уже есть ссылки для доступа к текущему типу, после смены они могут потерять доступ.", "checkbox_label": "Метка флажка", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Отключить отображение прогресса опроса.", "display_an_estimate_of_completion_time_for_survey": "Показывать примерное время прохождения опроса", "display_number_of_responses_for_survey": "Показывать количество ответов на опрос", + "display_type": "Тип отображения", "divide": "Разделить /", "does_not_contain": "Не содержит", "does_not_end_with": "Не заканчивается на", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Не включает все из", "does_not_include_one_of": "Не включает ни одного из", "does_not_start_with": "Не начинается с", + "dropdown": "Выпадающий список", "duplicate_block": "Дублировать блок", "duplicate_question": "Дублировать вопрос", "edit_link": "Редактировать ссылку", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Скрыть индикатор прогресса", "hide_question_settings": "Скрыть настройки вопроса", "hostname": "Имя хоста", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Насколько необычными вы хотите сделать карточки в опросах типа {surveyTypeDerived}", "if_you_need_more_please": "Если вам нужно больше, пожалуйста", "if_you_really_want_that_answer_ask_until_you_get_it": "Показывать каждый раз при срабатывании, пока не будет получен ответ.", "ignore_global_waiting_time": "Игнорировать период ожидания", @@ -1379,7 +1455,9 @@ "initial_value": "Начальное значение", "inner_text": "Внутренний текст", "input_border_color": "Цвет рамки поля ввода", + "input_border_color_description": "Обводит текстовые поля и текстовые области.", "input_color": "Цвет поля ввода", + "input_color_description": "Заполняет внутреннюю часть текстовых полей.", "insert_link": "Вставить ссылку", "invalid_targeting": "Некорректный таргетинг: проверьте фильтры аудитории", "invalid_video_url_warning": "Пожалуйста, введите корректную ссылку на YouTube, Vimeo или Loom. В настоящее время другие видеохостинги не поддерживаются.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Ограничьте максимальный размер загружаемых файлов.", "limit_upload_file_size_to": "Ограничить размер загружаемого файла до", "link_survey_description": "Поделитесь ссылкой на страницу опроса или вставьте её на веб-страницу или в электронное письмо.", + "list": "Список", "load_segment": "Загрузить сегмент", "logic_error_warning": "Изменение приведёт к логическим ошибкам", "logic_error_warning_text": "Изменение типа вопроса удалит логические условия из этого вопроса", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Только пользователи, у которых есть PIN-код, могут получить доступ к опросу.", "publish": "Опубликовать", "question": "Вопрос", - "question_color": "Цвет вопроса", "question_deleted": "Вопрос удалён.", "question_duplicated": "Вопрос дублирован.", "question_id_updated": "ID вопроса обновлён", "question_used_in_logic_warning_text": "Элементы из этого блока используются в правиле логики. Вы уверены, что хотите удалить его?", "question_used_in_logic_warning_title": "Несогласованность логики", - "question_used_in_quota": "Этот вопрос используется в квоте \"{quotaName}\"", + "question_used_in_quota": "Этот вопрос используется в квоте «{quotaName}»", "question_used_in_recall": "Этот вопрос используется в отзыве в вопросе {questionIndex}.", "question_used_in_recall_ending_card": "Этот вопрос используется в отзыве на финальной карточке", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Лимиты ответов, перенаправления и другое.", "response_options": "Параметры ответа", "roundness": "Скругление", + "roundness_description": "Определяет степень скругления углов карточки.", "row_used_in_logic_error": "Эта строка используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите её из логики.", "rows": "Строки", "save_and_close": "Сохранить и закрыть", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Оформление установлено в соответствии с темой", "subheading": "Подзаголовок", "subtract": "Вычесть -", - "suggest_colors": "Предложить цвета", "survey_completed_heading": "Опрос завершён", "survey_completed_subheading": "Этот бесплатный и открытый опрос был закрыт", "survey_display_settings": "Настройки отображения опроса", @@ -1642,7 +1720,7 @@ "validation_rules": "Правила валидации", "validation_rules_description": "Принимать только ответы, соответствующие следующим критериям", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите его из логики.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Переменная «{variableName}» используется в квоте «{quotaName}»", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Переменная «{variableName}» используется в квоте «{quotaName}». Сначала удалите её из квоты.", "variable_name_conflicts_with_hidden_field": "Имя переменной конфликтует с существующим ID скрытого поля.", "variable_name_is_already_taken_please_choose_another": "Это имя переменной уже занято, выберите другое.", "variable_name_must_start_with_a_letter": "Имя переменной должно начинаться с буквы.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Добавить цвет фона", "add_background_color_description": "Добавьте цвет фона для контейнера с логотипом.", + "advanced_styling_field_border_radius": "Радиус скругления", + "advanced_styling_field_button_bg": "Фон кнопки", + "advanced_styling_field_button_bg_description": "Заполняет кнопку «Далее» / «Отправить».", + "advanced_styling_field_button_border_radius_description": "Скругляет углы кнопки.", + "advanced_styling_field_button_font_size_description": "Масштабирует текст на кнопке.", + "advanced_styling_field_button_font_weight_description": "Делает текст на кнопке тоньше или жирнее.", + "advanced_styling_field_button_height_description": "Определяет высоту кнопки.", + "advanced_styling_field_button_padding_x_description": "Добавляет отступы слева и справа.", + "advanced_styling_field_button_padding_y_description": "Добавляет отступы сверху и снизу.", + "advanced_styling_field_button_text": "Текст кнопки", + "advanced_styling_field_button_text_description": "Задаёт цвет текста на кнопках.", + "advanced_styling_field_description_color": "Цвет описания", + "advanced_styling_field_description_color_description": "Задаёт цвет текста под каждым заголовком.", + "advanced_styling_field_description_size": "Размер шрифта описания", + "advanced_styling_field_description_size_description": "Масштабирует текст описания.", + "advanced_styling_field_description_weight": "Толщина шрифта описания", + "advanced_styling_field_description_weight_description": "Делает текст описания тоньше или жирнее.", + "advanced_styling_field_font_size": "Размер шрифта", + "advanced_styling_field_font_weight": "Толщина шрифта", + "advanced_styling_field_headline_color": "Цвет заголовка", + "advanced_styling_field_headline_color_description": "Задаёт цвет основного текста вопроса.", + "advanced_styling_field_headline_size": "Размер шрифта заголовка", + "advanced_styling_field_headline_size_description": "Масштабирует текст заголовка.", + "advanced_styling_field_headline_weight": "Толщина шрифта заголовка", + "advanced_styling_field_headline_weight_description": "Делает текст заголовка тоньше или жирнее.", + "advanced_styling_field_height": "Минимальная высота", + "advanced_styling_field_indicator_bg": "Фон индикатора", + "advanced_styling_field_indicator_bg_description": "Задаёт цвет заполненной части полосы.", + "advanced_styling_field_input_border_radius_description": "Скругляет углы полей ввода.", + "advanced_styling_field_input_font_size_description": "Масштабирует введённый текст в полях ввода.", + "advanced_styling_field_input_height_description": "Определяет минимальную высоту поля ввода.", + "advanced_styling_field_input_padding_x_description": "Добавляет отступы слева и справа.", + "advanced_styling_field_input_padding_y_description": "Добавляет пространство сверху и снизу.", + "advanced_styling_field_input_placeholder_opacity_description": "Делает текст подсказки менее заметным.", + "advanced_styling_field_input_shadow_description": "Добавляет тень вокруг полей ввода.", + "advanced_styling_field_input_text": "Текст ввода", + "advanced_styling_field_input_text_description": "Задаёт цвет введённого текста в полях.", + "advanced_styling_field_option_bg": "Фон", + "advanced_styling_field_option_bg_description": "Заливает фон элементов опций.", + "advanced_styling_field_option_border_radius_description": "Скругляет углы опций.", + "advanced_styling_field_option_font_size_description": "Изменяет размер текста метки опции.", + "advanced_styling_field_option_label": "Цвет метки", + "advanced_styling_field_option_label_description": "Задаёт цвет текста метки опции.", + "advanced_styling_field_option_padding_x_description": "Добавляет пространство слева и справа.", + "advanced_styling_field_option_padding_y_description": "Добавляет пространство сверху и снизу.", + "advanced_styling_field_padding_x": "Внутренний отступ по X", + "advanced_styling_field_padding_y": "Внутренний отступ по Y", + "advanced_styling_field_placeholder_opacity": "Прозрачность плейсхолдера", + "advanced_styling_field_shadow": "Тень", + "advanced_styling_field_track_bg": "Фон трека", + "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_section_buttons": "Кнопки", + "advanced_styling_section_headlines": "Заголовки и описания", + "advanced_styling_section_inputs": "Поля ввода", + "advanced_styling_section_options": "Опции (радио/чекбокс)", "app_survey_placement": "Размещение опроса в приложении", "app_survey_placement_settings_description": "Измените, где будут отображаться опросы в вашем веб-приложении или на сайте.", - "centered_modal_overlay_color": "Цвет оверлея центрированного модального окна", "email_customization": "Настройка email", "email_customization_description": "Измените внешний вид писем, которые Formbricks отправляет от вашего имени.", "enable_custom_styling": "Включить пользовательское оформление", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Брендинг Formbricks скрыт.", "formbricks_branding_settings_description": "Мы ценим вашу поддержку, но понимаем, если вы захотите отключить это.", "formbricks_branding_shown": "Брендинг Formbricks отображается.", + "generate_theme_btn": "Сгенерировать", + "generate_theme_confirmation": "Сгенерировать подходящую цветовую тему на основе цвета твоего бренда? Это перезапишет текущие цветовые настройки.", + "generate_theme_header": "Сгенерировать цветовую тему?", "logo_removed_successfully": "Логотип успешно удалён", "logo_settings_description": "Загрузите логотип вашей компании для брендирования опросов и предпросмотра ссылок.", "logo_updated_successfully": "Логотип успешно обновлён", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Показывать брендинг Formbricks в опросах типа {type}", "show_powered_by_formbricks": "Показывать подпись «Работает на Formbricks»", "styling_updated_successfully": "Стили успешно обновлены", + "suggest_colors": "Предложить цвета", + "suggested_colors_applied_please_save": "Рекомендованные цвета успешно сгенерированы. Нажми «Сохранить», чтобы применить изменения.", "theme": "Тема", "theme_settings_description": "Создайте стиль для всех опросов. Вы можете включить индивидуальное оформление для каждого опроса." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Да, держите меня в курсе.", "preview_survey_question_2_choice_2_label": "Нет, спасибо!", "preview_survey_question_2_headline": "Хотите быть в курсе событий?", + "preview_survey_question_2_subheader": "Это пример описания.", "preview_survey_welcome_card_headline": "Добро пожаловать!", "prioritize_features_description": "Определите, какие функции наиболее и наименее важны для ваших пользователей.", "prioritize_features_name": "Приоритизация функций", diff --git a/apps/web/locales/sv-SE.json b/apps/web/locales/sv-SE.json index d74d00c161..802e8a9b4d 100644 --- a/apps/web/locales/sv-SE.json +++ b/apps/web/locales/sv-SE.json @@ -187,6 +187,7 @@ "customer_success": "Kundframgång", "dark_overlay": "Mörkt överlägg", "date": "Datum", + "days": "dagar", "default": "Standard", "delete": "Ta bort", "description": "Beskrivning", @@ -253,6 +254,7 @@ "label": "Etikett", "language": "Språk", "learn_more": "Läs mer", + "license_expired": "License Expired", "light_overlay": "Ljust överlägg", "limits_reached": "Gränser nådda", "link": "Länk", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks fungerar bäst på en större skärm. Byt till en annan enhet för att hantera eller bygga enkäter.", "mobile_overlay_surveys_look_good": "Oroa dig inte – dina enkäter ser bra ut på alla enheter och skärmstorlekar!", "mobile_overlay_title": "Hoppsan, liten skärm upptäckt!", + "months": "månader", "move_down": "Flytta ner", "move_up": "Flytta upp", "multiple_languages": "Flera språk", @@ -283,6 +286,7 @@ "no_background_image_found": "Ingen bakgrundsbild hittades.", "no_code": "Ingen kod", "no_files_uploaded": "Inga filer laddades upp", + "no_overlay": "Ingen overlay", "no_quotas_found": "Inga kvoter hittades", "no_result_found": "Inget resultat hittades", "no_results": "Inga resultat", @@ -309,6 +313,7 @@ "organization_teams_not_found": "Organisationsteam hittades inte", "other": "Annat", "others": "Andra", + "overlay_color": "Overlay-färg", "overview": "Översikt", "password": "Lösenord", "paused": "Pausad", @@ -348,6 +353,7 @@ "request_trial_license": "Begär provlicens", "reset_to_default": "Återställ till standard", "response": "Svar", + "response_id": "Svar-ID", "responses": "Svar", "restart": "Starta om", "role": "Roll", @@ -388,6 +394,7 @@ "status": "Status", "step_by_step_manual": "Steg-för-steg-manual", "storage_not_configured": "Fillagring är inte konfigurerad, uppladdningar kommer sannolikt att misslyckas", + "string": "Text", "styling": "Styling", "submit": "Skicka", "summary": "Sammanfattning", @@ -443,6 +450,7 @@ "website_and_app_connection": "Webbplats- och appanslutning", "website_app_survey": "Webbplats- och appenkät", "website_survey": "Webbplatsenkät", + "weeks": "veckor", "welcome_card": "Välkomstkort", "workspace_configuration": "Arbetsytans konfiguration", "workspace_created_successfully": "Arbetsytan har skapats", @@ -453,13 +461,15 @@ "workspace_not_found": "Arbetsyta hittades inte", "workspace_permission_not_found": "Arbetsytebehörighet hittades inte", "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.", "you_have_reached_your_monthly_miu_limit_of": "Du har nått din månatliga MIU-gräns på", "you_have_reached_your_monthly_response_limit_of": "Du har nått din månatliga svarsgräns på", - "you_will_be_downgraded_to_the_community_edition_on_date": "Du kommer att nedgraderas till Community Edition den {date}." + "you_will_be_downgraded_to_the_community_edition_on_date": "Du kommer att nedgraderas till Community Edition den {date}.", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "Acceptera", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "Attributet har uppdaterats", "attribute_value": "Värde", "attribute_value_placeholder": "Attributvärde", + "attributes_msg_attribute_limit_exceeded": "Kunde inte skapa {count} nya attribut eftersom det skulle överskrida maxgränsen på {limit} attributklasser. Befintliga attribut uppdaterades utan problem.", + "attributes_msg_attribute_type_validation_error": "{error} (attributet '{key}' har dataTyp: {dataType})", + "attributes_msg_email_already_exists": "E-postadressen finns redan för den här miljön och uppdaterades inte.", + "attributes_msg_email_or_userid_required": "Antingen e-post eller userId krävs. De befintliga värdena behölls.", + "attributes_msg_new_attribute_created": "Nytt attribut '{key}' med typen '{dataType}' har skapats", + "attributes_msg_userid_already_exists": "UserId finns redan för den här miljön och uppdaterades inte.", "contact_deleted_successfully": "Kontakt borttagen", "contact_not_found": "Ingen sådan kontakt hittades", "contacts_table_refresh": "Uppdatera kontakter", @@ -631,6 +647,11 @@ "create_key": "Skapa nyckel", "create_new_attribute": "Skapa nytt attribut", "create_new_attribute_description": "Skapa ett nytt attribut för segmenteringsändamål.", + "custom_attributes": "Anpassade attribut", + "data_type": "Datatyp", + "data_type_cannot_be_changed": "Datatypen kan inte ändras efter skapande", + "data_type_description": "Välj hur detta attribut ska lagras och filtreras", + "date_value_required": "Datumvärde krävs. Använd ta bort-knappen om du inte vill ange ett datum.", "delete_attribute_confirmation": "{value, plural, one {Detta kommer att ta bort det valda attributet. All kontaktdata som är kopplad till detta attribut kommer att gå förlorad.} other {Detta kommer att ta bort de valda attributen. All kontaktdata som är kopplad till dessa attribut kommer att gå förlorad.}}", "delete_contact_confirmation": "Detta kommer att ta bort alla enkätsvar och kontaktattribut som är kopplade till denna kontakt. All målgruppsinriktning och personalisering baserad på denna kontakts data kommer att gå förlorad.", "delete_contact_confirmation_with_quotas": "{value, plural, one {Detta kommer att ta bort alla enkätsvar och kontaktattribut som är kopplade till denna kontakt. All målgruppsinriktning och personalisering baserad på denna kontakts data kommer att gå förlorad. Om denna kontakt har svar som räknas mot enkätkvoter, kommer kvotantalet att minskas men kvotgränserna förblir oförändrade.} other {Detta kommer att ta bort alla enkätsvar och kontaktattribut som är kopplade till dessa kontakter. All målgruppsinriktning och personalisering baserad på dessa kontakters data kommer att gå förlorad. Om dessa kontakter har svar som räknas mot enkätkvoter, kommer kvotantalet att minskas men kvotgränserna förblir oförändrade.}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "Uppdatera etikett och beskrivning för detta attribut.", "edit_attribute_values": "Redigera attribut", "edit_attribute_values_description": "Ändra värdena för specifika attribut för denna kontakt.", + "edit_attributes": "Redigera attribut", "edit_attributes_success": "Kontaktens attribut har uppdaterats", "generate_personal_link": "Generera personlig länk", "generate_personal_link_description": "Välj en publicerad enkät för att generera en personlig länk för denna kontakt.", + "invalid_csv_column_names": "Ogiltiga CSV-kolumnnamn: {columns}. Kolumnnamn som ska bli nya attribut får bara innehålla små bokstäver, siffror och understreck, och måste börja med en bokstav.", + "invalid_date_format": "Ogiltigt datumformat. Ange ett giltigt datum.", + "invalid_number_format": "Ogiltigt nummerformat. Ange ett giltigt nummer.", "no_published_link_surveys_available": "Inga publicerade länkenkäter tillgängliga. Vänligen publicera en länkenkät först.", "no_published_surveys": "Inga publicerade enkäter", "no_responses_found": "Inga svar hittades", "not_provided": "Ej angiven", + "number_value_required": "Ett numeriskt värde krävs. Använd ta bort-knappen för att ta bort den här attributen.", "personal_link_generated": "Personlig länk genererad", "personal_link_generated_but_clipboard_failed": "Personlig länk genererad men kunde inte kopieras till urklipp: {url}", "personal_survey_link": "Personlig enkätlänk", @@ -653,13 +679,22 @@ "search_contact": "Sök kontakt", "select_a_survey": "Välj en enkät", "select_attribute": "Välj attribut", + "select_attribute_key": "Välj attributnyckel", + "system_attributes": "Systemattribut", "unlock_contacts_description": "Hantera kontakter och skicka ut riktade enkäter", "unlock_contacts_title": "Lås upp kontakter med en högre plan", + "upload_contacts_error_attribute_type_mismatch": "Attributet \"{key}\" är av typen \"{dataType}\" men CSV-filen innehåller ogiltiga värden: {values}", + "upload_contacts_error_duplicate_mappings": "Dubblettmappningar hittades för följande attribut: {attributes}", + "upload_contacts_error_file_too_large": "Filstorleken överskrider maxgränsen på 800 KB", + "upload_contacts_error_generic": "Ett fel uppstod vid uppladdning av kontakter. Försök igen senare.", + "upload_contacts_error_invalid_file_type": "Ladda upp en CSV-fil", + "upload_contacts_error_no_valid_contacts": "Den uppladdade CSV-filen innehåller inga giltiga kontakter, se exempel på CSV-fil för korrekt format.", + "upload_contacts_modal_attribute_header": "Formbricks-attribut", "upload_contacts_modal_attributes_description": "Mappa kolumnerna i din CSV till attributen i Formbricks.", "upload_contacts_modal_attributes_new": "Nytt attribut", "upload_contacts_modal_attributes_search_or_add": "Sök eller lägg till attribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "ska mappas till", "upload_contacts_modal_attributes_title": "Attribut", + "upload_contacts_modal_csv_column_header": "CSV-kolumn", "upload_contacts_modal_description": "Ladda upp en CSV för att snabbt importera kontakter med attribut", "upload_contacts_modal_download_example_csv": "Ladda ner exempel-CSV", "upload_contacts_modal_duplicates_description": "Hur ska vi hantera om en kontakt redan finns i dina kontakter?", @@ -840,6 +875,40 @@ "no_attributes_yet": "Inga attribut ännu!", "no_filters_yet": "Det finns inga filter ännu!", "no_segments_yet": "Du har för närvarande inga sparade segment.", + "operator_contains": "innehåller", + "operator_does_not_contain": "innehåller inte", + "operator_ends_with": "slutar med", + "operator_is_after": "är efter", + "operator_is_before": "är före", + "operator_is_between": "är mellan", + "operator_is_newer_than": "är nyare än", + "operator_is_not_set": "är inte satt", + "operator_is_older_than": "är äldre än", + "operator_is_same_day": "är samma dag", + "operator_is_set": "är satt", + "operator_starts_with": "börjar med", + "operator_title_contains": "Innehåller", + "operator_title_does_not_contain": "Innehåller inte", + "operator_title_ends_with": "Slutar med", + "operator_title_equals": "Är lika med", + "operator_title_greater_equal": "Större än eller lika med", + "operator_title_greater_than": "Större än", + "operator_title_is_after": "Är efter", + "operator_title_is_before": "Är före", + "operator_title_is_between": "Är mellan", + "operator_title_is_newer_than": "Är nyare än", + "operator_title_is_not_set": "Är inte satt", + "operator_title_is_older_than": "Är äldre än", + "operator_title_is_same_day": "Är samma dag", + "operator_title_is_set": "Är satt", + "operator_title_less_equal": "Mindre än eller lika med", + "operator_title_less_than": "Mindre än", + "operator_title_not_equals": "Är inte lika med", + "operator_title_starts_with": "Börjar med", + "operator_title_user_is_in": "Användaren är i", + "operator_title_user_is_not_in": "Användaren är inte i", + "operator_user_is_in": "Användaren är i", + "operator_user_is_not_in": "Användaren är inte i", "person_and_attributes": "Person och attribut", "phone": "Telefon", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "Vänligen ta bort segmentet från dessa enkäter för att kunna ta bort det.", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "Användarinriktning är för närvarande endast tillgänglig när", "value_cannot_be_empty": "Värdet kan inte vara tomt.", "value_must_be_a_number": "Värdet måste vara ett nummer.", + "value_must_be_positive": "Värdet måste vara ett positivt nummer.", "view_filters": "Visa filter", "where": "Där", "with_the_formbricks_sdk": "med Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "Enterprise-funktioner", "get_an_enterprise_license_to_get_access_to_all_features": "Skaffa en Enterprise-licens för att få tillgång till alla funktioner.", "keep_full_control_over_your_data_privacy_and_security": "Behåll full kontroll över din datasekretess och säkerhet.", + "license_invalid_description": "Licensnyckeln i din ENTERPRISE_LICENSE_KEY-miljövariabel är ogiltig. Kontrollera om det finns stavfel eller begär en ny nyckel.", + "license_status": "Licensstatus", + "license_status_active": "Aktiv", + "license_status_description": "Status för din företagslicens.", + "license_status_expired": "Utgången", + "license_status_invalid": "Ogiltig licens", + "license_status_unreachable": "Otillgänglig", + "license_unreachable_grace_period": "Licensservern kan inte nås. Dina enterprise-funktioner är aktiva under en 3-dagars respitperiod som slutar {gracePeriodEnd}.", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Inget samtal behövs, inga åtaganden: Begär en gratis 30-dagars provlicens för att testa alla funktioner genom att fylla i detta formulär:", "no_credit_card_no_sales_call_just_test_it": "Inget kreditkort. Inget säljsamtal. Testa bara :)", "on_request": "På begäran", "organization_roles": "Organisationsroller (Admin, Redaktör, Utvecklare, etc.)", "questions_please_reach_out_to": "Frågor? Kontakta", + "recheck_license": "Kontrollera licensen igen", + "recheck_license_failed": "Licenskontrollen misslyckades. Licensservern kan vara otillgänglig.", + "recheck_license_invalid": "Licensnyckeln är ogiltig. Kontrollera din ENTERPRISE_LICENSE_KEY.", + "recheck_license_success": "Licenskontrollen lyckades", + "recheck_license_unreachable": "Licensservern är otillgänglig. Försök igen senare.", + "rechecking": "Kontrollerar igen...", "request_30_day_trial_license": "Begär 30-dagars provlicens", "saml_sso": "SAML SSO", "service_level_agreement": "Servicenivåavtal", "soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 efterlevnadskontroll", "sso": "SSO (Google, Microsoft, OpenID Connect)", "teams": "Team och åtkomstroller (Läs, Läs och skriv, Hantera)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar.", - "your_enterprise_license_is_active_all_features_unlocked": "Din Enterprise-licens är aktiv. Alla funktioner upplåsta." + "unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar." }, "general": { "bulk_invite_warning_description": "På gratisplanen tilldelas alla organisationsmedlemmar alltid rollen \"Ägare\".", @@ -986,7 +1069,7 @@ "from_your_organization": "från din organisation", "invitation_sent_once_more": "Inbjudan skickad igen.", "invite_deleted_successfully": "Inbjudan borttagen", - "invited_on": "Inbjuden den {date}", + "invite_expires_on": "Inbjudan går ut den {date}", "invites_failed": "Inbjudningar misslyckades", "leave_organization": "Lämna organisation", "leave_organization_description": "Du kommer att lämna denna organisation och förlora åtkomst till alla enkäter och svar. Du kan endast återansluta om du blir inbjuden igen.", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "Fyll i alla fält för att lägga till en ny arbetsyta.", "read": "Läs", "read_write": "Läs och skriv", - "select_member": "Välj medlem", - "select_workspace": "Välj arbetsyta", "team_admin": "Teamadministratör", "team_created_successfully": "Team skapat.", "team_deleted_successfully": "Team borttaget.", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "Lägg till en platshållare att visa om det inte finns något värde att återkalla.", "add_hidden_field_id": "Lägg till dolt fält-ID", "add_highlight_border": "Lägg till markerad kant", - "add_highlight_border_description": "Lägg till en yttre kant till ditt enkätkort.", "add_logic": "Lägg till logik", "add_none_of_the_above": "Lägg till \"Inget av ovanstående\"", "add_option": "Lägg till alternativ", @@ -1189,6 +1269,7 @@ "block_duplicated": "Block duplicerat.", "bold": "Fet", "brand_color": "Varumärkesfärg", + "brand_color_description": "Används för knappar, länkar och markeringar.", "brightness": "Ljusstyrka", "bulk_edit": "Massredigera", "bulk_edit_description": "Redigera alla alternativ nedan, ett per rad. Tomma rader kommer att hoppas över och dubbletter tas bort.", @@ -1206,7 +1287,9 @@ "capture_new_action": "Fånga ny åtgärd", "card_arrangement_for_survey_type_derived": "Kortarrangemang för {surveyTypeDerived}-enkäter", "card_background_color": "Kortets bakgrundsfärg", + "card_background_color_description": "Fyller enkätkortets yta.", "card_border_color": "Kortets kantfärg", + "card_border_color_description": "Markerar enkätkortets kant.", "card_styling": "Kortstil", "casual": "Avslappnad", "caution_edit_duplicate": "Duplicera och redigera", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "Äldre och nyare svar blandas vilket kan leda till vilseledande datasammanfattningar.", "caution_recommendation": "Detta kan orsaka datainkonsekvenser i enkätsammanfattningen. Vi rekommenderar att duplicera enkäten istället.", "caution_text": "Ändringar kommer att leda till inkonsekvenser", - "centered_modal_overlay_color": "Centrerad modal överläggsfärg", "change_anyway": "Ändra ändå", "change_background": "Ändra bakgrund", "change_question_type": "Ändra frågetyp", "change_survey_type": "Byte av enkättyp påverkar befintlig åtkomst", - "change_the_background_color_of_the_card": "Ändra kortets bakgrundsfärg.", - "change_the_background_color_of_the_input_fields": "Ändra inmatningsfältens bakgrundsfärg.", "change_the_background_to_a_color_image_or_animation": "Ändra bakgrunden till en färg, bild eller animering.", - "change_the_border_color_of_the_card": "Ändra kortets kantfärg.", - "change_the_border_color_of_the_input_fields": "Ändra inmatningsfältens kantfärg.", - "change_the_border_radius_of_the_card_and_the_inputs": "Ändra kantradie för kortet och inmatningsfälten.", - "change_the_brand_color_of_the_survey": "Ändra enkätens varumärkesfärg.", "change_the_placement_of_this_survey": "Ändra placeringen av denna enkät.", - "change_the_question_color_of_the_survey": "Ändra enkätens frågefärg.", "changes_saved": "Ändringar sparade.", "changing_survey_type_will_remove_existing_distribution_channels": "Att ändra enkättypen påverkar hur den kan delas. Om respondenter redan har åtkomstlänkar för den nuvarande typen kan de förlora åtkomst efter bytet.", "checkbox_label": "Kryssruteetikett", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "Inaktivera synligheten av enkätens framsteg.", "display_an_estimate_of_completion_time_for_survey": "Visa en uppskattning av tid för att slutföra enkäten", "display_number_of_responses_for_survey": "Visa antal svar för enkäten", + "display_type": "Visningstyp", "divide": "Dividera /", "does_not_contain": "Innehåller inte", "does_not_end_with": "Slutar inte med", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "Inkluderar inte alla av", "does_not_include_one_of": "Inkluderar inte en av", "does_not_start_with": "Börjar inte med", + "dropdown": "Rullgardinsmeny", "duplicate_block": "Duplicera block", "duplicate_question": "Duplicera fråga", "edit_link": "Redigera länk", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "Dölj framstegsindikator", "hide_question_settings": "Dölj frågeinställningar", "hostname": "Värdnamn", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Hur coola vill du att dina kort ska vara i {surveyTypeDerived}-enkäter", "if_you_need_more_please": "Om du behöver mer, vänligen", "if_you_really_want_that_answer_ask_until_you_get_it": "Fortsätt visa när villkoren är uppfyllda tills ett svar skickas in.", "ignore_global_waiting_time": "Ignorera väntetid", @@ -1379,7 +1455,9 @@ "initial_value": "Initialt värde", "inner_text": "Inre text", "input_border_color": "Inmatningsfältets kantfärg", + "input_border_color_description": "Markerar kanten på textfält och textområden.", "input_color": "Inmatningsfärg", + "input_color_description": "Fyller insidan av textfält.", "insert_link": "Infoga länk", "invalid_targeting": "Ogiltig målgruppsinriktning: Vänligen kontrollera dina målgruppsfilter", "invalid_video_url_warning": "Vänligen ange en giltig YouTube-, Vimeo- eller Loom-URL. Vi stöder för närvarande inte andra videohostingleverantörer.", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "Begränsa den maximala filstorleken för uppladdningar.", "limit_upload_file_size_to": "Begränsa uppladdad filstorlek till", "link_survey_description": "Dela en länk till en enkätsida eller bädda in den på en webbsida eller i e-post.", + "list": "Lista", "load_segment": "Ladda segment", "logic_error_warning": "Ändring kommer att orsaka logikfel", "logic_error_warning_text": "Att ändra frågetypen kommer att ta bort logikvillkoren från denna fråga", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "Endast användare som har PIN-koden kan komma åt enkäten.", "publish": "Publicera", "question": "Fråga", - "question_color": "Frågefärg", "question_deleted": "Fråga borttagen.", "question_duplicated": "Fråga duplicerad.", "question_id_updated": "Fråge-ID uppdaterat", "question_used_in_logic_warning_text": "Element från det här blocket används i en logikregel. Är du säker på att du vill ta bort det?", "question_used_in_logic_warning_title": "Logikkonflikt", - "question_used_in_quota": "Denna fråga används i kvoten \"{quotaName}\"", + "question_used_in_quota": "Denna fråga används i kvoten “{quotaName}”", "question_used_in_recall": "Denna fråga återkallas i fråga {questionIndex}.", "question_used_in_recall_ending_card": "Denna fråga återkallas i avslutningskortet", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "Svarsgränser, omdirigeringar och mer.", "response_options": "Svarsalternativ", "roundness": "Rundhet", + "roundness_description": "Styr hur rundade kortets hörn är.", "row_used_in_logic_error": "Denna rad används i logiken för fråga {questionIndex}. Vänligen ta bort den från logiken först.", "rows": "Rader", "save_and_close": "Spara och stäng", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "Styling inställd på temastil", "subheading": "Underrubrik", "subtract": "Subtrahera -", - "suggest_colors": "Föreslå färger", "survey_completed_heading": "Enkät slutförd", "survey_completed_subheading": "Denna gratis och öppenkällkodsenkät har stängts", "survey_display_settings": "Visningsinställningar för enkät", @@ -1642,7 +1720,7 @@ "validation_rules": "Valideringsregler", "validation_rules_description": "Acceptera endast svar som uppfyller följande kriterier", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} används i logiken för fråga {questionIndex}. Vänligen ta bort den från logiken först.", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabel \"{variableName}\" används i kvoten \"{quotaName}\"", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabeln “{variableName}” används i kvoten “{quotaName}”", "variable_name_conflicts_with_hidden_field": "Variabelnamnet krockar med ett befintligt dolt fält-ID.", "variable_name_is_already_taken_please_choose_another": "Variabelnamnet är redan taget, vänligen välj ett annat.", "variable_name_must_start_with_a_letter": "Variabelnamnet måste börja med en bokstav.", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "Lägg till bakgrundsfärg", "add_background_color_description": "Lägg till en bakgrundsfärg i logobehållaren.", + "advanced_styling_field_border_radius": "Hörnradie", + "advanced_styling_field_button_bg": "Knappens bakgrund", + "advanced_styling_field_button_bg_description": "Fyller Nästa / Skicka-knappen.", + "advanced_styling_field_button_border_radius_description": "Rundar av knappens hörn.", + "advanced_styling_field_button_font_size_description": "Ändrar storleken på knappens text.", + "advanced_styling_field_button_font_weight_description": "Gör knapptexten tunnare eller fetare.", + "advanced_styling_field_button_height_description": "Styr knappens höjd.", + "advanced_styling_field_button_padding_x_description": "Lägger till utrymme till vänster och höger.", + "advanced_styling_field_button_padding_y_description": "Lägger till utrymme upptill och nedtill.", + "advanced_styling_field_button_text": "Knapptext", + "advanced_styling_field_button_text_description": "Färglägger texten i knappar.", + "advanced_styling_field_description_color": "Beskrivningsfärg", + "advanced_styling_field_description_color_description": "Färglägger texten under varje rubrik.", + "advanced_styling_field_description_size": "Beskrivningens teckenstorlek", + "advanced_styling_field_description_size_description": "Ändrar storleken på beskrivningstexten.", + "advanced_styling_field_description_weight": "Beskrivningens teckentjocklek", + "advanced_styling_field_description_weight_description": "Gör beskrivningstexten tunnare eller fetare.", + "advanced_styling_field_font_size": "Teckenstorlek", + "advanced_styling_field_font_weight": "Teckentjocklek", + "advanced_styling_field_headline_color": "Rubrikfärg", + "advanced_styling_field_headline_color_description": "Färglägger huvudfrågan.", + "advanced_styling_field_headline_size": "Rubrikens teckenstorlek", + "advanced_styling_field_headline_size_description": "Ändrar storleken på rubriken.", + "advanced_styling_field_headline_weight": "Rubrikens teckentjocklek", + "advanced_styling_field_headline_weight_description": "Gör rubriktexten tunnare eller fetare.", + "advanced_styling_field_height": "Minsta höjd", + "advanced_styling_field_indicator_bg": "Indikatorns bakgrund", + "advanced_styling_field_indicator_bg_description": "Färglägger den fyllda delen av stapeln.", + "advanced_styling_field_input_border_radius_description": "Rundar av hörnen på inmatningsfält.", + "advanced_styling_field_input_font_size_description": "Ändrar storleken på texten i inmatningsfält.", + "advanced_styling_field_input_height_description": "Styr den minsta höjden på inmatningsfältet.", + "advanced_styling_field_input_padding_x_description": "Lägger till utrymme till vänster och höger.", + "advanced_styling_field_input_padding_y_description": "Lägger till utrymme upptill och nedtill.", + "advanced_styling_field_input_placeholder_opacity_description": "Tonar ut platshållartexten.", + "advanced_styling_field_input_shadow_description": "Lägger till en skugga runt inmatningsfälten.", + "advanced_styling_field_input_text": "Inmatningstext", + "advanced_styling_field_input_text_description": "Färgar den inmatade texten i fälten.", + "advanced_styling_field_option_bg": "Bakgrund", + "advanced_styling_field_option_bg_description": "Fyller alternativraderna.", + "advanced_styling_field_option_border_radius_description": "Rundar hörnen på alternativen.", + "advanced_styling_field_option_font_size_description": "Skalar textstorleken på alternativetiketten.", + "advanced_styling_field_option_label": "Etikettfärg", + "advanced_styling_field_option_label_description": "Färgar texten på alternativetiketten.", + "advanced_styling_field_option_padding_x_description": "Lägger till utrymme till vänster och höger.", + "advanced_styling_field_option_padding_y_description": "Lägger till utrymme upptill och nedtill.", + "advanced_styling_field_padding_x": "Horisontell padding", + "advanced_styling_field_padding_y": "Vertikal padding", + "advanced_styling_field_placeholder_opacity": "Platshållarens opacitet", + "advanced_styling_field_shadow": "Skugga", + "advanced_styling_field_track_bg": "Spårets bakgrund", + "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_section_buttons": "Knappar", + "advanced_styling_section_headlines": "Rubriker & beskrivningar", + "advanced_styling_section_inputs": "Inmatningar", + "advanced_styling_section_options": "Alternativ (Radio/Checkbox)", "app_survey_placement": "App-enkätplacering", "app_survey_placement_settings_description": "Ändra var enkäter visas i din webbapp eller på din webbplats.", - "centered_modal_overlay_color": "Centrerad modal överläggsfärg", "email_customization": "E-postanpassning", "email_customization_description": "Ändra utseendet på de e-postmeddelanden som Formbricks skickar åt dig.", "enable_custom_styling": "Aktivera anpassad styling", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks-varumärket är dolt.", "formbricks_branding_settings_description": "Vi uppskattar ditt stöd men förstår om du vill stänga av det.", "formbricks_branding_shown": "Formbricks-varumärket visas.", + "generate_theme_btn": "Generera", + "generate_theme_confirmation": "Vill du generera ett matchande färgtema baserat på din varumärkesfärg? Detta kommer att skriva över dina nuvarande färginställningar.", + "generate_theme_header": "Generera färgtema?", "logo_removed_successfully": "Logotyp borttagen", "logo_settings_description": "Ladda upp företagets logotyp för att profilera enkäter och länkförhandsvisningar.", "logo_updated_successfully": "Logotyp uppdaterad", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "Visa Formbricks-varumärket i {type}-enkäter", "show_powered_by_formbricks": "Visa 'Powered by Formbricks'-signatur", "styling_updated_successfully": "Stiluppdatering lyckades", + "suggest_colors": "Föreslå färger", + "suggested_colors_applied_please_save": "Föreslagna färger har skapats. Tryck på \"Spara\" för att spara ändringarna.", "theme": "Tema", "theme_settings_description": "Skapa ett stilmall för alla undersökningar. Du kan aktivera anpassad stil för varje undersökning." }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "Ja, håll mig informerad.", "preview_survey_question_2_choice_2_label": "Nej, tack!", "preview_survey_question_2_headline": "Vill du hållas uppdaterad?", + "preview_survey_question_2_subheader": "Det här är ett exempel på en beskrivning.", "preview_survey_welcome_card_headline": "Välkommen!", "prioritize_features_description": "Identifiera vilka funktioner dina användare behöver mest och minst.", "prioritize_features_name": "Prioritera funktioner", diff --git a/apps/web/locales/zh-Hans-CN.json b/apps/web/locales/zh-Hans-CN.json index 67814de1b2..dc1d446a78 100644 --- a/apps/web/locales/zh-Hans-CN.json +++ b/apps/web/locales/zh-Hans-CN.json @@ -187,6 +187,7 @@ "customer_success": "客户成功", "dark_overlay": "深色遮罩层", "date": "日期", + "days": "天", "default": "默认", "delete": "删除", "description": "描述", @@ -253,6 +254,7 @@ "label": "标签", "language": "语言", "learn_more": "了解 更多", + "license_expired": "License Expired", "light_overlay": "浅色遮罩层", "limits_reached": "限制 达到", "link": "链接", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks 在 更大 的 屏幕 上 效果 最佳。 若 需要 管理 或 构建 调查, 请 切换 到 其他 设备。", "mobile_overlay_surveys_look_good": "别 担心 – 您 的 调查 在 每 一 种 设备 和 屏幕 尺寸 上 看起来 都 很 棒!", "mobile_overlay_title": "噢, 检测 到 小 屏幕!", + "months": "月", "move_down": "下移", "move_up": "上移", "multiple_languages": "多种 语言", @@ -283,6 +286,7 @@ "no_background_image_found": "未找到 背景 图片。", "no_code": "无代码", "no_files_uploaded": "没有 文件 被 上传", + "no_overlay": "无覆盖层", "no_quotas_found": "未找到配额", "no_result_found": "没有 结果", "no_results": "没有 结果", @@ -309,6 +313,7 @@ "organization_teams_not_found": "未找到 组织 团队", "other": "其他", "others": "其他", + "overlay_color": "覆盖层颜色", "overview": "概览", "password": "密码", "paused": "暂停", @@ -348,6 +353,7 @@ "request_trial_license": "申请试用许可证", "reset_to_default": "重置为 默认", "response": "响应", + "response_id": "响应 ID", "responses": "反馈", "restart": "重新启动", "role": "角色", @@ -388,6 +394,7 @@ "status": "状态", "step_by_step_manual": "分步 手册", "storage_not_configured": "文件存储 未设置,上传 可能 失败", + "string": "文本", "styling": "样式", "submit": "提交", "summary": "概要", @@ -443,6 +450,7 @@ "website_and_app_connection": "网站 & 应用程序 连接", "website_app_survey": "网站 & 应用 调查", "website_survey": "网站 调查", + "weeks": "周", "welcome_card": "欢迎 卡片", "workspace_configuration": "工作区配置", "workspace_created_successfully": "工作区创建成功", @@ -453,13 +461,15 @@ "workspace_not_found": "未找到工作区", "workspace_permission_not_found": "未找到工作区权限", "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} 个工作区的上限。", "you_have_reached_your_monthly_miu_limit_of": "您 已经 达到 每月 的 MIU 限制", "you_have_reached_your_monthly_response_limit_of": "您 已经 达到 每月 的 响应 限制", - "you_will_be_downgraded_to_the_community_edition_on_date": "您将在 {date} 降级到社区版。" + "you_will_be_downgraded_to_the_community_edition_on_date": "您将在 {date} 降级到社区版。", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "接受", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "属性更新成功", "attribute_value": "值", "attribute_value_placeholder": "属性值", + "attributes_msg_attribute_limit_exceeded": "无法创建 {count} 个新属性,因为这将超过最多 {limit} 个属性类别的限制。已有属性已成功更新。", + "attributes_msg_attribute_type_validation_error": "{error}(属性“{key}”的数据类型为:{dataType})", + "attributes_msg_email_already_exists": "该邮箱已存在于当前环境,未进行更新。", + "attributes_msg_email_or_userid_required": "必须填写邮箱或 userId。已保留原有值。", + "attributes_msg_new_attribute_created": "已创建新属性“{key}”,类型为“{dataType}”", + "attributes_msg_userid_already_exists": "该 userId 已存在于当前环境,未进行更新。", "contact_deleted_successfully": "联系人 删除 成功", "contact_not_found": "未找到此 联系人", "contacts_table_refresh": "刷新 联系人", @@ -631,6 +647,11 @@ "create_key": "创建键", "create_new_attribute": "创建新属性", "create_new_attribute_description": "为细分目的创建新属性。", + "custom_attributes": "自定义属性", + "data_type": "数据类型", + "data_type_cannot_be_changed": "数据类型创建后无法更改", + "data_type_description": "选择此属性的存储和筛选方式", + "date_value_required": "需要日期值。如果你不想设置日期,请使用删除按钮移除此属性。", "delete_attribute_confirmation": "{value, plural, one {这将删除所选属性。与该属性相关的任何联系人数据都将丢失。} other {这将删除所选属性。与这些属性相关的任何联系人数据都将丢失。}}", "delete_contact_confirmation": "这将删除与此联系人相关的所有调查问卷回复和联系人属性。基于此联系人数据的任何定位和个性化将会丢失。", "delete_contact_confirmation_with_quotas": "{value, plural, one {这将删除与此联系人相关的所有调查回复和联系人属性。基于此联系人数据的任何定位和个性化将丢失。如果此联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。} other {这将删除与这些联系人相关的所有调查回复和联系人属性。基于这些联系人数据的任何定位和个性化将丢失。如果这些联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "更新此属性的标签和描述。", "edit_attribute_values": "编辑属性", "edit_attribute_values_description": "更改此联系人的特定属性值。", + "edit_attributes": "编辑属性", "edit_attributes_success": "联系人属性更新成功", "generate_personal_link": "生成个人链接", "generate_personal_link_description": "选择一个已发布的调查,为此联系人生成个性化链接。", + "invalid_csv_column_names": "无效的 CSV 列名:{columns}。作为新属性的列名只能包含小写字母、数字和下划线,并且必须以字母开头。", + "invalid_date_format": "日期格式无效。请使用有效日期。", + "invalid_number_format": "数字格式无效。请输入有效的数字。", "no_published_link_surveys_available": "没有可用的已发布链接调查。请先发布一个链接调查。", "no_published_surveys": "没有已发布的调查", "no_responses_found": "未找到 响应", "not_provided": "未提供", + "number_value_required": "需要填写数字值。要移除此属性,请使用删除按钮。", "personal_link_generated": "个人链接生成成功", "personal_link_generated_but_clipboard_failed": "个性化链接已生成,但复制到剪贴板失败:{url}", "personal_survey_link": "个人调查链接", @@ -653,13 +679,22 @@ "search_contact": "搜索 联系人", "select_a_survey": "选择一个调查", "select_attribute": "选择 属性", + "select_attribute_key": "选择属性键", + "system_attributes": "系统属性", "unlock_contacts_description": "管理 联系人 并 发送 定向 调查", "unlock_contacts_title": "通过 更 高级 划解锁 联系人", + "upload_contacts_error_attribute_type_mismatch": "属性“{key}”的数据类型为“{dataType}”,但 CSV 文件中包含无效值:{values}", + "upload_contacts_error_duplicate_mappings": "以下属性存在重复映射:{attributes}", + "upload_contacts_error_file_too_large": "文件大小超过最大限制 800KB", + "upload_contacts_error_generic": "上传联系人时发生错误,请稍后再试。", + "upload_contacts_error_invalid_file_type": "请上传 CSV 文件", + "upload_contacts_error_no_valid_contacts": "上传的 CSV 文件中不包含任何有效联系人,请参考示例 CSV 文件获取正确格式。", + "upload_contacts_modal_attribute_header": "Formbricks 属性", "upload_contacts_modal_attributes_description": "将您 CSV 中的列映射到 Formbricks 中的属性。", "upload_contacts_modal_attributes_new": "新 属性", "upload_contacts_modal_attributes_search_or_add": "搜索或添加属性", - "upload_contacts_modal_attributes_should_be_mapped_to": "应该映射到", "upload_contacts_modal_attributes_title": "属性", + "upload_contacts_modal_csv_column_header": "CSV 列", "upload_contacts_modal_description": "上传 CSV,快速 导入 具有 属性 的 联系人", "upload_contacts_modal_download_example_csv": "下载 示例 CSV", "upload_contacts_modal_duplicates_description": "如果联系人已经存在,应该如何处理?", @@ -840,6 +875,40 @@ "no_attributes_yet": "暂无属性!", "no_filters_yet": "还 没有 筛选器!", "no_segments_yet": "您 目前 尚无 保存 的 段。", + "operator_contains": "包含", + "operator_does_not_contain": "不包含", + "operator_ends_with": "以...结束", + "operator_is_after": "在...之后", + "operator_is_before": "在...之前", + "operator_is_between": "介于...之间", + "operator_is_newer_than": "比...更新", + "operator_is_not_set": "未设置", + "operator_is_older_than": "比...更早", + "operator_is_same_day": "同一天", + "operator_is_set": "已设置", + "operator_starts_with": "以...开始", + "operator_title_contains": "包含", + "operator_title_does_not_contain": "不包含", + "operator_title_ends_with": "以...结束", + "operator_title_equals": "等于", + "operator_title_greater_equal": "大于或等于", + "operator_title_greater_than": "大于", + "operator_title_is_after": "在...之后", + "operator_title_is_before": "在...之前", + "operator_title_is_between": "介于...之间", + "operator_title_is_newer_than": "比...更新", + "operator_title_is_not_set": "未设置", + "operator_title_is_older_than": "比...更早", + "operator_title_is_same_day": "同一天", + "operator_title_is_set": "已设置", + "operator_title_less_equal": "小于或等于", + "operator_title_less_than": "小于", + "operator_title_not_equals": "不等于", + "operator_title_starts_with": "以...开始", + "operator_title_user_is_in": "用户属于", + "operator_title_user_is_not_in": "用户不属于", + "operator_user_is_in": "用户属于", + "operator_user_is_not_in": "用户不属于", "person_and_attributes": "人员 及 属性", "phone": "电话", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "请 从 这些 调查 中 移除 该 部分 以 进行 删除。", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "目标用户 功能 当前 仅 限于 当", "value_cannot_be_empty": "值 不能为空。", "value_must_be_a_number": "值 必须 是 一个 数字。", + "value_must_be_positive": "值必须是正数。", "view_filters": "查看 筛选条件", "where": "位置", "with_the_formbricks_sdk": "与 Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "企业 功能", "get_an_enterprise_license_to_get_access_to_all_features": "获取 企业 许可证 来 访问 所有 功能。", "keep_full_control_over_your_data_privacy_and_security": "保持 对 您 的 数据 隐私 和 安全 的 完全 控制。", + "license_invalid_description": "你在 ENTERPRISE_LICENSE_KEY 环境变量中填写的许可证密钥无效。请检查是否有拼写错误,或者申请一个新的密钥。", + "license_status": "许可证状态", + "license_status_active": "已激活", + "license_status_description": "你的企业许可证状态。", + "license_status_expired": "已过期", + "license_status_invalid": "许可证无效", + "license_status_unreachable": "无法访问", + "license_unreachable_grace_period": "无法连接到许可证服务器。在为期 3 天的宽限期内,你的企业功能仍然可用,宽限期将于 {gracePeriodEnd} 结束。", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "无需 电话 ,无需 附加 条件: 申请 免费 30 天 试用 授权以 通过 填写 此 表格 测试 所有 功能:", "no_credit_card_no_sales_call_just_test_it": "无需信用卡 。无需销售电话 。只需测试一下 :)", "on_request": "按请求", "organization_roles": "组织角色(管理员,编辑,开发者等)", "questions_please_reach_out_to": "问题 ? 请 联系", + "recheck_license": "重新检查许可证", + "recheck_license_failed": "许可证检查失败。许可证服务器可能无法访问。", + "recheck_license_invalid": "许可证密钥无效。请确认你的 ENTERPRISE_LICENSE_KEY。", + "recheck_license_success": "许可证检查成功", + "recheck_license_unreachable": "许可证服务器无法访问,请稍后再试。", + "rechecking": "正在重新检查...", "request_30_day_trial_license": "申请 30 天 的 试用许可证", "saml_sso": "SAML SSO", "service_level_agreement": "服务水平协议", "soc2_hipaa_iso_27001_compliance_check": "SOC2 , HIPAA , ISO 27001 合规检查", "sso": "SSO (Google 、Microsoft 、OpenID Connect)", "teams": "团队 & 访问 角色(读取, 读取 & 写入, 管理)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。", - "your_enterprise_license_is_active_all_features_unlocked": "您的企业许可证已激活 所有功能已解锁" + "unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。" }, "general": { "bulk_invite_warning_description": "在免费计划中,所有组织成员都会被分配为 \"Owner \"角色。", @@ -986,7 +1069,7 @@ "from_your_organization": "来自你的组织", "invitation_sent_once_more": "再次发送邀请。", "invite_deleted_successfully": "邀请 删除 成功", - "invited_on": "受邀于 {date}", + "invite_expires_on": "邀请将于 {date} 过期", "invites_failed": "邀请失败", "leave_organization": "离开 组织", "leave_organization_description": "您将离开此组织,并失去对所有调查和响应的访问权限。只有再次被邀请后,您才能重新加入。", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "请填写所有字段以添加新工作区。", "read": "阅读", "read_write": "读 & 写", - "select_member": "选择成员", - "select_workspace": "选择工作区", "team_admin": "团队管理员", "team_created_successfully": "团队 创建 成功", "team_deleted_successfully": "团队 删除 成功", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "添加 占位符 显示 如果 没有 值以 回忆", "add_hidden_field_id": "添加 隐藏 字段 ID", "add_highlight_border": "添加 高亮 边框", - "add_highlight_border_description": "在 你的 调查 卡片 添加 外 边框。", "add_logic": "添加逻辑", "add_none_of_the_above": "添加 “以上 都 不 是”", "add_option": "添加 选项", @@ -1189,6 +1269,7 @@ "block_duplicated": "区块已复制。", "bold": "粗体", "brand_color": "品牌 颜色", + "brand_color_description": "应用于按钮、链接和高亮部分。", "brightness": "亮度", "bulk_edit": "批量编辑", "bulk_edit_description": "编辑以下所有选项,每行一个。空行将被跳过,重复项将被移除。", @@ -1206,7 +1287,9 @@ "capture_new_action": "捕获 新动作", "card_arrangement_for_survey_type_derived": "{surveyTypeDerived} 调查 的 卡片 布局", "card_background_color": "卡片 的 背景 颜色", + "card_background_color_description": "填充调查卡区域。", "card_border_color": "卡片 的 边框 颜色", + "card_border_color_description": "勾勒调查卡边框。", "card_styling": "卡片样式", "casual": "休闲", "caution_edit_duplicate": "复制 并 编辑", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "旧 与 新 的 回复 混合 , 这 可能 导致 数据 总结 有误 。", "caution_recommendation": "这 可能 会 导致 调查 统计 数据 的 不一致 。 我们 建议 复制 调查 。", "caution_text": "更改 会导致 不一致", - "centered_modal_overlay_color": "居中 模态遮罩层颜色", "change_anyway": "还是更改", "change_background": "更改 背景", "change_question_type": "更改 问题类型", "change_survey_type": "更改 调查 类型 会影 响 现有 访问", - "change_the_background_color_of_the_card": "更改 卡片 的 背景 颜色", - "change_the_background_color_of_the_input_fields": "更改 输入字段 的 背景颜色", "change_the_background_to_a_color_image_or_animation": "将 背景 更改为 颜色 、 图像 或 动画。", - "change_the_border_color_of_the_card": "更改 卡片 的 边框 颜色", - "change_the_border_color_of_the_input_fields": "更改 输入字段 的边框颜色。", - "change_the_border_radius_of_the_card_and_the_inputs": "更改 卡片 和 输入 的 边框 半径", - "change_the_brand_color_of_the_survey": "更改调查的品牌颜色", "change_the_placement_of_this_survey": "更改 此 调查 的 放置。", - "change_the_question_color_of_the_survey": "更改调查的 问题颜色", "changes_saved": "更改 已 保存", "changing_survey_type_will_remove_existing_distribution_channels": "更改 调查 类型 会影 响 分享 方式 。 如果 受访者 已经 拥有 当前 类型 的 访问 链接 , 在 更改 之后 ,他们 可能 会 失去 访问 权限 。", "checkbox_label": "复选框 标签", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "禁用问卷 进度 的可见性。", "display_an_estimate_of_completion_time_for_survey": "显示 调查 预计 完成 时间", "display_number_of_responses_for_survey": "显示 调查 响应 数量", + "display_type": "显示类型", "divide": "划分 /", "does_not_contain": "不包含", "does_not_end_with": "不 以 结尾", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "不包括所有 ", "does_not_include_one_of": "不包括一 个", "does_not_start_with": "不 以 开头", + "dropdown": "下拉菜单", "duplicate_block": "复制区块", "duplicate_question": "复制问题", "edit_link": "编辑 链接", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "隐藏 进度 条", "hide_question_settings": "隐藏问题设置", "hostname": "主 机 名", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "在 {surveyTypeDerived} 调查 中,您 想要 卡片 多么 有趣", "if_you_need_more_please": "如果您需要更多,请", "if_you_really_want_that_answer_ask_until_you_get_it": "每次触发时都会显示,直到提交回应为止。", "ignore_global_waiting_time": "忽略冷却期", @@ -1379,7 +1455,9 @@ "initial_value": "初始 值", "inner_text": "内文", "input_border_color": "输入 边框 颜色", + "input_border_color_description": "勾勒文本输入框和多行文本框的边框。", "input_color": "输入颜色", + "input_color_description": "填充文本输入框内部。", "insert_link": "插入 链接", "invalid_targeting": "无效的目标: 请检查 您 的受众过滤器", "invalid_video_url_warning": "请输入有效的 YouTube、Vimeo 或 Loom URL 。我们目前不支持其他 视频 托管服务提供商。", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "限制上传文件的最大大小。", "limit_upload_file_size_to": "将上传文件大小限制为", "link_survey_description": "分享 问卷 页面 链接 或 将其 嵌入 网页 或 电子邮件 中。", + "list": "列表", "load_segment": "载入 段落", "logic_error_warning": "更改 将 导致 逻辑 错误", "logic_error_warning_text": "更改问题类型 会 移除 此问题 的 逻辑条件", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "只有 拥有 PIN 的 用户 可以 访问 调查。", "publish": "发布", "question": "问题", - "question_color": "问题颜色", "question_deleted": "问题 已删除", "question_duplicated": "问题重复。", "question_id_updated": "问题 ID 更新", "question_used_in_logic_warning_text": "此区块中的元素已被用于逻辑规则,您确定要删除吗?", "question_used_in_logic_warning_title": "逻辑不一致", - "question_used_in_quota": "此 问题 正在 被 \"{quotaName}\" 配额 使用", + "question_used_in_quota": "此问题正在被“{quotaName}”配额使用", "question_used_in_recall": "此问题正在召回于问题 {questionIndex}。", "question_used_in_recall_ending_card": "此 问题 正在召回于结束 卡片。", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "响应 限制 、 重定向 和 更多 。", "response_options": "响应 选项", "roundness": "圆度", + "roundness_description": "控制卡片角的圆润程度。", "row_used_in_logic_error": "\"这个 行 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"", "rows": "行", "save_and_close": "保存 和 关闭", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "样式 设置 为 主题 风格", "subheading": "子标题", "subtract": "减 -", - "suggest_colors": "建议颜色", "survey_completed_heading": "调查 完成", "survey_completed_subheading": "此 免费 & 开源 调查 已 关闭", "survey_display_settings": "调查显示设置", @@ -1642,7 +1720,7 @@ "validation_rules": "校验规则", "validation_rules_description": "仅接受符合以下条件的回复", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "\"{variable} 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "变量 \"{variableName}\" 正在 被 \"{quotaName}\" 配额 使用", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "变量“{variableName}”正在被“{quotaName}”配额使用,请先将其从配额中移除", "variable_name_conflicts_with_hidden_field": "变量名与已有的隐藏字段 ID 冲突。", "variable_name_is_already_taken_please_choose_another": "变量名已被占用,请选择其他。", "variable_name_must_start_with_a_letter": "变量名 必须 以字母开头。", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "添加背景色", "add_background_color_description": "为 logo 容器添加背景色。", + "advanced_styling_field_border_radius": "边框圆角", + "advanced_styling_field_button_bg": "按钮背景", + "advanced_styling_field_button_bg_description": "填充“下一步/提交”按钮。", + "advanced_styling_field_button_border_radius_description": "设置按钮圆角。", + "advanced_styling_field_button_font_size_description": "调整按钮标签文字大小。", + "advanced_styling_field_button_font_weight_description": "设置按钮文字的粗细。", + "advanced_styling_field_button_height_description": "控制按钮高度。", + "advanced_styling_field_button_padding_x_description": "增加左右间距。", + "advanced_styling_field_button_padding_y_description": "增加上下间距。", + "advanced_styling_field_button_text": "按钮文字", + "advanced_styling_field_button_text_description": "设置按钮内标签的颜色。", + "advanced_styling_field_description_color": "描述颜色", + "advanced_styling_field_description_color_description": "设置每个标题下方文字的颜色。", + "advanced_styling_field_description_size": "描述字体大小", + "advanced_styling_field_description_size_description": "调整描述文字大小。", + "advanced_styling_field_description_weight": "描述字体粗细", + "advanced_styling_field_description_weight_description": "设置描述文字的粗细。", + "advanced_styling_field_font_size": "字体大小", + "advanced_styling_field_font_weight": "字体粗细", + "advanced_styling_field_headline_color": "标题颜色", + "advanced_styling_field_headline_color_description": "设置主问题文字的颜色。", + "advanced_styling_field_headline_size": "标题字体大小", + "advanced_styling_field_headline_size_description": "调整主标题文字大小。", + "advanced_styling_field_headline_weight": "标题字体粗细", + "advanced_styling_field_headline_weight_description": "设置主标题文字的粗细。", + "advanced_styling_field_height": "最小高度", + "advanced_styling_field_indicator_bg": "指示器背景", + "advanced_styling_field_indicator_bg_description": "设置进度条已填充部分的颜色。", + "advanced_styling_field_input_border_radius_description": "设置输入框圆角。", + "advanced_styling_field_input_font_size_description": "调整输入框内文字大小。", + "advanced_styling_field_input_height_description": "设置输入框的最小高度。", + "advanced_styling_field_input_padding_x_description": "增加输入框左右间距。", + "advanced_styling_field_input_padding_y_description": "为输入框上下添加间距。", + "advanced_styling_field_input_placeholder_opacity_description": "调整占位提示文字的透明度。", + "advanced_styling_field_input_shadow_description": "为输入框添加投影效果。", + "advanced_styling_field_input_text": "输入文字", + "advanced_styling_field_input_text_description": "设置输入框内已输入文字的颜色。", + "advanced_styling_field_option_bg": "背景色", + "advanced_styling_field_option_bg_description": "设置选项项的背景色。", + "advanced_styling_field_option_border_radius_description": "设置选项的圆角。", + "advanced_styling_field_option_font_size_description": "调整选项标签文字的大小。", + "advanced_styling_field_option_label": "标签颜色", + "advanced_styling_field_option_label_description": "设置选项标签文字的颜色。", + "advanced_styling_field_option_padding_x_description": "为选项左右添加间距。", + "advanced_styling_field_option_padding_y_description": "为选项上下添加间距。", + "advanced_styling_field_padding_x": "横向内边距", + "advanced_styling_field_padding_y": "纵向内边距", + "advanced_styling_field_placeholder_opacity": "占位符透明度", + "advanced_styling_field_shadow": "阴影", + "advanced_styling_field_track_bg": "轨道背景", + "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_section_buttons": "按钮", + "advanced_styling_section_headlines": "标题和描述", + "advanced_styling_section_inputs": "输入项", + "advanced_styling_section_options": "选项(单选/多选)", "app_survey_placement": "应用调查放置位置", "app_survey_placement_settings_description": "更改调查在您的 Web 应用或网站中显示的位置。", - "centered_modal_overlay_color": "居中模态遮罩层颜色", "email_customization": "邮件自定义", "email_customization_description": "更改 Formbricks 代表您发送邮件的外观和风格。", "enable_custom_styling": "启用自定义样式", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks 品牌标识已隐藏。", "formbricks_branding_settings_description": "我们很感谢您的支持,但如果您关闭它,我们也能理解。", "formbricks_branding_shown": "Formbricks 品牌标识已显示。", + "generate_theme_btn": "生成", + "generate_theme_confirmation": "要根据你的品牌色生成一个匹配的配色主题吗?这将覆盖你当前的颜色设置。", + "generate_theme_header": "生成配色主题?", "logo_removed_successfully": "logo 移除成功", "logo_settings_description": "上传您的公司 logo,用于品牌调查和链接预览。", "logo_updated_successfully": "logo 更新成功", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "在 {type} 调查中显示 Formbricks 品牌标识", "show_powered_by_formbricks": "显示“Powered by Formbricks”标识", "styling_updated_successfully": "样式更新成功", + "suggest_colors": "推荐颜色", + "suggested_colors_applied_please_save": "已成功生成推荐配色。请点击“保存”以保留更改。", "theme": "主题", "theme_settings_description": "为所有问卷创建一个样式主题。你可以为每个问卷启用自定义样式。" }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "是 , 保持我 更新 。", "preview_survey_question_2_choice_2_label": "不,谢谢!", "preview_survey_question_2_headline": "想 了解 最新信息吗?", + "preview_survey_question_2_subheader": "这是一个示例描述。", "preview_survey_welcome_card_headline": "欢迎!", "prioritize_features_description": "确定 用户 最 需要 和 最 不 需要 的 功能。", "prioritize_features_name": "优先 功能", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index 080399005c..37fb08552b 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -187,6 +187,7 @@ "customer_success": "客戶成功", "dark_overlay": "深色覆蓋", "date": "日期", + "days": "天", "default": "預設", "delete": "刪除", "description": "描述", @@ -253,6 +254,7 @@ "label": "標籤", "language": "語言", "learn_more": "瞭解更多", + "license_expired": "License Expired", "light_overlay": "淺色覆蓋", "limits_reached": "已達上限", "link": "連結", @@ -273,6 +275,7 @@ "mobile_overlay_app_works_best_on_desktop": "Formbricks 適合在大螢幕上使用。若要管理或建立問卷,請切換到其他裝置。", "mobile_overlay_surveys_look_good": "別擔心 -你的 問卷 在每個 裝置 和 螢幕尺寸 上 都 很出色!", "mobile_overlay_title": "糟糕 ,偵測到小螢幕!", + "months": "月", "move_down": "下移", "move_up": "上移", "multiple_languages": "多種語言", @@ -283,6 +286,7 @@ "no_background_image_found": "找不到背景圖片。", "no_code": "無程式碼", "no_files_uploaded": "沒有上傳任何檔案", + "no_overlay": "無覆蓋層", "no_quotas_found": "找不到 配額", "no_result_found": "找不到結果", "no_results": "沒有結果", @@ -309,6 +313,7 @@ "organization_teams_not_found": "找不到組織團隊", "other": "其他", "others": "其他", + "overlay_color": "覆蓋層顏色", "overview": "概覽", "password": "密碼", "paused": "已暫停", @@ -348,6 +353,7 @@ "request_trial_license": "請求試用授權", "reset_to_default": "重設為預設值", "response": "回應", + "response_id": "回應 ID", "responses": "回應", "restart": "重新開始", "role": "角色", @@ -388,6 +394,7 @@ "status": "狀態", "step_by_step_manual": "逐步手冊", "storage_not_configured": "檔案儲存未設定,上傳可能會失敗", + "string": "文字", "styling": "樣式設定", "submit": "提交", "summary": "摘要", @@ -443,6 +450,7 @@ "website_and_app_connection": "網站與應用程式連線", "website_app_survey": "網站與應用程式問卷", "website_survey": "網站問卷", + "weeks": "週", "welcome_card": "歡迎卡片", "workspace_configuration": "工作區設定", "workspace_created_successfully": "工作區已成功建立", @@ -453,13 +461,15 @@ "workspace_not_found": "找不到工作區", "workspace_permission_not_found": "找不到工作區權限", "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} 個工作區的上限。", "you_have_reached_your_monthly_miu_limit_of": "您已達到每月 MIU 上限:", "you_have_reached_your_monthly_response_limit_of": "您已達到每月回應上限:", - "you_will_be_downgraded_to_the_community_edition_on_date": "您將於 '{'date'}' 降級至社群版。" + "you_will_be_downgraded_to_the_community_edition_on_date": "您將於 '{'date'}' 降級至社群版。", + "your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features." }, "emails": { "accept": "接受", @@ -623,6 +633,12 @@ "attribute_updated_successfully": "屬性更新成功", "attribute_value": "值", "attribute_value_placeholder": "屬性值", + "attributes_msg_attribute_limit_exceeded": "無法建立 {count} 個新屬性,因為這樣會超過 {limit} 個屬性類別的上限。現有屬性已成功更新。", + "attributes_msg_attribute_type_validation_error": "{error}(屬性「{key}」的資料型別為:{dataType})", + "attributes_msg_email_already_exists": "此環境已存在該 email,未進行更新。", + "attributes_msg_email_or_userid_required": "必須提供 email 或 userId。已保留現有值。", + "attributes_msg_new_attribute_created": "已建立新屬性「{key}」,型別為「{dataType}」", + "attributes_msg_userid_already_exists": "此環境已存在該 userId,未進行更新。", "contact_deleted_successfully": "聯絡人已成功刪除", "contact_not_found": "找不到此聯絡人", "contacts_table_refresh": "重新整理聯絡人", @@ -631,6 +647,11 @@ "create_key": "建立金鑰", "create_new_attribute": "建立新屬性", "create_new_attribute_description": "建立新屬性以進行分群用途。", + "custom_attributes": "自訂屬性", + "data_type": "資料型態", + "data_type_cannot_be_changed": "建立後無法變更資料型態", + "data_type_description": "選擇此屬性要如何儲存與篩選", + "date_value_required": "必須填寫日期值。如果你不想設定日期,請用刪除按鈕移除此屬性。", "delete_attribute_confirmation": "{value, plural, one {這將刪除所選屬性。與此屬性相關的聯絡人資料將會遺失。} other {這將刪除所選屬性。與這些屬性相關的聯絡人資料將會遺失。}}", "delete_contact_confirmation": "這將刪除與此聯繫人相關的所有調查回應和聯繫屬性。任何基於此聯繫人數據的定位和個性化將會丟失。", "delete_contact_confirmation_with_quotas": "{value, plural, one {這將刪除與這個 contact 相關的所有調查響應和聯繫人屬性。基於這個 contact 數據的任何定向和個性化功能將會丟失。如果這個 contact 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。} other {這將刪除與這些 contacts 相關的所有調查響應和聯繫人屬性。基於這些 contacts 數據的任何定向和個性化功能將會丟失。如果這些 contacts 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。}}", @@ -638,13 +659,18 @@ "edit_attribute_description": "更新此屬性的標籤與描述。", "edit_attribute_values": "編輯屬性", "edit_attribute_values_description": "變更此聯絡人特定屬性的值。", + "edit_attributes": "編輯屬性", "edit_attributes_success": "聯絡人屬性已成功更新", "generate_personal_link": "產生個人連結", "generate_personal_link_description": "選擇一個已發佈的問卷,為此聯絡人產生個人化連結。", + "invalid_csv_column_names": "無效的 CSV 欄位名稱:{columns}。作為新屬性的欄位名稱只能包含小寫字母、數字和底線,且必須以字母開頭。", + "invalid_date_format": "日期格式無效。請使用有效的日期。", + "invalid_number_format": "數字格式無效。請輸入有效的數字。", "no_published_link_surveys_available": "沒有可用的已發佈連結問卷。請先發佈一個連結問卷。", "no_published_surveys": "沒有已發佈的問卷", "no_responses_found": "找不到回應", "not_provided": "未提供", + "number_value_required": "必須填寫數字值。如要移除此屬性,請使用刪除按鈕。", "personal_link_generated": "個人連結已成功產生", "personal_link_generated_but_clipboard_failed": "已生成個人連結,但無法複製到剪貼簿:{url}", "personal_survey_link": "個人調查連結", @@ -653,13 +679,22 @@ "search_contact": "搜尋聯絡人", "select_a_survey": "選擇問卷", "select_attribute": "選取屬性", + "select_attribute_key": "選取屬性鍵值", + "system_attributes": "系統屬性", "unlock_contacts_description": "管理聯絡人並發送目標問卷", "unlock_contacts_title": "使用更高等級的方案解鎖聯絡人", + "upload_contacts_error_attribute_type_mismatch": "屬性「{key}」的類型為「{dataType}」,但 CSV 檔案中包含無效的值:{values}", + "upload_contacts_error_duplicate_mappings": "以下屬性有重複對應:{attributes}", + "upload_contacts_error_file_too_large": "檔案大小超過 800KB 的上限", + "upload_contacts_error_generic": "上傳聯絡人時發生錯誤,請稍後再試。", + "upload_contacts_error_invalid_file_type": "請上傳 CSV 檔案", + "upload_contacts_error_no_valid_contacts": "上傳的 CSV 檔案中沒有任何有效的聯絡人,請參考範例 CSV 檔案以取得正確格式。", + "upload_contacts_modal_attribute_header": "Formbricks 屬性", "upload_contacts_modal_attributes_description": "將 CSV 中的欄位對應到 Formbricks 中的屬性。", "upload_contacts_modal_attributes_new": "新增屬性", "upload_contacts_modal_attributes_search_or_add": "搜尋或新增屬性", - "upload_contacts_modal_attributes_should_be_mapped_to": "應對應到", "upload_contacts_modal_attributes_title": "屬性", + "upload_contacts_modal_csv_column_header": "CSV 欄位", "upload_contacts_modal_description": "上傳 CSV 以快速匯入具有屬性的聯絡人", "upload_contacts_modal_download_example_csv": "下載範例 CSV", "upload_contacts_modal_duplicates_description": "如果聯絡人已存在於您的聯絡人中,我們應該如何處理?", @@ -840,6 +875,40 @@ "no_attributes_yet": "尚無屬性!", "no_filters_yet": "尚無篩選器!", "no_segments_yet": "您目前沒有已儲存的區隔。", + "operator_contains": "包含", + "operator_does_not_contain": "不包含", + "operator_ends_with": "結尾為", + "operator_is_after": "在之後", + "operator_is_before": "在之前", + "operator_is_between": "介於", + "operator_is_newer_than": "較新於", + "operator_is_not_set": "未設定", + "operator_is_older_than": "較舊於", + "operator_is_same_day": "同一天", + "operator_is_set": "已設定", + "operator_starts_with": "開頭為", + "operator_title_contains": "包含", + "operator_title_does_not_contain": "不包含", + "operator_title_ends_with": "結尾為", + "operator_title_equals": "等於", + "operator_title_greater_equal": "大於或等於", + "operator_title_greater_than": "大於", + "operator_title_is_after": "在之後", + "operator_title_is_before": "在之前", + "operator_title_is_between": "介於", + "operator_title_is_newer_than": "較新於", + "operator_title_is_not_set": "未設定", + "operator_title_is_older_than": "較舊於", + "operator_title_is_same_day": "同一天", + "operator_title_is_set": "已設定", + "operator_title_less_equal": "小於或等於", + "operator_title_less_than": "小於", + "operator_title_not_equals": "不等於", + "operator_title_starts_with": "開頭為", + "operator_title_user_is_in": "使用者屬於", + "operator_title_user_is_not_in": "使用者不屬於", + "operator_user_is_in": "使用者屬於", + "operator_user_is_not_in": "使用者不屬於", "person_and_attributes": "人員與屬性", "phone": "電話", "please_remove_the_segment_from_these_surveys_in_order_to_delete_it": "請從這些問卷中移除區隔,以便將其刪除。", @@ -864,6 +933,7 @@ "user_targeting_is_currently_only_available_when": "使用者目標設定目前僅在以下情況下可用:", "value_cannot_be_empty": "值不能為空。", "value_must_be_a_number": "值必須是數字。", + "value_must_be_positive": "值必須是正數。", "view_filters": "檢視篩選器", "where": "何處", "with_the_formbricks_sdk": "使用 Formbricks SDK" @@ -950,19 +1020,32 @@ "enterprise_features": "企業版功能", "get_an_enterprise_license_to_get_access_to_all_features": "取得企業授權以存取所有功能。", "keep_full_control_over_your_data_privacy_and_security": "完全掌控您的資料隱私權和安全性。", + "license_invalid_description": "你在 ENTERPRISE_LICENSE_KEY 環境變數中填寫的授權金鑰無效。請檢查是否有輸入錯誤,或申請新的金鑰。", + "license_status": "授權狀態", + "license_status_active": "有效", + "license_status_description": "你的企業授權狀態。", + "license_status_expired": "已過期", + "license_status_invalid": "授權無效", + "license_status_unreachable": "無法連線", + "license_unreachable_grace_period": "無法連線至授權伺服器。在 3 天的寬限期內,你的企業功能仍可使用,寬限期將於 {gracePeriodEnd} 結束。", "no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "無需通話,無附加條件:填寫此表單,請求免費 30 天試用授權以測試所有功能:", "no_credit_card_no_sales_call_just_test_it": "無需信用卡。無需銷售電話。只需測試一下 :)", "on_request": "依要求", "organization_roles": "組織角色(管理員、編輯者、開發人員等)", "questions_please_reach_out_to": "有任何問題?請聯絡", + "recheck_license": "重新檢查授權", + "recheck_license_failed": "授權檢查失敗。授權伺服器可能無法連線。", + "recheck_license_invalid": "授權金鑰無效。請確認你的 ENTERPRISE_LICENSE_KEY。", + "recheck_license_success": "授權檢查成功", + "recheck_license_unreachable": "授權伺服器無法連線,請稍後再試。", + "rechecking": "正在重新檢查...", "request_30_day_trial_license": "請求 30 天試用授權", "saml_sso": "SAML SSO", "service_level_agreement": "服務等級協定", "soc2_hipaa_iso_27001_compliance_check": "SOC2、HIPAA、ISO 27001 合規性檢查", "sso": "SSO(Google、Microsoft、OpenID Connect)", "teams": "團隊和存取角色(讀取、讀取和寫入、管理)", - "unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。", - "your_enterprise_license_is_active_all_features_unlocked": "您的企業授權處於活動狀態。所有功能都已解鎖。" + "unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。" }, "general": { "bulk_invite_warning_description": "在免費方案中,所有組織成員始終會被指派「擁有者」角色。", @@ -986,7 +1069,7 @@ "from_your_organization": "來自您的組織", "invitation_sent_once_more": "已再次發送邀請。", "invite_deleted_successfully": "邀請已成功刪除", - "invited_on": "邀請於 '{'date'}'", + "invite_expires_on": "邀請將於 '{'date'}' 過期", "invites_failed": "邀請失敗", "leave_organization": "離開組織", "leave_organization_description": "您將離開此組織並失去對所有問卷和回應的存取權限。只有再次收到邀請,您才能重新加入。", @@ -1099,8 +1182,6 @@ "please_fill_all_workspace_fields": "請填寫所有欄位以新增工作區。", "read": "讀取", "read_write": "讀取和寫入", - "select_member": "選擇成員", - "select_workspace": "選擇工作區", "team_admin": "團隊管理員", "team_created_successfully": "團隊已成功建立。", "team_deleted_successfully": "團隊已成功刪除。", @@ -1150,7 +1231,6 @@ "add_fallback_placeholder": "新增 預設 以顯示是否沒 有 值 可 回憶 。", "add_hidden_field_id": "新增隱藏欄位 ID", "add_highlight_border": "新增醒目提示邊框", - "add_highlight_border_description": "在您的問卷卡片新增外邊框。", "add_logic": "新增邏輯", "add_none_of_the_above": "新增 \"以上皆非\"", "add_option": "新增選項", @@ -1189,6 +1269,7 @@ "block_duplicated": "區塊已複製。", "bold": "粗體", "brand_color": "品牌顏色", + "brand_color_description": "應用於按鈕、連結和重點標示。", "brightness": "亮度", "bulk_edit": "批次編輯", "bulk_edit_description": "在下方逐行編輯所有選項。空白行將被略過,重複項目將被移除。", @@ -1206,7 +1287,9 @@ "capture_new_action": "擷取新操作", "card_arrangement_for_survey_type_derived": "'{'surveyTypeDerived'}' 問卷的卡片排列", "card_background_color": "卡片背景顏色", + "card_background_color_description": "填滿問卷卡片區域。", "card_border_color": "卡片邊框顏色", + "card_border_color_description": "描繪問卷卡片的邊框。", "card_styling": "卡片樣式", "casual": "隨意", "caution_edit_duplicate": "複製 & 編輯", @@ -1217,20 +1300,12 @@ "caution_explanation_responses_are_safe": "較舊和較新的回應會混在一起,可能導致數據摘要失準。", "caution_recommendation": "這可能導致調查摘要中的數據不一致。我們建議複製這個調查。", "caution_text": "變更會導致不一致", - "centered_modal_overlay_color": "置中彈窗覆蓋顏色", "change_anyway": "仍然變更", "change_background": "變更背景", "change_question_type": "變更問題類型", "change_survey_type": "切換問卷類型會影響現有訪問", - "change_the_background_color_of_the_card": "變更卡片的背景顏色。", - "change_the_background_color_of_the_input_fields": "變更輸入欄位的背景顏色。", "change_the_background_to_a_color_image_or_animation": "將背景變更為顏色、圖片或動畫。", - "change_the_border_color_of_the_card": "變更卡片的邊框顏色。", - "change_the_border_color_of_the_input_fields": "變更輸入欄位的邊框顏色。", - "change_the_border_radius_of_the_card_and_the_inputs": "變更卡片和輸入的邊框半徑。", - "change_the_brand_color_of_the_survey": "變更問卷的品牌顏色。", "change_the_placement_of_this_survey": "變更此問卷的位置。", - "change_the_question_color_of_the_survey": "變更問卷的問題顏色。", "changes_saved": "已儲存變更。", "changing_survey_type_will_remove_existing_distribution_channels": "更改問卷類型會影響其共享方式。如果受訪者已擁有當前類型的存取連結,則在切換後可能會失去存取權限。", "checkbox_label": "核取方塊標籤", @@ -1270,6 +1345,7 @@ "disable_the_visibility_of_survey_progress": "停用問卷進度的可見性。", "display_an_estimate_of_completion_time_for_survey": "顯示問卷的估計完成時間", "display_number_of_responses_for_survey": "顯示問卷的回應數", + "display_type": "顯示類型", "divide": "除 /", "does_not_contain": "不包含", "does_not_end_with": "不以...結尾", @@ -1277,6 +1353,7 @@ "does_not_include_all_of": "不包含全部", "does_not_include_one_of": "不包含其中之一", "does_not_start_with": "不以...開頭", + "dropdown": "下拉選單", "duplicate_block": "複製區塊", "duplicate_question": "複製問題", "edit_link": "編輯 連結", @@ -1368,7 +1445,6 @@ "hide_progress_bar": "隱藏進度列", "hide_question_settings": "隱藏問題設定", "hostname": "主機名稱", - "how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "您希望 '{'surveyTypeDerived'}' 問卷中的卡片有多酷炫", "if_you_need_more_please": "如果您需要更多,請", "if_you_really_want_that_answer_ask_until_you_get_it": "每次觸發時都顯示,直到提交回應為止。", "ignore_global_waiting_time": "忽略冷卻期", @@ -1379,7 +1455,9 @@ "initial_value": "初始值", "inner_text": "內部文字", "input_border_color": "輸入邊框顏色", + "input_border_color_description": "描繪文字輸入框和文字區域的邊框。", "input_color": "輸入顏色", + "input_color_description": "填滿文字輸入框的內部。", "insert_link": "插入 連結", "invalid_targeting": "目標設定無效:請檢查您的受眾篩選器", "invalid_video_url_warning": "請輸入有效的 YouTube、Vimeo 或 Loom 網址。我們目前不支援其他影片託管提供者。", @@ -1409,6 +1487,7 @@ "limit_the_maximum_file_size": "限制上傳檔案的最大大小。", "limit_upload_file_size_to": "將上傳檔案大小限制為", "link_survey_description": "分享問卷頁面的連結或將其嵌入網頁或電子郵件中。", + "list": "清單", "load_segment": "載入區隔", "logic_error_warning": "變更將導致邏輯錯誤", "logic_error_warning_text": "變更問題類型將會從此問題中移除邏輯條件", @@ -1462,13 +1541,12 @@ "protect_survey_with_pin_description": "只有擁有 PIN 碼的使用者才能存取問卷。", "publish": "發布", "question": "問題", - "question_color": "問題顏色", "question_deleted": "問題已刪除。", "question_duplicated": "問題已複製。", "question_id_updated": "問題 ID 已更新", "question_used_in_logic_warning_text": "此區塊中的元素已用於邏輯規則,確定要刪除嗎?", "question_used_in_logic_warning_title": "邏輯不一致", - "question_used_in_quota": "此問題 正被使用於 \"{quotaName}\" 配額中", + "question_used_in_quota": "此問題正被使用於「{quotaName}」配額中", "question_used_in_recall": "此問題於問題 {questionIndex} 中被召回。", "question_used_in_recall_ending_card": "此問題於結尾卡中被召回。", "quotas": { @@ -1524,6 +1602,7 @@ "response_limits_redirections_and_more": "回應限制、重新導向等。", "response_options": "回應選項", "roundness": "圓角", + "roundness_description": "調整卡片邊角的圓弧度。", "row_used_in_logic_error": "此 row 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。", "rows": "列", "save_and_close": "儲存並關閉", @@ -1565,7 +1644,6 @@ "styling_set_to_theme_styles": "樣式設定為主題樣式", "subheading": "副標題", "subtract": "減 -", - "suggest_colors": "建議顏色", "survey_completed_heading": "問卷已完成", "survey_completed_subheading": "此免費且開源的問卷已關閉", "survey_display_settings": "問卷顯示設定", @@ -1642,7 +1720,7 @@ "validation_rules": "驗證規則", "validation_rules_description": "僅接受符合下列條件的回應", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "'{'variable'}' 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。", - "variable_is_used_in_quota_please_remove_it_from_quota_first": "變數 \"{variableName}\" 正被使用於 \"{quotaName}\" 配額中", + "variable_is_used_in_quota_please_remove_it_from_quota_first": "變數「{variableName}」正被使用於「{quotaName}」配額中", "variable_name_conflicts_with_hidden_field": "變數名稱與現有的隱藏欄位 ID 衝突。", "variable_name_is_already_taken_please_choose_another": "已使用此變數名稱,請選擇另一個名稱。", "variable_name_must_start_with_a_letter": "變數名稱必須以字母開頭。", @@ -2049,9 +2127,71 @@ "look": { "add_background_color": "新增背景顏色", "add_background_color_description": "為標誌容器新增背景顏色。", + "advanced_styling_field_border_radius": "邊框圓角", + "advanced_styling_field_button_bg": "按鈕背景", + "advanced_styling_field_button_bg_description": "填滿「下一步」/「送出」按鈕。", + "advanced_styling_field_button_border_radius_description": "調整按鈕的圓角。", + "advanced_styling_field_button_font_size_description": "調整按鈕標籤文字的大小。", + "advanced_styling_field_button_font_weight_description": "讓按鈕文字變細或變粗。", + "advanced_styling_field_button_height_description": "調整按鈕的高度。", + "advanced_styling_field_button_padding_x_description": "在左右兩側增加間距。", + "advanced_styling_field_button_padding_y_description": "在上下兩側增加間距。", + "advanced_styling_field_button_text": "按鈕文字", + "advanced_styling_field_button_text_description": "設定按鈕內標籤的顏色。", + "advanced_styling_field_description_color": "說明文字顏色", + "advanced_styling_field_description_color_description": "設定每個標題下方文字的顏色。", + "advanced_styling_field_description_size": "說明文字大小", + "advanced_styling_field_description_size_description": "調整說明文字的大小。", + "advanced_styling_field_description_weight": "說明文字粗細", + "advanced_styling_field_description_weight_description": "讓說明文字變細或變粗。", + "advanced_styling_field_font_size": "字體大小", + "advanced_styling_field_font_weight": "字體粗細", + "advanced_styling_field_headline_color": "標題顏色", + "advanced_styling_field_headline_color_description": "設定主要問題文字的顏色。", + "advanced_styling_field_headline_size": "標題字體大小", + "advanced_styling_field_headline_size_description": "調整標題文字的大小。", + "advanced_styling_field_headline_weight": "標題字體粗細", + "advanced_styling_field_headline_weight_description": "讓標題文字變細或變粗。", + "advanced_styling_field_height": "最小高度", + "advanced_styling_field_indicator_bg": "指示器背景", + "advanced_styling_field_indicator_bg_description": "設定進度條已填滿部分的顏色。", + "advanced_styling_field_input_border_radius_description": "調整輸入框的圓角。", + "advanced_styling_field_input_font_size_description": "調整輸入框內輸入文字的大小。", + "advanced_styling_field_input_height_description": "設定輸入欄位的最小高度。", + "advanced_styling_field_input_padding_x_description": "在左右兩側增加間距。", + "advanced_styling_field_input_padding_y_description": "在上方和下方增加間距。", + "advanced_styling_field_input_placeholder_opacity_description": "讓提示文字變得更淡。", + "advanced_styling_field_input_shadow_description": "在輸入框周圍加上陰影。", + "advanced_styling_field_input_text": "輸入文字", + "advanced_styling_field_input_text_description": "設定輸入文字的顏色。", + "advanced_styling_field_option_bg": "背景", + "advanced_styling_field_option_bg_description": "填滿選項項目背景。", + "advanced_styling_field_option_border_radius_description": "讓選項的邊角變圓。", + "advanced_styling_field_option_font_size_description": "調整選項標籤文字的大小。", + "advanced_styling_field_option_label": "標籤顏色", + "advanced_styling_field_option_label_description": "設定選項標籤文字的顏色。", + "advanced_styling_field_option_padding_x_description": "在左側和右側增加間距。", + "advanced_styling_field_option_padding_y_description": "在上方和下方增加間距。", + "advanced_styling_field_padding_x": "左右內距", + "advanced_styling_field_padding_y": "上下內距", + "advanced_styling_field_placeholder_opacity": "預設文字透明度", + "advanced_styling_field_shadow": "陰影", + "advanced_styling_field_track_bg": "軌道背景", + "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_section_buttons": "按鈕", + "advanced_styling_section_headlines": "標題與說明", + "advanced_styling_section_inputs": "輸入欄位", + "advanced_styling_section_options": "選項(單選/複選)", "app_survey_placement": "應用程式問卷位置", "app_survey_placement_settings_description": "變更問卷在您的網頁應用程式或網站中顯示的位置。", - "centered_modal_overlay_color": "置中彈窗覆蓋顏色", "email_customization": "電子郵件自訂化", "email_customization_description": "變更 Formbricks 代表您發送的電子郵件外觀與風格。", "enable_custom_styling": "啟用自訂樣式", @@ -2062,6 +2202,9 @@ "formbricks_branding_hidden": "Formbricks 品牌標示已隱藏。", "formbricks_branding_settings_description": "我們很感謝您的支持,但若您選擇關閉我們也能理解。", "formbricks_branding_shown": "Formbricks 品牌標示已顯示。", + "generate_theme_btn": "產生", + "generate_theme_confirmation": "你想根據品牌色產生一組相符的主題色嗎?這將會覆蓋你目前的顏色設定。", + "generate_theme_header": "要產生主題色嗎?", "logo_removed_successfully": "標誌已成功移除", "logo_settings_description": "上傳您的公司標誌,以用於問卷和連結預覽的品牌展示。", "logo_updated_successfully": "標誌已成功更新", @@ -2076,6 +2219,8 @@ "show_formbricks_branding_in": "在 {type} 問卷中顯示 Formbricks 品牌標示", "show_powered_by_formbricks": "顯示「Powered by Formbricks」標記", "styling_updated_successfully": "樣式已成功更新", + "suggest_colors": "建議顏色", + "suggested_colors_applied_please_save": "已成功產生建議色彩。請按「儲存」以保存變更。", "theme": "主題", "theme_settings_description": "為所有調查建立樣式主題。您可以為每個調查啟用自訂樣式。" }, @@ -2839,6 +2984,7 @@ "preview_survey_question_2_choice_1_label": "是,請保持通知我。", "preview_survey_question_2_choice_2_label": "不用了,謝謝!", "preview_survey_question_2_headline": "想要緊跟最新動態嗎?", + "preview_survey_question_2_subheader": "這是一個範例說明。", "preview_survey_welcome_card_headline": "歡迎!", "prioritize_features_description": "找出您的使用者最需要和最不需要的功能。", "prioritize_features_name": "優先排序功能", diff --git a/apps/web/modules/api/v2/management/responses/lib/validation.test.ts b/apps/web/modules/api/lib/validation.test.ts similarity index 81% rename from apps/web/modules/api/v2/management/responses/lib/validation.test.ts rename to apps/web/modules/api/lib/validation.test.ts index cb1752fc90..ebb75d8d56 100644 --- a/apps/web/modules/api/v2/management/responses/lib/validation.test.ts +++ b/apps/web/modules/api/lib/validation.test.ts @@ -5,10 +5,10 @@ import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements"; import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types"; import { TValidationErrorMap } from "@formbricks/types/surveys/validation-rules"; import { - formatValidationErrorsForApi, formatValidationErrorsForV1Api, + formatValidationErrorsForV2Api, validateResponseData, -} from "./validation"; +} from "@/modules/api/lib/validation"; const mockTransformQuestionsToBlocks = vi.fn(); const mockGetElementsFromBlocks = vi.fn(); @@ -95,7 +95,7 @@ describe("validateResponseData", () => { mockGetElementsFromBlocks.mockReturnValue(mockElements); mockValidateBlockResponses.mockReturnValue({}); - validateResponseData([], mockResponseData, "en", mockQuestions); + validateResponseData([], mockResponseData, "en", true, mockQuestions); expect(mockTransformQuestionsToBlocks).toHaveBeenCalledWith(mockQuestions, []); expect(mockGetElementsFromBlocks).toHaveBeenCalledWith(transformedBlocks); @@ -105,15 +105,15 @@ describe("validateResponseData", () => { mockGetElementsFromBlocks.mockReturnValue(mockElements); mockValidateBlockResponses.mockReturnValue({}); - validateResponseData(mockBlocks, mockResponseData, "en", mockQuestions); + validateResponseData(mockBlocks, mockResponseData, "en", true, mockQuestions); expect(mockTransformQuestionsToBlocks).not.toHaveBeenCalled(); }); test("should return null when both blocks and questions are empty", () => { - expect(validateResponseData([], mockResponseData, "en", [])).toBeNull(); - expect(validateResponseData(null, mockResponseData, "en", [])).toBeNull(); - expect(validateResponseData(undefined, mockResponseData, "en", null)).toBeNull(); + expect(validateResponseData([], mockResponseData, "en", true, [])).toBeNull(); + expect(validateResponseData(null, mockResponseData, "en", true, [])).toBeNull(); + expect(validateResponseData(undefined, mockResponseData, "en", true, null)).toBeNull(); }); test("should use default language code", () => { @@ -124,15 +124,36 @@ describe("validateResponseData", () => { expect(mockValidateBlockResponses).toHaveBeenCalledWith(mockElements, mockResponseData, "en"); }); + + test("should validate only present fields when finished is false", () => { + const partialResponseData: TResponseData = { element1: "test" }; + const partialElements = [mockElements[0]]; + mockGetElementsFromBlocks.mockReturnValue(mockElements); + mockValidateBlockResponses.mockReturnValue({}); + + validateResponseData(mockBlocks, partialResponseData, "en", false); + + expect(mockValidateBlockResponses).toHaveBeenCalledWith(partialElements, partialResponseData, "en"); + }); + + test("should validate all fields when finished is true", () => { + const partialResponseData: TResponseData = { element1: "test" }; + mockGetElementsFromBlocks.mockReturnValue(mockElements); + mockValidateBlockResponses.mockReturnValue({}); + + validateResponseData(mockBlocks, partialResponseData, "en", true); + + expect(mockValidateBlockResponses).toHaveBeenCalledWith(mockElements, partialResponseData, "en"); + }); }); -describe("formatValidationErrorsForApi", () => { +describe("formatValidationErrorsForV2Api", () => { test("should convert error map to V2 API format", () => { const errorMap: TValidationErrorMap = { element1: [{ ruleId: "minLength", ruleType: "minLength", message: "Min length required" }], }; - const result = formatValidationErrorsForApi(errorMap); + const result = formatValidationErrorsForV2Api(errorMap); expect(result).toEqual([ { @@ -151,7 +172,7 @@ describe("formatValidationErrorsForApi", () => { ], }; - const result = formatValidationErrorsForApi(errorMap); + const result = formatValidationErrorsForV2Api(errorMap); expect(result).toHaveLength(2); expect(result[0].field).toBe("response.data.element1"); @@ -164,7 +185,7 @@ describe("formatValidationErrorsForApi", () => { element2: [{ ruleId: "maxLength", ruleType: "maxLength", message: "Max length" }], }; - const result = formatValidationErrorsForApi(errorMap); + const result = formatValidationErrorsForV2Api(errorMap); expect(result).toHaveLength(2); expect(result[0].field).toBe("response.data.element1"); diff --git a/apps/web/modules/api/v2/management/responses/lib/validation.ts b/apps/web/modules/api/lib/validation.ts similarity index 77% rename from apps/web/modules/api/v2/management/responses/lib/validation.ts rename to apps/web/modules/api/lib/validation.ts index b1057a3253..67861c6bbd 100644 --- a/apps/web/modules/api/v2/management/responses/lib/validation.ts +++ b/apps/web/modules/api/lib/validation.ts @@ -10,17 +10,20 @@ import { ApiErrorDetails } from "@/modules/api/v2/types/api-error"; /** * Validates response data against survey validation rules + * Handles partial responses (in-progress) by only validating present fields when finished is false * * @param blocks - Survey blocks containing elements with validation rules (preferred) - * @param questions - Survey questions (legacy format, used as fallback if blocks are empty) * @param responseData - Response data to validate (keyed by element ID) * @param languageCode - Language code for error messages (defaults to "en") + * @param finished - Whether the response is finished (defaults to true for management APIs) + * @param questions - Survey questions (legacy format, used as fallback if blocks are empty) * @returns Validation error map keyed by element ID, or null if validation passes */ export const validateResponseData = ( blocks: TSurveyBlock[] | undefined | null, responseData: TResponseData, languageCode: string = "en", + finished: boolean = true, questions?: TSurveyQuestion[] | undefined | null ): TValidationErrorMap | null => { // Use blocks if available, otherwise transform questions to blocks @@ -37,22 +40,28 @@ export const validateResponseData = ( } // Extract elements from blocks - const elements = getElementsFromBlocks(blocksToUse); + const allElements = getElementsFromBlocks(blocksToUse); - // Validate all elements - const errorMap = validateBlockResponses(elements, responseData, languageCode); + // If response is not finished, only validate elements that are present in the response data + // This prevents "required" errors for fields the user hasn't reached yet + const elementsToValidate = finished + ? allElements + : allElements.filter((element) => Object.keys(responseData).includes(element.id)); + + // Validate selected elements + const errorMap = validateBlockResponses(elementsToValidate, responseData, languageCode); // Return null if no errors (validation passed), otherwise return error map return Object.keys(errorMap).length === 0 ? null : errorMap; }; /** - * Converts validation error map to API error response format (V2) + * Converts validation error map to V2 API error response format * * @param errorMap - Validation error map from validateResponseData - * @returns API error response details + * @returns V2 API error response details */ -export const formatValidationErrorsForApi = (errorMap: TValidationErrorMap) => { +export const formatValidationErrorsForV2Api = (errorMap: TValidationErrorMap) => { const details: ApiErrorDetails = []; for (const [elementId, errors] of Object.entries(errorMap)) { diff --git a/apps/web/modules/api/v2/management/contact-attribute-keys/lib/contact-attribute-key.ts b/apps/web/modules/api/v2/management/contact-attribute-keys/lib/contact-attribute-key.ts index 9478e2cfbd..e972c1b8eb 100644 --- a/apps/web/modules/api/v2/management/contact-attribute-keys/lib/contact-attribute-key.ts +++ b/apps/web/modules/api/v2/management/contact-attribute-keys/lib/contact-attribute-key.ts @@ -3,6 +3,7 @@ import { cache as reactCache } from "react"; import { prisma } from "@formbricks/database"; import { PrismaErrorType } from "@formbricks/database/types/error"; import { Result, err, ok } from "@formbricks/types/error-handlers"; +import { formatSnakeCaseToTitleCase } from "@/lib/utils/safe-identifier"; import { getContactAttributeKeysQuery } from "@/modules/api/v2/management/contact-attribute-keys/lib/utils"; import { TContactAttributeKeyInput, @@ -37,7 +38,7 @@ export const getContactAttributeKeys = reactCache( export const createContactAttributeKey = async ( contactAttributeKey: TContactAttributeKeyInput ): Promise> => { - const { environmentId, name, description, key } = contactAttributeKey; + const { environmentId, name, description, key, dataType } = contactAttributeKey; try { const prismaData: Prisma.ContactAttributeKeyCreateInput = { @@ -46,9 +47,10 @@ export const createContactAttributeKey = async ( id: environmentId, }, }, - name, + name: name ?? formatSnakeCaseToTitleCase(key), description, key, + ...(dataType && { dataType }), }; const createdContactAttributeKey = await prisma.contactAttributeKey.create({ diff --git a/apps/web/modules/api/v2/management/contact-attribute-keys/types/contact-attribute-keys.ts b/apps/web/modules/api/v2/management/contact-attribute-keys/types/contact-attribute-keys.ts index a1f26aa806..b9382a7306 100644 --- a/apps/web/modules/api/v2/management/contact-attribute-keys/types/contact-attribute-keys.ts +++ b/apps/web/modules/api/v2/management/contact-attribute-keys/types/contact-attribute-keys.ts @@ -30,6 +30,9 @@ export const ZContactAttributeKeyInput = ZContactAttributeKey.pick({ description: true, environmentId: true, }) + .extend({ + dataType: ZContactAttributeKey.shape.dataType.optional(), + }) .superRefine((data, ctx) => { // Enforce safe identifier format for key if (!isSafeIdentifier(data.key)) { diff --git a/apps/web/modules/api/v2/management/responses/[responseId]/route.ts b/apps/web/modules/api/v2/management/responses/[responseId]/route.ts index af8c3b72e2..c8bd252fd5 100644 --- a/apps/web/modules/api/v2/management/responses/[responseId]/route.ts +++ b/apps/web/modules/api/v2/management/responses/[responseId]/route.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { sendToPipeline } from "@/app/lib/pipelines"; +import { formatValidationErrorsForV2Api, validateResponseData } from "@/modules/api/lib/validation"; import { authenticatedApiClient } from "@/modules/api/v2/auth/authenticated-api-client"; import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element"; import { responses } from "@/modules/api/v2/lib/response"; @@ -15,7 +16,6 @@ import { getSurveyQuestions } from "@/modules/api/v2/management/responses/[respo import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils"; import { validateFileUploads } from "@/modules/storage/utils"; -import { formatValidationErrorsForApi, validateResponseData } from "../lib/validation"; import { ZResponseIdSchema, ZResponseUpdateSchema } from "./types/responses"; export const GET = async (request: Request, props: { params: Promise<{ responseId: string }> }) => @@ -198,6 +198,7 @@ export const PUT = (request: Request, props: { params: Promise<{ responseId: str questionsResponse.data.blocks, body.data, body.language ?? "en", + body.finished, questionsResponse.data.questions ); @@ -206,7 +207,7 @@ export const PUT = (request: Request, props: { params: Promise<{ responseId: str request, { type: "bad_request", - details: formatValidationErrorsForApi(validationErrors), + details: formatValidationErrorsForV2Api(validationErrors), }, auditLog ); diff --git a/apps/web/modules/api/v2/management/responses/route.ts b/apps/web/modules/api/v2/management/responses/route.ts index 75de7f1e1c..438e0dff55 100644 --- a/apps/web/modules/api/v2/management/responses/route.ts +++ b/apps/web/modules/api/v2/management/responses/route.ts @@ -1,6 +1,7 @@ import { Response } from "@prisma/client"; import { NextRequest } from "next/server"; import { sendToPipeline } from "@/app/lib/pipelines"; +import { formatValidationErrorsForV2Api, validateResponseData } from "@/modules/api/lib/validation"; import { authenticatedApiClient } from "@/modules/api/v2/auth/authenticated-api-client"; import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element"; import { responses } from "@/modules/api/v2/lib/response"; @@ -13,7 +14,6 @@ import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils"; import { validateFileUploads } from "@/modules/storage/utils"; import { createResponseWithQuotaEvaluation, getResponses } from "./lib/response"; -import { formatValidationErrorsForApi, validateResponseData } from "./lib/validation"; export const GET = async (request: NextRequest) => authenticatedApiClient({ @@ -134,6 +134,7 @@ export const POST = async (request: Request) => surveyQuestions.data.blocks, body.data, body.language ?? "en", + body.finished, surveyQuestions.data.questions ); @@ -142,7 +143,7 @@ export const POST = async (request: Request) => request, { type: "bad_request", - details: formatValidationErrorsForApi(validationErrors), + details: formatValidationErrorsForV2Api(validationErrors), }, auditLog ); diff --git a/apps/web/modules/core/rate-limit/rate-limit-configs.test.ts b/apps/web/modules/core/rate-limit/rate-limit-configs.test.ts index f7cd0c74b0..25755fa112 100644 --- a/apps/web/modules/core/rate-limit/rate-limit-configs.test.ts +++ b/apps/web/modules/core/rate-limit/rate-limit-configs.test.ts @@ -68,12 +68,17 @@ describe("rateLimitConfigs", () => { test("should have all API configurations", () => { const apiConfigs = Object.keys(rateLimitConfigs.api); - expect(apiConfigs).toEqual(["v1", "v2", "client", "syncUserIdentification"]); + expect(apiConfigs).toEqual(["v1", "v2", "client"]); }); test("should have all action configurations", () => { const actionConfigs = Object.keys(rateLimitConfigs.actions); - expect(actionConfigs).toEqual(["emailUpdate", "surveyFollowUp", "sendLinkSurveyEmail"]); + expect(actionConfigs).toEqual([ + "emailUpdate", + "surveyFollowUp", + "sendLinkSurveyEmail", + "licenseRecheck", + ]); }); }); @@ -132,7 +137,6 @@ describe("rateLimitConfigs", () => { { config: rateLimitConfigs.api.v1, identifier: "api-v1-key" }, { config: rateLimitConfigs.api.v2, identifier: "api-v2-key" }, { config: rateLimitConfigs.api.client, identifier: "client-api-key" }, - { config: rateLimitConfigs.api.syncUserIdentification, identifier: "sync-user-id" }, { config: rateLimitConfigs.actions.emailUpdate, identifier: "user-profile" }, { config: rateLimitConfigs.storage.upload, identifier: "storage-upload" }, { config: rateLimitConfigs.storage.delete, identifier: "storage-delete" }, @@ -155,31 +159,6 @@ describe("rateLimitConfigs", () => { } }); - test("should properly configure syncUserIdentification rate limit", async () => { - const config = rateLimitConfigs.api.syncUserIdentification; - - // Verify configuration values - expect(config.interval).toBe(60); // 1 minute - expect(config.allowedPerInterval).toBe(5); // 5 requests per minute - expect(config.namespace).toBe("api:sync-user-identification"); - - // Test with allowed request - mockEval.mockResolvedValue([1, 1]); // 1 request used, allowed (1 = true) - const allowedResult = await checkRateLimit(config, "env-user-123"); - expect(allowedResult.ok).toBe(true); - if (allowedResult.ok) { - expect(allowedResult.data.allowed).toBe(true); - } - - // Test when limit is exceeded - mockEval.mockResolvedValue([6, 0]); // 6 requests used (exceeds limit of 5), not allowed (0 = false) - const exceededResult = await checkRateLimit(config, "env-user-123"); - expect(exceededResult.ok).toBe(true); - if (exceededResult.ok) { - expect(exceededResult.data.allowed).toBe(false); - } - }); - test("should properly configure storage upload rate limit", async () => { const config = rateLimitConfigs.storage.upload; diff --git a/apps/web/modules/core/rate-limit/rate-limit-configs.ts b/apps/web/modules/core/rate-limit/rate-limit-configs.ts index dfc3814028..d729d0dcea 100644 --- a/apps/web/modules/core/rate-limit/rate-limit-configs.ts +++ b/apps/web/modules/core/rate-limit/rate-limit-configs.ts @@ -12,11 +12,6 @@ export const rateLimitConfigs = { v1: { interval: 60, allowedPerInterval: 100, namespace: "api:v1" }, // 100 per minute (Management API) v2: { interval: 60, allowedPerInterval: 100, namespace: "api:v2" }, // 100 per minute client: { interval: 60, allowedPerInterval: 100, namespace: "api:client" }, // 100 per minute (Client API) - syncUserIdentification: { - interval: 60, - allowedPerInterval: 5, - namespace: "api:sync-user-identification", - }, // 5 per minute per environment-user pair }, // Server actions - varies by action type @@ -28,10 +23,11 @@ export const rateLimitConfigs = { allowedPerInterval: 10, namespace: "action:send-link-survey-email", }, // 10 per hour + licenseRecheck: { interval: 60, allowedPerInterval: 5, namespace: "action:license-recheck" }, // 5 per minute }, storage: { upload: { interval: 60, allowedPerInterval: 5, namespace: "storage:upload" }, // 5 per minute delete: { interval: 60, allowedPerInterval: 5, namespace: "storage:delete" }, // 5 per minute }, -}; +} as const; diff --git a/apps/web/modules/ee/contacts/[contactId]/components/attributes-section.tsx b/apps/web/modules/ee/contacts/[contactId]/components/attributes-section.tsx index 14a3b26559..301b5fe308 100644 --- a/apps/web/modules/ee/contacts/[contactId]/components/attributes-section.tsx +++ b/apps/web/modules/ee/contacts/[contactId]/components/attributes-section.tsx @@ -1,12 +1,17 @@ import { getResponsesByContactId } from "@/lib/response/service"; import { getTranslate } from "@/lingodotdev/server"; -import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes"; +import { getContactAttributesWithKeyInfo } from "@/modules/ee/contacts/lib/contact-attributes"; import { getContact } from "@/modules/ee/contacts/lib/contacts"; +import { formatAttributeValue } from "@/modules/ee/contacts/lib/format-attribute-value"; +import { getContactAttributeDataTypeIcon } from "@/modules/ee/contacts/utils"; import { IdBadge } from "@/modules/ui/components/id-badge"; export const AttributesSection = async ({ contactId }: { contactId: string }) => { const t = await getTranslate(); - const [contact, attributes] = await Promise.all([getContact(contactId), getContactAttributes(contactId)]); + const [contact, attributesWithKeyInfo] = await Promise.all([ + getContact(contactId), + getContactAttributesWithKeyInfo(contactId), + ]); if (!contact) { throw new Error(t("environments.contacts.contact_not_found")); @@ -15,54 +20,65 @@ export const AttributesSection = async ({ contactId }: { contactId: string }) => const responses = await getResponsesByContactId(contactId); const numberOfResponses = responses?.length || 0; + const systemAttributes = attributesWithKeyInfo + .filter((attr) => attr.type === "default") + .sort((a, b) => (a.name || a.key).localeCompare(b.name || b.key)); + + const customAttributes = attributesWithKeyInfo + .filter((attr) => attr.type === "custom") + .sort((a, b) => (a.name || a.key).localeCompare(b.name || b.key)); + + const renderAttributeValue = (attr: (typeof attributesWithKeyInfo)[number]) => { + if (!attr.value) { + return {t("environments.contacts.not_provided")}; + } + + // Special handling for userId to show IdBadge + if (attr.key === "userId") { + return ; + } + + return formatAttributeValue(attr.value, attr.dataType); + }; + return (
    -

    {t("common.attributes")}

    +

    {t("environments.contacts.system_attributes")}

    + + {systemAttributes.map((attr) => ( +
    +
    + {getContactAttributeDataTypeIcon(attr.dataType)} + {attr.name || attr.key} +
    +
    {renderAttributeValue(attr)}
    +
    + ))} +
    -
    email
    -
    - {attributes.email ? ( - {attributes.email} - ) : ( - {t("environments.contacts.not_provided")} - )} -
    -
    -
    -
    language
    -
    - {attributes.language ? ( - {attributes.language} - ) : ( - {t("environments.contacts.not_provided")} - )} -
    -
    -
    -
    userId
    -
    - {attributes.userId ? ( - - ) : ( - {t("environments.contacts.not_provided")} - )} -
    -
    -
    -
    contactId
    +
    + {getContactAttributeDataTypeIcon("string")} + contactId +
    {contact.id}
    - {Object.entries(attributes) - .filter(([key, _]) => key !== "email" && key !== "userId" && key !== "language") - .map(([key, attributeData]) => { - return ( -
    -
    {key}
    -
    {attributeData}
    + {customAttributes.length > 0 && ( + <> +
    +

    {t("environments.contacts.custom_attributes")}

    + {customAttributes.map((attr) => ( +
    +
    + {getContactAttributeDataTypeIcon(attr.dataType)} + {attr.name || attr.key} +
    +
    {renderAttributeValue(attr)}
    - ); - })} + ))} + + )} +
    diff --git a/apps/web/modules/ee/contacts/[contactId]/components/contact-control-bar.tsx b/apps/web/modules/ee/contacts/[contactId]/components/contact-control-bar.tsx index 87e3af8157..294f387eb5 100644 --- a/apps/web/modules/ee/contacts/[contactId]/components/contact-control-bar.tsx +++ b/apps/web/modules/ee/contacts/[contactId]/components/contact-control-bar.tsx @@ -5,8 +5,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; -import { TContactAttributes } from "@formbricks/types/contact-attribute"; -import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; +import { TContactAttributeDataType, TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { deleteContactAction } from "@/modules/ee/contacts/actions"; import { EditContactAttributesModal } from "@/modules/ee/contacts/components/edit-contact-attributes-modal"; @@ -15,14 +14,21 @@ import { DeleteDialog } from "@/modules/ui/components/delete-dialog"; import { IconBar } from "@/modules/ui/components/iconbar"; import { GeneratePersonalLinkModal } from "./generate-personal-link-modal"; +interface TContactAttributeWithKeyInfo { + key: string; + name: string | null; + value: string; + dataType: TContactAttributeDataType; +} + interface ContactControlBarProps { environmentId: string; contactId: string; isReadOnly: boolean; isQuotasAllowed: boolean; publishedLinkSurveys: PublishedLinkSurvey[]; - currentAttributes: TContactAttributes; - attributeKeys: TContactAttributeKey[]; + allAttributeKeys: TContactAttributeKey[]; + currentAttributes: TContactAttributeWithKeyInfo[]; } export const ContactControlBar = ({ @@ -31,8 +37,8 @@ export const ContactControlBar = ({ isReadOnly, isQuotasAllowed, publishedLinkSurveys, + allAttributeKeys, currentAttributes, - attributeKeys, }: ContactControlBarProps) => { const router = useRouter(); const { t } = useTranslation(); @@ -63,7 +69,7 @@ export const ContactControlBar = ({ const iconActions = [ { icon: PencilIcon, - tooltip: t("environments.contacts.edit_attribute_values"), + tooltip: t("environments.contacts.edit_attributes"), onClick: () => { setIsEditAttributesModalOpen(true); }, @@ -115,7 +121,7 @@ export const ContactControlBar = ({ setOpen={setIsEditAttributesModalOpen} contactId={contactId} currentAttributes={currentAttributes} - attributeKeys={attributeKeys} + attributeKeys={allAttributeKeys} /> ); diff --git a/apps/web/modules/ee/contacts/[contactId]/page.tsx b/apps/web/modules/ee/contacts/[contactId]/page.tsx index 280e41fa8e..652f142db5 100644 --- a/apps/web/modules/ee/contacts/[contactId]/page.tsx +++ b/apps/web/modules/ee/contacts/[contactId]/page.tsx @@ -3,10 +3,9 @@ import { getTranslate } from "@/lingodotdev/server"; import { AttributesSection } from "@/modules/ee/contacts/[contactId]/components/attributes-section"; import { ContactControlBar } from "@/modules/ee/contacts/[contactId]/components/contact-control-bar"; import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys"; -import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes"; +import { getContactAttributesWithKeyInfo } from "@/modules/ee/contacts/lib/contact-attributes"; import { getContact } from "@/modules/ee/contacts/lib/contacts"; import { getPublishedLinkSurveys } from "@/modules/ee/contacts/lib/surveys"; -import { getContactIdentifier } from "@/modules/ee/contacts/lib/utils"; import { getIsQuotasEnabled } from "@/modules/ee/license-check/lib/utils"; import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { GoBackButton } from "@/modules/ui/components/go-back-button"; @@ -22,12 +21,12 @@ export const SingleContactPage = async (props: { const { environment, isReadOnly, organization } = await getEnvironmentAuth(params.environmentId); - const [environmentTags, contact, contactAttributes, publishedLinkSurveys, contactAttributeKeys] = + const [environmentTags, contact, publishedLinkSurveys, attributesWithKeyInfo, allAttributeKeys] = await Promise.all([ getTagsByEnvironmentId(params.environmentId), getContact(params.contactId), - getContactAttributes(params.contactId), getPublishedLinkSurveys(params.environmentId), + getContactAttributesWithKeyInfo(params.contactId), getContactAttributeKeys(params.environmentId), ]); @@ -37,6 +36,13 @@ export const SingleContactPage = async (props: { const isQuotasAllowed = await getIsQuotasEnabled(organization.billing.plan); + // Derive contact identifier from metadata array + const getAttributeValue = (key: string): string | undefined => { + return attributesWithKeyInfo.find((attr) => attr.key === key)?.value; + }; + + const contactIdentifier = getAttributeValue("email") || getAttributeValue("userId") || ""; + const getContactControlBar = () => { return ( ); }; @@ -54,8 +60,8 @@ export const SingleContactPage = async (props: { return ( - -
    + +
    ; diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.test.ts deleted file mode 100644 index 8db61e016f..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { getContactByUserIdWithAttributes } from "./contact"; - -vi.mock("@formbricks/database", () => ({ - prisma: { - contact: { - findFirst: vi.fn(), - }, - }, -})); - -const mockEnvironmentId = "testEnvironmentId"; -const mockUserId = "testUserId"; -const mockContactId = "testContactId"; - -describe("getContactByUserIdWithAttributes", () => { - test("should return contact with filtered attributes when found", async () => { - const mockUpdatedAttributes = { email: "new@example.com", plan: "premium" }; - const mockDbContact = { - id: mockContactId, - attributes: [ - { attributeKey: { key: "email" }, value: "new@example.com" }, - { attributeKey: { key: "plan" }, value: "premium" }, - ], - }; - vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockDbContact as any); - - const result = await getContactByUserIdWithAttributes( - mockEnvironmentId, - mockUserId, - mockUpdatedAttributes - ); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - environmentId: mockEnvironmentId, - attributes: { - some: { attributeKey: { key: "userId", environmentId: mockEnvironmentId }, value: mockUserId }, - }, - }, - select: { - id: true, - attributes: { - where: { - attributeKey: { - key: { - in: Object.keys(mockUpdatedAttributes), - }, - }, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }, - }, - }); - expect(result).toEqual(mockDbContact); - }); - - test("should return null if contact not found", async () => { - const mockUpdatedAttributes = { email: "new@example.com" }; - vi.mocked(prisma.contact.findFirst).mockResolvedValue(null); - - const result = await getContactByUserIdWithAttributes( - mockEnvironmentId, - mockUserId, - mockUpdatedAttributes - ); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - environmentId: mockEnvironmentId, - attributes: { - some: { attributeKey: { key: "userId", environmentId: mockEnvironmentId }, value: mockUserId }, - }, - }, - select: { - id: true, - attributes: { - where: { - attributeKey: { - key: { - in: Object.keys(mockUpdatedAttributes), - }, - }, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }, - }, - }); - expect(result).toBeNull(); - }); - - test("should handle empty updatedAttributes", async () => { - const mockUpdatedAttributes = {}; - const mockDbContact = { - id: mockContactId, - attributes: [], // No attributes should be fetched if updatedAttributes is empty - }; - vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockDbContact as any); - - const result = await getContactByUserIdWithAttributes( - mockEnvironmentId, - mockUserId, - mockUpdatedAttributes - ); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - environmentId: mockEnvironmentId, - attributes: { - some: { attributeKey: { key: "userId", environmentId: mockEnvironmentId }, value: mockUserId }, - }, - }, - select: { - id: true, - attributes: { - where: { - attributeKey: { - key: { - in: [], // Object.keys({}) results in an empty array - }, - }, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }, - }, - }); - expect(result).toEqual(mockDbContact); - }); - - test("should return contact with only requested attributes even if DB stores more", async () => { - const mockUpdatedAttributes = { email: "new@example.com" }; // only request email - // The prisma call will filter attributes based on `Object.keys(mockUpdatedAttributes)` - const mockPrismaResponse = { - id: mockContactId, - attributes: [{ attributeKey: { key: "email" }, value: "new@example.com" }], - }; - vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockPrismaResponse as any); - - const result = await getContactByUserIdWithAttributes( - mockEnvironmentId, - mockUserId, - mockUpdatedAttributes - ); - - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - environmentId: mockEnvironmentId, - attributes: { - some: { attributeKey: { key: "userId", environmentId: mockEnvironmentId }, value: mockUserId }, - }, - }, - select: { - id: true, - attributes: { - where: { - attributeKey: { - key: { - in: ["email"], - }, - }, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }, - }, - }); - expect(result).toEqual(mockPrismaResponse); - }); -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.ts deleted file mode 100644 index 315da5a39e..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/lib/contact.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { cache as reactCache } from "react"; -import { prisma } from "@formbricks/database"; - -export const getContactByUserIdWithAttributes = reactCache( - async (environmentId: string, userId: string, updatedAttributes: Record) => { - const contact = await prisma.contact.findFirst({ - where: { - environmentId, - attributes: { some: { attributeKey: { key: "userId", environmentId }, value: userId } }, - }, - select: { - id: true, - attributes: { - where: { - attributeKey: { - key: { - in: Object.keys(updatedAttributes), - }, - }, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }, - }, - }); - - if (!contact) { - return null; - } - - return contact; - } -); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts deleted file mode 100644 index 1a143c8e60..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/contacts/[userId]/attributes/route.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { NextRequest } from "next/server"; -import { logger } from "@formbricks/logger"; -import { ResourceNotFoundError } from "@formbricks/types/errors"; -import { ZJsContactsUpdateAttributeInput } from "@formbricks/types/js"; -import { responses } from "@/app/lib/api/response"; -import { transformErrorToDetails } from "@/app/lib/api/validator"; -import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; -import { updateAttributes } from "@/modules/ee/contacts/lib/attributes"; -import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils"; -import { getContactByUserIdWithAttributes } from "./lib/contact"; - -const validateParams = ( - environmentId: string, - userId: string -): { isValid: true } | { isValid: false; error: Response } => { - if (!environmentId) { - return { - isValid: false, - error: responses.badRequestResponse("environmentId is required", { environmentId }, true), - }; - } - if (!userId) { - return { isValid: false, error: responses.badRequestResponse("userId is required", { userId }, true) }; - } - return { isValid: true }; -}; - -const checkIfAttributesNeedUpdate = (contact: any, updatedAttributes: Record) => { - const oldAttributes = new Map(contact.attributes.map((attr: any) => [attr.attributeKey.key, attr.value])); - - for (const [key, value] of Object.entries(updatedAttributes)) { - if (value !== oldAttributes.get(key)) { - return false; // needs update - } - } - return true; // up to date -}; - -export const OPTIONS = async () => { - // cors headers - return responses.successResponse({}, true); -}; - -export const PUT = withV1ApiWrapper({ - handler: async ({ - req, - props, - }: { - req: NextRequest; - props: { params: Promise<{ environmentId: string; userId: string }> }; - }) => { - try { - const params = await props.params; - const { environmentId, userId } = params; - - // Validate required parameters - const paramValidation = validateParams(environmentId, userId); - if (!paramValidation.isValid) { - return { response: paramValidation.error }; - } - - // Parse and validate input - const jsonInput = await req.json(); - const parsedInput = ZJsContactsUpdateAttributeInput.safeParse(jsonInput); - if (!parsedInput.success) { - return { - response: responses.badRequestResponse( - "Fields are missing or incorrectly formatted", - transformErrorToDetails(parsedInput.error), - true - ), - }; - } - - // Check enterprise license - const isContactsEnabled = await getIsContactsEnabled(); - if (!isContactsEnabled) { - return { - response: responses.forbiddenResponse( - "User identification is only available for enterprise users.", - true - ), - }; - } - - // Process attributes (ignore userId and id) - const { userId: userIdAttr, id: idAttr, ...updatedAttributes } = parsedInput.data.attributes; - - const contact = await getContactByUserIdWithAttributes(environmentId, userId, updatedAttributes); - if (!contact) { - return { response: responses.notFoundResponse("contact", userId, true) }; - } - - // Check if update is needed - const isUpToDate = checkIfAttributesNeedUpdate(contact, updatedAttributes); - if (isUpToDate) { - return { - response: responses.successResponse( - { changed: false, message: "No updates were necessary; the person is already up to date." }, - true - ), - }; - } - - // Perform update - const { messages } = await updateAttributes(contact.id, userId, environmentId, updatedAttributes); - - return { - response: responses.successResponse( - { - changed: true, - message: "The person was successfully updated.", - ...(messages && messages.length > 0 ? { messages } : {}), - }, - true - ), - }; - } catch (err) { - logger.error({ err, url: req.url }, "Error updating attributes"); - if (err.statusCode === 403) { - return { - response: responses.forbiddenResponse(err.message || "Forbidden", true, { ignore: true }), - }; - } - - if (err instanceof ResourceNotFoundError) { - return { - response: responses.notFoundResponse(err.resourceType, err.resourceId, true), - }; - } - - return { - response: responses.internalServerErrorResponse("Something went wrong", true), - }; - } - }, -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.test.ts deleted file mode 100644 index a1949194e6..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { ValidationError } from "@formbricks/types/errors"; -import { getContactAttributes } from "./attributes"; - -vi.mock("@formbricks/database", () => ({ - prisma: { - contactAttribute: { - findMany: vi.fn(), - }, - }, -})); - -const mockContactId = "xn8b8ol97q2pcp8dnlpsfs1m"; - -describe("getContactAttributes", () => { - test("should return transformed attributes when found", async () => { - const mockContactAttributes = [ - { attributeKey: { key: "email" }, value: "test@example.com" }, - { attributeKey: { key: "name" }, value: "Test User" }, - ]; - const expectedTransformedAttributes = { - email: "test@example.com", - name: "Test User", - }; - - vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue(mockContactAttributes); - - const result = await getContactAttributes(mockContactId); - - expect(result).toEqual(expectedTransformedAttributes); - expect(prisma.contactAttribute.findMany).toHaveBeenCalledWith({ - where: { - contactId: mockContactId, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }); - }); - - test("should return an empty object when no attributes are found", async () => { - vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([]); - - const result = await getContactAttributes(mockContactId); - - expect(result).toEqual({}); - expect(prisma.contactAttribute.findMany).toHaveBeenCalledWith({ - where: { - contactId: mockContactId, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }); - }); - - test("should throw a ValidationError when contactId is invalid", async () => { - const invalidContactId = "hello-world"; - - await expect(getContactAttributes(invalidContactId)).rejects.toThrowError(ValidationError); - }); -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.ts deleted file mode 100644 index bba573651d..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { cache as reactCache } from "react"; -import { prisma } from "@formbricks/database"; -import { ZId } from "@formbricks/types/common"; -import { validateInputs } from "@/lib/utils/validate"; - -export const getContactAttributes = reactCache(async (contactId: string): Promise> => { - validateInputs([contactId, ZId]); - - const contactAttributes = await prisma.contactAttribute.findMany({ - where: { - contactId, - }, - select: { attributeKey: { select: { key: true } }, value: true }, - }); - - const transformedContactAttributes: Record = contactAttributes.reduce((acc, attr) => { - acc[attr.attributeKey.key] = attr.value; - - return acc; - }, {}); - - return transformedContactAttributes; -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.test.ts deleted file mode 100644 index 4bb85223b1..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { getContactByUserId } from "./contact"; - -// Mock prisma -vi.mock("@formbricks/database", () => ({ - prisma: { - contact: { - findFirst: vi.fn(), - }, - }, -})); - -const mockEnvironmentId = "clxmg5n79000008l9df7b8nh8"; -const mockUserId = "dpqs2axc6v3b5cjcgtnqhwov"; -const mockContactId = "clxmg5n79000108l9df7b8xyz"; - -const mockReturnedContact = { - id: mockContactId, - environmentId: mockEnvironmentId, - createdAt: new Date("2024-01-01T10:00:00.000Z"), - updatedAt: new Date("2024-01-01T11:00:00.000Z"), -}; - -describe("getContactByUserId", () => { - beforeEach(() => { - vi.resetAllMocks(); - }); - - test("should return contact if found", async () => { - vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockReturnedContact as any); - - const result = await getContactByUserId(mockEnvironmentId, mockUserId); - - expect(result).toEqual(mockReturnedContact); - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId: mockEnvironmentId, - }, - value: mockUserId, - }, - }, - }, - }); - }); - - test("should return null if contact not found", async () => { - vi.mocked(prisma.contact.findFirst).mockResolvedValue(null); - - const result = await getContactByUserId(mockEnvironmentId, mockUserId); - - expect(result).toBeNull(); - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId: mockEnvironmentId, - }, - value: mockUserId, - }, - }, - }, - }); - }); - - test("should call prisma.contact.findFirst with correct parameters", async () => { - vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockReturnedContact as any); - await getContactByUserId(mockEnvironmentId, mockUserId); - - expect(prisma.contact.findFirst).toHaveBeenCalledTimes(1); - expect(prisma.contact.findFirst).toHaveBeenCalledWith({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId: mockEnvironmentId, - }, - value: mockUserId, - }, - }, - }, - }); - }); -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.ts deleted file mode 100644 index 48eccda307..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { cache as reactCache } from "react"; -import { prisma } from "@formbricks/database"; - -export const getContactByUserId = reactCache(async (environmentId: string, userId: string) => { - const contact = await prisma.contact.findFirst({ - where: { - attributes: { - some: { - attributeKey: { - key: "userId", - environmentId, - }, - value: userId, - }, - }, - }, - }); - - if (!contact) { - return null; - } - - return contact; -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.test.ts deleted file mode 100644 index b73d1463bd..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { TEnvironment } from "@formbricks/types/environment"; -import { ResourceNotFoundError } from "@formbricks/types/errors"; -import { TOrganization } from "@formbricks/types/organizations"; -import { getEnvironment } from "@/lib/environment/service"; -import { getOrganizationByEnvironmentId } from "@/lib/organization/service"; -import { getPersonSegmentIds } from "@/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments"; -import { getContactByUserId } from "./contact"; -import { getPersonState } from "./person-state"; - -vi.mock("@/lib/environment/service", () => ({ - getEnvironment: vi.fn(), -})); - -vi.mock("@/lib/organization/service", () => ({ - getOrganizationByEnvironmentId: vi.fn(), -})); - -vi.mock("@/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact", () => ({ - getContactByUserId: vi.fn(), -})); - -vi.mock("@formbricks/database", () => ({ - prisma: { - contact: { - create: vi.fn(), - }, - response: { - findMany: vi.fn(), - }, - display: { - findMany: vi.fn(), - }, - }, -})); - -vi.mock( - "@/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes", - () => ({ - getContactAttributes: vi.fn(), - }) -); - -vi.mock("@/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments", () => ({ - getPersonSegmentIds: vi.fn(), -})); - -const mockEnvironmentId = "jubz514cwdmjvnbadsfd7ez3"; -const mockUserId = "huli1kfpw1r6vn00vjxetdob"; -const mockContactId = "e71zwzi6zgrdzutbb0q8spui"; -const mockProjectId = "d6o07l7ieizdioafgelrioao"; -const mockOrganizationId = "xa4oltlfkmqq3r4e3m3ocss1"; -const mockDevice = "desktop"; - -const mockEnvironment: TEnvironment = { - id: mockEnvironmentId, - createdAt: new Date(), - updatedAt: new Date(), - type: "development", - projectId: mockProjectId, - appSetupCompleted: false, -}; - -const mockOrganization: TOrganization = { - id: mockOrganizationId, - createdAt: new Date(), - updatedAt: new Date(), - name: "Test Organization", - billing: { - stripeCustomerId: null, - plan: "free", - period: "monthly", - limits: { projects: 1, monthly: { responses: 100, miu: 100 } }, - periodStart: new Date(), - }, - isAIEnabled: false, -}; - -const mockResolvedContactFromGetContactByUserId = { - id: mockContactId, - createdAt: new Date(), - updatedAt: new Date(), - environmentId: mockEnvironmentId, - userId: mockUserId, -}; - -const mockResolvedContactFromPrismaCreate = { - id: mockContactId, - createdAt: new Date(), - updatedAt: new Date(), - environmentId: mockEnvironmentId, - userId: mockUserId, -}; - -describe("getPersonState", () => { - beforeEach(() => { - vi.resetAllMocks(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - test("should throw ResourceNotFoundError if environment is not found", async () => { - vi.mocked(getEnvironment).mockResolvedValue(null); - await expect( - getPersonState({ environmentId: mockEnvironmentId, userId: mockUserId, device: mockDevice }) - ).rejects.toThrow(new ResourceNotFoundError("environment", mockEnvironmentId)); - }); - - test("should throw ResourceNotFoundError if organization is not found", async () => { - vi.mocked(getEnvironment).mockResolvedValue(mockEnvironment as TEnvironment); - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(null); - await expect( - getPersonState({ environmentId: mockEnvironmentId, userId: mockUserId, device: mockDevice }) - ).rejects.toThrow(new ResourceNotFoundError("organization", mockEnvironmentId)); - }); - - test("should return person state if contact exists", async () => { - vi.mocked(getEnvironment).mockResolvedValue(mockEnvironment as TEnvironment); - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(mockOrganization as TOrganization); - vi.mocked(getContactByUserId).mockResolvedValue(mockResolvedContactFromGetContactByUserId); - vi.mocked(prisma.response.findMany).mockResolvedValue([]); - vi.mocked(prisma.display.findMany).mockResolvedValue([]); - vi.mocked(getPersonSegmentIds).mockResolvedValue([]); - - const result = await getPersonState({ - environmentId: mockEnvironmentId, - userId: mockUserId, - device: mockDevice, - }); - - expect(result.state.contactId).toBe(mockContactId); - expect(result.state.userId).toBe(mockUserId); - expect(result.state.segments).toEqual([]); - expect(result.state.displays).toEqual([]); - expect(result.state.responses).toEqual([]); - expect(result.state.lastDisplayAt).toBeNull(); - expect(result.revalidateProps).toBeUndefined(); - expect(prisma.contact.create).not.toHaveBeenCalled(); - }); - - test("should create contact and return person state if contact does not exist", async () => { - vi.mocked(getEnvironment).mockResolvedValue(mockEnvironment as TEnvironment); - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(mockOrganization as TOrganization); - vi.mocked(getContactByUserId).mockResolvedValue(null); - vi.mocked(prisma.contact.create).mockResolvedValue(mockResolvedContactFromPrismaCreate as any); - vi.mocked(prisma.response.findMany).mockResolvedValue([]); - vi.mocked(prisma.display.findMany).mockResolvedValue([]); - vi.mocked(getPersonSegmentIds).mockResolvedValue(["segment1"]); - - const result = await getPersonState({ - environmentId: mockEnvironmentId, - userId: mockUserId, - device: mockDevice, - }); - - expect(prisma.contact.create).toHaveBeenCalledWith({ - data: { - environment: { connect: { id: mockEnvironmentId } }, - attributes: { - create: [ - { - attributeKey: { - connect: { key_environmentId: { key: "userId", environmentId: mockEnvironmentId } }, - }, - value: mockUserId, - }, - ], - }, - }, - }); - expect(result.state.contactId).toBe(mockContactId); - expect(result.state.userId).toBe(mockUserId); - expect(result.state.segments).toEqual(["segment1"]); - expect(result.revalidateProps).toEqual({ contactId: mockContactId, revalidate: true }); - }); - - test("should correctly map displays and responses", async () => { - const displayDate = new Date(); - const mockDisplays = [ - { surveyId: "survey1", createdAt: displayDate }, - { surveyId: "survey2", createdAt: new Date(displayDate.getTime() - 1000) }, - ]; - const mockResponses = [{ surveyId: "survey1" }, { surveyId: "survey3" }]; - - vi.mocked(getEnvironment).mockResolvedValue(mockEnvironment as TEnvironment); - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(mockOrganization as TOrganization); - vi.mocked(getContactByUserId).mockResolvedValue(mockResolvedContactFromGetContactByUserId); - vi.mocked(prisma.response.findMany).mockResolvedValue(mockResponses as any); - vi.mocked(prisma.display.findMany).mockResolvedValue(mockDisplays as any); - vi.mocked(getPersonSegmentIds).mockResolvedValue([]); - - const result = await getPersonState({ - environmentId: mockEnvironmentId, - userId: mockUserId, - device: mockDevice, - }); - - expect(result.state.displays).toEqual( - mockDisplays.map((d) => ({ surveyId: d.surveyId, createdAt: d.createdAt })) - ); - expect(result.state.responses).toEqual(mockResponses.map((r) => r.surveyId)); - expect(result.state.lastDisplayAt).toEqual(displayDate); - }); -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.ts deleted file mode 100644 index 0cfe5b62cc..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/person-state.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { prisma } from "@formbricks/database"; -import { ResourceNotFoundError } from "@formbricks/types/errors"; -import { TJsPersonState } from "@formbricks/types/js"; -import { getEnvironment } from "@/lib/environment/service"; -import { getOrganizationByEnvironmentId } from "@/lib/organization/service"; -import { getContactAttributes } from "@/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/attributes"; -import { getContactByUserId } from "@/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/contact"; -import { getPersonSegmentIds } from "@/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments"; - -/** - * - * @param environmentId - The environment id - * @param userId - The user id - * @param device - The device type - * @returns The person state - * @throws {ValidationError} - If the input is invalid - * @throws {ResourceNotFoundError} - If the environment or organization is not found - */ -export const getPersonState = async ({ - environmentId, - userId, - device, -}: { - environmentId: string; - userId: string; - device: "phone" | "desktop"; -}): Promise<{ - state: TJsPersonState["data"]; - revalidateProps?: { contactId: string; revalidate: boolean }; -}> => { - let revalidatePerson = false; - const environment = await getEnvironment(environmentId); - - if (!environment) { - throw new ResourceNotFoundError(`environment`, environmentId); - } - - const organization = await getOrganizationByEnvironmentId(environmentId); - - if (!organization) { - throw new ResourceNotFoundError(`organization`, environmentId); - } - - let contact = await getContactByUserId(environmentId, userId); - - if (!contact) { - contact = await prisma.contact.create({ - data: { - environment: { - connect: { - id: environmentId, - }, - }, - attributes: { - create: [ - { - attributeKey: { - connect: { key_environmentId: { key: "userId", environmentId } }, - }, - value: userId, - }, - ], - }, - }, - }); - - revalidatePerson = true; - } - - const contactResponses = await prisma.response.findMany({ - where: { - contactId: contact.id, - }, - select: { - surveyId: true, - }, - }); - - const contactDisplays = await prisma.display.findMany({ - where: { - contactId: contact.id, - }, - select: { - surveyId: true, - createdAt: true, - }, - }); - - // Get contact attributes for optimized segment evaluation - const contactAttributes = await getContactAttributes(contact.id); - - const segments = await getPersonSegmentIds(environmentId, contact.id, userId, contactAttributes, device); - - const sortedContactDisplaysDate = contactDisplays?.toSorted( - (a, b) => b.createdAt.getTime() - a.createdAt.getTime() - )[0]?.createdAt; - - // If the person exists, return the persons's state - const userState: TJsPersonState["data"] = { - contactId: contact.id, - userId, - segments, - displays: - contactDisplays?.map((display) => ({ - surveyId: display.surveyId, - createdAt: display.createdAt, - })) ?? [], - responses: contactResponses?.map((response) => response.surveyId) ?? [], - lastDisplayAt: contactDisplays?.length > 0 ? sortedContactDisplaysDate : null, - }; - - return { - state: userState, - revalidateProps: revalidatePerson ? { contactId: contact.id, revalidate: true } : undefined, - }; -}; diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/segments.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/segments.test.ts deleted file mode 100644 index bb90e8b6b4..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/lib/segments.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { Prisma } from "@prisma/client"; -import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { prisma } from "@formbricks/database"; -import { PrismaErrorType } from "@formbricks/database/types/error"; -import { DatabaseError } from "@formbricks/types/errors"; -import { TBaseFilter } from "@formbricks/types/segment"; -import { - getPersonSegmentIds, - getSegments, -} from "@/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments"; -import { evaluateSegment } from "@/modules/ee/contacts/segments/lib/segments"; - -// Mock the cache functions -vi.mock("@/lib/cache", () => ({ - cache: { - withCache: vi.fn(async (fn) => await fn()), // Just execute the function without caching for tests - }, -})); - -vi.mock("@formbricks/cache", () => ({ - createCacheKey: { - environment: { - segments: vi.fn((environmentId) => `segments-${environmentId}`), - }, - }, -})); - -// Mock React cache -vi.mock("react", async () => { - const actual = await vi.importActual("react"); - return { - ...actual, - cache: any>(fn: T): T => fn, // Return the function with the same type signature - }; -}); - -vi.mock("@/modules/ee/contacts/segments/lib/segments", () => ({ - evaluateSegment: vi.fn(), -})); - -vi.mock("@formbricks/database", () => ({ - prisma: { - segment: { - findMany: vi.fn(), - }, - }, -})); - -const mockEnvironmentId = "bbn7e47f6etoai6usxezxd4a"; -const mockContactId = "cworhmq5yqvnb0tsfw9yka4b"; -const mockContactUserId = "xrgbcxn5y9so92igacthutfw"; -const mockDeviceType = "desktop"; - -const mockSegmentsData = [ - { - id: "segment1", - filters: [{}] as TBaseFilter[], - createdAt: new Date(), - updatedAt: new Date(), - environmentId: mockEnvironmentId, - description: null, - title: "Segment 1", - isPrivate: false, - }, - { - id: "segment2", - filters: [{}] as TBaseFilter[], - createdAt: new Date(), - updatedAt: new Date(), - environmentId: mockEnvironmentId, - description: null, - title: "Segment 2", - isPrivate: false, - }, -]; - -const mockContactAttributesData = { - attribute1: "value1", - attribute2: "value2", -}; - -describe("segments lib", () => { - beforeEach(() => { - vi.resetAllMocks(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe("getSegments", () => { - test("should return segments successfully", async () => { - vi.mocked(prisma.segment.findMany).mockResolvedValue(mockSegmentsData); - - const result = await getSegments(mockEnvironmentId); - - expect(prisma.segment.findMany).toHaveBeenCalledWith({ - where: { environmentId: mockEnvironmentId }, - select: { id: true, filters: true }, - }); - - expect(result).toEqual(mockSegmentsData); - }); - - test("should throw DatabaseError on Prisma known request error", async () => { - const mockErrorMessage = "Prisma error"; - const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, { - code: PrismaErrorType.UniqueConstraintViolation, - clientVersion: "0.0.1", - }); - - vi.mocked(prisma.segment.findMany).mockRejectedValueOnce(errToThrow); - await expect(getSegments(mockEnvironmentId)).rejects.toThrow(DatabaseError); - }); - - test("should throw original error on other errors", async () => { - const genericError = new Error("Test Generic Error"); - - vi.mocked(prisma.segment.findMany).mockRejectedValueOnce(genericError); - await expect(getSegments(mockEnvironmentId)).rejects.toThrow("Test Generic Error"); - }); - }); - - describe("getPersonSegmentIds", () => { - beforeEach(() => { - vi.mocked(prisma.segment.findMany).mockResolvedValue(mockSegmentsData); // Mock for getSegments call - }); - - test("should return person segment IDs successfully", async () => { - vi.mocked(evaluateSegment).mockResolvedValue(true); // All segments evaluate to true - - const result = await getPersonSegmentIds( - mockEnvironmentId, - mockContactId, - mockContactUserId, - mockContactAttributesData, - mockDeviceType - ); - - expect(evaluateSegment).toHaveBeenCalledTimes(mockSegmentsData.length); - - mockSegmentsData.forEach((segment) => { - expect(evaluateSegment).toHaveBeenCalledWith( - { - attributes: mockContactAttributesData, - deviceType: mockDeviceType, - environmentId: mockEnvironmentId, - contactId: mockContactId, - userId: mockContactUserId, - }, - segment.filters - ); - }); - - expect(result).toEqual(mockSegmentsData.map((s) => s.id)); - }); - - test("should return empty array if no segments exist", async () => { - // @ts-expect-error -- this is a valid test case to check for null - vi.mocked(prisma.segment.findMany).mockResolvedValue(null); // No segments - - const result = await getPersonSegmentIds( - mockEnvironmentId, - mockContactId, - mockContactUserId, - mockContactAttributesData, - mockDeviceType - ); - - expect(result).toEqual([]); - expect(evaluateSegment).not.toHaveBeenCalled(); - }); - - test("should return empty array if segments is null", async () => { - vi.mocked(prisma.segment.findMany).mockResolvedValue(null as any); // segments is null - - const result = await getPersonSegmentIds( - mockEnvironmentId, - mockContactId, - mockContactUserId, - mockContactAttributesData, - mockDeviceType - ); - - expect(result).toEqual([]); - expect(evaluateSegment).not.toHaveBeenCalled(); - }); - - test("should return only matching segment IDs", async () => { - vi.mocked(evaluateSegment) - .mockResolvedValueOnce(true) // First segment matches - .mockResolvedValueOnce(false); // Second segment does not match - - const result = await getPersonSegmentIds( - mockEnvironmentId, - mockContactId, - mockContactUserId, - mockContactAttributesData, - mockDeviceType - ); - - expect(result).toEqual([mockSegmentsData[0].id]); - expect(evaluateSegment).toHaveBeenCalledTimes(mockSegmentsData.length); - }); - }); -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/route.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/route.ts deleted file mode 100644 index 95aa04b600..0000000000 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/identify/contacts/[userId]/route.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { NextRequest, userAgent } from "next/server"; -import { logger } from "@formbricks/logger"; -import { ResourceNotFoundError } from "@formbricks/types/errors"; -import { ZJsUserIdentifyInput } from "@formbricks/types/js"; -import { responses } from "@/app/lib/api/response"; -import { transformErrorToDetails } from "@/app/lib/api/validator"; -import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; -import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils"; -import { getPersonState } from "./lib/person-state"; - -export const OPTIONS = async (): Promise => { - return responses.successResponse({}, true); -}; - -export const GET = withV1ApiWrapper({ - handler: async ({ - req, - props, - }: { - req: NextRequest; - props: { params: Promise<{ environmentId: string; userId: string }> }; - }) => { - const params = await props.params; - - try { - const { environmentId, userId } = params; - - // Validate input - const syncInputValidation = ZJsUserIdentifyInput.safeParse({ - environmentId, - userId, - }); - if (!syncInputValidation.success) { - return { - response: responses.badRequestResponse( - "Fields are missing or incorrectly formatted", - transformErrorToDetails(syncInputValidation.error), - true - ), - }; - } - - const isContactsEnabled = await getIsContactsEnabled(); - if (!isContactsEnabled) { - return { - response: responses.forbiddenResponse( - "User identification is only available for enterprise users.", - true - ), - }; - } - - const { device } = userAgent(req); - const deviceType = device ? "phone" : "desktop"; - - try { - const personState = await getPersonState({ - environmentId, - userId, - device: deviceType, - }); - - return { - response: responses.successResponse(personState.state, true), - }; - } catch (err) { - if (err instanceof ResourceNotFoundError) { - return { - response: responses.notFoundResponse(err.resourceType, err.resourceId), - }; - } - - logger.error({ err, url: req.url }, "Error fetching person state"); - return { - response: responses.internalServerErrorResponse( - err.message ?? "Unable to fetch person state", - true - ), - }; - } - } catch (error) { - logger.error({ error, url: req.url }, "Error fetching person state"); - return { - response: responses.internalServerErrorResponse( - `Unable to complete response: ${error.message}`, - true - ), - }; - } - }, -}); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.test.ts index 593c3d0781..9f5d4f7fb0 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.test.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.test.ts @@ -4,7 +4,7 @@ import { prisma } from "@formbricks/database"; import { DatabaseError } from "@formbricks/types/errors"; import { TBaseFilter } from "@formbricks/types/segment"; import { validateInputs } from "@/lib/utils/validate"; -import { evaluateSegment } from "@/modules/ee/contacts/segments/lib/segments"; +import { segmentFilterToPrismaQuery } from "@/modules/ee/contacts/segments/lib/filter/prisma-query"; import { getPersonSegmentIds, getSegments } from "./segments"; // Mock the cache functions @@ -18,8 +18,8 @@ vi.mock("@/lib/utils/validate", () => ({ validateInputs: vi.fn(), })); -vi.mock("@/modules/ee/contacts/segments/lib/segments", () => ({ - evaluateSegment: vi.fn(), +vi.mock("@/modules/ee/contacts/segments/lib/filter/prisma-query", () => ({ + segmentFilterToPrismaQuery: vi.fn(), })); vi.mock("@formbricks/database", () => ({ @@ -27,6 +27,9 @@ vi.mock("@formbricks/database", () => ({ segment: { findMany: vi.fn(), }, + contact: { + findFirst: vi.fn(), + }, }, })); @@ -42,8 +45,7 @@ vi.mock("react", async () => { const mockEnvironmentId = "test-environment-id"; const mockContactId = "test-contact-id"; const mockContactUserId = "test-contact-user-id"; -const mockAttributes = { email: "test@example.com" }; -const mockDeviceType = "desktop"; +const mockDeviceType = "desktop" as const; const mockSegmentsData = [ { id: "segment1", filters: [{}] as TBaseFilter[] }, @@ -61,7 +63,9 @@ describe("segments lib", () => { describe("getSegments", () => { test("should return segments successfully", async () => { - vi.mocked(prisma.segment.findMany).mockResolvedValue(mockSegmentsData as any); + vi.mocked(prisma.segment.findMany).mockResolvedValue( + mockSegmentsData as Prisma.Result + ); const result = await getSegments(mockEnvironmentId); @@ -94,17 +98,26 @@ describe("segments lib", () => { describe("getPersonSegmentIds", () => { beforeEach(() => { - vi.mocked(prisma.segment.findMany).mockResolvedValue(mockSegmentsData as any); // Mock for getSegments call + vi.mocked(prisma.segment.findMany).mockResolvedValue( + mockSegmentsData as Prisma.Result + ); + vi.mocked(segmentFilterToPrismaQuery).mockResolvedValue({ + ok: true, + data: { whereClause: { AND: [{ environmentId: mockEnvironmentId }, {}] } }, + }); }); test("should return person segment IDs successfully", async () => { - vi.mocked(evaluateSegment).mockResolvedValue(true); // All segments evaluate to true + vi.mocked(prisma.contact.findFirst).mockResolvedValue({ id: mockContactId } as Prisma.Result< + typeof prisma.contact, + unknown, + "findFirst" + >); const result = await getPersonSegmentIds( mockEnvironmentId, mockContactId, mockContactUserId, - mockAttributes, mockDeviceType ); @@ -114,19 +127,8 @@ describe("segments lib", () => { select: { id: true, filters: true }, }); - expect(evaluateSegment).toHaveBeenCalledTimes(mockSegmentsData.length); - mockSegmentsData.forEach((segment) => { - expect(evaluateSegment).toHaveBeenCalledWith( - { - attributes: mockAttributes, - deviceType: mockDeviceType, - environmentId: mockEnvironmentId, - contactId: mockContactId, - userId: mockContactUserId, - }, - segment.filters - ); - }); + expect(segmentFilterToPrismaQuery).toHaveBeenCalledTimes(mockSegmentsData.length); + expect(prisma.contact.findFirst).toHaveBeenCalledTimes(mockSegmentsData.length); expect(result).toEqual(mockSegmentsData.map((s) => s.id)); }); @@ -137,36 +139,34 @@ describe("segments lib", () => { mockEnvironmentId, mockContactId, mockContactUserId, - mockAttributes, mockDeviceType ); expect(result).toEqual([]); - expect(evaluateSegment).not.toHaveBeenCalled(); + expect(segmentFilterToPrismaQuery).not.toHaveBeenCalled(); }); test("should return empty array if segments exist but none match", async () => { - vi.mocked(evaluateSegment).mockResolvedValue(false); // All segments evaluate to false + vi.mocked(prisma.contact.findFirst).mockResolvedValue(null); const result = await getPersonSegmentIds( mockEnvironmentId, mockContactId, mockContactUserId, - mockAttributes, mockDeviceType ); expect(result).toEqual([]); - expect(evaluateSegment).toHaveBeenCalledTimes(mockSegmentsData.length); + expect(segmentFilterToPrismaQuery).toHaveBeenCalledTimes(mockSegmentsData.length); }); test("should call validateInputs with correct parameters", async () => { - await getPersonSegmentIds( - mockEnvironmentId, - mockContactId, - mockContactUserId, - mockAttributes, - mockDeviceType - ); + vi.mocked(prisma.contact.findFirst).mockResolvedValue({ id: mockContactId } as Prisma.Result< + typeof prisma.contact, + unknown, + "findFirst" + >); + + await getPersonSegmentIds(mockEnvironmentId, mockContactId, mockContactUserId, mockDeviceType); expect(validateInputs).toHaveBeenCalledWith( [mockEnvironmentId, expect.anything()], [mockContactId, expect.anything()], @@ -175,20 +175,24 @@ describe("segments lib", () => { }); test("should return only matching segment IDs", async () => { - vi.mocked(evaluateSegment) - .mockResolvedValueOnce(true) // First segment matches - .mockResolvedValueOnce(false); // Second segment does not match + // First segment matches, second doesn't + vi.mocked(prisma.contact.findFirst) + .mockResolvedValueOnce({ id: mockContactId } as Prisma.Result< + typeof prisma.contact, + unknown, + "findFirst" + >) // First segment matches + .mockResolvedValueOnce(null); // Second segment does not match const result = await getPersonSegmentIds( mockEnvironmentId, mockContactId, mockContactUserId, - mockAttributes, mockDeviceType ); expect(result).toEqual([mockSegmentsData[0].id]); - expect(evaluateSegment).toHaveBeenCalledTimes(mockSegmentsData.length); + expect(segmentFilterToPrismaQuery).toHaveBeenCalledTimes(mockSegmentsData.length); }); }); }); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.ts index cafecedb7a..b01e837402 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/segments.ts @@ -5,10 +5,10 @@ import { prisma } from "@formbricks/database"; import { logger } from "@formbricks/logger"; import { ZId, ZString } from "@formbricks/types/common"; import { DatabaseError } from "@formbricks/types/errors"; -import { TBaseFilter } from "@formbricks/types/segment"; +import { TBaseFilters } from "@formbricks/types/segment"; import { cache } from "@/lib/cache"; import { validateInputs } from "@/lib/utils/validate"; -import { evaluateSegment } from "@/modules/ee/contacts/segments/lib/segments"; +import { segmentFilterToPrismaQuery } from "@/modules/ee/contacts/segments/lib/filter/prisma-query"; export const getSegments = reactCache( async (environmentId: string) => @@ -17,7 +17,6 @@ export const getSegments = reactCache( try { const segments = await prisma.segment.findMany({ where: { environmentId }, - // Include all necessary fields for evaluateSegment to work select: { id: true, filters: true, @@ -38,11 +37,51 @@ export const getSegments = reactCache( ) ); +/** + * Checks if a contact matches a segment using Prisma query + * This leverages native DB types (valueDate, valueNumber) for accurate comparisons + * Device filters are evaluated at query build time using the provided deviceType + */ +const isContactInSegment = async ( + contactId: string, + segmentId: string, + filters: TBaseFilters, + environmentId: string, + deviceType: "phone" | "desktop" +): Promise => { + // If no filters, segment matches all contacts + if (!filters || filters.length === 0) { + return true; + } + + const queryResult = await segmentFilterToPrismaQuery(segmentId, filters, environmentId, deviceType); + + if (!queryResult.ok) { + logger.warn( + { segmentId, environmentId, error: queryResult.error }, + "Failed to build Prisma query for segment" + ); + return false; + } + + const { whereClause } = queryResult.data; + + // Check if this specific contact matches the segment filters + const matchingContact = await prisma.contact.findFirst({ + where: { + id: contactId, + ...whereClause, + }, + select: { id: true }, + }); + + return matchingContact !== null; +}; + export const getPersonSegmentIds = async ( environmentId: string, contactId: string, contactUserId: string, - attributes: Record, deviceType: "phone" | "desktop" ): Promise => { try { @@ -55,26 +94,16 @@ export const getPersonSegmentIds = async ( return []; } - const personSegments: { id: string; filters: TBaseFilter[] }[] = []; + // Device filters are evaluated at query build time using the provided deviceType + const segmentPromises = segments.map(async (segment) => { + const filters = segment.filters; + const isIncluded = await isContactInSegment(contactId, segment.id, filters, environmentId, deviceType); + return isIncluded ? segment.id : null; + }); - for (const segment of segments) { - const isIncluded = await evaluateSegment( - { - attributes, - deviceType, - environmentId, - contactId: contactId, - userId: contactUserId, - }, - segment.filters - ); + const results = await Promise.all(segmentPromises); - if (isIncluded) { - personSegments.push(segment); - } - } - - return personSegments.map((segment) => segment.id); + return results.filter((id): id is string => id !== null); } catch (error) { // Log error for debugging but don't throw to prevent "segments is not iterable" error logger.warn( diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.test.ts index b157633488..b6034708f9 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.test.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.test.ts @@ -12,9 +12,13 @@ vi.mock("@/lib/cache", () => ({ }, })); -vi.mock("@/modules/ee/contacts/lib/attributes", () => ({ - updateAttributes: vi.fn(), -})); +vi.mock("@/modules/ee/contacts/lib/attributes", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + updateAttributes: vi.fn(), + }; +}); vi.mock("@formbricks/database", () => ({ prisma: { @@ -121,7 +125,7 @@ describe("updateUser", () => { lastDisplayAt: null, }) ); - expect(result.messages).toEqual([]); + expect(result.messages).toBeUndefined(); }); test("should update existing contact attributes", async () => { @@ -137,7 +141,7 @@ describe("updateUser", () => { newAttributes ); expect(result.state.data?.language).toBe("en"); - expect(result.messages).toEqual([]); + expect(result.messages).toBeUndefined(); }); test("should not update attributes if they are the same", async () => { @@ -152,7 +156,9 @@ describe("updateUser", () => { test("should return messages from updateAttributes if any", async () => { vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockContactData as any); const newAttributes = { company: "Formbricks" }; - const updateMessages = ["Attribute 'company' created."]; + const updateMessages = [ + { code: "new_attribute_created", params: { key: "company", dataType: "string" } }, + ]; vi.mocked(updateAttributes).mockResolvedValue({ success: true, messages: updateMessages }); const result = await updateUser(mockEnvironmentId, mockUserId, "desktop", newAttributes); @@ -163,30 +169,18 @@ describe("updateUser", () => { mockEnvironmentId, newAttributes ); - expect(result.messages).toEqual(updateMessages); + expect(result.messages).toEqual(["Created new attribute 'company' with type 'string'"]); }); test("should use device type 'phone'", async () => { vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockContactData as any); await updateUser(mockEnvironmentId, mockUserId, "phone"); - expect(getPersonSegmentIds).toHaveBeenCalledWith( - mockEnvironmentId, - mockContactId, - mockUserId, - { userId: mockUserId, email: "test@example.com" }, - "phone" - ); + expect(getPersonSegmentIds).toHaveBeenCalledWith(mockEnvironmentId, mockContactId, mockUserId, "phone"); }); test("should use device type 'desktop'", async () => { vi.mocked(prisma.contact.findFirst).mockResolvedValue(mockContactData as any); await updateUser(mockEnvironmentId, mockUserId, "desktop"); - expect(getPersonSegmentIds).toHaveBeenCalledWith( - mockEnvironmentId, - mockContactId, - mockUserId, - { userId: mockUserId, email: "test@example.com" }, - "desktop" - ); + expect(getPersonSegmentIds).toHaveBeenCalledWith(mockEnvironmentId, mockContactId, mockUserId, "desktop"); }); }); diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.ts index 4f150b5c28..ac487aa146 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/update-user.ts @@ -1,9 +1,10 @@ import { createCacheKey } from "@formbricks/cache"; import { prisma } from "@formbricks/database"; +import { TContactAttributesInput } from "@formbricks/types/contact-attribute"; import { ResourceNotFoundError } from "@formbricks/types/errors"; import { TJsPersonState } from "@formbricks/types/js"; import { cache } from "@/lib/cache"; -import { updateAttributes } from "@/modules/ee/contacts/lib/attributes"; +import { formatAttributeMessage, updateAttributes } from "@/modules/ee/contacts/lib/attributes"; import { getPersonSegmentIds } from "./segments"; /** @@ -110,19 +111,18 @@ const buildUserStateFromContact = async ( contactData: NonNullable>>, environmentId: string, userId: string, - device: "phone" | "desktop", - attributes: Record + device: "phone" | "desktop" ) => { // Get segments (only remaining external call) // Ensure segments is always an array to prevent "segments is not iterable" error let segments: string[] = []; try { - segments = await getPersonSegmentIds(environmentId, contactData.id, userId, attributes, device); + segments = await getPersonSegmentIds(environmentId, contactData.id, userId, device); // Double-check that segments is actually an array if (!Array.isArray(segments)) { segments = []; } - } catch (error) { + } catch { // If segments fetching fails, use empty array as fallback segments = []; } @@ -151,8 +151,8 @@ export const updateUser = async ( environmentId: string, userId: string, device: "phone" | "desktop", - attributes?: Record -): Promise<{ state: TJsPersonState; messages?: string[] }> => { + attributes?: TContactAttributesInput +): Promise<{ state: TJsPersonState; messages?: string[]; errors?: string[] }> => { // Cached environment validation (rarely changes) const environment = await getEnvironment(environmentId); if (!environment) { @@ -177,6 +177,7 @@ export const updateUser = async ( ); let messages: string[] = []; + let errors: string[] = []; let language = contactAttributes.language; // Handle attribute updates efficiently @@ -188,40 +189,21 @@ export const updateUser = async ( const { success, messages: updateAttrMessages, - ignoreEmailAttribute, + errors: updateAttrErrors, } = await updateAttributes(contactData.id, userId, environmentId, attributes); - messages = updateAttrMessages ?? []; + messages = updateAttrMessages?.map(formatAttributeMessage) ?? []; + errors = updateAttrErrors?.map(formatAttributeMessage) ?? []; - // Update local attributes if successful - if (success) { - let attributesToUpdate = { ...attributes }; - - if (ignoreEmailAttribute) { - const { email, ...rest } = attributes; - attributesToUpdate = rest; - } - - contactAttributes = { - ...contactAttributes, - ...attributesToUpdate, - }; - - if (attributes.language) { - language = attributes.language; - } + // Update language if provided (used in response state) + if (success && attributes.language) { + language = String(attributes.language); } } } // Build user state from already-fetched data (no additional query needed) - const userStateData = await buildUserStateFromContact( - contactData, - environmentId, - userId, - device, - contactAttributes - ); + const userStateData = await buildUserStateFromContact(contactData, environmentId, userId, device); return { state: { @@ -231,6 +213,7 @@ export const updateUser = async ( }, expiresAt: new Date(Date.now() + 1000 * 60 * 30), // 30 minutes }, - messages, + messages: messages.length > 0 ? messages : undefined, + errors: errors.length > 0 ? errors : undefined, }; }; diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.test.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.test.ts index 91169caaf9..380678c18d 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.test.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.test.ts @@ -20,7 +20,6 @@ const mockEnvironmentId = "test-environment-id"; const mockUserId = "test-user-id"; const mockContactId = "test-contact-id"; const mockDevice = "desktop"; -const mockAttributes = { email: "test@example.com" }; describe("getUserState", () => { beforeEach(() => { @@ -45,7 +44,6 @@ describe("getUserState", () => { userId: mockUserId, contactId: mockContactId, device: mockDevice, - attributes: mockAttributes, }); expect(prisma.contact.findUniqueOrThrow).toHaveBeenCalledWith({ @@ -65,7 +63,6 @@ describe("getUserState", () => { mockEnvironmentId, mockContactId, mockUserId, - mockAttributes, mockDevice ); expect(result).toEqual({ @@ -98,7 +95,6 @@ describe("getUserState", () => { userId: mockUserId, contactId: mockContactId, device: mockDevice, - attributes: mockAttributes, }); expect(result).toEqual({ @@ -129,7 +125,6 @@ describe("getUserState", () => { userId: mockUserId, contactId: mockContactId, device: mockDevice, - attributes: mockAttributes, }); expect(result).toEqual({ diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.ts index c996b1e9a9..7c224bcf52 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/lib/user-state.ts @@ -33,7 +33,6 @@ const getUserStateDataOptimized = async (contactId: string) => { * @param environmentId - The environment id * @param userId - The user id * @param device - The device type - * @param attributes - The contact attributes * @returns The person state * @throws {ValidationError} - If the input is invalid * @throws {ResourceNotFoundError} - If the environment or organization is not found @@ -43,19 +42,17 @@ export const getUserState = async ({ userId, contactId, device, - attributes, }: { environmentId: string; userId: string; contactId: string; device: "phone" | "desktop"; - attributes: Record; }): Promise => { // Single optimized query for all contact data const contactData = await getUserStateDataOptimized(contactId); - // Get segments (this might have its own optimization) - const segments = await getPersonSegmentIds(environmentId, contactId, userId, attributes, device); + // Get segments using Prisma-based evaluation (no attributes needed - fetched from DB) + const segments = await getPersonSegmentIds(environmentId, contactId, userId, device); // Process displays efficiently const displays = (contactData.displays ?? []).map((display) => ({ diff --git a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/route.ts b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/route.ts index aca184cda6..7cccf236a7 100644 --- a/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/route.ts +++ b/apps/web/modules/ee/contacts/api/v1/client/[environmentId]/user/route.ts @@ -1,14 +1,27 @@ import { NextRequest, userAgent } from "next/server"; import { logger } from "@formbricks/logger"; -import { TContactAttributes } from "@formbricks/types/contact-attribute"; +import { TContactAttributesInput } from "@formbricks/types/contact-attribute"; import { ZEnvironmentId } from "@formbricks/types/environment"; -import { ResourceNotFoundError } from "@formbricks/types/errors"; +import { ResourceNotFoundError, ValidationError } from "@formbricks/types/errors"; import { TJsPersonState } from "@formbricks/types/js"; import { responses } from "@/app/lib/api/response"; import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging"; import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils"; import { updateUser } from "./lib/update-user"; +const handleError = (err: unknown, url: string): { response: Response } => { + if (err instanceof ResourceNotFoundError) { + return { response: responses.notFoundResponse(err.resourceType, err.resourceId) }; + } + + if (err instanceof ValidationError) { + return { response: responses.badRequestResponse(err.message, undefined, true) }; + } + + logger.error({ error: err, url }, "Error in POST /api/v1/client/[environmentId]/user"); + return { response: responses.internalServerErrorResponse("Unable to fetch user state", true) }; +}; + export const OPTIONS = async (): Promise => { return responses.successResponse( {}, @@ -96,43 +109,34 @@ export const POST = withV1ApiWrapper({ }; } - let attributeUpdatesToSend: TContactAttributes | null = null; + let attributeUpdatesToSend: TContactAttributesInput | null = null; if (attributes) { // remove userId and id from attributes const { userId: userIdAttr, id: idAttr, ...updatedAttributes } = attributes; - attributeUpdatesToSend = updatedAttributes; + attributeUpdatesToSend = updatedAttributes as TContactAttributesInput; } const { device } = userAgent(req); const deviceType = device ? "phone" : "desktop"; - const { state: userState, messages } = await updateUser( - environmentId, - userId, - deviceType, - attributeUpdatesToSend ?? undefined - ); + const { + state: userState, + messages, + errors, + } = await updateUser(environmentId, userId, deviceType, attributeUpdatesToSend ?? undefined); // Build response (simplified structure) - const responseJson: { state: TJsPersonState; messages?: string[] } = { + const responseJson: { state: TJsPersonState; messages?: string[]; errors?: string[] } = { state: userState, ...(messages && messages.length > 0 && { messages }), + ...(errors && errors.length > 0 && { errors }), }; return { response: responses.successResponse(responseJson, true), }; } catch (err) { - if (err instanceof ResourceNotFoundError) { - return { - response: responses.notFoundResponse(err.resourceType, err.resourceId), - }; - } - - logger.error({ error: err, url: req.url }, "Error in POST /api/v1/client/[environmentId]/user"); - return { - response: responses.internalServerErrorResponse(err.message ?? "Unable to fetch person state", true), - }; + return handleError(err, req.url); } }, }); diff --git a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/lib/contact-attribute-key.ts b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/lib/contact-attribute-key.ts index bc65681fc7..494e7181f6 100644 --- a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/lib/contact-attribute-key.ts +++ b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/lib/contact-attribute-key.ts @@ -65,6 +65,7 @@ export const updateContactAttributeKey = async ( description: data.description, name: data.name, key: data.key, + ...(data.dataType && { dataType: data.dataType }), }, }); diff --git a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts index 7d1de633c0..4c28d00a50 100644 --- a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts +++ b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts @@ -1,9 +1,15 @@ import { z } from "zod"; +import { ZContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; +import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; export const ZContactAttributeKeyCreateInput = z.object({ - key: z.string(), + key: z.string().refine((val) => isSafeIdentifier(val), { + message: + "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", + }), description: z.string().optional(), type: z.enum(["custom"]), + dataType: ZContactAttributeDataType.optional(), environmentId: z.string(), name: z.string().optional(), }); @@ -12,7 +18,14 @@ export type TContactAttributeKeyCreateInput = z.infer isSafeIdentifier(val), { + message: + "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", + }) + .optional(), + dataType: ZContactAttributeDataType.optional(), }); export type TContactAttributeKeyUpdateInput = z.infer; diff --git a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.test.ts b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.test.ts index 6fa31e494e..cb859b657c 100644 --- a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.test.ts +++ b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.test.ts @@ -177,7 +177,7 @@ describe("createContactAttributeKey", () => { expect(prisma.contactAttributeKey.create).toHaveBeenCalledWith({ data: { key: inputWithoutName.key, - name: inputWithoutName.key, // Should fall back to key when name is not provided + name: "TestKey", // formatSnakeCaseToTitleCase("testKey") capitalizes first letter type: inputWithoutName.type, description: inputWithoutName.description || "", environment: { connect: { id: environmentId } }, diff --git a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.ts b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.ts index b49853d1c4..6fee2f53b3 100644 --- a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.ts +++ b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/lib/contact-attribute-keys.ts @@ -5,6 +5,7 @@ import { PrismaErrorType } from "@formbricks/database/types/error"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { DatabaseError, OperationNotAllowedError } from "@formbricks/types/errors"; import { MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT } from "@/lib/constants"; +import { formatSnakeCaseToTitleCase } from "@/lib/utils/safe-identifier"; import { TContactAttributeKeyCreateInput } from "@/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys"; export const getContactAttributeKeys = reactCache( @@ -44,9 +45,10 @@ export const createContactAttributeKey = async ( const contactAttributeKey = await prisma.contactAttributeKey.create({ data: { key: data.key, - name: data.name ?? data.key, + name: data.name ?? formatSnakeCaseToTitleCase(data.key), type: data.type, description: data.description ?? "", + ...(data.dataType && { dataType: data.dataType }), environment: { connect: { id: environmentId, diff --git a/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts b/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts index 915e6a24a1..c374b20452 100644 --- a/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts +++ b/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts @@ -2,10 +2,481 @@ import { createId } from "@paralleldrive/cuid2"; import { Prisma } from "@prisma/client"; import { prisma } from "@formbricks/database"; import { logger } from "@formbricks/logger"; +import { TContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { Result, err, ok } from "@formbricks/types/error-handlers"; +import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; +import { prepareAttributeColumnsForStorage } from "@/modules/ee/contacts/lib/attribute-storage"; +import { detectAttributeDataType } from "@/modules/ee/contacts/lib/detect-attribute-type"; import { TContactBulkUploadContact } from "@/modules/ee/contacts/types/contact"; +const EMAIL_ATTRIBUTE_KEY = "email"; + +type TExistingContact = { + contactId: string; + attributes: { id: string; attributeKey: { key: string }; createdAt: Date; value: string }[]; +}; + +type TContactToUpdate = { + contactId: string; + attributes: { + id: string; + createdAt: Date; + value: string; + attributeKey: { key: string }; + }[]; +}; + +type TContactToCreate = { + attributes: { + value: string; + attributeKey: { key: string }; + }[]; +}; + +/** + * Extracts user IDs and unique attribute keys from contacts + */ +const extractContactMetadata = ( + contacts: TContactBulkUploadContact[] +): { + userIdsInContacts: string[]; + attributeKeys: string[]; +} => { + const userIdsInContacts: string[] = []; + const attributeKeysSet = new Set(); + const attributeKeys: string[] = []; + + for (const contact of contacts) { + for (const attr of contact.attributes) { + if (attr.attributeKey.key === "userId") { + userIdsInContacts.push(attr.value); + } + + if (!attributeKeysSet.has(attr.attributeKey.key)) { + attributeKeys.push(attr.attributeKey.key); + attributeKeysSet.add(attr.attributeKey.key); + } + } + } + + return { userIdsInContacts, attributeKeys }; +}; + +/** + * Builds a map of attribute keys to their values for type detection + */ +const buildAttributeValuesByKey = (contacts: TContactBulkUploadContact[]): Map => { + const attributeValuesByKey = new Map(); + + for (const contact of contacts) { + for (const attr of contact.attributes) { + if (!attributeValuesByKey.has(attr.attributeKey.key)) { + attributeValuesByKey.set(attr.attributeKey.key, []); + } + if (attr.value.trim() !== "") { + attributeValuesByKey.get(attr.attributeKey.key)!.push(attr.value); + } + } + } + + return attributeValuesByKey; +}; + +/** + * Determines data types for attribute keys based on existing keys and detected types + */ +const determineAttributeTypes = ( + attributeValuesByKey: Map, + existingAttributeKeys: { key: string; dataType: TContactAttributeDataType }[] +): Map => { + const attributeTypeMap = new Map(); + + for (const [key, values] of attributeValuesByKey) { + const existingKey = existingAttributeKeys.find((ak) => ak.key === key); + + if (existingKey) { + attributeTypeMap.set(key, existingKey.dataType); + } else { + const firstValue = values.find((v) => v !== ""); + const detectedType = firstValue ? detectAttributeDataType(firstValue) : "string"; + attributeTypeMap.set(key, detectedType); + } + } + + return attributeTypeMap; +}; + +/** + * Finds values that fail parsing for the given data type. + */ +const findInvalidValues = (values: string[], dataType: TContactAttributeDataType): string[] => { + return values.filter((value) => { + const columns = prepareAttributeColumnsForStorage(value, dataType); + return ( + (dataType === "number" && columns.valueNumber === null) || + (dataType === "date" && columns.valueDate === null) + ); + }); +}; + +/** + * Builds a human-readable error message for invalid attribute values. + */ +const buildInvalidValuesError = ( + key: string, + dataType: TContactAttributeDataType, + invalidValues: string[] +): string => { + const sampleInvalid = invalidValues.slice(0, 3).join(", "); + const additionalCount = invalidValues.length - 3; + const suffix = additionalCount > 0 ? ` (and ${additionalCount.toString()} more)` : ""; + return `Attribute "${key}" is typed as "${dataType}" but received invalid values: ${sampleInvalid}${suffix}`; +}; + +/** + * Validates attribute values against their expected types. + * For NEW keys (not yet in DB): downgrades to string if values are mixed/invalid. + * For EXISTING keys: returns errors for invalid values (the type is already set in the DB and must be respected). + */ +const validateAndAdjustAttributeTypes = ( + attributeTypeMap: Map, + attributeValuesByKey: Map, + existingAttributeKeys: { key: string; dataType: TContactAttributeDataType }[] +): { existingKeyErrors: string[] } => { + const existingKeySet = new Set(existingAttributeKeys.map((ak) => ak.key)); + const newKeyWarnings: string[] = []; + const existingKeyErrors: string[] = []; + + for (const [key, dataType] of attributeTypeMap) { + if (dataType === "string") continue; + + const values = attributeValuesByKey.get(key) || []; + const invalidValues = findInvalidValues(values, dataType); + + if (invalidValues.length === 0) continue; + + if (existingKeySet.has(key)) { + existingKeyErrors.push(buildInvalidValuesError(key, dataType, invalidValues)); + } else { + attributeTypeMap.set(key, "string"); + newKeyWarnings.push( + `New attribute "${key}" has mixed or invalid values for type "${dataType}", treating as string type` + ); + } + } + + if (newKeyWarnings.length > 0) { + logger.warn({ errors: newKeyWarnings }, "Type validation warnings during bulk upload"); + } + + return { existingKeyErrors }; +}; + +/** + * Builds a map from email to contact data for existing contacts + */ +const buildExistingContactMap = ( + existingContactsByEmail: { + id: string; + attributes: { id: string; attributeKey: { key: string }; createdAt: Date; value: string }[]; + }[] +): Map => { + const contactMap = new Map(); + + for (const contact of existingContactsByEmail) { + const emailAttr = contact.attributes.find((attr) => attr.attributeKey.key === EMAIL_ATTRIBUTE_KEY); + + if (emailAttr) { + contactMap.set(emailAttr.value, { + contactId: contact.id, + attributes: contact.attributes.map((attr) => ({ + id: attr.id, + attributeKey: { key: attr.attributeKey.key }, + createdAt: attr.createdAt, + value: attr.value, + })), + }); + } + } + + return contactMap; +}; + +/** + * Processes a contact that exists in the database to determine if it needs updating + */ +const processExistingContact = ( + contact: TContactBulkUploadContact, + existingContact: TExistingContact, + existingUserIds: { value: string }[], + idx: number, + contactIdxWithConflictingUserIds: number[], + contactsToUpdate: TContactToUpdate[], + filteredContacts: TContactBulkUploadContact[] +): void => { + const existingAttributesByKey = new Map( + existingContact.attributes.map((attr) => [attr.attributeKey.key, attr.value]) + ); + + const attributesToUpdate = contact.attributes.filter( + (attr) => existingAttributesByKey.get(attr.attributeKey.key) !== attr.value + ); + + if (attributesToUpdate.length === 0) { + filteredContacts.push(contact); + return; + } + + const userIdAttr = attributesToUpdate.find((attr) => attr.attributeKey.key === "userId"); + if (userIdAttr && existingUserIds.some((u) => u.value === userIdAttr.value)) { + contactIdxWithConflictingUserIds.push(idx); + return; + } + + filteredContacts.push(contact); + contactsToUpdate.push({ + contactId: existingContact.contactId, + attributes: attributesToUpdate.map((attr) => { + const existingAttr = existingContact.attributes.find( + (a) => a.attributeKey.key === attr.attributeKey.key + ); + + return existingAttr + ? { + id: existingAttr.id, + createdAt: existingAttr.createdAt, + value: attr.value, + attributeKey: attr.attributeKey, + } + : { id: createId(), createdAt: new Date(), value: attr.value, attributeKey: attr.attributeKey }; + }), + }); +}; + +/** + * Processes a new contact to determine if it can be created + */ +const processNewContact = ( + contact: TContactBulkUploadContact, + existingUserIds: { value: string }[], + idx: number, + contactIdxWithConflictingUserIds: number[], + contactsToCreate: TContactToCreate[], + filteredContacts: TContactBulkUploadContact[] +): void => { + const userIdAttr = contact.attributes.find((attr) => attr.attributeKey.key === "userId"); + + if (userIdAttr && existingUserIds.some((u) => u.value === userIdAttr.value)) { + contactIdxWithConflictingUserIds.push(idx); + return; + } + + filteredContacts.push(contact); + contactsToCreate.push(contact); +}; + +/** + * Collects missing attribute keys and keys needing name updates + */ +const collectAttributeKeyChanges = ( + filteredContacts: TContactBulkUploadContact[], + attributeKeyMap: Record, + existingAttributeKeys: { key: string; name: string | null; dataType: TContactAttributeDataType }[] +): { + missingKeysMap: Map; + attributeKeyNameUpdates: Map; +} => { + const missingKeysMap = new Map(); + const attributeKeyNameUpdates = new Map(); + + for (const contact of filteredContacts) { + for (const attr of contact.attributes) { + if (attributeKeyMap[attr.attributeKey.key]) { + const existingKey = existingAttributeKeys.find((ak) => ak.key === attr.attributeKey.key); + if (existingKey && existingKey.name !== attr.attributeKey.name) { + attributeKeyNameUpdates.set(attr.attributeKey.key, attr.attributeKey); + } + } else { + missingKeysMap.set(attr.attributeKey.key, attr.attributeKey); + } + } + } + + return { missingKeysMap, attributeKeyNameUpdates }; +}; + +/** + * Builds the map of keys to upsert with their data types + */ +const buildKeysToUpsert = ( + missingKeysMap: Map, + attributeKeyNameUpdates: Map, + attributeTypeMap: Map, + existingAttributeKeys: { key: string; dataType: TContactAttributeDataType }[] +): Map => { + const keysToUpsert = new Map(); + + for (const [key, value] of missingKeysMap) { + const dataType = attributeTypeMap.get(key) ?? "string"; + keysToUpsert.set(key, { key: value.key, name: value.name, dataType }); + } + + for (const [key, value] of attributeKeyNameUpdates) { + const existingKey = existingAttributeKeys.find((ak) => ak.key === key); + const dataType = existingKey?.dataType ?? "string"; + keysToUpsert.set(key, { key: value.key, name: value.name, dataType }); + } + + return keysToUpsert; +}; + +type TAttributeUpsertData = { + id: string; + contactId: string; + attributeKeyId: string; + value: string; + valueNumber: number | null; + valueDate: Date | null; + createdAt: Date; + updatedAt: Date; +}; + +/** + * Prepares attribute data for new contacts + */ +const prepareAttributesForNewContacts = ( + contactsToCreate: TContactToCreate[], + newContacts: { id: string; environmentId: string }[], + attributeKeyMap: Record, + attributeTypeMap: Map +): TAttributeUpsertData[] => { + return contactsToCreate.flatMap((contact, idx) => + contact.attributes.map((attr) => { + const dataType = attributeTypeMap.get(attr.attributeKey.key) ?? "string"; + const columns = prepareAttributeColumnsForStorage(attr.value, dataType); + + return { + id: createId(), + contactId: newContacts[idx].id, + attributeKeyId: attributeKeyMap[attr.attributeKey.key], + value: columns.value, + valueNumber: columns.valueNumber, + valueDate: columns.valueDate, + createdAt: new Date(), + updatedAt: new Date(), + }; + }) + ); +}; + +/** + * Prepares attribute data for existing contacts + */ +const prepareAttributesForExistingContacts = ( + contactsToUpdate: TContactToUpdate[], + attributeKeyMap: Record, + attributeTypeMap: Map +): TAttributeUpsertData[] => { + return contactsToUpdate.flatMap((contact) => + contact.attributes.map((attr) => { + const dataType = attributeTypeMap.get(attr.attributeKey.key) ?? "string"; + const columns = prepareAttributeColumnsForStorage(attr.value, dataType); + + return { + id: attr.id, + contactId: contact.contactId, + attributeKeyId: attributeKeyMap[attr.attributeKey.key], + value: columns.value, + valueNumber: columns.valueNumber, + valueDate: columns.valueDate, + createdAt: attr.createdAt, + updatedAt: new Date(), + }; + }) + ); +}; + +const BATCH_SIZE = 10000; + +/** + * Upserts attribute keys in batches using raw SQL + */ +const upsertAttributeKeysInBatches = async ( + tx: Prisma.TransactionClient, + keysToUpsert: Map, + environmentId: string, + attributeKeyMap: Record +): Promise => { + const keysArray = Array.from(keysToUpsert.values()); + + for (let i = 0; i < keysArray.length; i += BATCH_SIZE) { + const batch = keysArray.slice(i, i + BATCH_SIZE); + + const upsertedKeys = await tx.$queryRaw<{ id: string; key: string }[]>` + INSERT INTO "ContactAttributeKey" ("id", "key", "name", "environmentId", "dataType", "created_at", "updated_at") + SELECT + unnest(${Prisma.sql`ARRAY[${batch.map(() => createId())}]`}), + unnest(${Prisma.sql`ARRAY[${batch.map((k) => k.key)}]`}), + unnest(${Prisma.sql`ARRAY[${batch.map((k) => k.name)}]`}), + ${environmentId}, + unnest(${Prisma.sql`ARRAY[${batch.map((k) => k.dataType)}]`}::text[]::"ContactAttributeDataType"[]), + NOW(), + NOW() + ON CONFLICT ("key", "environmentId") + DO UPDATE SET + "name" = EXCLUDED."name", + "updated_at" = NOW() + RETURNING "id", "key" + `; + + for (const key of upsertedKeys) { + attributeKeyMap[key.key] = key.id; + } + } +}; + +/** + * Upserts contact attributes in batches using raw SQL + */ +const upsertAttributesInBatches = async ( + tx: Prisma.TransactionClient, + attributesToUpsert: TAttributeUpsertData[] +): Promise => { + for (let i = 0; i < attributesToUpsert.length; i += BATCH_SIZE) { + const batch = attributesToUpsert.slice(i, i + BATCH_SIZE); + + const ids = batch.map((a) => a.id); + const createdAts = batch.map((a) => a.createdAt.toISOString()); + const updatedAts = batch.map((a) => a.updatedAt.toISOString()); + const contactIds = batch.map((a) => a.contactId); + const values = batch.map((a) => a.value); + const valueNumbers = batch.map((a) => (a.valueNumber === null ? null : String(a.valueNumber))); + const valueDates = batch.map((a) => (a.valueDate ? a.valueDate.toISOString() : null)); + const attributeKeyIds = batch.map((a) => a.attributeKeyId); + + await tx.$executeRaw` + INSERT INTO "ContactAttribute" ( + "id", "created_at", "updated_at", "contactId", "value", "valueNumber", "valueDate", "attributeKeyId" + ) + SELECT + unnest(${ids}::text[]), + unnest(${createdAts}::text[])::timestamp, + unnest(${updatedAts}::text[])::timestamp, + unnest(${contactIds}::text[]), + unnest(${values}::text[]), + unnest(${valueNumbers}::text[])::double precision, + unnest(${valueDates}::text[])::timestamp, + unnest(${attributeKeyIds}::text[]) + ON CONFLICT ("contactId", "attributeKeyId") DO UPDATE SET + "value" = EXCLUDED."value", + "valueNumber" = EXCLUDED."valueNumber", + "valueDate" = EXCLUDED."valueDate", + "updated_at" = EXCLUDED."updated_at" + `; + } +}; + export const upsertBulkContacts = async ( contacts: TContactBulkUploadContact[], environmentId: string, @@ -18,43 +489,16 @@ export const upsertBulkContacts = async ( ApiErrorResponseV2 > > => { - const emailAttributeKey = "email"; const contactIdxWithConflictingUserIds: number[] = []; - - let userIdsInContacts: string[] = []; - let attributeKeysSet: Set = new Set(); - let attributeKeys: string[] = []; - - // both can be done with a single loop: - contacts.forEach((contact) => { - contact.attributes.forEach((attr) => { - if (attr.attributeKey.key === "userId") { - userIdsInContacts.push(attr.value); - } - - if (!attributeKeysSet.has(attr.attributeKey.key)) { - attributeKeys.push(attr.attributeKey.key); - } - - // Add the attribute key to the set - attributeKeysSet.add(attr.attributeKey.key); - }); - }); + const { userIdsInContacts, attributeKeys } = extractContactMetadata(contacts); const [existingUserIds, existingContactsByEmail, existingAttributeKeys] = await Promise.all([ prisma.contactAttribute.findMany({ where: { - attributeKey: { - environmentId, - key: "userId", - }, - value: { - in: userIdsInContacts, - }, - }, - select: { - value: true, + attributeKey: { environmentId, key: "userId" }, + value: { in: userIdsInContacts }, }, + select: { value: true }, }), prisma.contact.findMany({ @@ -62,7 +506,7 @@ export const upsertBulkContacts = async ( environmentId, attributes: { some: { - attributeKey: { key: emailAttributeKey }, + attributeKey: { key: EMAIL_ATTRIBUTE_KEY }, value: { in: parsedEmails }, }, }, @@ -81,146 +525,76 @@ export const upsertBulkContacts = async ( }), prisma.contactAttributeKey.findMany({ - where: { - key: { in: attributeKeys }, - environmentId, - }, + where: { key: { in: attributeKeys }, environmentId }, }), ]); - // Build a map from email to contact id (if the email attribute exists) - const contactMap = new Map< - string, - { - contactId: string; - attributes: { id: string; attributeKey: { key: string }; createdAt: Date; value: string }[]; - } - >(); + // Validate new attribute keys are safe identifiers before proceeding + const existingKeySet = new Set(existingAttributeKeys.map((ak) => ak.key)); + const invalidNewKeys = attributeKeys.filter((key) => !existingKeySet.has(key) && !isSafeIdentifier(key)); - existingContactsByEmail.forEach((contact) => { - const emailAttr = contact.attributes.find((attr) => attr.attributeKey.key === emailAttributeKey); + if (invalidNewKeys.length > 0) { + return err({ + type: "bad_request", + details: [ + { + field: "attributes", + issue: `Invalid attribute key(s): ${invalidNewKeys.join(", ")}. Keys must only contain lowercase letters, numbers, and underscores, and must start with a letter.`, + }, + ], + }); + } - if (emailAttr) { - contactMap.set(emailAttr.value, { - contactId: contact.id, - attributes: contact.attributes.map((attr) => ({ - id: attr.id, - attributeKey: { key: attr.attributeKey.key }, - createdAt: attr.createdAt, - value: attr.value, - })), - }); - } - }); + // Type Detection Phase + const attributeValuesByKey = buildAttributeValuesByKey(contacts); + const attributeTypeMap = determineAttributeTypes(attributeValuesByKey, existingAttributeKeys); + const { existingKeyErrors } = validateAndAdjustAttributeTypes( + attributeTypeMap, + attributeValuesByKey, + existingAttributeKeys + ); + + if (existingKeyErrors.length > 0) { + return err({ + type: "bad_request", + details: existingKeyErrors.map((issue) => ({ + field: "attributes", + issue, + })), + }); + } + + // Build contact lookup map + const contactMap = buildExistingContactMap(existingContactsByEmail); // Split contacts into ones to update and ones to create - const contactsToUpdate: { - contactId: string; - attributes: { - id: string; - createdAt: Date; - value: string; - attributeKey: { - key: string; - }; - }[]; - }[] = []; - - const contactsToCreate: { - attributes: { - value: string; - attributeKey: { - key: string; - }; - }[]; - }[] = []; - - let filteredContacts: TContactBulkUploadContact[] = []; + const contactsToUpdate: TContactToUpdate[] = []; + const contactsToCreate: TContactToCreate[] = []; + const filteredContacts: TContactBulkUploadContact[] = []; contacts.forEach((contact, idx) => { - const emailAttr = contact.attributes.find((attr) => attr.attributeKey.key === emailAttributeKey); + const emailAttr = contact.attributes.find((attr) => attr.attributeKey.key === EMAIL_ATTRIBUTE_KEY); + const existingContact = emailAttr ? contactMap.get(emailAttr.value) : undefined; - if (emailAttr && contactMap.has(emailAttr.value)) { - // if all the attributes passed are the same as the existing attributes, skip the update: - const existingContact = contactMap.get(emailAttr.value); - if (existingContact) { - // Create maps of existing attributes by key - const existingAttributesByKey = new Map( - existingContact.attributes.map((attr) => [attr.attributeKey.key, attr.value]) - ); - - // Determine which attributes need updating by comparing values. - const attributesToUpdate = contact.attributes.filter( - (attr) => existingAttributesByKey.get(attr.attributeKey.key) !== attr.value - ); - - // Check if any attributes need updating - const needsUpdate = attributesToUpdate.length > 0; - - if (!needsUpdate) { - filteredContacts.push(contact); - // No attributes need to be updated - return; - } - - // if the attributes to update have a userId that exists in the db, we need to skip the update - const userIdAttr = attributesToUpdate.find((attr) => attr.attributeKey.key === "userId"); - - if (userIdAttr) { - const existingUserId = existingUserIds.find( - (existingUserId) => existingUserId.value === userIdAttr.value - ); - - if (existingUserId) { - contactIdxWithConflictingUserIds.push(idx); - return; - } - } - - filteredContacts.push(contact); - contactsToUpdate.push({ - contactId: existingContact.contactId, - attributes: attributesToUpdate.map((attr) => { - const existingAttr = existingContact.attributes.find( - (a) => a.attributeKey.key === attr.attributeKey.key - ); - - if (!existingAttr) { - return { - id: createId(), - createdAt: new Date(), - value: attr.value, - attributeKey: attr.attributeKey, - }; - } - - return { - id: existingAttr.id, - createdAt: existingAttr.createdAt, - value: attr.value, - attributeKey: attr.attributeKey, - }; - }), - }); - } + if (existingContact) { + processExistingContact( + contact, + existingContact, + existingUserIds, + idx, + contactIdxWithConflictingUserIds, + contactsToUpdate, + filteredContacts + ); } else { - // There can't be a case where the emailAttr is not defined since that should be caught by zod. - - // if the contact has a userId that already exists in the db, we need to skip the create - const userIdAttr = contact.attributes.find((attr) => attr.attributeKey.key === "userId"); - if (userIdAttr) { - const existingUserId = existingUserIds.find( - (existingUserId) => existingUserId.value === userIdAttr.value - ); - - if (existingUserId) { - contactIdxWithConflictingUserIds.push(idx); - return; - } - } - - filteredContacts.push(contact); - contactsToCreate.push(contact); + processNewContact( + contact, + existingUserIds, + idx, + contactIdxWithConflictingUserIds, + contactsToCreate, + filteredContacts + ); } }); @@ -233,133 +607,55 @@ export const upsertBulkContacts = async ( return acc; }, {}); - // Check for missing attribute keys and create them if needed. - const missingKeysMap = new Map(); - const attributeKeyNameUpdates = new Map(); + // Collect missing keys and name updates + const { missingKeysMap, attributeKeyNameUpdates } = collectAttributeKeyChanges( + filteredContacts, + attributeKeyMap, + existingAttributeKeys + ); - for (const contact of filteredContacts) { - for (const attr of contact.attributes) { - if (!attributeKeyMap[attr.attributeKey.key]) { - missingKeysMap.set(attr.attributeKey.key, attr.attributeKey); - } else { - // Check if the name has changed for existing attribute keys - const existingKey = existingAttributeKeys.find((ak) => ak.key === attr.attributeKey.key); - if (existingKey && existingKey.name !== attr.attributeKey.name) { - attributeKeyNameUpdates.set(attr.attributeKey.key, attr.attributeKey); - } - } - } - } - - // Handle both missing keys and name updates in a single batch operation - const keysToUpsert = new Map(); - - // Collect all keys that need to be created or updated - for (const [key, value] of missingKeysMap) { - keysToUpsert.set(key, value); - } - - for (const [key, value] of attributeKeyNameUpdates) { - keysToUpsert.set(key, value); - } + // Build keys to upsert + const keysToUpsert = buildKeysToUpsert( + missingKeysMap, + attributeKeyNameUpdates, + attributeTypeMap, + existingAttributeKeys + ); + // Upsert attribute keys in batches if (keysToUpsert.size > 0) { - const keysArray = Array.from(keysToUpsert.values()); - const BATCH_SIZE = 10000; - - for (let i = 0; i < keysArray.length; i += BATCH_SIZE) { - const batch = keysArray.slice(i, i + BATCH_SIZE); - - // Use raw query to perform upsert - const upsertedKeys = await tx.$queryRaw<{ id: string; key: string }[]>` - INSERT INTO "ContactAttributeKey" ("id", "key", "name", "environmentId", "created_at", "updated_at") - SELECT - unnest(${Prisma.sql`ARRAY[${batch.map(() => createId())}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((k) => k.key)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((k) => k.name)}]`}), - ${environmentId}, - NOW(), - NOW() - ON CONFLICT ("key", "environmentId") - DO UPDATE SET - "name" = EXCLUDED."name", - "updated_at" = NOW() - RETURNING "id", "key" - `; - - // Update attribute key map with upserted keys - for (const key of upsertedKeys) { - attributeKeyMap[key.key] = key.id; - } - } + await upsertAttributeKeysInBatches(tx, keysToUpsert, environmentId, attributeKeyMap); } - // Create new contacts -- should be at most 1000, no need to batch - const newContacts = contactsToCreate.map(() => ({ - id: createId(), - environmentId, - })); + // Create new contacts + const newContacts = contactsToCreate.map(() => ({ id: createId(), environmentId })); if (newContacts.length > 0) { - await tx.contact.createMany({ - data: newContacts, - }); + await tx.contact.createMany({ data: newContacts }); } // Prepare attributes for both new and existing contacts - const attributesUpsertForCreatedUsers = contactsToCreate.flatMap((contact, idx) => - contact.attributes.map((attr) => ({ - id: createId(), - contactId: newContacts[idx].id, - attributeKeyId: attributeKeyMap[attr.attributeKey.key], - value: attr.value, - createdAt: new Date(), - updatedAt: new Date(), - })) + const attributesForNewContacts = prepareAttributesForNewContacts( + contactsToCreate, + newContacts, + attributeKeyMap, + attributeTypeMap ); - const attributesUpsertForExistingUsers = contactsToUpdate.flatMap((contact) => - contact.attributes.map((attr) => ({ - id: attr.id, - contactId: contact.contactId, - attributeKeyId: attributeKeyMap[attr.attributeKey.key], - value: attr.value, - createdAt: attr.createdAt, - updatedAt: new Date(), - })) + const attributesForExistingContacts = prepareAttributesForExistingContacts( + contactsToUpdate, + attributeKeyMap, + attributeTypeMap ); - const attributesToUpsert = [...attributesUpsertForCreatedUsers, ...attributesUpsertForExistingUsers]; + const attributesToUpsert = [...attributesForNewContacts, ...attributesForExistingContacts]; - // Skip the raw query if there are no attributes to upsert + // Upsert attributes in batches if (attributesToUpsert.length > 0) { - // Process attributes in batches of 10,000 - const BATCH_SIZE = 10000; - for (let i = 0; i < attributesToUpsert.length; i += BATCH_SIZE) { - const batch = attributesToUpsert.slice(i, i + BATCH_SIZE); - - // Use a raw query to perform a bulk insert with an ON CONFLICT clause - await tx.$executeRaw` - INSERT INTO "ContactAttribute" ( - "id", "created_at", "updated_at", "contactId", "value", "attributeKeyId" - ) - SELECT - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.id)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.createdAt)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.updatedAt)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.contactId)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.value)}]`}), - unnest(${Prisma.sql`ARRAY[${batch.map((a) => a.attributeKeyId)}]`}) - ON CONFLICT ("contactId", "attributeKeyId") DO UPDATE SET - "value" = EXCLUDED."value", - "updated_at" = EXCLUDED."updated_at" - `; - } + await upsertAttributesInBatches(tx, attributesToUpsert); } }, - { - timeout: 10 * 1000, // 10 seconds - } + { timeout: 10 * 1000 } ); return ok({ diff --git a/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.test.ts b/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.test.ts index 129eb2cdeb..e4fc6bf9d5 100644 --- a/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.test.ts +++ b/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.test.ts @@ -92,7 +92,6 @@ describe("contact.ts", () => { vi.mocked(prisma.contact.findFirst).mockResolvedValueOnce({ id: "existing-contact-id", environmentId: "env123", - userId: null, createdAt: new Date(), updatedAt: new Date(), }); @@ -122,7 +121,6 @@ describe("contact.ts", () => { .mockResolvedValueOnce({ id: "existing-contact-id", environmentId: "env123", - userId: "user123", createdAt: new Date(), updatedAt: new Date(), }); // Existing contact by userId @@ -160,12 +158,16 @@ describe("contact.ts", () => { userId: null, attributes: [ { - attributeKey: existingAttributeKeys[0], + attributeKey: { ...existingAttributeKeys[0], dataType: "string" }, value: "john@example.com", + valueNumber: null, + valueDate: null, }, { - attributeKey: existingAttributeKeys[1], + attributeKey: { ...existingAttributeKeys[1], dataType: "string" }, value: "John", + valueNumber: null, + valueDate: null, }, ], }; @@ -256,8 +258,10 @@ describe("contact.ts", () => { userId: null, attributes: [ { - attributeKey: existingAttributeKeys[0], + attributeKey: { ...existingAttributeKeys[0], dataType: "string" }, value: "john@example.com", + valueNumber: null, + valueDate: null, }, ], }; @@ -323,9 +327,24 @@ describe("contact.ts", () => { updatedAt: new Date("2023-01-01T00:00:00.000Z"), userId: null, attributes: [ - { attributeKey: existingAttributeKeys[0], value: "john@example.com" }, - { attributeKey: existingAttributeKeys[1], value: "user123" }, - { attributeKey: existingAttributeKeys[2], value: "John" }, + { + attributeKey: { ...existingAttributeKeys[0], dataType: "string" }, + value: "john@example.com", + valueNumber: null, + valueDate: null, + }, + { + attributeKey: { ...existingAttributeKeys[1], dataType: "string" }, + value: "user123", + valueNumber: null, + valueDate: null, + }, + { + attributeKey: { ...existingAttributeKeys[2], dataType: "string" }, + value: "John", + valueNumber: null, + valueDate: null, + }, ], }; diff --git a/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.ts b/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.ts index f0d23e2144..8c6709f1d9 100644 --- a/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.ts +++ b/apps/web/modules/ee/contacts/api/v2/management/contacts/lib/contact.ts @@ -1,6 +1,7 @@ import { prisma } from "@formbricks/database"; import { Result, err, ok } from "@formbricks/types/error-handlers"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; +import { readAttributeValue } from "@/modules/ee/contacts/lib/attribute-storage"; import { TContactCreateRequest, TContactResponse } from "@/modules/ee/contacts/types/contact"; export const createContact = async ( @@ -115,10 +116,10 @@ export const createContact = async ( }, }); - // Format the response with flattened attributes + // Format the response with flattened attributes, resolving from typed columns const flattenedAttributes: Record = {}; result.attributes.forEach((attr) => { - flattenedAttributes[attr.attributeKey.key] = attr.value; + flattenedAttributes[attr.attributeKey.key] = readAttributeValue(attr, attr.attributeKey.dataType); }); const response: TContactResponse = { diff --git a/apps/web/modules/ee/contacts/attributes/actions.ts b/apps/web/modules/ee/contacts/attributes/actions.ts index 322d16b5fb..9772fd86b4 100644 --- a/apps/web/modules/ee/contacts/attributes/actions.ts +++ b/apps/web/modules/ee/contacts/attributes/actions.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { ZId } from "@formbricks/types/common"; +import { ZContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { ResourceNotFoundError } from "@formbricks/types/errors"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; @@ -24,6 +25,7 @@ const ZCreateContactAttributeKeyAction = z.object({ }), name: z.string().optional(), description: z.string().optional(), + dataType: ZContactAttributeDataType.optional(), }); type TCreateContactAttributeKeyActionInput = z.infer; @@ -66,6 +68,7 @@ export const createContactAttributeKeyAction = authenticatedActionClient key: parsedInput.key, name: parsedInput.name, description: parsedInput.description, + dataType: parsedInput.dataType, }); ctx.auditLoggingCtx.newObject = contactAttributeKey; diff --git a/apps/web/modules/ee/contacts/attributes/components/attribute-table-column.tsx b/apps/web/modules/ee/contacts/attributes/components/attribute-table-column.tsx index b7d971e81d..24d76aea3f 100644 --- a/apps/web/modules/ee/contacts/attributes/components/attribute-table-column.tsx +++ b/apps/web/modules/ee/contacts/attributes/components/attribute-table-column.tsx @@ -3,9 +3,11 @@ import { ColumnDef } from "@tanstack/react-table"; import { format } from "date-fns"; import { TFunction } from "i18next"; +import { CalendarIcon, HashIcon, TagIcon } from "lucide-react"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { TUserLocale } from "@formbricks/types/user"; import { timeSince } from "@/lib/time"; +import { Badge } from "@/modules/ui/components/badge"; import { getSelectionColumn } from "@/modules/ui/components/data-table"; import { HighlightedText } from "@/modules/ui/components/highlighted-text"; import { IdBadge } from "@/modules/ui/components/id-badge"; @@ -44,7 +46,7 @@ export const generateAttributeTableColumns = ( cell: ({ row }) => { const description = row.original.description; return description ? ( -
    +
    ) : ( @@ -63,6 +65,45 @@ export const generateAttributeTableColumns = ( }, }; + const dataTypeColumn: ColumnDef = { + id: "dataType", + accessorKey: "dataType", + header: t("environments.contacts.data_type"), + cell: ({ row }) => { + const dataType = row.original.dataType; + const getIcon = () => { + switch (dataType) { + case "date": + return ; + case "number": + return ; + case "string": + default: + return ; + } + }; + + const getLabel = () => { + switch (dataType) { + case "date": + return t("common.date"); + case "number": + return t("common.number"); + case "string": + default: + return t("common.text"); + } + }; + + return ( +
    + {getIcon()} + +
    + ); + }, + }; + const updatedAtColumn: ColumnDef = { id: "updatedAt", accessorKey: "updatedAt", @@ -73,7 +114,14 @@ export const generateAttributeTableColumns = ( }, }; - const baseColumns = [labelColumn, keyColumn, descriptionColumn, createdAtColumn, updatedAtColumn]; + const baseColumns = [ + createdAtColumn, + labelColumn, + keyColumn, + descriptionColumn, + dataTypeColumn, + updatedAtColumn, + ]; return isReadOnly ? baseColumns : [getSelectionColumn(), ...baseColumns]; }; diff --git a/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx b/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx index 6b6dc11e77..14cda6ce93 100644 --- a/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx +++ b/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx @@ -1,12 +1,13 @@ "use client"; -import { PlusIcon } from "lucide-react"; +import { Calendar1Icon, HashIcon, PlusIcon, TagIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; +import { TContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; -import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; +import { formatSnakeCaseToTitleCase, isSafeIdentifier } from "@/lib/utils/safe-identifier"; import { Button } from "@/modules/ui/components/button"; import { Dialog, @@ -18,6 +19,13 @@ import { DialogTitle, } from "@/modules/ui/components/dialog"; import { Input } from "@/modules/ui/components/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/modules/ui/components/select"; import { createContactAttributeKeyAction } from "../actions"; interface CreateAttributeModalProps { @@ -33,6 +41,7 @@ export function CreateAttributeModal({ environmentId }: Readonly(""); @@ -41,6 +50,7 @@ export function CreateAttributeModal({ environmentId }: Readonly { - setFormData((prev) => ({ ...prev, key: value })); + const previousAutoLabel = formData.key ? formatSnakeCaseToTitleCase(formData.key) : ""; + const newAutoLabel = value ? formatSnakeCaseToTitleCase(value) : ""; + + setFormData((prev) => { + // Auto-update name if it's empty or matches the previous auto-generated label + const shouldAutoUpdateName = !prev.name || prev.name === previousAutoLabel; + return { + ...prev, + key: value, + name: shouldAutoUpdateName ? newAutoLabel : prev.name, + }; + }); validateKey(value); }; @@ -90,8 +111,9 @@ export function CreateAttributeModal({ environmentId }: Readonly
    +
    + + +

    {t("environments.contacts.data_type_description")}

    +
    +
    +
    + +
    + {getDataTypeIcon(attribute.dataType)} + +
    +

    + {t("environments.contacts.data_type_cannot_be_changed")} +

    +
    +