Compare commits

...

53 Commits

Author SHA1 Message Date
Dhruwang a6b777db6f fix: translation 2026-03-10 11:27:57 +05:30
Balázs Úr 34c587342c update translatable keys 2026-03-06 12:19:45 +01:00
Balázs Úr 5a0b421153 merged main 2026-03-06 11:47:59 +01:00
Chowdhury Tafsir Ahmed Siddiki af02ce9ea6 fix: display native language names in profile language selector (#7349)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-03-06 10:18:52 +00:00
Bhagya Amarasinghe fc1c91896a fix: add server-side SSRF validation for webhook URLs (#7414)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-03-06 07:36:49 +00:00
Balázs Úr f5c7dbdc71 fix: mark duplicated survey name as translatable (#7379)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-03-06 06:37:05 +00:00
Balázs Úr b88ea5cc66 fix: use proper plural forms (#7322)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-03-06 06:30:27 +00:00
bharath kumar f31085a9e7 fix(i18n): resolve duplicate Hungarian translations causing Career Development Survey creation to fail (#7410)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-03-06 05:39:05 +00:00
Dhruwang Jariwala 2ab0441404 fix: z-index for multi select question with dropdwon display type (#7420) 2026-03-06 04:56:39 +00:00
Dhruwang Jariwala 299ae81b21 chore: mls tweaks (#7416) 2026-03-05 14:55:45 +00:00
Bhagya Amarasinghe f73f13f16c perf: fix Prisma connection pool saturation from unbounded Promise.all fan-outs (#7404) 2026-03-05 14:35:40 +00:00
Matti Nannt e9bcbf6e4c fix: patch @isaacs/brace-expansion to 5.0.1 (#7424) 2026-03-05 13:35:48 +00:00
Matti Nannt 32eda35a71 chore: clean up stale turbo task config (#7423) 2026-03-05 11:49:24 +00:00
Dhruwang Jariwala 84999cddfd feat: danish support to surveys package (#7415) 2026-03-05 11:05:40 +00:00
Matti Nannt f0a0cf531a chore: clean up unused npm dependencies (#7417)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-03-05 10:48:13 +00:00
Matti Nannt f3e02fa466 chore: optimize monorepo build performance (#7419) 2026-03-05 10:18:54 +00:00
Dhruwang Jariwala f0a93ae092 fix: add Tailwind v3 config for Prettier in apps/web and packages/email (#7421) 2026-03-05 10:05:05 +00:00
Matti Nannt 1c922dfe2c chore: remove legacy post-checkout hook (#7418) 2026-03-05 08:14:19 +00:00
Bhagya Amarasinghe 33010fb6f5 fix: auto-save creates duplicate follow ups (#7413)
Co-authored-by: gulshank0 <gulshanbahadur002@gmail.com>
2026-03-05 00:44:29 +00:00
Matti Nannt d5fdacadd7 chore: update dependencies and fix build/lint/test regressions (#7403) 2026-03-03 17:03:03 +00:00
bharath kumar d939263472 fix(sdk): add userId length limit to mitigate DoS attack risk (#7378)
Co-authored-by: Matti Nannt <matti@formbricks.com>
2026-03-03 10:10:01 +01:00
Dhruwang Jariwala e4aa66b067 fix: removed legacy response note traces (#7396) 2026-03-02 12:58:37 +00:00
Dhruwang Jariwala ffcc101ed9 chore: make productionBrowserSourceMaps conditional to decrease build time (#7400) 2026-03-02 09:49:00 +00:00
Balázs Úr 2740cd16b9 fix: delete confirmation dialog title translation (#7358)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-03-02 07:06:14 +00:00
Dhruwang Jariwala 7eb94f0bd5 fix: theme styling preview, option border color, and enable custom styling behavior (#7387)
Co-authored-by: Johannes <johannes@formbricks.com>
2026-03-02 06:17:52 +00:00
Johannes 6dd2e707fe feat: display Formbricks version alongside organization ID in settings (#7363) 2026-03-02 05:54:23 +00:00
Matti Nannt 58d5de7d45 fix: resolve Dependabot Next.js deserialization alert (#7393) 2026-02-27 22:18:38 +01:00
Dhruwang Jariwala 7c3fa8b5ea fix: restore bullet points in survey preview and public survey (#7356) (#7360)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-27 18:24:15 +00:00
Harsh Bhat 2601169877 docs: add advanced CSS variable updates (#7389)
Co-authored-by: Johannes <johannes@formbricks.com>
2026-02-27 17:19:22 +00:00
bharath kumar aecf85815a fix(js-core): use closest() fallback for nested click target matching (#7327)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-27 06:24:58 +00:00
Bhagya Amarasinghe c6ebaea989 fix: set success_action_status on S3 presigned POST to fix CORS on Ceph-based providers (#7362) 2026-02-26 10:26:49 +00:00
Bhagya Amarasinghe 68c1422733 fix: copy database package.json to Docker runner stage (#7371) 2026-02-26 10:25:28 +00:00
Dhruwang Jariwala 6942502baf fix: slack missing redirect uri (#7372) 2026-02-26 10:01:25 +00:00
Balázs Úr f6fab9a996 fix connector rendering 2026-02-26 09:39:15 +01:00
Balázs Úr fe33527da8 fix: mark strings as translatable in survey editor 2026-02-26 08:29:05 +01:00
Theodór Tómas a4bd217761 chore: update to zod 3.25.76 (#7366) 2026-02-26 05:17:20 +00:00
Bhagya Amarasinghe fee770358c perf(contacts): build segment WHERE clauses sequentially to prevent pool saturation (#7354)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-02-25 15:25:32 +00:00
Dhruwang Jariwala 44f8f80cac docs: clarify startAt is block-based, not question-based (#1404) (#7352)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 13:19:30 +00:00
Chowdhury Tafsir Ahmed Siddiki 858a7f7aa9 fix: replace toSorted in breadcrumb switchers for compatibility (#7325)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-25 06:29:31 +00:00
Gulshan ac40b90e81 fix: made "Filter" string translatable (#7301)
Signed-off-by: gulshank0 <gulshanbahadur002@gmail.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-25 06:28:51 +00:00
Balázs Úr aa21b4e442 fix: made Contact's page titles and table headers translatable (#7313)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-24 14:07:05 +00:00
Dhruwang Jariwala fa72296de5 fix: error state for multi select question (#7335) 2026-02-24 13:34:48 +00:00
Johannes 3776b31794 feat: add impressions tab and display data retrieval for surveys (#7266)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-24 11:00:58 +00:00
Bhagya Amarasinghe 5c7ea33fb0 feat: add pod disruption budget for helm chart (#7339) 2026-02-24 10:43:16 +00:00
Balázs Úr 33f60ce2be fix: button label on create attribute dialog (#7331)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-24 08:30:20 +00:00
Bhagya Amarasinghe c0386cea5a perf(contacts): batch segment evaluation queries into single transaction (#7333)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 08:26:46 +00:00
Anshuman Pandey 7cea53130c chore: adds webhook signing to test event (#7320) 2026-02-23 12:36:50 +00:00
Dhruwang Jariwala 0636989d67 fix: update test configuration to exclude .next directory from testing (#7334) 2026-02-23 11:33:17 +01:00
Anshuman Pandey 219883266c fix: add bool support (#7323) 2026-02-20 15:30:40 +00:00
Theodór Tómas 55fc2b2bc8 chore: removing i18n from pre-commit hook (#7318) 2026-02-20 10:48:44 +00:00
neila 6e4ef9a099 fix: make pretty URL paths accessible from public domain (#7264)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-20 09:55:40 +00:00
Chowdhury Tafsir Ahmed Siddiki ebf7d1e3a1 fix: prevent crash in NotificationSwitch via optional chaining (#7268)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-20 09:55:06 +00:00
Dhruwang Jariwala 998162bc48 fix: Google Sheets integration — token expiry & permission error handling (#7282) (#7285) 2026-02-20 08:56:24 +00:00
309 changed files with 10102 additions and 9154 deletions
+20 -22
View File
@@ -6,19 +6,9 @@ permissions:
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
paths:
- "apps/web/**/*.ts"
- "apps/web/**/*.tsx"
- "apps/web/locales/**/*.json"
- "scan-translations.ts"
push: push:
branches: branches:
- main - main
paths:
- "apps/web/**/*.ts"
- "apps/web/**/*.tsx"
- "apps/web/locales/**/*.json"
- "scan-translations.ts"
jobs: jobs:
validate-translations: validate-translations:
@@ -33,30 +23,38 @@ jobs:
egress-policy: audit egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check for relevant changes
id: changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis filters: |
translations:
- 'apps/web/**/*.ts'
- 'apps/web/**/*.tsx'
- 'apps/web/locales/**/*.json'
- 'packages/surveys/src/**/*.{ts,tsx}'
- 'packages/surveys/locales/**/*.json'
- 'packages/email/**/*.{ts,tsx}'
- name: Setup Node.js 22.x - name: Setup Node.js 22.x
if: steps.changes.outputs.translations == 'true'
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with: with:
node-version: 22.x node-version: 22.x
- name: Install pnpm - name: Install pnpm
if: steps.changes.outputs.translations == 'true'
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Install dependencies - name: Install dependencies
if: steps.changes.outputs.translations == 'true'
run: pnpm install --config.platform=linux --config.architecture=x64 run: pnpm install --config.platform=linux --config.architecture=x64
- name: Validate translation keys - name: Validate translation keys
run: | if: steps.changes.outputs.translations == 'true'
echo "" run: pnpm run scan-translations
echo "🔍 Validating translation keys..."
echo ""
pnpm run scan-translations
- name: Summary - name: Skip (no translation-related changes)
if: success() if: steps.changes.outputs.translations != 'true'
run: | run: echo "No translation-related files changed — skipping validation."
echo ""
echo "✅ Translation validation completed successfully!"
echo ""
-2
View File
@@ -1,2 +0,0 @@
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ./branch.json
prettier --write ./branch.json
+1 -40
View File
@@ -1,40 +1 @@
# Load environment variables from .env files pnpm lint-staged
if [ -f .env ]; then
set -a
. .env
set +a
fi
pnpm lint-staged
# Run Lingo.dev i18n workflow if LINGODOTDEV_API_KEY is set
if [ -n "$LINGODOTDEV_API_KEY" ]; then
echo ""
echo "🌍 Running Lingo.dev translation workflow..."
echo ""
# Run translation generation and validation
if pnpm run i18n; then
echo ""
echo "✅ Translation validation passed"
echo ""
# Add updated locale files to git
git add apps/web/locales/*.json
else
echo ""
echo "❌ Translation validation failed!"
echo ""
echo "Please fix the translation issues above before committing:"
echo " • Add missing translation keys to your locale files"
echo " • Remove unused translation keys"
echo ""
echo "Or run 'pnpm i18n' to see the detailed report"
echo ""
exit 1
fi
else
echo ""
echo "⚠️ Skipping translation validation: LINGODOTDEV_API_KEY is not set"
echo " (This is expected for community contributors)"
echo ""
fi
+12 -17
View File
@@ -10,25 +10,20 @@
"build-storybook": "storybook build", "build-storybook": "storybook build",
"clean": "rimraf .turbo node_modules dist storybook-static" "clean": "rimraf .turbo node_modules dist storybook-static"
}, },
"dependencies": {
"@formbricks/survey-ui": "workspace:*"
},
"devDependencies": { "devDependencies": {
"@chromatic-com/storybook": "^5.0.0", "@chromatic-com/storybook": "^5.0.1",
"@storybook/addon-a11y": "10.1.11", "@storybook/addon-a11y": "10.2.14",
"@storybook/addon-links": "10.1.11", "@storybook/addon-links": "10.2.14",
"@storybook/addon-onboarding": "10.1.11", "@storybook/addon-onboarding": "10.2.14",
"@storybook/react-vite": "10.1.11", "@storybook/react-vite": "10.2.14",
"@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/eslint-plugin": "8.56.1",
"@tailwindcss/vite": "4.1.18", "@tailwindcss/vite": "4.2.1",
"@typescript-eslint/parser": "8.53.0", "@typescript-eslint/parser": "8.56.1",
"@vitejs/plugin-react": "5.1.2", "@vitejs/plugin-react": "5.1.4",
"esbuild": "0.25.12",
"eslint-plugin-react-refresh": "0.4.26", "eslint-plugin-react-refresh": "0.4.26",
"eslint-plugin-storybook": "10.1.11", "eslint-plugin-storybook": "10.2.14",
"prop-types": "15.8.1", "storybook": "10.2.14",
"storybook": "10.1.11",
"vite": "7.3.1", "vite": "7.3.1",
"@storybook/addon-docs": "10.1.11" "@storybook/addon-docs": "10.2.14"
} }
} }
+6
View File
@@ -0,0 +1,6 @@
const baseConfig = require("../../.prettierrc.js");
module.exports = {
...baseConfig,
tailwindConfig: "./tailwind.config.js",
};
+3
View File
@@ -101,6 +101,9 @@ RUN chown -R nextjs:nextjs ./apps/web/public && chmod -R 755 ./apps/web/public
# Create packages/database directory structure with proper ownership for runtime migrations # Create packages/database directory structure with proper ownership for runtime migrations
RUN mkdir -p ./packages/database/migrations && chown -R nextjs:nextjs ./packages/database RUN mkdir -p ./packages/database/migrations && chown -R nextjs:nextjs ./packages/database
COPY --from=installer /app/packages/database/package.json ./packages/database/package.json
RUN chown nextjs:nextjs ./packages/database/package.json && chmod 644 ./packages/database/package.json
COPY --from=installer /app/packages/database/schema.prisma ./packages/database/schema.prisma COPY --from=installer /app/packages/database/schema.prisma ./packages/database/schema.prisma
RUN chown nextjs:nextjs ./packages/database/schema.prisma && chmod 644 ./packages/database/schema.prisma RUN chown nextjs:nextjs ./packages/database/schema.prisma && chmod 644 ./packages/database/schema.prisma
@@ -69,7 +69,7 @@ export const ConnectWithFormbricks = ({
) : ( ) : (
<div className="flex animate-pulse flex-col items-center space-y-4"> <div className="flex animate-pulse flex-col items-center space-y-4">
<span className="relative flex h-10 w-10"> <span className="relative flex h-10 w-10">
<span className="animate-ping-slow absolute inline-flex h-full w-full rounded-full bg-slate-400 opacity-75"></span> <span className="absolute inline-flex h-full w-full animate-ping-slow rounded-full bg-slate-400 opacity-75"></span>
<span className="relative inline-flex h-10 w-10 rounded-full bg-slate-500"></span> <span className="relative inline-flex h-10 w-10 rounded-full bg-slate-500"></span>
</span> </span>
<p className="pt-4 text-sm font-medium text-slate-600"> <p className="pt-4 text-sm font-medium text-slate-600">
@@ -46,7 +46,7 @@ const Page = async (props: ConnectPageProps) => {
channel={channel} channel={channel}
/> />
<Button <Button
className="absolute top-5 right-5 !mt-0 text-slate-500 hover:text-slate-700" className="absolute right-5 top-5 !mt-0 text-slate-500 hover:text-slate-700"
variant="ghost" variant="ghost"
asChild> asChild>
<Link href={`/environments/${environment.id}`}> <Link href={`/environments/${environment.id}`}>
@@ -49,7 +49,7 @@ const Page = async (props: XMTemplatePageProps) => {
<XMTemplateList project={project} user={user} environmentId={environment.id} /> <XMTemplateList project={project} user={user} environmentId={environment.id} />
{projects.length >= 2 && ( {projects.length >= 2 && (
<Button <Button
className="absolute top-5 right-5 !mt-0 text-slate-500 hover:text-slate-700" className="absolute right-5 top-5 !mt-0 text-slate-500 hover:text-slate-700"
variant="ghost" variant="ghost"
asChild> asChild>
<Link href={`/environments/${environment.id}/surveys`}> <Link href={`/environments/${environment.id}/surveys`}>
@@ -42,7 +42,7 @@ export const LandingSidebar = ({ user, organization }: LandingSidebarProps) => {
return ( return (
<aside <aside
className={cn( className={cn(
"w-sidebar-collapsed z-40 flex flex-col justify-between rounded-r-xl border-r border-slate-200 bg-white pt-3 shadow-md transition-all duration-100" "z-40 flex w-sidebar-collapsed flex-col justify-between rounded-r-xl border-r border-slate-200 bg-white pt-3 shadow-md transition-all duration-100"
)}> )}>
<Image src={FBLogo} width={160} height={30} alt={t("environments.formbricks_logo")} /> <Image src={FBLogo} width={160} height={30} alt={t("environments.formbricks_logo")} />
@@ -50,7 +50,7 @@ const Page = async (props: ChannelPageProps) => {
<OnboardingOptionsContainer options={channelOptions} /> <OnboardingOptionsContainer options={channelOptions} />
{projects.length >= 1 && ( {projects.length >= 1 && (
<Button <Button
className="absolute top-5 right-5 !mt-0 text-slate-500 hover:text-slate-700" className="absolute right-5 top-5 !mt-0 text-slate-500 hover:text-slate-700"
variant="ghost" variant="ghost"
asChild> asChild>
<Link href={"/"}> <Link href={"/"}>
@@ -47,7 +47,7 @@ const Page = async (props: ModePageProps) => {
<OnboardingOptionsContainer options={channelOptions} /> <OnboardingOptionsContainer options={channelOptions} />
{projects.length >= 1 && ( {projects.length >= 1 && (
<Button <Button
className="absolute top-5 right-5 !mt-0 text-slate-500 hover:text-slate-700" className="absolute right-5 top-5 !mt-0 text-slate-500 hover:text-slate-700"
variant="ghost" variant="ghost"
asChild> asChild>
<Link href={"/"}> <Link href={"/"}>
@@ -228,7 +228,7 @@ export const ProjectSettings = ({
</FormProvider> </FormProvider>
</div> </div>
<div className="relative flex h-[30rem] w-1/2 flex-col items-center justify-center space-y-2 rounded-lg border bg-slate-200 shadow"> <div className="relative flex w-1/2 flex-col items-center justify-center space-y-2 rounded-lg border bg-slate-200 p-6 shadow">
{logoUrl && ( {logoUrl && (
<Image <Image
src={logoUrl} src={logoUrl}
@@ -239,18 +239,16 @@ export const ProjectSettings = ({
/> />
)} )}
<p className="text-sm text-slate-400">{t("common.preview")}</p> <p className="text-sm text-slate-400">{t("common.preview")}</p>
<div className="z-0 h-3/4 w-3/4"> <SurveyInline
<SurveyInline appUrl={publicDomain}
appUrl={publicDomain} isPreviewMode={true}
isPreviewMode={true} survey={previewSurvey(projectName || t("common.my_product"), t)}
survey={previewSurvey(projectName || "my Product", t)} styling={previewStyling}
styling={previewStyling} isBrandingEnabled={false}
isBrandingEnabled={false} languageCode="default"
languageCode="default" onFileUpload={async (file) => file.name}
onFileUpload={async (file) => file.name} autoFocus={false}
autoFocus={false} />
/>
</div>
</div> </div>
<CreateTeamModal <CreateTeamModal
open={createTeamModalOpen} open={createTeamModalOpen}
@@ -69,7 +69,7 @@ const Page = async (props: ProjectSettingsPageProps) => {
/> />
{projects.length >= 1 && ( {projects.length >= 1 && (
<Button <Button
className="absolute top-5 right-5 !mt-0 text-slate-500 hover:text-slate-700" className="absolute right-5 top-5 !mt-0 text-slate-500 hover:text-slate-700"
variant="ghost" variant="ghost"
asChild> asChild>
<Link href={"/"}> <Link href={"/"}>
@@ -188,7 +188,7 @@ export const MainNavigation = ({
size="icon" size="icon"
onClick={toggleSidebar} onClick={toggleSidebar}
className={cn( className={cn(
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:ring-0 focus:ring-transparent focus:outline-none" "rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:outline-none focus:ring-0 focus:ring-transparent"
)}> )}>
{isCollapsed ? ( {isCollapsed ? (
<PanelLeftOpenIcon strokeWidth={1.5} /> <PanelLeftOpenIcon strokeWidth={1.5} />
@@ -53,7 +53,7 @@ export const WidgetStatusIndicator = ({ environment }: WidgetStatusIndicatorProp
<currentStatus.icon /> <currentStatus.icon />
</div> </div>
<p className="text-md font-bold text-slate-800 md:text-xl">{currentStatus.title}</p> <p className="text-md font-bold text-slate-800 md:text-xl">{currentStatus.title}</p>
<p className="w-2/3 text-sm text-balance text-slate-600">{currentStatus.subtitle}</p> <p className="w-2/3 text-balance text-sm text-slate-600">{currentStatus.subtitle}</p>
{status === "notImplemented" && ( {status === "notImplemented" && (
<Button variant="outline" size="sm" className="bg-white" onClick={() => router.refresh()}> <Button variant="outline" size="sm" className="bg-white" onClick={() => router.refresh()}>
<RotateCcwIcon /> <RotateCcwIcon />
@@ -81,7 +81,7 @@ export const OrganizationBreadcrumb = ({
getOrganizationsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => { getOrganizationsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => {
if (result?.data) { if (result?.data) {
// Sort organizations by name // Sort organizations by name
const sorted = result.data.toSorted((a, b) => a.name.localeCompare(b.name)); const sorted = [...result.data].sort((a, b) => a.name.localeCompare(b.name));
setOrganizations(sorted); setOrganizations(sorted);
} else { } else {
// Handle server errors or validation errors // Handle server errors or validation errors
@@ -82,7 +82,7 @@ export const ProjectBreadcrumb = ({
getProjectsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => { getProjectsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => {
if (result?.data) { if (result?.data) {
// Sort projects by name // Sort projects by name
const sorted = result.data.toSorted((a, b) => a.name.localeCompare(b.name)); const sorted = [...result.data].sort((a, b) => a.name.localeCompare(b.name));
setProjects(sorted); setProjects(sorted);
} else { } else {
// Handle server errors or validation errors // Handle server errors or validation errors
@@ -30,7 +30,7 @@ export const NotificationSwitch = ({
const isChecked = const isChecked =
notificationType === "unsubscribedOrganizationIds" notificationType === "unsubscribedOrganizationIds"
? !notificationSettings.unsubscribedOrganizationIds?.includes(surveyOrProjectOrOrganizationId) ? !notificationSettings.unsubscribedOrganizationIds?.includes(surveyOrProjectOrOrganizationId)
: notificationSettings[notificationType][surveyOrProjectOrOrganizationId] === true; : notificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId] === true;
const handleSwitchChange = async () => { const handleSwitchChange = async () => {
setIsLoading(true); setIsLoading(true);
@@ -49,8 +49,11 @@ export const NotificationSwitch = ({
]; ];
} }
} else { } else {
updatedNotificationSettings[notificationType][surveyOrProjectOrOrganizationId] = updatedNotificationSettings[notificationType] = {
!updatedNotificationSettings[notificationType][surveyOrProjectOrOrganizationId]; ...updatedNotificationSettings[notificationType],
[surveyOrProjectOrOrganizationId]:
!updatedNotificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId],
};
} }
const updatedNotificationSettingsActionResponse = await updateNotificationSettingsAction({ const updatedNotificationSettingsActionResponse = await updateNotificationSettingsAction({
@@ -78,7 +81,7 @@ export const NotificationSwitch = ({
) { ) {
switch (notificationType) { switch (notificationType) {
case "alert": case "alert":
if (notificationSettings[notificationType][surveyOrProjectOrOrganizationId] === true) { if (notificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId] === true) {
handleSwitchChange(); handleSwitchChange();
toast.success( toast.success(
t( t(
@@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
import { z } from "zod"; import { z } from "zod";
import { TUser, TUserUpdateInput, ZUser, ZUserEmail } from "@formbricks/types/user"; import { TUser, TUserUpdateInput, ZUser, ZUserEmail } from "@formbricks/types/user";
import { PasswordConfirmationModal } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/password-confirmation-modal"; import { PasswordConfirmationModal } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/password-confirmation-modal";
import { appLanguages } from "@/lib/i18n/utils"; import { appLanguages, sortedAppLanguages } from "@/lib/i18n/utils";
import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { useSignOut } from "@/modules/auth/hooks/use-sign-out"; import { useSignOut } from "@/modules/auth/hooks/use-sign-out";
import { Button } from "@/modules/ui/components/button"; import { Button } from "@/modules/ui/components/button";
@@ -198,41 +198,54 @@ export const EditProfileDetailsForm = ({
<FormField <FormField
control={form.control} control={form.control}
name="locale" name="locale"
render={({ field }) => ( render={({ field }) => {
<FormItem className="mt-4"> const selectedLanguage = appLanguages.find((l) => l.code === field.value);
<FormLabel>{t("common.language")}</FormLabel>
<FormControl> return (
<DropdownMenu> <FormItem className="mt-4">
<DropdownMenuTrigger asChild> <FormLabel>{t("common.language")}</FormLabel>
<Button <FormControl>
type="button" <DropdownMenu>
variant="ghost" <DropdownMenuTrigger asChild>
className="h-10 w-full border border-slate-300 px-3 text-left"> <Button
<div className="flex w-full items-center justify-between"> type="button"
{appLanguages.find((l) => l.code === field.value)?.label["en-US"] ?? "NA"} variant="ghost"
<ChevronDownIcon className="h-4 w-4 text-slate-500" /> className="h-10 w-full border border-slate-300 px-3 text-left">
</div> <div className="flex w-full items-center justify-between">
</Button> {selectedLanguage ? (
</DropdownMenuTrigger> <>
<DropdownMenuContent {selectedLanguage.label["en-US"]}
className="min-w-[var(--radix-dropdown-menu-trigger-width)] bg-white text-slate-700" {selectedLanguage.label.native !== selectedLanguage.label["en-US"] &&
align="start"> ` (${selectedLanguage.label.native})`}
<DropdownMenuRadioGroup value={field.value} onValueChange={field.onChange}> </>
{appLanguages.map((lang) => ( ) : (
<DropdownMenuRadioItem t("common.select")
key={lang.code} )}
value={lang.code} <ChevronDownIcon className="h-4 w-4 text-slate-500" />
className="min-h-8 cursor-pointer"> </div>
{lang.label["en-US"]} </Button>
</DropdownMenuRadioItem> </DropdownMenuTrigger>
))} <DropdownMenuContent
</DropdownMenuRadioGroup> className="min-w-[var(--radix-dropdown-menu-trigger-width)] bg-white text-slate-700"
</DropdownMenuContent> align="start">
</DropdownMenu> <DropdownMenuRadioGroup value={field.value} onValueChange={field.onChange}>
</FormControl> {sortedAppLanguages.map((lang) => (
<FormError /> <DropdownMenuRadioItem
</FormItem> key={lang.code}
)} value={lang.code}
className="min-h-8 cursor-pointer">
{lang.label["en-US"]}
{lang.label.native !== lang.label["en-US"] && ` (${lang.label.native})`}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</FormControl>
<FormError />
</FormItem>
);
}}
/> />
{isPasswordResetEnabled && ( {isPasswordResetEnabled && (
@@ -98,7 +98,7 @@ export const PasswordConfirmationModal = ({
aria-label="password" aria-label="password"
aria-required="true" aria-required="true"
required required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm" className="block w-full rounded-md border-slate-300 shadow-sm focus:border-brand-dark focus:ring-brand-dark sm:text-sm"
value={field.value} value={field.value}
onChange={(password) => field.onChange(password)} onChange={(password) => field.onChange(password)}
/> />
@@ -9,6 +9,7 @@ import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { IdBadge } from "@/modules/ui/components/id-badge"; import { IdBadge } from "@/modules/ui/components/id-badge";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header"; import { PageHeader } from "@/modules/ui/components/page-header";
import packageJson from "@/package.json";
import { SettingsCard } from "../../components/SettingsCard"; import { SettingsCard } from "../../components/SettingsCard";
import { DeleteOrganization } from "./components/DeleteOrganization"; import { DeleteOrganization } from "./components/DeleteOrganization";
import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm"; import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm";
@@ -81,7 +82,10 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
</SettingsCard> </SettingsCard>
)} )}
<IdBadge id={organization.id} label={t("common.organization_id")} variant="column" /> <div className="space-y-2">
<IdBadge id={organization.id} label={t("common.organization_id")} variant="column" />
<IdBadge id={packageJson.version} label={t("common.formbricks_version")} variant="column" />
</div>
</PageContentWrapper> </PageContentWrapper>
); );
}; };
@@ -4,6 +4,7 @@ import { revalidatePath } from "next/cache";
import { z } from "zod"; import { z } from "zod";
import { ZId } from "@formbricks/types/common"; import { ZId } from "@formbricks/types/common";
import { ZResponseFilterCriteria } from "@formbricks/types/responses"; import { ZResponseFilterCriteria } from "@formbricks/types/responses";
import { getDisplaysBySurveyIdWithContact } from "@/lib/display/service";
import { getResponseCountBySurveyId, getResponses } from "@/lib/response/service"; import { getResponseCountBySurveyId, getResponses } from "@/lib/response/service";
import { authenticatedActionClient } from "@/lib/utils/action-client"; import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
@@ -106,3 +107,31 @@ export const getResponseCountAction = authenticatedActionClient
return getResponseCountBySurveyId(parsedInput.surveyId, parsedInput.filterCriteria); return getResponseCountBySurveyId(parsedInput.surveyId, parsedInput.filterCriteria);
}); });
const ZGetDisplaysWithContactAction = z.object({
surveyId: ZId,
limit: z.number().int().min(1).max(100),
offset: z.number().int().nonnegative(),
});
export const getDisplaysWithContactAction = authenticatedActionClient
.schema(ZGetDisplaysWithContactAction)
.action(async ({ ctx, parsedInput }) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: await getOrganizationIdFromSurveyId(parsedInput.surveyId),
access: [
{
type: "organization",
roles: ["owner", "manager"],
},
{
type: "projectTeam",
minPermission: "read",
projectId: await getProjectIdFromSurveyId(parsedInput.surveyId),
},
],
});
return getDisplaysBySurveyIdWithContact(parsedInput.surveyId, parsedInput.limit, parsedInput.offset);
});
@@ -3,6 +3,7 @@ import { getServerSession } from "next-auth";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/response-filter-context"; import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/response-filter-context";
import { getResponseCountBySurveyId } from "@/lib/response/service"; import { getResponseCountBySurveyId } from "@/lib/response/service";
import { getSurvey } from "@/lib/survey/service"; import { getSurvey } from "@/lib/survey/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions"; import { authOptions } from "@/modules/auth/lib/authOptions";
type Props = { type Props = {
@@ -14,10 +15,11 @@ export const generateMetadata = async (props: Props): Promise<Metadata> => {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
const survey = await getSurvey(params.surveyId); const survey = await getSurvey(params.surveyId);
const responseCount = await getResponseCountBySurveyId(params.surveyId); const responseCount = await getResponseCountBySurveyId(params.surveyId);
const t = await getTranslate();
if (session) { if (session) {
return { return {
title: `${responseCount} Responses | ${survey?.name} Results`, title: `${t("common.count_responses", { count: responseCount })} | ${t("environments.surveys.summary.survey_results", { surveyName: survey?.name })}`,
}; };
} }
return { return {
@@ -30,8 +30,7 @@ export const CalSummary = ({ elementSummary, survey }: CalSummaryProps) => {
</div> </div>
</div> </div>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{elementSummary.booked.count}{" "} {t("common.count_responses", { count: elementSummary.booked.count })}
{elementSummary.booked.count === 1 ? t("common.response") : t("common.responses")}
</p> </p>
</div> </div>
<ProgressBar barColor="bg-brand-dark" progress={elementSummary.booked.percentage / 100} /> <ProgressBar barColor="bg-brand-dark" progress={elementSummary.booked.percentage / 100} />
@@ -47,8 +46,7 @@ export const CalSummary = ({ elementSummary, survey }: CalSummaryProps) => {
</div> </div>
</div> </div>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{elementSummary.skipped.count}{" "} {t("common.count_responses", { count: elementSummary.skipped.count })}
{elementSummary.skipped.count === 1 ? t("common.response") : t("common.responses")}
</p> </p>
</div> </div>
<ProgressBar barColor="bg-brand-dark" progress={elementSummary.skipped.percentage / 100} /> <ProgressBar barColor="bg-brand-dark" progress={elementSummary.skipped.percentage / 100} />
@@ -64,7 +64,7 @@ export const ConsentSummary = ({ elementSummary, survey, setFilter }: ConsentSum
</div> </div>
</div> </div>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{summaryItem.count} {summaryItem.count === 1 ? t("common.response") : t("common.responses")} {t("common.count_responses", { count: summaryItem.count })}
</p> </p>
</div> </div>
<div className="group-hover:opacity-80"> <div className="group-hover:opacity-80">
@@ -48,7 +48,7 @@ export const ElementSummaryHeader = ({
{showResponses && ( {showResponses && (
<div className="flex items-center rounded-lg bg-slate-100 p-2"> <div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxIcon className="mr-2 h-4 w-4" /> <InboxIcon className="mr-2 h-4 w-4" />
{`${elementSummary.responseCount} ${t("common.responses")}`} {t("common.count_responses", { count: elementSummary.responseCount })}
</div> </div>
)} )}
{additionalInfo} {additionalInfo}
@@ -41,8 +41,7 @@ export const HiddenFieldsSummary = ({ environment, elementSummary, locale }: Hid
</div> </div>
<div className="flex items-center rounded-lg bg-slate-100 p-2"> <div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxIcon className="mr-2 h-4 w-4" /> <InboxIcon className="mr-2 h-4 w-4" />
{elementSummary.responseCount}{" "} {t("common.count_responses", { count: elementSummary.responseCount })}
{elementSummary.responseCount === 1 ? t("common.response") : t("common.responses")}
</div> </div>
</div> </div>
</div> </div>
@@ -31,7 +31,7 @@ export const MatrixElementSummary = ({ elementSummary, survey, setFilter }: Matr
if (label) { if (label) {
return label; return label;
} else if (percentage !== undefined && totalResponsesForRow !== undefined) { } else if (percentage !== undefined && totalResponsesForRow !== undefined) {
return `${Math.round((percentage / 100) * totalResponsesForRow)} ${t("common.responses")}`; return t("common.count_responses", { count: Math.round((percentage / 100) * totalResponsesForRow) });
} }
return ""; return "";
}; };
@@ -77,7 +77,7 @@ export const MatrixElementSummary = ({ elementSummary, survey, setFilter }: Matr
)}> )}>
<button <button
style={{ backgroundColor: `rgba(0,196,184,${getOpacityLevel(percentage)})` }} style={{ backgroundColor: `rgba(0,196,184,${getOpacityLevel(percentage)})` }}
className="hover:outline-brand-dark m-1 flex h-full w-40 cursor-pointer items-center justify-center rounded p-4 text-sm text-slate-950 hover:outline" className="m-1 flex h-full w-40 cursor-pointer items-center justify-center rounded p-4 text-sm text-slate-950 hover:outline hover:outline-brand-dark"
onClick={() => onClick={() =>
setFilter( setFilter(
elementSummary.element.id, elementSummary.element.id,
@@ -75,7 +75,7 @@ export const MultipleChoiceSummary = ({
elementSummary.type === "multipleChoiceMulti" ? ( elementSummary.type === "multipleChoiceMulti" ? (
<div className="flex items-center rounded-lg bg-slate-100 p-2"> <div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxIcon className="mr-2 h-4 w-4" /> <InboxIcon className="mr-2 h-4 w-4" />
{`${elementSummary.selectionCount} ${t("common.selections")}`} {t("common.count_selections", { count: elementSummary.selectionCount })}
</div> </div>
) : undefined ) : undefined
} }
@@ -110,7 +110,7 @@ export const MultipleChoiceSummary = ({
</div> </div>
<div className="flex w-full space-x-2"> <div className="flex w-full space-x-2">
<p className="flex w-full pt-1 text-slate-600 sm:items-end sm:justify-end sm:pt-0"> <p className="flex w-full pt-1 text-slate-600 sm:items-end sm:justify-end sm:pt-0">
{result.count} {result.count === 1 ? t("common.selection") : t("common.selections")} {t("common.count_selections", { count: result.count })}
</p> </p>
<p className="rounded-lg bg-slate-100 px-2 text-slate-700"> <p className="rounded-lg bg-slate-100 px-2 text-slate-700">
{convertFloatToNDecimal(result.percentage, 2)}% {convertFloatToNDecimal(result.percentage, 2)}%
@@ -123,8 +123,7 @@ export const NPSSummary = ({ elementSummary, survey, setFilter }: NPSSummaryProp
</div> </div>
</div> </div>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{elementSummary[group]?.count}{" "} {t("common.count_responses", { count: elementSummary[group]?.count })}
{elementSummary[group]?.count === 1 ? t("common.response") : t("common.responses")}
</p> </p>
</div> </div>
<ProgressBar <ProgressBar
@@ -158,7 +157,7 @@ export const NPSSummary = ({ elementSummary, survey, setFilter }: NPSSummaryProp
}> }>
<div className="flex h-32 w-full flex-col items-center justify-end"> <div className="flex h-32 w-full flex-col items-center justify-end">
<div <div
className="bg-brand-dark w-full rounded-t-lg border border-slate-200 transition-all group-hover:brightness-110" className="w-full rounded-t-lg border border-slate-200 bg-brand-dark transition-all group-hover:brightness-110"
style={{ style={{
height: `${Math.max(choice.percentage, 2)}%`, height: `${Math.max(choice.percentage, 2)}%`,
opacity, opacity,
@@ -37,7 +37,7 @@ export const PictureChoiceSummary = ({ elementSummary, survey, setFilter }: Pict
elementSummary.element.allowMulti ? ( elementSummary.element.allowMulti ? (
<div className="flex items-center rounded-lg bg-slate-100 p-2"> <div className="flex items-center rounded-lg bg-slate-100 p-2">
<InboxIcon className="mr-2 h-4 w-4" /> <InboxIcon className="mr-2 h-4 w-4" />
{`${elementSummary.selectionCount} ${t("common.selections")}`} {t("common.count_selections", { count: elementSummary.selectionCount })}
</div> </div>
) : undefined ) : undefined
} }
@@ -74,7 +74,7 @@ export const PictureChoiceSummary = ({ elementSummary, survey, setFilter }: Pict
</div> </div>
<div className="flex w-full space-x-2"> <div className="flex w-full space-x-2">
<p className="flex w-full pt-1 text-slate-600 sm:items-end sm:justify-end sm:pt-0"> <p className="flex w-full pt-1 text-slate-600 sm:items-end sm:justify-end sm:pt-0">
{result.count} {result.count === 1 ? t("common.selection") : t("common.selections")} {t("common.count_selections", { count: result.count })}
</p> </p>
<p className="self-end rounded-lg bg-slate-100 px-2 text-slate-700"> <p className="self-end rounded-lg bg-slate-100 px-2 text-slate-700">
{convertFloatToNDecimal(result.percentage, 2)}% {convertFloatToNDecimal(result.percentage, 2)}%
@@ -116,7 +116,7 @@ export const RatingSummary = ({ elementSummary, survey, setFilter }: RatingSumma
) )
}> }>
<div <div
className={`bg-brand-dark h-full ${isFirst ? "rounded-tl-lg" : ""} ${isLast ? "rounded-tr-lg" : ""}`} className={`h-full bg-brand-dark ${isFirst ? "rounded-tl-lg" : ""} ${isLast ? "rounded-tr-lg" : ""}`}
style={{ opacity }} style={{ opacity }}
/> />
</ClickableBarSegment> </ClickableBarSegment>
@@ -198,7 +198,7 @@ export const RatingSummary = ({ elementSummary, survey, setFilter }: RatingSumma
</div> </div>
</div> </div>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{result.count} {result.count === 1 ? t("common.response") : t("common.responses")} {t("common.count_responses", { count: result.count })}
</p> </p>
</div> </div>
<ProgressBar barColor="bg-brand-dark" progress={result.percentage / 100} /> <ProgressBar barColor="bg-brand-dark" progress={result.percentage / 100} />
@@ -215,8 +215,7 @@ export const RatingSummary = ({ elementSummary, survey, setFilter }: RatingSumma
<div className="text flex justify-between px-2"> <div className="text flex justify-between px-2">
<p className="font-semibold text-slate-700">{t("common.dismissed")}</p> <p className="font-semibold text-slate-700">{t("common.dismissed")}</p>
<p className="flex w-32 items-end justify-end text-slate-600"> <p className="flex w-32 items-end justify-end text-slate-600">
{elementSummary.dismissed.count}{" "} {t("common.count_responses", { count: elementSummary.dismissed.count })}
{elementSummary.dismissed.count === 1 ? t("common.response") : t("common.responses")}
</p> </p>
</div> </div>
</div> </div>
@@ -0,0 +1,125 @@
"use client";
import { AlertCircleIcon, InfoIcon } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { TDisplayWithContact } from "@formbricks/types/displays";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
import { Button } from "@/modules/ui/components/button";
interface SummaryImpressionsProps {
displays: TDisplayWithContact[];
isLoading: boolean;
hasMore: boolean;
displaysError: string | null;
environmentId: string;
locale: TUserLocale;
onLoadMore: () => void;
onRetry: () => void;
}
const getDisplayContactIdentifier = (display: TDisplayWithContact): string => {
if (!display.contact) return "";
return display.contact.attributes?.email || display.contact.attributes?.userId || display.contact.id;
};
export const SummaryImpressions = ({
displays,
isLoading,
hasMore,
displaysError,
environmentId,
locale,
onLoadMore,
onRetry,
}: SummaryImpressionsProps) => {
const { t } = useTranslation();
const renderContent = () => {
if (displaysError) {
return (
<div className="p-8">
<div className="flex flex-col items-center gap-4 text-center">
<div className="flex items-center gap-2 text-red-600">
<AlertCircleIcon className="h-5 w-5" />
<span className="text-sm font-medium">{t("common.error_loading_data")}</span>
</div>
<p className="text-sm text-slate-500">{displaysError}</p>
<Button onClick={onRetry} variant="secondary" size="sm">
{t("common.try_again")}
</Button>
</div>
</div>
);
}
if (displays.length === 0) {
return (
<div className="p-8 text-center text-sm text-slate-500">
{t("environments.surveys.summary.no_identified_impressions")}
</div>
);
}
return (
<>
<div className="grid min-h-10 grid-cols-4 items-center border-b border-slate-200 bg-slate-100 text-sm font-semibold text-slate-600">
<div className="col-span-2 px-4 md:px-6">{t("common.user")}</div>
<div className="col-span-2 px-4 md:px-6">{t("environments.contacts.survey_viewed_at")}</div>
</div>
<div className="max-h-[62vh] overflow-y-auto">
{displays.map((display) => (
<div
key={display.id}
className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-xs text-slate-800 last:border-transparent md:text-sm">
<div className="col-span-2 pl-4 md:pl-6">
{display.contact ? (
<Link
className="ph-no-capture break-all text-slate-600 hover:underline"
href={`/environments/${environmentId}/contacts/${display.contact.id}`}>
{getDisplayContactIdentifier(display)}
</Link>
) : (
<span className="break-all text-slate-600">{t("common.anonymous")}</span>
)}
</div>
<div className="col-span-2 px-4 text-slate-500 md:px-6">
{timeSince(display.createdAt.toString(), locale)}
</div>
</div>
))}
</div>
{hasMore && (
<div className="flex justify-center border-t border-slate-100 py-4">
<Button onClick={onLoadMore} variant="secondary" size="sm">
{t("common.load_more")}
</Button>
</div>
)}
</>
);
};
if (isLoading) {
return (
<div className="rounded-xl border border-slate-200 bg-white p-8 shadow-sm">
<div className="flex items-center justify-center">
<div className="h-6 w-32 animate-pulse rounded-full bg-slate-200"></div>
</div>
</div>
);
}
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="flex items-center gap-2 rounded-t-xl border-b border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<InfoIcon className="h-4 w-4 shrink-0" />
<span>{t("environments.surveys.summary.impressions_identified_only")}</span>
</div>
{renderContent()}
</div>
);
};
@@ -10,8 +10,8 @@ interface SummaryMetadataProps {
surveySummary: TSurveySummary["meta"]; surveySummary: TSurveySummary["meta"];
quotasCount: number; quotasCount: number;
isLoading: boolean; isLoading: boolean;
tab: "dropOffs" | "quotas" | undefined; tab: "dropOffs" | "quotas" | "impressions" | undefined;
setTab: React.Dispatch<React.SetStateAction<"dropOffs" | "quotas" | undefined>>; setTab: React.Dispatch<React.SetStateAction<"dropOffs" | "quotas" | "impressions" | undefined>>;
isQuotasAllowed: boolean; isQuotasAllowed: boolean;
} }
@@ -53,7 +53,7 @@ export const SummaryMetadata = ({
const { t } = useTranslation(); const { t } = useTranslation();
const dropoffCountValue = dropOffCount === 0 ? <span>-</span> : dropOffCount; const dropoffCountValue = dropOffCount === 0 ? <span>-</span> : dropOffCount;
const handleTabChange = (val: "dropOffs" | "quotas") => { const handleTabChange = (val: "dropOffs" | "quotas" | "impressions") => {
const change = tab === val ? undefined : val; const change = tab === val ? undefined : val;
setTab(change); setTab(change);
}; };
@@ -65,12 +65,16 @@ export const SummaryMetadata = ({
`grid gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-x-2 lg:grid-cols-3 2xl:grid-cols-5`, `grid gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-x-2 lg:grid-cols-3 2xl:grid-cols-5`,
isQuotasAllowed && quotasCount > 0 && "2xl:grid-cols-6" isQuotasAllowed && quotasCount > 0 && "2xl:grid-cols-6"
)}> )}>
<StatCard <InteractiveCard
key="impressions"
tab="impressions"
label={t("environments.surveys.summary.impressions")} label={t("environments.surveys.summary.impressions")}
percentage={null} percentage={null}
value={displayCount === 0 ? <span>-</span> : displayCount} value={displayCount === 0 ? <span>-</span> : displayCount}
tooltipText={t("environments.surveys.summary.impressions_tooltip")} tooltipText={t("environments.surveys.summary.impressions_tooltip")}
isLoading={isLoading} isLoading={isLoading}
onClick={() => handleTabChange("impressions")}
isActive={tab === "impressions"}
/> />
<StatCard <StatCard
label={t("environments.surveys.summary.starts")} label={t("environments.surveys.summary.starts")}
@@ -1,21 +1,31 @@
"use client"; "use client";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TDisplayWithContact } from "@formbricks/types/displays";
import { TEnvironment } from "@formbricks/types/environment"; import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey, TSurveySummary } from "@formbricks/types/surveys/types"; import { TSurvey, TSurveySummary } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user"; import { TUserLocale } from "@formbricks/types/user";
import { getSurveySummaryAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions"; import {
getDisplaysWithContactAction,
getSurveySummaryAction,
} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions";
import { useResponseFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/response-filter-context"; import { useResponseFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/response-filter-context";
import ScrollToTop from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop"; import ScrollToTop from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop";
import { SummaryDropOffs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs"; import { SummaryDropOffs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs";
import { SummaryImpressions } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryImpressions";
import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter"; import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter";
import { getFormattedFilters } from "@/app/lib/surveys/surveys"; import { getFormattedFilters } from "@/app/lib/surveys/surveys";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { replaceHeadlineRecall } from "@/lib/utils/recall"; import { replaceHeadlineRecall } from "@/lib/utils/recall";
import { QuotasSummary } from "@/modules/ee/quotas/components/quotas-summary"; import { QuotasSummary } from "@/modules/ee/quotas/components/quotas-summary";
import { SummaryList } from "./SummaryList"; import { SummaryList } from "./SummaryList";
import { SummaryMetadata } from "./SummaryMetadata"; import { SummaryMetadata } from "./SummaryMetadata";
const DISPLAYS_PER_PAGE = 15;
const defaultSurveySummary: TSurveySummary = { const defaultSurveySummary: TSurveySummary = {
meta: { meta: {
completedPercentage: 0, completedPercentage: 0,
@@ -51,17 +61,76 @@ export const SummaryPage = ({
initialSurveySummary, initialSurveySummary,
isQuotasAllowed, isQuotasAllowed,
}: SummaryPageProps) => { }: SummaryPageProps) => {
const { t } = useTranslation();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [surveySummary, setSurveySummary] = useState<TSurveySummary>( const [surveySummary, setSurveySummary] = useState<TSurveySummary>(
initialSurveySummary || defaultSurveySummary initialSurveySummary || defaultSurveySummary
); );
const [tab, setTab] = useState<"dropOffs" | "quotas" | undefined>(undefined); const [tab, setTab] = useState<"dropOffs" | "quotas" | "impressions" | undefined>(undefined);
const [isLoading, setIsLoading] = useState(!initialSurveySummary); const [isLoading, setIsLoading] = useState(!initialSurveySummary);
const { selectedFilter, dateRange, resetState } = useResponseFilter(); const { selectedFilter, dateRange, resetState } = useResponseFilter();
const [displays, setDisplays] = useState<TDisplayWithContact[]>([]);
const [isDisplaysLoading, setIsDisplaysLoading] = useState(false);
const [hasMoreDisplays, setHasMoreDisplays] = useState(true);
const [displaysError, setDisplaysError] = useState<string | null>(null);
const displaysFetchedRef = useRef(false);
const fetchDisplays = useCallback(
async (offset: number) => {
const response = await getDisplaysWithContactAction({
surveyId,
limit: DISPLAYS_PER_PAGE,
offset,
});
if (!response?.data) {
const errorMessage = getFormattedErrorMessage(response);
throw new Error(errorMessage);
}
return response?.data ?? [];
},
[surveyId]
);
const loadInitialDisplays = useCallback(async () => {
setIsDisplaysLoading(true);
setDisplaysError(null);
try {
const data = await fetchDisplays(0);
setDisplays(data);
setHasMoreDisplays(data.length === DISPLAYS_PER_PAGE);
} catch (error) {
toast.error(error);
setDisplays([]);
setHasMoreDisplays(false);
} finally {
setIsDisplaysLoading(false);
}
}, [fetchDisplays, t]);
const handleLoadMoreDisplays = useCallback(async () => {
try {
const data = await fetchDisplays(displays.length);
setDisplays((prev) => [...prev, ...data]);
setHasMoreDisplays(data.length === DISPLAYS_PER_PAGE);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : t("common.something_went_wrong");
toast.error(errorMessage);
}
}, [fetchDisplays, displays.length, t]);
useEffect(() => {
if (tab === "impressions" && !displaysFetchedRef.current) {
displaysFetchedRef.current = true;
loadInitialDisplays();
}
}, [tab, loadInitialDisplays]);
// Only fetch data when filters change or when there's no initial data // Only fetch data when filters change or when there's no initial data
useEffect(() => { useEffect(() => {
// If we have initial data and no filters are applied, don't fetch // If we have initial data and no filters are applied, don't fetch
@@ -121,6 +190,18 @@ export const SummaryPage = ({
setTab={setTab} setTab={setTab}
isQuotasAllowed={isQuotasAllowed} isQuotasAllowed={isQuotasAllowed}
/> />
{tab === "impressions" && (
<SummaryImpressions
displays={displays}
isLoading={isDisplaysLoading}
hasMore={hasMoreDisplays}
displaysError={displaysError}
environmentId={environment.id}
locale={locale}
onLoadMore={handleLoadMoreDisplays}
onRetry={loadInitialDisplays}
/>
)}
{tab === "dropOffs" && <SummaryDropOffs dropOff={surveySummary.dropOff} survey={surveyMemoized} />} {tab === "dropOffs" && <SummaryDropOffs dropOff={surveySummary.dropOff} survey={surveyMemoized} />}
{isQuotasAllowed && tab === "quotas" && <QuotasSummary quotas={surveySummary.quotas} />} {isQuotasAllowed && tab === "quotas" && <QuotasSummary quotas={surveySummary.quotas} />}
<div className="flex gap-1.5"> <div className="flex gap-1.5">
@@ -4,9 +4,9 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { BaseCard } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/base-card"; import { BaseCard } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/base-card";
interface InteractiveCardProps { interface InteractiveCardProps {
tab: "dropOffs" | "quotas"; tab: "dropOffs" | "quotas" | "impressions";
label: string; label: string;
percentage: number; percentage: number | null;
value: React.ReactNode; value: React.ReactNode;
tooltipText: string; tooltipText: string;
isLoading: boolean; isLoading: boolean;
@@ -352,7 +352,7 @@ export const AnonymousLinksTab = ({
}, },
{ {
title: t("environments.surveys.share.anonymous_links.custom_start_point"), title: t("environments.surveys.share.anonymous_links.custom_start_point"),
href: "https://formbricks.com/docs/xm-and-surveys/surveys/link-surveys/start-at-question", href: "https://formbricks.com/docs/xm-and-surveys/surveys/link-surveys/start-at-block",
}, },
]} ]}
/> />
@@ -105,7 +105,7 @@ export const CustomHtmlTab = ({ projectCustomScripts, isReadOnly }: CustomHtmlTa
<div className={scriptsMode === "replace" ? "opacity-50" : ""}> <div className={scriptsMode === "replace" ? "opacity-50" : ""}>
<FormLabel>{t("environments.surveys.share.custom_html.workspace_scripts_label")}</FormLabel> <FormLabel>{t("environments.surveys.share.custom_html.workspace_scripts_label")}</FormLabel>
<div className="mt-2 max-h-32 overflow-auto rounded-md border border-slate-200 bg-slate-50 p-3"> <div className="mt-2 max-h-32 overflow-auto rounded-md border border-slate-200 bg-slate-50 p-3">
<pre className="font-mono text-xs whitespace-pre-wrap text-slate-600"> <pre className="whitespace-pre-wrap font-mono text-xs text-slate-600">
{projectCustomScripts} {projectCustomScripts}
</pre> </pre>
</div> </div>
@@ -135,7 +135,7 @@ export const CustomHtmlTab = ({ projectCustomScripts, isReadOnly }: CustomHtmlTa
rows={8} rows={8}
placeholder={t("environments.surveys.share.custom_html.placeholder")} placeholder={t("environments.surveys.share.custom_html.placeholder")}
className={cn( className={cn(
"focus:border-brand-dark flex w-full rounded-md border border-slate-300 bg-white px-3 py-2 font-mono text-xs text-slate-800 placeholder:text-slate-400 focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50" "flex w-full rounded-md border border-slate-300 bg-white px-3 py-2 font-mono text-xs text-slate-800 placeholder:text-slate-400 focus:border-brand-dark focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
)} )}
{...field} {...field}
disabled={isReadOnly} disabled={isReadOnly}
@@ -66,7 +66,7 @@ export const SuccessView: React.FC<SuccessViewProps> = ({
className="relative flex flex-col items-center gap-3 rounded-lg border border-slate-100 bg-white p-4 text-center text-sm text-slate-900 hover:border-slate-200 md:p-8"> className="relative flex flex-col items-center gap-3 rounded-lg border border-slate-100 bg-white p-4 text-center text-sm text-slate-900 hover:border-slate-200 md:p-8">
<UserIcon className="h-8 w-8 stroke-1 text-slate-900" /> <UserIcon className="h-8 w-8 stroke-1 text-slate-900" />
{t("environments.surveys.summary.use_personal_links")} {t("environments.surveys.summary.use_personal_links")}
<Badge size="normal" type="success" className="absolute top-3 right-3" text={t("common.new")} /> <Badge size="normal" type="success" className="absolute right-3 top-3" text={t("common.new")} />
</button> </button>
<Link <Link
href={`/environments/${environmentId}/settings/notifications`} href={`/environments/${environmentId}/settings/notifications`}
@@ -192,7 +192,7 @@ export const ElementsComboBox = ({ options, selected, onChangeValue }: ElementCo
value={inputValue} value={inputValue}
onValueChange={setInputValue} onValueChange={setInputValue}
placeholder={open ? `${t("common.search")}...` : t("common.select_filter")} placeholder={open ? `${t("common.search")}...` : t("common.select_filter")}
className="max-w-full grow border-none p-0 pl-2 text-sm shadow-none ring-offset-transparent outline-none focus:border-none focus:shadow-none focus:ring-offset-0 focus:outline-none" className="max-w-full grow border-none p-0 pl-2 text-sm shadow-none outline-none ring-offset-transparent focus:border-none focus:shadow-none focus:outline-none focus:ring-offset-0"
/> />
)} )}
<Button <Button
@@ -241,7 +241,7 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
<Popover open={isOpen} onOpenChange={handleOpenChange}> <Popover open={isOpen} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<PopoverTriggerButton isOpen={isOpen}> <PopoverTriggerButton isOpen={isOpen}>
Filter <b>{activeFilterCount > 0 && `(${activeFilterCount})`}</b> {t("common.filter")} <b>{activeFilterCount > 0 && `(${activeFilterCount})`}</b>
</PopoverTriggerButton> </PopoverTriggerButton>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
@@ -329,7 +329,7 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
</div> </div>
{i !== filterValue.filter.length - 1 && ( {i !== filterValue.filter.length - 1 && (
<div className="my-4 flex items-center"> <div className="my-4 flex items-center">
<p className="mr-4 font-semibold text-slate-800">and</p> <p className="mr-4 font-semibold text-slate-800">{t("common.and")}</p>
<hr className="w-full text-slate-600" /> <hr className="w-full text-slate-600" />
</div> </div>
)} )}
@@ -1,12 +1,49 @@
"use server"; "use server";
import { z } from "zod"; import { z } from "zod";
import { ZIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet"; import { ZId } from "@formbricks/types/common";
import { getSpreadsheetNameById } from "@/lib/googleSheet/service"; import {
TIntegrationGoogleSheets,
ZIntegrationGoogleSheets,
} from "@formbricks/types/integration/google-sheet";
import { getSpreadsheetNameById, validateGoogleSheetsConnection } from "@/lib/googleSheet/service";
import { getIntegrationByType } from "@/lib/integration/service";
import { authenticatedActionClient } from "@/lib/utils/action-client"; import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper"; import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper";
const ZValidateGoogleSheetsConnectionAction = z.object({
environmentId: ZId,
});
export const validateGoogleSheetsConnectionAction = authenticatedActionClient
.schema(ZValidateGoogleSheetsConnectionAction)
.action(async ({ ctx, parsedInput }) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: await getOrganizationIdFromEnvironmentId(parsedInput.environmentId),
access: [
{
type: "organization",
roles: ["owner", "manager"],
},
{
type: "projectTeam",
projectId: await getProjectIdFromEnvironmentId(parsedInput.environmentId),
minPermission: "readWrite",
},
],
});
const integration = await getIntegrationByType(parsedInput.environmentId, "googleSheets");
if (!integration) {
return { data: false };
}
await validateGoogleSheetsConnection(integration as TIntegrationGoogleSheets);
return { data: true };
});
const ZGetSpreadsheetNameByIdAction = z.object({ const ZGetSpreadsheetNameByIdAction = z.object({
googleSheetIntegration: ZIntegrationGoogleSheets, googleSheetIntegration: ZIntegrationGoogleSheets,
environmentId: z.string(), environmentId: z.string(),
@@ -20,6 +20,10 @@ import {
isValidGoogleSheetsUrl, isValidGoogleSheetsUrl,
} from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/util"; } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/util";
import GoogleSheetLogo from "@/images/googleSheetsLogo.png"; import GoogleSheetLogo from "@/images/googleSheetsLogo.png";
import {
GOOGLE_SHEET_INTEGRATION_INSUFFICIENT_PERMISSION,
GOOGLE_SHEET_INTEGRATION_INVALID_GRANT,
} from "@/lib/googleSheet/constants";
import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { recallToHeadline } from "@/lib/utils/recall"; import { recallToHeadline } from "@/lib/utils/recall";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils"; import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
@@ -118,6 +122,17 @@ export const AddIntegrationModal = ({
resetForm(); resetForm();
}, [selectedIntegration, surveys]); }, [selectedIntegration, surveys]);
const showErrorMessageToast = (response: Awaited<ReturnType<typeof getSpreadsheetNameByIdAction>>) => {
const errorMessage = getFormattedErrorMessage(response);
if (errorMessage === GOOGLE_SHEET_INTEGRATION_INVALID_GRANT) {
toast.error(t("environments.integrations.google_sheets.token_expired_error"));
} else if (errorMessage === GOOGLE_SHEET_INTEGRATION_INSUFFICIENT_PERMISSION) {
toast.error(t("environments.integrations.google_sheets.spreadsheet_permission_error"));
} else {
toast.error(errorMessage);
}
};
const linkSheet = async () => { const linkSheet = async () => {
try { try {
if (!isValidGoogleSheetsUrl(spreadsheetUrl)) { if (!isValidGoogleSheetsUrl(spreadsheetUrl)) {
@@ -129,6 +144,7 @@ export const AddIntegrationModal = ({
if (selectedElements.length === 0) { if (selectedElements.length === 0) {
throw new Error(t("environments.integrations.select_at_least_one_question_error")); throw new Error(t("environments.integrations.select_at_least_one_question_error"));
} }
setIsLinkingSheet(true);
const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl); const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl);
const spreadsheetNameResponse = await getSpreadsheetNameByIdAction({ const spreadsheetNameResponse = await getSpreadsheetNameByIdAction({
googleSheetIntegration, googleSheetIntegration,
@@ -137,13 +153,11 @@ export const AddIntegrationModal = ({
}); });
if (!spreadsheetNameResponse?.data) { if (!spreadsheetNameResponse?.data) {
const errorMessage = getFormattedErrorMessage(spreadsheetNameResponse); showErrorMessageToast(spreadsheetNameResponse);
throw new Error(errorMessage); return;
} }
const spreadsheetName = spreadsheetNameResponse.data; const spreadsheetName = spreadsheetNameResponse.data;
setIsLinkingSheet(true);
integrationData.spreadsheetId = spreadsheetId; integrationData.spreadsheetId = spreadsheetId;
integrationData.spreadsheetName = spreadsheetName; integrationData.spreadsheetName = spreadsheetName;
integrationData.surveyId = selectedSurvey.id; integrationData.surveyId = selectedSurvey.id;
@@ -280,7 +294,7 @@ export const AddIntegrationModal = ({
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<Label htmlFor="Surveys">{t("common.questions")}</Label> <Label htmlFor="Surveys">{t("common.questions")}</Label>
<div className="mt-1 max-h-[15vh] overflow-x-hidden overflow-y-auto rounded-lg border border-slate-200"> <div className="mt-1 max-h-[15vh] overflow-y-auto overflow-x-hidden rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900"> <div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
{surveyElements.map((question) => ( {surveyElements.map((question) => (
<div key={question.id} className="my-1 flex items-center space-x-2"> <div key={question.id} className="my-1 flex items-center space-x-2">
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { TEnvironment } from "@formbricks/types/environment"; import { TEnvironment } from "@formbricks/types/environment";
import { import {
TIntegrationGoogleSheets, TIntegrationGoogleSheets,
@@ -8,9 +8,11 @@ import {
} from "@formbricks/types/integration/google-sheet"; } from "@formbricks/types/integration/google-sheet";
import { TSurvey } from "@formbricks/types/surveys/types"; import { TSurvey } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user"; import { TUserLocale } from "@formbricks/types/user";
import { validateGoogleSheetsConnectionAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/actions";
import { ManageIntegration } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/ManageIntegration"; import { ManageIntegration } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/components/ManageIntegration";
import { authorize } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/google"; import { authorize } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/google";
import googleSheetLogo from "@/images/googleSheetsLogo.png"; import googleSheetLogo from "@/images/googleSheetsLogo.png";
import { GOOGLE_SHEET_INTEGRATION_INVALID_GRANT } from "@/lib/googleSheet/constants";
import { ConnectIntegration } from "@/modules/ui/components/connect-integration"; import { ConnectIntegration } from "@/modules/ui/components/connect-integration";
import { AddIntegrationModal } from "./AddIntegrationModal"; import { AddIntegrationModal } from "./AddIntegrationModal";
@@ -35,10 +37,23 @@ export const GoogleSheetWrapper = ({
googleSheetIntegration ? googleSheetIntegration.config?.key : false googleSheetIntegration ? googleSheetIntegration.config?.key : false
); );
const [isModalOpen, setIsModalOpen] = useState<boolean>(false); const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [showReconnectButton, setShowReconnectButton] = useState<boolean>(false);
const [selectedIntegration, setSelectedIntegration] = useState< const [selectedIntegration, setSelectedIntegration] = useState<
(TIntegrationGoogleSheetsConfigData & { index: number }) | null (TIntegrationGoogleSheetsConfigData & { index: number }) | null
>(null); >(null);
const validateConnection = useCallback(async () => {
if (!isConnected || !googleSheetIntegration) return;
const response = await validateGoogleSheetsConnectionAction({ environmentId: environment.id });
if (response?.serverError === GOOGLE_SHEET_INTEGRATION_INVALID_GRANT) {
setShowReconnectButton(true);
}
}, [environment.id, isConnected, googleSheetIntegration]);
useEffect(() => {
validateConnection();
}, [validateConnection]);
const handleGoogleAuthorization = async () => { const handleGoogleAuthorization = async () => {
authorize(environment.id, webAppUrl).then((url: string) => { authorize(environment.id, webAppUrl).then((url: string) => {
if (url) { if (url) {
@@ -64,6 +79,8 @@ export const GoogleSheetWrapper = ({
setOpenAddIntegrationModal={setIsModalOpen} setOpenAddIntegrationModal={setIsModalOpen}
setIsConnected={setIsConnected} setIsConnected={setIsConnected}
setSelectedIntegration={setSelectedIntegration} setSelectedIntegration={setSelectedIntegration}
showReconnectButton={showReconnectButton}
handleGoogleAuthorization={handleGoogleAuthorization}
locale={locale} locale={locale}
/> />
</> </>
@@ -1,6 +1,6 @@
"use client"; "use client";
import { Trash2Icon } from "lucide-react"; import { RefreshCcwIcon, Trash2Icon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -12,15 +12,19 @@ import { TUserLocale } from "@formbricks/types/user";
import { deleteIntegrationAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/actions"; import { deleteIntegrationAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/actions";
import { timeSince } from "@/lib/time"; import { timeSince } from "@/lib/time";
import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertButton, AlertDescription } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button"; import { Button } from "@/modules/ui/components/button";
import { DeleteDialog } from "@/modules/ui/components/delete-dialog"; import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
import { EmptyState } from "@/modules/ui/components/empty-state"; import { EmptyState } from "@/modules/ui/components/empty-state";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
interface ManageIntegrationProps { interface ManageIntegrationProps {
googleSheetIntegration: TIntegrationGoogleSheets; googleSheetIntegration: TIntegrationGoogleSheets;
setOpenAddIntegrationModal: (v: boolean) => void; setOpenAddIntegrationModal: (v: boolean) => void;
setIsConnected: (v: boolean) => void; setIsConnected: (v: boolean) => void;
setSelectedIntegration: (v: (TIntegrationGoogleSheetsConfigData & { index: number }) | null) => void; setSelectedIntegration: (v: (TIntegrationGoogleSheetsConfigData & { index: number }) | null) => void;
showReconnectButton: boolean;
handleGoogleAuthorization: () => void;
locale: TUserLocale; locale: TUserLocale;
} }
@@ -29,6 +33,8 @@ export const ManageIntegration = ({
setOpenAddIntegrationModal, setOpenAddIntegrationModal,
setIsConnected, setIsConnected,
setSelectedIntegration, setSelectedIntegration,
showReconnectButton,
handleGoogleAuthorization,
locale, locale,
}: ManageIntegrationProps) => { }: ManageIntegrationProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -68,7 +74,17 @@ export const ManageIntegration = ({
return ( return (
<div className="mt-6 flex w-full flex-col items-center justify-center p-6"> <div className="mt-6 flex w-full flex-col items-center justify-center p-6">
<div className="flex w-full justify-end"> {showReconnectButton && (
<Alert variant="warning" size="small" className="mb-4 w-full">
<AlertDescription>
{t("environments.integrations.google_sheets.reconnect_button_description")}
</AlertDescription>
<AlertButton onClick={handleGoogleAuthorization}>
{t("environments.integrations.google_sheets.reconnect_button")}
</AlertButton>
</Alert>
)}
<div className="flex w-full justify-end space-x-2">
<div className="mr-6 flex items-center"> <div className="mr-6 flex items-center">
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span> <span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
<span className="text-slate-500"> <span className="text-slate-500">
@@ -77,6 +93,19 @@ export const ManageIntegration = ({
})} })}
</span> </span>
</div> </div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" onClick={handleGoogleAuthorization}>
<RefreshCcwIcon className="mr-2 h-4 w-4" />
{t("environments.integrations.google_sheets.reconnect_button")}
</Button>
</TooltipTrigger>
<TooltipContent>
{t("environments.integrations.google_sheets.reconnect_button_tooltip")}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Button <Button
onClick={() => { onClick={() => {
setSelectedIntegration(null); setSelectedIntegration(null);
@@ -10,7 +10,7 @@ const Loading = () => {
<div className="mt-6 p-6"> <div className="mt-6 p-6">
<GoBackButton /> <GoBackButton />
<div className="mb-6 text-right"> <div className="mb-6 text-right">
<Button className="pointer-events-none animate-pulse cursor-not-allowed bg-slate-200 select-none"> <Button className="pointer-events-none animate-pulse cursor-not-allowed select-none bg-slate-200">
{t("environments.integrations.google_sheets.link_new_sheet")} {t("environments.integrations.google_sheets.link_new_sheet")}
</Button> </Button>
</div> </div>
@@ -51,7 +51,7 @@ const Loading = () => {
<div className="mt-0 h-4 w-24 animate-pulse rounded-full bg-slate-200"></div> <div className="mt-0 h-4 w-24 animate-pulse rounded-full bg-slate-200"></div>
</div> </div>
</div> </div>
<div className="col-span-2 my-auto flex items-center justify-center text-center text-sm whitespace-nowrap text-slate-500"> <div className="col-span-2 my-auto flex items-center justify-center whitespace-nowrap text-center text-sm text-slate-500">
<div className="h-4 w-16 animate-pulse rounded-full bg-slate-200"></div> <div className="h-4 w-16 animate-pulse rounded-full bg-slate-200"></div>
</div> </div>
<div className="text-center"></div> <div className="text-center"></div>
@@ -10,7 +10,7 @@ const Loading = () => {
<div className="mt-6 p-6"> <div className="mt-6 p-6">
<GoBackButton /> <GoBackButton />
<div className="mb-6 text-right"> <div className="mb-6 text-right">
<Button className="pointer-events-none animate-pulse cursor-not-allowed bg-slate-200 select-none"> <Button className="pointer-events-none animate-pulse cursor-not-allowed select-none bg-slate-200">
{t("environments.integrations.notion.link_database")} {t("environments.integrations.notion.link_database")}
</Button> </Button>
</div> </div>
@@ -48,7 +48,7 @@ const Loading = () => {
<div className="mt-0 h-4 w-24 animate-pulse rounded-full bg-slate-200"></div> <div className="mt-0 h-4 w-24 animate-pulse rounded-full bg-slate-200"></div>
</div> </div>
</div> </div>
<div className="col-span-2 my-auto flex items-center justify-center text-center text-sm whitespace-nowrap text-slate-500"> <div className="col-span-2 my-auto flex items-center justify-center whitespace-nowrap text-center text-sm text-slate-500">
<div className="h-4 w-16 animate-pulse rounded-full bg-slate-200"></div> <div className="h-4 w-16 animate-pulse rounded-full bg-slate-200"></div>
</div> </div>
<div className="text-center"></div> <div className="text-center"></div>
+12 -7
View File
@@ -15,6 +15,7 @@ import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { getResponseCountBySurveyId } from "@/lib/response/service"; import { getResponseCountBySurveyId } from "@/lib/response/service";
import { getSurvey, updateSurvey } from "@/lib/survey/service"; import { getSurvey, updateSurvey } from "@/lib/survey/service";
import { convertDatesInObject } from "@/lib/time"; import { convertDatesInObject } from "@/lib/time";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler"; import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler";
import { TAuditStatus, UNKNOWN_DATA } from "@/modules/ee/audit-logs/types/audit-log"; import { TAuditStatus, UNKNOWN_DATA } from "@/modules/ee/audit-logs/types/audit-log";
import { sendResponseFinishedEmail } from "@/modules/email"; import { sendResponseFinishedEmail } from "@/modules/email";
@@ -135,13 +136,17 @@ export const POST = async (request: Request) => {
); );
} }
return fetchWithTimeout(webhook.url, { return validateWebhookUrl(webhook.url)
method: "POST", .then(() =>
headers: requestHeaders, fetchWithTimeout(webhook.url, {
body, method: "POST",
}).catch((error) => { headers: requestHeaders,
logger.error({ error, url: request.url }, `Webhook call to ${webhook.url} failed`); body,
}); })
)
.catch((error) => {
logger.error({ error, url: request.url }, `Webhook call to ${webhook.url} failed`);
});
}); });
if (event === "responseFinished") { if (event === "responseFinished") {
+29 -22
View File
@@ -1,5 +1,6 @@
import { google } from "googleapis"; import { google } from "googleapis";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { TIntegrationGoogleSheetsConfig } from "@formbricks/types/integration/google-sheet";
import { responses } from "@/app/lib/api/response"; import { responses } from "@/app/lib/api/response";
import { import {
GOOGLE_SHEETS_CLIENT_ID, GOOGLE_SHEETS_CLIENT_ID,
@@ -8,7 +9,7 @@ import {
WEBAPP_URL, WEBAPP_URL,
} from "@/lib/constants"; } from "@/lib/constants";
import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { hasUserEnvironmentAccess } from "@/lib/environment/auth";
import { createOrUpdateIntegration } from "@/lib/integration/service"; import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service";
import { authOptions } from "@/modules/auth/lib/authOptions"; import { authOptions } from "@/modules/auth/lib/authOptions";
export const GET = async (req: Request) => { export const GET = async (req: Request) => {
@@ -42,33 +43,39 @@ export const GET = async (req: Request) => {
if (!redirect_uri) return responses.internalServerErrorResponse("Google redirect url is missing"); if (!redirect_uri) return responses.internalServerErrorResponse("Google redirect url is missing");
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
let key; if (!code) {
let userEmail; return Response.redirect(
`${WEBAPP_URL}/environments/${environmentId}/workspace/integrations/google-sheets`
if (code) { );
const token = await oAuth2Client.getToken(code);
key = token.res?.data;
// Set credentials using the provided token
oAuth2Client.setCredentials({
access_token: key.access_token,
});
// Fetch user's email
const oauth2 = google.oauth2({
auth: oAuth2Client,
version: "v2",
});
const userInfo = await oauth2.userinfo.get();
userEmail = userInfo.data.email;
} }
const token = await oAuth2Client.getToken(code);
const key = token.res?.data;
if (!key) {
return Response.redirect(
`${WEBAPP_URL}/environments/${environmentId}/workspace/integrations/google-sheets`
);
}
oAuth2Client.setCredentials({ access_token: key.access_token });
const oauth2 = google.oauth2({ auth: oAuth2Client, version: "v2" });
const userInfo = await oauth2.userinfo.get();
const userEmail = userInfo.data.email;
if (!userEmail) {
return responses.internalServerErrorResponse("Failed to get user email");
}
const integrationType = "googleSheets" as const;
const existingIntegration = await getIntegrationByType(environmentId, integrationType);
const existingConfig = existingIntegration?.config as TIntegrationGoogleSheetsConfig;
const googleSheetIntegration = { const googleSheetIntegration = {
type: "googleSheets" as "googleSheets", type: integrationType,
environment: environmentId, environment: environmentId,
config: { config: {
key, key,
data: [], data: existingConfig?.data ?? [],
email: userEmail, email: userEmail,
}, },
}; };
+112 -114
View File
@@ -6,140 +6,138 @@ export const GET = async (req: NextRequest) => {
let brandColor = req.nextUrl.searchParams.get("brandColor"); let brandColor = req.nextUrl.searchParams.get("brandColor");
return new ImageResponse( return new ImageResponse(
( <div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
alignItems: "center",
backgroundColor: brandColor ? brandColor + "BF" : "#0000BFBF", // /75 opacity is approximately BF in hex
borderRadius: "0.75rem",
}}>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", width: "80%",
height: "100%", height: "60%",
alignItems: "center", backgroundColor: "white",
backgroundColor: brandColor ? brandColor + "BF" : "#0000BFBF", // /75 opacity is approximately BF in hex
borderRadius: "0.75rem", borderRadius: "0.75rem",
marginTop: "3.25rem",
position: "absolute",
left: "3rem",
top: "0.75rem",
opacity: 0.2,
transform: "rotate(356deg)",
}}></div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "84%",
height: "60%",
backgroundColor: "white",
borderRadius: "0.75rem",
marginTop: "3rem",
position: "absolute",
top: "1.25rem",
left: "3.25rem",
borderWidth: "2px",
opacity: 0.6,
transform: "rotate(357deg)",
}}></div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "85%",
height: "67%",
alignItems: "center",
backgroundColor: "white",
borderRadius: "0.75rem",
marginTop: "2rem",
position: "absolute",
top: "2.3rem",
left: "3.5rem",
transform: "rotate(360deg)",
}}> }}>
<div <div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
style={{ <div
display: "flex", style={{
flexDirection: "column", display: "flex",
width: "80%", flexDirection: "column",
height: "60%", width: "100%",
backgroundColor: "white", justifyContent: "space-between",
borderRadius: "0.75rem", }}>
marginTop: "3.25rem",
position: "absolute",
left: "3rem",
top: "0.75rem",
opacity: 0.2,
transform: "rotate(356deg)",
}}></div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "84%",
height: "60%",
backgroundColor: "white",
borderRadius: "0.75rem",
marginTop: "3rem",
position: "absolute",
top: "1.25rem",
left: "3.25rem",
borderWidth: "2px",
opacity: 0.6,
transform: "rotate(357deg)",
}}></div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "85%",
height: "67%",
alignItems: "center",
backgroundColor: "white",
borderRadius: "0.75rem",
marginTop: "2rem",
position: "absolute",
top: "2.3rem",
left: "3.5rem",
transform: "rotate(360deg)",
}}>
<div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", paddingLeft: "2rem",
justifyContent: "space-between", paddingRight: "2rem",
}}>
<h2
style={{
display: "flex",
flexDirection: "column",
fontSize: "2rem",
fontWeight: "700",
letterSpacing: "-0.025em",
color: "#0f172a",
textAlign: "left",
marginTop: "3.75rem",
}}>
{name}
</h2>
</div>
</div>
<div style={{ display: "flex", justifyContent: "flex-end", marginRight: "2.5rem" }}>
<div
style={{
display: "flex",
borderRadius: "1rem",
position: "absolute",
right: "-0.5rem",
marginTop: "0.5rem",
}}>
<div
content=""
style={{
borderRadius: "0.75rem",
border: "1px solid transparent",
backgroundColor: brandColor ?? "#000",
height: "4.5rem",
width: "9.5rem",
opacity: 0.5,
}}></div>
</div>
<div
style={{
display: "flex",
borderRadius: "1rem",
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
}}> }}>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", alignItems: "center",
paddingLeft: "2rem", justifyContent: "center",
paddingRight: "2rem", borderRadius: "0.75rem",
border: "1px solid transparent",
backgroundColor: brandColor ?? "#000",
fontSize: "1.5rem",
color: "white",
height: "4.5rem",
width: "9.5rem",
}}> }}>
<h2 Begin!
style={{
display: "flex",
flexDirection: "column",
fontSize: "2rem",
fontWeight: "700",
letterSpacing: "-0.025em",
color: "#0f172a",
textAlign: "left",
marginTop: "3.75rem",
}}>
{name}
</h2>
</div>
</div>
<div style={{ display: "flex", justifyContent: "flex-end", marginRight: "2.5rem" }}>
<div
style={{
display: "flex",
borderRadius: "1rem",
position: "absolute",
right: "-0.5rem",
marginTop: "0.5rem",
}}>
<div
content=""
style={{
borderRadius: "0.75rem",
border: "1px solid transparent",
backgroundColor: brandColor ?? "#000",
height: "4.5rem",
width: "9.5rem",
opacity: 0.5,
}}></div>
</div>
<div
style={{
display: "flex",
borderRadius: "1rem",
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
}}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "0.75rem",
border: "1px solid transparent",
backgroundColor: brandColor ?? "#000",
fontSize: "1.5rem",
color: "white",
height: "4.5rem",
width: "9.5rem",
}}>
Begin!
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
), </div>,
{ {
width: 800, width: 800,
height: 400, height: 400,
@@ -6,7 +6,7 @@ import {
} from "@formbricks/types/integration/slack"; } from "@formbricks/types/integration/slack";
import { responses } from "@/app/lib/api/response"; import { responses } from "@/app/lib/api/response";
import { TSessionAuthentication, 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 { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_REDIRECT_URI, WEBAPP_URL } from "@/lib/constants";
import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { hasUserEnvironmentAccess } from "@/lib/environment/auth";
import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service"; import { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service";
@@ -56,6 +56,7 @@ export const GET = withV1ApiWrapper({
code, code,
client_id: SLACK_CLIENT_ID, client_id: SLACK_CLIENT_ID,
client_secret: SLACK_CLIENT_SECRET, client_secret: SLACK_CLIENT_SECRET,
redirect_uri: SLACK_REDIRECT_URI,
}; };
const formBody: string[] = []; const formBody: string[] = [];
for (const property in formData) { for (const property in formData) {
@@ -2,10 +2,11 @@ import { Prisma, WebhookSource } from "@prisma/client";
import { cleanup } from "@testing-library/react"; import { cleanup } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest"; import { afterEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { DatabaseError, ValidationError } from "@formbricks/types/errors"; import { DatabaseError, InvalidInputError, ValidationError } from "@formbricks/types/errors";
import { createWebhook } from "@/app/api/v1/webhooks/lib/webhook"; import { createWebhook } from "@/app/api/v1/webhooks/lib/webhook";
import { TWebhookInput } from "@/app/api/v1/webhooks/types/webhooks"; import { TWebhookInput } from "@/app/api/v1/webhooks/types/webhooks";
import { validateInputs } from "@/lib/utils/validate"; import { validateInputs } from "@/lib/utils/validate";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
vi.mock("@formbricks/database", () => ({ vi.mock("@formbricks/database", () => ({
prisma: { prisma: {
@@ -23,6 +24,10 @@ vi.mock("@/lib/crypto", () => ({
generateWebhookSecret: vi.fn(() => "whsec_test_secret_1234567890"), generateWebhookSecret: vi.fn(() => "whsec_test_secret_1234567890"),
})); }));
vi.mock("@/lib/utils/validate-webhook-url", () => ({
validateWebhookUrl: vi.fn().mockResolvedValue(undefined),
}));
describe("createWebhook", () => { describe("createWebhook", () => {
afterEach(() => { afterEach(() => {
cleanup(); cleanup();
@@ -75,6 +80,41 @@ describe("createWebhook", () => {
expect(result).toEqual(createdWebhook); expect(result).toEqual(createdWebhook);
}); });
test("should call validateWebhookUrl with the provided URL", async () => {
const webhookInput: TWebhookInput = {
environmentId: "test-env-id",
name: "Test Webhook",
url: "https://example.com",
source: "user",
triggers: ["responseCreated"],
surveyIds: ["survey1"],
};
vi.mocked(prisma.webhook.create).mockResolvedValueOnce({} as any);
await createWebhook(webhookInput);
expect(validateWebhookUrl).toHaveBeenCalledWith("https://example.com");
});
test("should throw InvalidInputError and skip Prisma create when URL fails SSRF validation", async () => {
const webhookInput: TWebhookInput = {
environmentId: "test-env-id",
name: "Test Webhook",
url: "http://169.254.169.254/latest/meta-data/",
source: "user",
triggers: ["responseCreated"],
surveyIds: ["survey1"],
};
vi.mocked(validateWebhookUrl).mockRejectedValueOnce(
new InvalidInputError("Webhook URL must not point to private or internal IP addresses")
);
await expect(createWebhook(webhookInput)).rejects.toThrow(InvalidInputError);
expect(prisma.webhook.create).not.toHaveBeenCalled();
});
test("should throw a ValidationError if the input data does not match the ZWebhookInput schema", async () => { test("should throw a ValidationError if the input data does not match the ZWebhookInput schema", async () => {
const invalidWebhookInput = { const invalidWebhookInput = {
environmentId: "test-env-id", environmentId: "test-env-id",
@@ -6,9 +6,11 @@ import { TWebhookInput, ZWebhookInput } from "@/app/api/v1/webhooks/types/webhoo
import { ITEMS_PER_PAGE } from "@/lib/constants"; import { ITEMS_PER_PAGE } from "@/lib/constants";
import { generateWebhookSecret } from "@/lib/crypto"; import { generateWebhookSecret } from "@/lib/crypto";
import { validateInputs } from "@/lib/utils/validate"; import { validateInputs } from "@/lib/utils/validate";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
export const createWebhook = async (webhookInput: TWebhookInput): Promise<Webhook> => { export const createWebhook = async (webhookInput: TWebhookInput): Promise<Webhook> => {
validateInputs([webhookInput, ZWebhookInput]); validateInputs([webhookInput, ZWebhookInput]);
await validateWebhookUrl(webhookInput.url);
try { try {
const secret = generateWebhookSecret(); const secret = generateWebhookSecret();
+30 -45
View File
@@ -131,13 +131,11 @@ describe("withV1ApiWrapper", () => {
}); });
test("logs and audits on error response with API key authentication", async () => { test("logs and audits on error response with API key authentication", async () => {
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
@@ -185,13 +183,11 @@ describe("withV1ApiWrapper", () => {
}); });
test("does not log Sentry if not 500", async () => { test("does not log Sentry if not 500", async () => {
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
@@ -233,13 +229,11 @@ describe("withV1ApiWrapper", () => {
}); });
test("logs and audits on thrown error", async () => { test("logs and audits on thrown error", async () => {
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
@@ -291,13 +285,11 @@ describe("withV1ApiWrapper", () => {
}); });
test("does not log on success response but still audits", async () => { test("does not log on success response but still audits", async () => {
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
@@ -347,13 +339,11 @@ describe("withV1ApiWrapper", () => {
REDIS_URL: "redis://localhost:6379", REDIS_URL: "redis://localhost:6379",
})); }));
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
const { withV1ApiWrapper } = await import("./with-api-logging"); const { withV1ApiWrapper } = await import("./with-api-logging");
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
@@ -376,9 +366,8 @@ describe("withV1ApiWrapper", () => {
}); });
test("handles client-side API routes without authentication", async () => { test("handles client-side API routes without authentication", async () => {
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { applyIPRateLimit } = await import("@/modules/core/rate-limit/helpers"); const { applyIPRateLimit } = await import("@/modules/core/rate-limit/helpers");
@@ -410,9 +399,8 @@ describe("withV1ApiWrapper", () => {
}); });
test("returns authentication error for non-client routes without auth", async () => { test("returns authentication error for non-client routes without auth", async () => {
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
@@ -435,9 +423,8 @@ describe("withV1ApiWrapper", () => {
test("handles rate limiting errors", async () => { test("handles rate limiting errors", async () => {
const { applyRateLimit } = await import("@/modules/core/rate-limit/helpers"); const { applyRateLimit } = await import("@/modules/core/rate-limit/helpers");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
@@ -462,13 +449,11 @@ describe("withV1ApiWrapper", () => {
}); });
test("skips audit log creation when no action/targetType provided", async () => { test("skips audit log creation when no action/targetType provided", async () => {
const { queueAuditEvent: mockedQueueAuditEvent } = (await import( const { queueAuditEvent: mockedQueueAuditEvent } =
"@/modules/ee/audit-logs/lib/handler" (await import("@/modules/ee/audit-logs/lib/handler")) as unknown as { queueAuditEvent: Mock };
)) as unknown as { queueAuditEvent: Mock };
const { authenticateRequest } = await import("@/app/api/v1/auth"); const { authenticateRequest } = await import("@/app/api/v1/auth");
const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } = await import( const { isClientSideApiRoute, isManagementApiRoute, isIntegrationRoute } =
"@/app/middleware/endpoint-validator" await import("@/app/middleware/endpoint-validator");
);
vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication); vi.mocked(authenticateRequest).mockResolvedValue(mockApiAuthentication);
vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true }); vi.mocked(isClientSideApiRoute).mockReturnValue({ isClientSideApi: false, isRateLimited: true });
File diff suppressed because it is too large Load Diff
@@ -257,6 +257,7 @@ describe("endpoint-validator", () => {
expect(isAuthProtectedRoute("/api/v1/client/test")).toBe(false); expect(isAuthProtectedRoute("/api/v1/client/test")).toBe(false);
expect(isAuthProtectedRoute("/")).toBe(false); expect(isAuthProtectedRoute("/")).toBe(false);
expect(isAuthProtectedRoute("/s/survey123")).toBe(false); expect(isAuthProtectedRoute("/s/survey123")).toBe(false);
expect(isAuthProtectedRoute("/p/pretty-url")).toBe(false);
expect(isAuthProtectedRoute("/c/jwt-token")).toBe(false); expect(isAuthProtectedRoute("/c/jwt-token")).toBe(false);
expect(isAuthProtectedRoute("/health")).toBe(false); expect(isAuthProtectedRoute("/health")).toBe(false);
}); });
@@ -313,6 +314,19 @@ describe("endpoint-validator", () => {
expect(isPublicDomainRoute("/contact/token")).toBe(false); expect(isPublicDomainRoute("/contact/token")).toBe(false);
}); });
test("should return true for pretty URL survey routes", () => {
expect(isPublicDomainRoute("/p/pretty123")).toBe(true);
expect(isPublicDomainRoute("/p/pretty-name-with-dashes")).toBe(true);
expect(isPublicDomainRoute("/p/survey_id_with_underscores")).toBe(true);
expect(isPublicDomainRoute("/p/abc123def456")).toBe(true);
});
test("should return false for malformed pretty URL survey routes", () => {
expect(isPublicDomainRoute("/p/")).toBe(false);
expect(isPublicDomainRoute("/p")).toBe(false);
expect(isPublicDomainRoute("/pretty/123")).toBe(false);
});
test("should return true for client API routes", () => { test("should return true for client API routes", () => {
expect(isPublicDomainRoute("/api/v1/client/something")).toBe(true); expect(isPublicDomainRoute("/api/v1/client/something")).toBe(true);
expect(isPublicDomainRoute("/api/v2/client/other")).toBe(true); expect(isPublicDomainRoute("/api/v2/client/other")).toBe(true);
@@ -375,6 +389,8 @@ describe("endpoint-validator", () => {
expect(isAdminDomainRoute("/s/survey-id-with-dashes")).toBe(false); expect(isAdminDomainRoute("/s/survey-id-with-dashes")).toBe(false);
expect(isAdminDomainRoute("/c/jwt-token")).toBe(false); expect(isAdminDomainRoute("/c/jwt-token")).toBe(false);
expect(isAdminDomainRoute("/c/very-long-jwt-token-123")).toBe(false); expect(isAdminDomainRoute("/c/very-long-jwt-token-123")).toBe(false);
expect(isAdminDomainRoute("/p/pretty123")).toBe(false);
expect(isAdminDomainRoute("/p/pretty-name-with-dashes")).toBe(false);
expect(isAdminDomainRoute("/api/v1/client/test")).toBe(false); expect(isAdminDomainRoute("/api/v1/client/test")).toBe(false);
expect(isAdminDomainRoute("/api/v2/client/other")).toBe(false); expect(isAdminDomainRoute("/api/v2/client/other")).toBe(false);
}); });
@@ -390,6 +406,7 @@ describe("endpoint-validator", () => {
test("should allow public routes on public domain", () => { test("should allow public routes on public domain", () => {
expect(isRouteAllowedForDomain("/s/survey123", true)).toBe(true); expect(isRouteAllowedForDomain("/s/survey123", true)).toBe(true);
expect(isRouteAllowedForDomain("/c/jwt-token", true)).toBe(true); expect(isRouteAllowedForDomain("/c/jwt-token", true)).toBe(true);
expect(isRouteAllowedForDomain("/p/pretty123", true)).toBe(true);
expect(isRouteAllowedForDomain("/api/v1/client/test", true)).toBe(true); expect(isRouteAllowedForDomain("/api/v1/client/test", true)).toBe(true);
expect(isRouteAllowedForDomain("/api/v2/client/other", true)).toBe(true); expect(isRouteAllowedForDomain("/api/v2/client/other", true)).toBe(true);
expect(isRouteAllowedForDomain("/health", true)).toBe(true); expect(isRouteAllowedForDomain("/health", true)).toBe(true);
@@ -426,6 +443,8 @@ describe("endpoint-validator", () => {
expect(isRouteAllowedForDomain("/s/survey-id-with-dashes", false)).toBe(false); expect(isRouteAllowedForDomain("/s/survey-id-with-dashes", false)).toBe(false);
expect(isRouteAllowedForDomain("/c/jwt-token", false)).toBe(false); expect(isRouteAllowedForDomain("/c/jwt-token", false)).toBe(false);
expect(isRouteAllowedForDomain("/c/very-long-jwt-token-123", false)).toBe(false); expect(isRouteAllowedForDomain("/c/very-long-jwt-token-123", false)).toBe(false);
expect(isRouteAllowedForDomain("/p/pretty123", false)).toBe(false);
expect(isRouteAllowedForDomain("/p/pretty-name-with-dashes", false)).toBe(false);
expect(isRouteAllowedForDomain("/api/v1/client/test", false)).toBe(false); expect(isRouteAllowedForDomain("/api/v1/client/test", false)).toBe(false);
expect(isRouteAllowedForDomain("/api/v2/client/other", false)).toBe(false); expect(isRouteAllowedForDomain("/api/v2/client/other", false)).toBe(false);
}); });
@@ -440,6 +459,8 @@ describe("endpoint-validator", () => {
test("should handle paths with query parameters and fragments", () => { test("should handle paths with query parameters and fragments", () => {
expect(isRouteAllowedForDomain("/s/survey123?param=value", true)).toBe(true); expect(isRouteAllowedForDomain("/s/survey123?param=value", true)).toBe(true);
expect(isRouteAllowedForDomain("/s/survey123#section", true)).toBe(true); expect(isRouteAllowedForDomain("/s/survey123#section", true)).toBe(true);
expect(isRouteAllowedForDomain("/p/pretty123?param=value", true)).toBe(true);
expect(isRouteAllowedForDomain("/p/pretty123#section", true)).toBe(true);
expect(isRouteAllowedForDomain("/environments/123?tab=settings", true)).toBe(false); expect(isRouteAllowedForDomain("/environments/123?tab=settings", true)).toBe(false);
expect(isRouteAllowedForDomain("/environments/123?tab=settings", false)).toBe(true); expect(isRouteAllowedForDomain("/environments/123?tab=settings", false)).toBe(true);
}); });
@@ -450,6 +471,7 @@ describe("endpoint-validator", () => {
describe("URL parsing edge cases", () => { describe("URL parsing edge cases", () => {
test("should handle paths with query parameters", () => { test("should handle paths with query parameters", () => {
expect(isPublicDomainRoute("/s/survey123?param=value&other=test")).toBe(true); expect(isPublicDomainRoute("/s/survey123?param=value&other=test")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123?param=value&other=test")).toBe(true);
expect(isPublicDomainRoute("/api/v1/client/test?query=data")).toBe(true); expect(isPublicDomainRoute("/api/v1/client/test?query=data")).toBe(true);
expect(isPublicDomainRoute("/environments/123?tab=settings")).toBe(false); expect(isPublicDomainRoute("/environments/123?tab=settings")).toBe(false);
expect(isAuthProtectedRoute("/environments/123?tab=overview")).toBe(true); expect(isAuthProtectedRoute("/environments/123?tab=overview")).toBe(true);
@@ -458,12 +480,14 @@ describe("endpoint-validator", () => {
test("should handle paths with fragments", () => { test("should handle paths with fragments", () => {
expect(isPublicDomainRoute("/s/survey123#section")).toBe(true); expect(isPublicDomainRoute("/s/survey123#section")).toBe(true);
expect(isPublicDomainRoute("/c/jwt-token#top")).toBe(true); expect(isPublicDomainRoute("/c/jwt-token#top")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123#section")).toBe(true);
expect(isPublicDomainRoute("/environments/123#overview")).toBe(false); expect(isPublicDomainRoute("/environments/123#overview")).toBe(false);
expect(isAuthProtectedRoute("/organizations/456#settings")).toBe(true); expect(isAuthProtectedRoute("/organizations/456#settings")).toBe(true);
}); });
test("should handle trailing slashes", () => { test("should handle trailing slashes", () => {
expect(isPublicDomainRoute("/s/survey123/")).toBe(true); expect(isPublicDomainRoute("/s/survey123/")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123/")).toBe(true);
expect(isPublicDomainRoute("/api/v1/client/test/")).toBe(true); expect(isPublicDomainRoute("/api/v1/client/test/")).toBe(true);
expect(isManagementApiRoute("/api/v1/management/test/")).toEqual({ expect(isManagementApiRoute("/api/v1/management/test/")).toEqual({
isManagementApi: true, isManagementApi: true,
@@ -478,6 +502,9 @@ describe("endpoint-validator", () => {
expect(isPublicDomainRoute("/s/survey123/preview")).toBe(true); expect(isPublicDomainRoute("/s/survey123/preview")).toBe(true);
expect(isPublicDomainRoute("/s/survey123/embed")).toBe(true); expect(isPublicDomainRoute("/s/survey123/embed")).toBe(true);
expect(isPublicDomainRoute("/s/survey123/thank-you")).toBe(true); expect(isPublicDomainRoute("/s/survey123/thank-you")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123/preview")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123/embed")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123/thank-you")).toBe(true);
}); });
test("should handle nested client API routes", () => { test("should handle nested client API routes", () => {
@@ -529,6 +556,7 @@ describe("endpoint-validator", () => {
test("should handle special characters in survey IDs", () => { test("should handle special characters in survey IDs", () => {
expect(isPublicDomainRoute("/s/survey-123_test.v2")).toBe(true); expect(isPublicDomainRoute("/s/survey-123_test.v2")).toBe(true);
expect(isPublicDomainRoute("/c/jwt.token.with.dots")).toBe(true); expect(isPublicDomainRoute("/c/jwt.token.with.dots")).toBe(true);
expect(isPublicDomainRoute("/p/pretty-123_test.v2")).toBe(true);
}); });
}); });
@@ -536,6 +564,7 @@ describe("endpoint-validator", () => {
test("should properly validate malicious or injection-like URLs", () => { test("should properly validate malicious or injection-like URLs", () => {
// SQL injection-like attempts // SQL injection-like attempts
expect(isPublicDomainRoute("/s/'; DROP TABLE users; --")).toBe(true); // Still valid survey ID format expect(isPublicDomainRoute("/s/'; DROP TABLE users; --")).toBe(true); // Still valid survey ID format
expect(isPublicDomainRoute("/p/'; DROP TABLE users; --")).toBe(true);
expect(isManagementApiRoute("/api/v1/management/'; DROP TABLE users; --")).toEqual({ expect(isManagementApiRoute("/api/v1/management/'; DROP TABLE users; --")).toEqual({
isManagementApi: true, isManagementApi: true,
authenticationMethod: AuthenticationMethod.ApiKey, authenticationMethod: AuthenticationMethod.ApiKey,
@@ -543,10 +572,12 @@ describe("endpoint-validator", () => {
// Path traversal attempts // Path traversal attempts
expect(isPublicDomainRoute("/s/../../../etc/passwd")).toBe(true); // Still matches pattern expect(isPublicDomainRoute("/s/../../../etc/passwd")).toBe(true); // Still matches pattern
expect(isPublicDomainRoute("/p/../../../etc/passwd")).toBe(true);
expect(isAuthProtectedRoute("/environments/../../../etc/passwd")).toBe(true); expect(isAuthProtectedRoute("/environments/../../../etc/passwd")).toBe(true);
// XSS-like attempts // XSS-like attempts
expect(isPublicDomainRoute("/s/<script>alert('xss')</script>")).toBe(true); expect(isPublicDomainRoute("/s/<script>alert('xss')</script>")).toBe(true);
expect(isPublicDomainRoute("/p/<script>alert('xss')</script>")).toBe(true);
expect(isClientSideApiRoute("/api/v1/client/<script>alert('xss')</script>")).toEqual({ expect(isClientSideApiRoute("/api/v1/client/<script>alert('xss')</script>")).toEqual({
isClientSideApi: true, isClientSideApi: true,
isRateLimited: true, isRateLimited: true,
@@ -556,6 +587,7 @@ describe("endpoint-validator", () => {
test("should handle URL encoding", () => { test("should handle URL encoding", () => {
expect(isPublicDomainRoute("/s/survey%20123")).toBe(true); expect(isPublicDomainRoute("/s/survey%20123")).toBe(true);
expect(isPublicDomainRoute("/c/jwt%2Etoken")).toBe(true); expect(isPublicDomainRoute("/c/jwt%2Etoken")).toBe(true);
expect(isPublicDomainRoute("/p/pretty%20123")).toBe(true);
expect(isAuthProtectedRoute("/environments%2F123")).toBe(true); expect(isAuthProtectedRoute("/environments%2F123")).toBe(true);
expect(isManagementApiRoute("/api/v1/management/test%20route")).toEqual({ expect(isManagementApiRoute("/api/v1/management/test%20route")).toEqual({
isManagementApi: true, isManagementApi: true,
@@ -591,6 +623,7 @@ describe("endpoint-validator", () => {
// These should not match due to case sensitivity // These should not match due to case sensitivity
expect(isPublicDomainRoute("/S/survey123")).toBe(false); expect(isPublicDomainRoute("/S/survey123")).toBe(false);
expect(isPublicDomainRoute("/C/jwt-token")).toBe(false); expect(isPublicDomainRoute("/C/jwt-token")).toBe(false);
expect(isPublicDomainRoute("/P/pretty123")).toBe(false);
expect(isClientSideApiRoute("/API/V1/CLIENT/test")).toEqual({ expect(isClientSideApiRoute("/API/V1/CLIENT/test")).toEqual({
isClientSideApi: false, isClientSideApi: false,
isRateLimited: true, isRateLimited: true,
+1
View File
@@ -7,6 +7,7 @@ const PUBLIC_ROUTES = {
SURVEY_ROUTES: [ SURVEY_ROUTES: [
/^\/s\/[^/]+/, // /s/[surveyId] - survey pages /^\/s\/[^/]+/, // /s/[surveyId] - survey pages
/^\/c\/[^/]+/, // /c/[jwt] - contact survey pages /^\/c\/[^/]+/, // /c/[jwt] - contact survey pages
/^\/p\/[^/]+/, // /p/[prettyUrl] - pretty URL pages
], ],
// API routes accessible from public domain // API routes accessible from public domain
+53 -14
View File
@@ -148,9 +148,12 @@ checksums:
common/copy: 627c00d2c850b9b45f7341a6ac01b6bb common/copy: 627c00d2c850b9b45f7341a6ac01b6bb
common/copy_code: 704c13d9bc01caad29a1cf3179baa111 common/copy_code: 704c13d9bc01caad29a1cf3179baa111
common/copy_link: 57a37acfe6d7ed71d00fbbc8079fbb35 common/copy_link: 57a37acfe6d7ed71d00fbbc8079fbb35
common/count_attributes: 042fba9baffef5afe2c24f13d4f50697 common/count_attributes: 48805e836a9b50f9635ad00fed953058
common/count_contacts: b1c413a4b06961b71b6aeee95d6775d7 common/count_contacts: 9f71d503455264f1eec1ae58894cf143
common/count_responses: 690118a456c01c5b4d437ae82b50b131 common/count_members: 31ce64ca63fdf95e02ab5543b6e2f717
common/count_questions: a7a34376a01eda781381fe7544541293
common/count_responses: 437e022825c7a08481d8f7e56926742d
common/count_selections: a1ec41682b9a7d8601c3905dfba34e16
common/create_new_organization: 51dae7b33143686ee218abf5bea764a5 common/create_new_organization: 51dae7b33143686ee218abf5bea764a5
common/create_segment: 9d8291cd4d778b53b73bbc84fd91c181 common/create_segment: 9d8291cd4d778b53b73bbc84fd91c181
common/create_survey: 1cfbba08d34876566d84b2960054a987 common/create_survey: 1cfbba08d34876566d84b2960054a987
@@ -164,6 +167,7 @@ checksums:
common/days: c95fe8aedde21a0b5653dbd0b3c58b48 common/days: c95fe8aedde21a0b5653dbd0b3c58b48
common/default: d9c6dc5c412fe94143dfd1d332ec81d4 common/default: d9c6dc5c412fe94143dfd1d332ec81d4
common/delete: 8bcf303dd10a645b5baacb02b47d72c9 common/delete: 8bcf303dd10a645b5baacb02b47d72c9
common/delete_what: 718ddfcc1dec7f3e8b67856fba838267
common/description: e17686a22ffad04cc7bb70524ed4478b common/description: e17686a22ffad04cc7bb70524ed4478b
common/dev_env: e650911d5e19ba256358e0cda154c005 common/dev_env: e650911d5e19ba256358e0cda154c005
common/development: 85211dbb918bda7a6e87649dcfc1b17a common/development: 85211dbb918bda7a6e87649dcfc1b17a
@@ -179,6 +183,8 @@ checksums:
common/download: 56b7d0834952b39ee394b44bd8179178 common/download: 56b7d0834952b39ee394b44bd8179178
common/draft: e8a92958ad300aacfe46c2bf6644927e common/draft: e8a92958ad300aacfe46c2bf6644927e
common/duplicate: 27756566785c2b8463e21582c4bb619b common/duplicate: 27756566785c2b8463e21582c4bb619b
common/duplicate_copy: 68d2201918610ca87c2914b61dc8010f
common/duplicate_copy_number: 083cfffd294672043dcbcc4c3dfeac6a
common/e_commerce: b9584e7d0449a6d1b0c182d7ff14061e common/e_commerce: b9584e7d0449a6d1b0c182d7ff14061e
common/edit: eee7f39ff90b18852afc1671f21fbaa9 common/edit: eee7f39ff90b18852afc1671f21fbaa9
common/email: e7f34943a0c2fb849db1839ff6ef5cb5 common/email: e7f34943a0c2fb849db1839ff6ef5cb5
@@ -191,13 +197,16 @@ checksums:
common/error: 3c95bcb32c2104b99a46f5b3dd015248 common/error: 3c95bcb32c2104b99a46f5b3dd015248
common/error_component_description: fa9eee04f864c3fe6e6681f716caa015 common/error_component_description: fa9eee04f864c3fe6e6681f716caa015
common/error_component_title: ae68fa341a143aaa13a5ea30dd57a63e common/error_component_title: ae68fa341a143aaa13a5ea30dd57a63e
common/error_loading_data: aaeffbfe4a2c2145442a57de524494be
common/error_rate_limit_description: 37791a33a947204662ee9c6544e90f51 common/error_rate_limit_description: 37791a33a947204662ee9c6544e90f51
common/error_rate_limit_title: 23ac9419e267e610e1bfd38e1dc35dc0 common/error_rate_limit_title: 23ac9419e267e610e1bfd38e1dc35dc0
common/expand_rows: b6e06327cb8718dfd6651720843e4dad common/expand_rows: b6e06327cb8718dfd6651720843e4dad
common/failed_to_copy_to_clipboard: de836a7d628d36c832809252f188f784 common/failed_to_copy_to_clipboard: de836a7d628d36c832809252f188f784
common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e
common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4 common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4
common/filter: 626325a05e4c8800f7ede7012b0cadaf
common/finish: ffa7a10f71182b48fefed7135bee24fa common/finish: ffa7a10f71182b48fefed7135bee24fa
common/first_name: cf040a5d6a9fd696be400380cc99f54b
common/follow_these: 3a730b242bb17a3f95e01bf0dae86885 common/follow_these: 3a730b242bb17a3f95e01bf0dae86885
common/formbricks_version: d9967c797f3e49ca0cae78bc0ebd19cb common/formbricks_version: d9967c797f3e49ca0cae78bc0ebd19cb
common/full_name: f45991923345e8322c9ff8cd6b7e2b16 common/full_name: f45991923345e8322c9ff8cd6b7e2b16
@@ -210,6 +219,7 @@ checksums:
common/hidden_field: 3ed5c58d0ed359e558cdf7bd33606d2d common/hidden_field: 3ed5c58d0ed359e558cdf7bd33606d2d
common/hidden_fields: 3de6cfd308293a826cb8679fd1d49972 common/hidden_fields: 3de6cfd308293a826cb8679fd1d49972
common/hide_column: 23ce94db148f2d8e4a0923defead6cf1 common/hide_column: 23ce94db148f2d8e4a0923defead6cf1
common/id: c8886d38aeea2ed5f785aba4fc96784b
common/image: 048ba7a239de0fbd883ade8558415830 common/image: 048ba7a239de0fbd883ade8558415830
common/images: 9305827c28694866f49db42b4c51831f common/images: 9305827c28694866f49db42b4c51831f
common/import: 348b8ab981de5b7f1fca6d7302263bbd common/import: 348b8ab981de5b7f1fca6d7302263bbd
@@ -227,6 +237,7 @@ checksums:
common/key: 3d1065ab98a1c2f1210507fd5c7bf515 common/key: 3d1065ab98a1c2f1210507fd5c7bf515
common/label: a5c71bf158481233f8215dbd38cc196b common/label: a5c71bf158481233f8215dbd38cc196b
common/language: 277fd1a41cc237a437cd1d5e4a80463b common/language: 277fd1a41cc237a437cd1d5e4a80463b
common/last_name: 2c9a7de7738ca007ba9023c385149c26
common/learn_more: e598091d132f890c37a6d4ed94f6d794 common/learn_more: e598091d132f890c37a6d4ed94f6d794
common/license_expired: 7af13535e320e4197989472c01387d2c common/license_expired: 7af13535e320e4197989472c01387d2c
common/light_overlay: 0499907ea7b8405f4267b117998b5a78 common/light_overlay: 0499907ea7b8405f4267b117998b5a78
@@ -241,7 +252,6 @@ checksums:
common/look_and_feel: 9125503712626d495cedec7a79f1418c common/look_and_feel: 9125503712626d495cedec7a79f1418c
common/manage: a3d40c0267b81ae53c9598eaeb05087d common/manage: a3d40c0267b81ae53c9598eaeb05087d
common/marketing: fcf0f06f8b64b458c7ca6d95541a3cc8 common/marketing: fcf0f06f8b64b458c7ca6d95541a3cc8
common/member: 1606dc30b369856b9dba1fe9aec425d2
common/members: 0932e80cba1e3e0a7f52bb67ff31da32 common/members: 0932e80cba1e3e0a7f52bb67ff31da32
common/members_and_teams: bf5c3fadcb9fc23533ec1532b805ac08 common/members_and_teams: bf5c3fadcb9fc23533ec1532b805ac08
common/membership_not_found: 7ac63584af23396aace9992ad919ffd4 common/membership_not_found: 7ac63584af23396aace9992ad919ffd4
@@ -253,6 +263,7 @@ checksums:
common/move_down: 4f4de55743043355ad4a839aff2c48ff common/move_down: 4f4de55743043355ad4a839aff2c48ff
common/move_up: 69f25b205c677abdb26cbb69d97cd10b common/move_up: 69f25b205c677abdb26cbb69d97cd10b
common/multiple_languages: 7d8ddd4b40d32fcd7bd6f7bac6485b1f common/multiple_languages: 7d8ddd4b40d32fcd7bd6f7bac6485b1f
common/my_product: ad022177062f9ef6e9acf33b13e889aa
common/name: 9368b5a047572b6051f334af5aa76819 common/name: 9368b5a047572b6051f334af5aa76819
common/new: 126d036fae5fb6b629728ecb97e6195b common/new: 126d036fae5fb6b629728ecb97e6195b
common/new_version_available: 399ddfc4232712e18ddab2587356b3dc common/new_version_available: 399ddfc4232712e18ddab2587356b3dc
@@ -348,8 +359,6 @@ checksums:
common/select_teams: ae5d451929846ae6367562bc671a1af9 common/select_teams: ae5d451929846ae6367562bc671a1af9
common/selected: 9f09e059ba20c88ed34e2b4e8e032d56 common/selected: 9f09e059ba20c88ed34e2b4e8e032d56
common/selected_questions: beffe92d5272d99a0022f004e6a6ad73 common/selected_questions: beffe92d5272d99a0022f004e6a6ad73
common/selection: 25b570dc6339916a7aada2142aca0cd1
common/selections: 82f0681bf0208e25d7efedc23c556b8f
common/send_test_email: 2fd3ea40199b9589132ac826a5b0f3f5 common/send_test_email: 2fd3ea40199b9589132ac826a5b0f3f5
common/session_not_found: e9622df3170dbfd9636403bb0c22295b common/session_not_found: e9622df3170dbfd9636403bb0c22295b
common/settings: 8df6777277469c1fd88cc18dde2f1cc3 common/settings: 8df6777277469c1fd88cc18dde2f1cc3
@@ -401,6 +410,7 @@ checksums:
common/top_right: 241f95c923846911aaf13af6109333e5 common/top_right: 241f95c923846911aaf13af6109333e5
common/try_again: 33dd8820e743e35a66e6977f69e9d3b5 common/try_again: 33dd8820e743e35a66e6977f69e9d3b5
common/type: f04471a7ddac844b9ad145eb9911ef75 common/type: f04471a7ddac844b9ad145eb9911ef75
common/unknown_survey: dd8f6985e17ccf19fac1776e18b2c498
common/unlock_more_workspaces_with_a_higher_plan: fe1590075b855bb4306c9388b65143b0 common/unlock_more_workspaces_with_a_higher_plan: fe1590075b855bb4306c9388b65143b0
common/update: 079fc039262fd31b10532929685c2d1b common/update: 079fc039262fd31b10532929685c2d1b
common/updated: 8aa8ff2dc2977ca4b269e80a513100b4 common/updated: 8aa8ff2dc2977ca4b269e80a513100b4
@@ -609,7 +619,6 @@ checksums:
environments/contacts/contacts_table_refresh: 6a959475991dd4ab28ad881bae569a09 environments/contacts/contacts_table_refresh: 6a959475991dd4ab28ad881bae569a09
environments/contacts/contacts_table_refresh_success: 40951396e88e5c8fdafa0b3bb4fadca8 environments/contacts/contacts_table_refresh_success: 40951396e88e5c8fdafa0b3bb4fadca8
environments/contacts/create_attribute: 87320615901f95b4f35ee83c290a3a6c environments/contacts/create_attribute: 87320615901f95b4f35ee83c290a3a6c
environments/contacts/create_key: 0d385c354af8963acbe35cd646710f86
environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f
environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26 environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26
environments/contacts/custom_attributes: fffc7722742d1291b102dc737cf2fc9e environments/contacts/custom_attributes: fffc7722742d1291b102dc737cf2fc9e
@@ -620,6 +629,7 @@ checksums:
environments/contacts/delete_attribute_confirmation: 01d99b89eb3d27ff468d0db1b4aeb394 environments/contacts/delete_attribute_confirmation: 01d99b89eb3d27ff468d0db1b4aeb394
environments/contacts/delete_contact_confirmation: 2d45579e0bb4bc40fb1ee75b43c0e7a4 environments/contacts/delete_contact_confirmation: 2d45579e0bb4bc40fb1ee75b43c0e7a4
environments/contacts/delete_contact_confirmation_with_quotas: d3d17f13ae46ce04c126c82bf01299ac environments/contacts/delete_contact_confirmation_with_quotas: d3d17f13ae46ce04c126c82bf01299ac
environments/contacts/displays: fcc4527002bd045021882be463b8ac72
environments/contacts/edit_attribute: 92a83c96a5d850e7d39002e8fd5898f4 environments/contacts/edit_attribute: 92a83c96a5d850e7d39002e8fd5898f4
environments/contacts/edit_attribute_description: 073a3084bb2f3b34ed1320ed1cd6db3c environments/contacts/edit_attribute_description: 073a3084bb2f3b34ed1320ed1cd6db3c
environments/contacts/edit_attribute_values: 44e4e7a661cc1b59200bb07c710072a7 environments/contacts/edit_attribute_values: 44e4e7a661cc1b59200bb07c710072a7
@@ -631,6 +641,7 @@ checksums:
environments/contacts/invalid_csv_column_names: dcb8534e7d4c00b9ea7bdaf389f72328 environments/contacts/invalid_csv_column_names: dcb8534e7d4c00b9ea7bdaf389f72328
environments/contacts/invalid_date_format: 5bad9730ac5a5bacd0792098f712b1c4 environments/contacts/invalid_date_format: 5bad9730ac5a5bacd0792098f712b1c4
environments/contacts/invalid_number_format: bd0422507385f671c3046730a6febc64 environments/contacts/invalid_number_format: bd0422507385f671c3046730a6febc64
environments/contacts/no_activity_yet: f88897ac05afd6bf8af0d4834ad24ffc
environments/contacts/no_published_link_surveys_available: 9c1abc5b21aba827443cdf87dd6c8bfe environments/contacts/no_published_link_surveys_available: 9c1abc5b21aba827443cdf87dd6c8bfe
environments/contacts/no_published_surveys: bd945b0e2e2328c17615c94143bdd62b environments/contacts/no_published_surveys: bd945b0e2e2328c17615c94143bdd62b
environments/contacts/no_responses_found: f10190cffdda4ca1bed479acbb89b13f environments/contacts/no_responses_found: f10190cffdda4ca1bed479acbb89b13f
@@ -645,6 +656,8 @@ checksums:
environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514 environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514
environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79 environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79
environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38 environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38
environments/contacts/survey_viewed: 646d413218626787b0373ffd71cb7451
environments/contacts/survey_viewed_at: 2ab535237af5c3c3f33acc792a7e70a4
environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6 environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6
environments/contacts/unlock_contacts_description: c5572047f02b4c39e5109f9de715499d environments/contacts/unlock_contacts_description: c5572047f02b4c39e5109f9de715499d
environments/contacts/unlock_contacts_title: a8b3d7db03eb404d9267fd5cdd6d5ddb environments/contacts/unlock_contacts_title: a8b3d7db03eb404d9267fd5cdd6d5ddb
@@ -711,7 +724,12 @@ checksums:
environments/integrations/google_sheets/link_google_sheet: fa78146ae26ce5b1d2aaf2678f628943 environments/integrations/google_sheets/link_google_sheet: fa78146ae26ce5b1d2aaf2678f628943
environments/integrations/google_sheets/link_new_sheet: 8ad2ea8708f50ed184c00b84577b325e environments/integrations/google_sheets/link_new_sheet: 8ad2ea8708f50ed184c00b84577b325e
environments/integrations/google_sheets/no_integrations_yet: ea46f7747937baf48a47a4c1b1776aee environments/integrations/google_sheets/no_integrations_yet: ea46f7747937baf48a47a4c1b1776aee
environments/integrations/google_sheets/reconnect_button: 8992a0f250278c116cb26be448b68ba2
environments/integrations/google_sheets/reconnect_button_description: 851fd2fda57211293090f371d5b2c734
environments/integrations/google_sheets/reconnect_button_tooltip: 210dd97470fde8264d2c076db3c98fde
environments/integrations/google_sheets/spreadsheet_permission_error: 94f0007a187d3b9a7ab8200fe26aad20
environments/integrations/google_sheets/spreadsheet_url: b1665f96e6ecce23ea2d9196f4a3e5dd environments/integrations/google_sheets/spreadsheet_url: b1665f96e6ecce23ea2d9196f4a3e5dd
environments/integrations/google_sheets/token_expired_error: 555d34c18c554ec8ac66614f21bd44fc
environments/integrations/include_created_at: 8011355b13e28e638d74e6f3d68a2bbf environments/integrations/include_created_at: 8011355b13e28e638d74e6f3d68a2bbf
environments/integrations/include_hidden_fields: 25f0ea5ca1c6ead2cd121f8754cb8d72 environments/integrations/include_hidden_fields: 25f0ea5ca1c6ead2cd121f8754cb8d72
environments/integrations/include_metadata: 750091d965d7cc8d02468b5239816dc5 environments/integrations/include_metadata: 750091d965d7cc8d02468b5239816dc5
@@ -1006,7 +1024,7 @@ checksums:
environments/settings/general/email_customization_preview_email_heading: 8b798cb8438b3dd356c02dab33b4c897 environments/settings/general/email_customization_preview_email_heading: 8b798cb8438b3dd356c02dab33b4c897
environments/settings/general/email_customization_preview_email_text: fa6ae92403cc8f3c35c03e6c94cbde51 environments/settings/general/email_customization_preview_email_text: fa6ae92403cc8f3c35c03e6c94cbde51
environments/settings/general/error_deleting_organization_please_try_again: 7f0fe257d4a0b40bff025408a7766706 environments/settings/general/error_deleting_organization_please_try_again: 7f0fe257d4a0b40bff025408a7766706
environments/settings/general/from_your_organization: 4b7970431edb3d0f13c394dbd755a055 environments/settings/general/from_your_organization: 9ebd6dcd79f7bfad3fea46ed2e3133d2
environments/settings/general/invitation_sent_once_more: e6e5ea066810f9dcb65788aa4f05d6e2 environments/settings/general/invitation_sent_once_more: e6e5ea066810f9dcb65788aa4f05d6e2
environments/settings/general/invite_deleted_successfully: 1c7dca6d0f6870d945288e38cfd2f943 environments/settings/general/invite_deleted_successfully: 1c7dca6d0f6870d945288e38cfd2f943
environments/settings/general/invite_expires_on: 6fd2356ad91a5f189070c43855904bb4 environments/settings/general/invite_expires_on: 6fd2356ad91a5f189070c43855904bb4
@@ -1161,6 +1179,7 @@ checksums:
environments/surveys/edit/add_fallback_placeholder: 0e77ea487ddd7bc7fc2f1574b018dc08 environments/surveys/edit/add_fallback_placeholder: 0e77ea487ddd7bc7fc2f1574b018dc08
environments/surveys/edit/add_hidden_field_id: a8f55b51b790cf5f4d898af7770ad1ed environments/surveys/edit/add_hidden_field_id: a8f55b51b790cf5f4d898af7770ad1ed
environments/surveys/edit/add_highlight_border: 66f52b21fbb9aa6561c98a090abaaf8f environments/surveys/edit/add_highlight_border: 66f52b21fbb9aa6561c98a090abaaf8f
environments/surveys/edit/add_highlight_border_description: fe548fe03ea10ef5cd9e553d6812b3c2
environments/surveys/edit/add_logic: f234c9f1393a9ed4792dfbd15838c951 environments/surveys/edit/add_logic: f234c9f1393a9ed4792dfbd15838c951
environments/surveys/edit/add_none_of_the_above: dbe1ada4512d6c3f80c54c8fac107ec6 environments/surveys/edit/add_none_of_the_above: dbe1ada4512d6c3f80c54c8fac107ec6
environments/surveys/edit/add_option: 143c54f0b201067fe5159284d6daeca2 environments/surveys/edit/add_option: 143c54f0b201067fe5159284d6daeca2
@@ -1177,11 +1196,13 @@ checksums:
environments/surveys/edit/adjust_survey_closed_message: ae6f38c9daf08656362bd84459a312fa environments/surveys/edit/adjust_survey_closed_message: ae6f38c9daf08656362bd84459a312fa
environments/surveys/edit/adjust_survey_closed_message_description: e906aebd9af6451a2a39c73287927299 environments/surveys/edit/adjust_survey_closed_message_description: e906aebd9af6451a2a39c73287927299
environments/surveys/edit/adjust_the_theme_in_the: bccdafda8af5871513266f668b55d690 environments/surveys/edit/adjust_the_theme_in_the: bccdafda8af5871513266f668b55d690
environments/surveys/edit/all_are_true: 05d02c5afac857da530b73dcf18dd8e4
environments/surveys/edit/all_other_answers_will_continue_to: 9a5d09eea42ff5fd1c18cc58a14dcabd environments/surveys/edit/all_other_answers_will_continue_to: 9a5d09eea42ff5fd1c18cc58a14dcabd
environments/surveys/edit/allow_multi_select: 7b4b83f7a0205e2a0a8971671a69a174 environments/surveys/edit/allow_multi_select: 7b4b83f7a0205e2a0a8971671a69a174
environments/surveys/edit/allow_multiple_files: dbd99f9d1026e4f7c5a5d03f71ba379d environments/surveys/edit/allow_multiple_files: dbd99f9d1026e4f7c5a5d03f71ba379d
environments/surveys/edit/allow_users_to_select_more_than_one_image: d683e0b538d1366400292a771f3fbd08 environments/surveys/edit/allow_users_to_select_more_than_one_image: d683e0b538d1366400292a771f3fbd08
environments/surveys/edit/and_launch_surveys_in_your_website_or_app: a3edcdb4aea792a27d90aad1930f001a environments/surveys/edit/and_launch_surveys_in_your_website_or_app: a3edcdb4aea792a27d90aad1930f001a
environments/surveys/edit/any_is_true: 32c9f3998984fd32a2b5bc53f2d97429
environments/surveys/edit/animation: 66a18eacfb92fc9fc9db188d2dde4f81 environments/surveys/edit/animation: 66a18eacfb92fc9fc9db188d2dde4f81
environments/surveys/edit/app_survey_description: bdfacfce478e97f70b700a1382dfa687 environments/surveys/edit/app_survey_description: bdfacfce478e97f70b700a1382dfa687
environments/surveys/edit/assign: e80715ab64bf7cf463abb3a9fd1ad516 environments/surveys/edit/assign: e80715ab64bf7cf463abb3a9fd1ad516
@@ -1359,7 +1380,6 @@ checksums:
environments/surveys/edit/follow_ups_modal_updated_successfull_toast: 61204fada3231f4f1fe3866e87e1130a environments/surveys/edit/follow_ups_modal_updated_successfull_toast: 61204fada3231f4f1fe3866e87e1130a
environments/surveys/edit/follow_ups_new: 224c779d252b3e75086e4ed456ba2548 environments/surveys/edit/follow_ups_new: 224c779d252b3e75086e4ed456ba2548
environments/surveys/edit/follow_ups_upgrade_button_text: 4cd167527fc6cdb5b0bfc9b486b142a8 environments/surveys/edit/follow_ups_upgrade_button_text: 4cd167527fc6cdb5b0bfc9b486b142a8
environments/surveys/edit/form_styling: 1278a2db4257b5500474161133acc857
environments/surveys/edit/formbricks_sdk_is_not_connected: 35165b0cac182a98408007a378cc677e environments/surveys/edit/formbricks_sdk_is_not_connected: 35165b0cac182a98408007a378cc677e
environments/surveys/edit/four_points: b289628a6b8a6cd0f7d17a14ca6cd7bf environments/surveys/edit/four_points: b289628a6b8a6cd0f7d17a14ca6cd7bf
environments/surveys/edit/heading: 79e9dfa461f38a239d34b9833ca103f1 environments/surveys/edit/heading: 79e9dfa461f38a239d34b9833ca103f1
@@ -1474,6 +1494,7 @@ checksums:
environments/surveys/edit/question_deleted: ecdeb22b81ae2d732656a7742c1eec7b environments/surveys/edit/question_deleted: ecdeb22b81ae2d732656a7742c1eec7b
environments/surveys/edit/question_duplicated: 3f02439fd0a8b818bc84c1b1b473898c environments/surveys/edit/question_duplicated: 3f02439fd0a8b818bc84c1b1b473898c
environments/surveys/edit/question_id_updated: e8d94dbefcbad00c7464b3d1fb0ee81a environments/surveys/edit/question_id_updated: e8d94dbefcbad00c7464b3d1fb0ee81a
environments/surveys/edit/question_number: 742636e9d2d5dcc7ee6ca1b3016bcee7
environments/surveys/edit/question_used_in_logic_warning_text: ec78767a7cf335222d41b98cb5baa6be 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_logic_warning_title: 4bb8528cdc3b8649c194487067737f6d
environments/surveys/edit/question_used_in_quota: ceb5e88f6916e4863e589c6be030bb3b environments/surveys/edit/question_used_in_quota: ceb5e88f6916e4863e589c6be030bb3b
@@ -1530,7 +1551,7 @@ checksums:
environments/surveys/edit/response_limits_redirections_and_more: e4f1cf94e56ad0e1b08701158d688802 environments/surveys/edit/response_limits_redirections_and_more: e4f1cf94e56ad0e1b08701158d688802
environments/surveys/edit/response_options: 2988136d5248d7726583108992dcbaee environments/surveys/edit/response_options: 2988136d5248d7726583108992dcbaee
environments/surveys/edit/roundness: 5a161c8f5f258defb57ed1d551737cc4 environments/surveys/edit/roundness: 5a161c8f5f258defb57ed1d551737cc4
environments/surveys/edit/roundness_description: bde131aa5674836416dcdf2ff517d899 environments/surveys/edit/roundness_description: 03940a6871ae43efa4810cba7cadb74b
environments/surveys/edit/row_used_in_logic_error: f89453ff1b6db77ad84af840fedd9813 environments/surveys/edit/row_used_in_logic_error: f89453ff1b6db77ad84af840fedd9813
environments/surveys/edit/rows: 8f41f34e6ca28221cf1ebd948af4c151 environments/surveys/edit/rows: 8f41f34e6ca28221cf1ebd948af4c151
environments/surveys/edit/save_and_close: 6ede705b3f82f30269ff3054a5049e34 environments/surveys/edit/save_and_close: 6ede705b3f82f30269ff3054a5049e34
@@ -1576,6 +1597,7 @@ checksums:
environments/surveys/edit/survey_completed_subheading: db537c356c3ab6564d24de0d11a0fee2 environments/surveys/edit/survey_completed_subheading: db537c356c3ab6564d24de0d11a0fee2
environments/surveys/edit/survey_display_settings: 8ed19e6a8e1376f7a1ba037d82c4ae11 environments/surveys/edit/survey_display_settings: 8ed19e6a8e1376f7a1ba037d82c4ae11
environments/surveys/edit/survey_placement: 083c10f257337f9648bf9d435b18ec2c environments/surveys/edit/survey_placement: 083c10f257337f9648bf9d435b18ec2c
environments/surveys/edit/survey_styling: 7f96d6563e934e65687b74374a33b1dc
environments/surveys/edit/survey_trigger: f0c7014a684ca566698b87074fad5579 environments/surveys/edit/survey_trigger: f0c7014a684ca566698b87074fad5579
environments/surveys/edit/switch_multi_language_on_to_get_started: cca0ef91ee49095da30cd1e3f26c406f environments/surveys/edit/switch_multi_language_on_to_get_started: cca0ef91ee49095da30cd1e3f26c406f
environments/surveys/edit/target_block_not_found: 0a0c401017ab32364fec2fcbf815d832 environments/surveys/edit/target_block_not_found: 0a0c401017ab32364fec2fcbf815d832
@@ -1662,9 +1684,9 @@ checksums:
environments/surveys/edit/waiting_time_across_surveys: 6873c18d51830e2cadef67cce6a2c95c environments/surveys/edit/waiting_time_across_surveys: 6873c18d51830e2cadef67cce6a2c95c
environments/surveys/edit/waiting_time_across_surveys_description: 6edafaeb3ccd8cadde81175776636c8e environments/surveys/edit/waiting_time_across_surveys_description: 6edafaeb3ccd8cadde81175776636c8e
environments/surveys/edit/welcome_message: 986a434e3895c8ee0b267df95cc40051 environments/surveys/edit/welcome_message: 986a434e3895c8ee0b267df95cc40051
environments/surveys/edit/when: a40ad3eed1b75e76226290eeb9bb20cd
environments/surveys/edit/without_a_filter_all_of_your_users_can_be_surveyed: 451990569c61f25d01044cc45b1ce122 environments/surveys/edit/without_a_filter_all_of_your_users_can_be_surveyed: 451990569c61f25d01044cc45b1ce122
environments/surveys/edit/you_have_not_created_a_segment_yet: c6658bd1cee9c5c957c675db044708dd environments/surveys/edit/you_have_not_created_a_segment_yet: c6658bd1cee9c5c957c675db044708dd
environments/surveys/edit/you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations: 04241177ba989ef4c1d8c01e1a7b8541
environments/surveys/edit/your_description_here_recall_information_with: 60f73a3cc9bdb9afea2166a7db8fd618 environments/surveys/edit/your_description_here_recall_information_with: 60f73a3cc9bdb9afea2166a7db8fd618
environments/surveys/edit/your_question_here_recall_information_with: 6395bd54f5167830c9d662ba403da167 environments/surveys/edit/your_question_here_recall_information_with: 6395bd54f5167830c9d662ba403da167
environments/surveys/edit/your_web_app: 07234bed03a33330dc50ae9fcf0174f3 environments/surveys/edit/your_web_app: 07234bed03a33330dc50ae9fcf0174f3
@@ -1846,6 +1868,7 @@ checksums:
environments/surveys/summary/filtered_responses_excel: 06e57bae9e41979fd7fc4b8bfe3466f9 environments/surveys/summary/filtered_responses_excel: 06e57bae9e41979fd7fc4b8bfe3466f9
environments/surveys/summary/generating_qr_code: 5026d4a76f995db458195e5215d9bbd9 environments/surveys/summary/generating_qr_code: 5026d4a76f995db458195e5215d9bbd9
environments/surveys/summary/impressions: 7fe38d42d68a64d3fd8436a063751584 environments/surveys/summary/impressions: 7fe38d42d68a64d3fd8436a063751584
environments/surveys/summary/impressions_identified_only: 10f8c491463c73b8e6534314ee00d165
environments/surveys/summary/impressions_tooltip: 4d0823cbf360304770c7c5913e33fdc8 environments/surveys/summary/impressions_tooltip: 4d0823cbf360304770c7c5913e33fdc8
environments/surveys/summary/in_app/connection_description: 9710bbf8048a8a5c3b2b56db9d946b73 environments/surveys/summary/in_app/connection_description: 9710bbf8048a8a5c3b2b56db9d946b73
environments/surveys/summary/in_app/connection_title: 29e8a40ad6a7fdb5af5ee9451a70a9aa environments/surveys/summary/in_app/connection_title: 29e8a40ad6a7fdb5af5ee9451a70a9aa
@@ -1886,6 +1909,7 @@ checksums:
environments/surveys/summary/last_quarter: 2e565a81de9b3d7b1ee709ebb6f6eda1 environments/surveys/summary/last_quarter: 2e565a81de9b3d7b1ee709ebb6f6eda1
environments/surveys/summary/last_year: fe7c268a48bf85bc40da000e6e437637 environments/surveys/summary/last_year: fe7c268a48bf85bc40da000e6e437637
environments/surveys/summary/limit: 347051f1a068e01e8c4e4f6744d8e727 environments/surveys/summary/limit: 347051f1a068e01e8c4e4f6744d8e727
environments/surveys/summary/no_identified_impressions: c3bc42e6feb9010ced905ded51c5afc4
environments/surveys/summary/no_responses_found: f10190cffdda4ca1bed479acbb89b13f environments/surveys/summary/no_responses_found: f10190cffdda4ca1bed479acbb89b13f
environments/surveys/summary/other_values_found: 48a74ee68c05f7fb162072b50c683b6a environments/surveys/summary/other_values_found: 48a74ee68c05f7fb162072b50c683b6a
environments/surveys/summary/overall: 6c6d6533013d4739766af84b2871bca6 environments/surveys/summary/overall: 6c6d6533013d4739766af84b2871bca6
@@ -1908,6 +1932,7 @@ checksums:
environments/surveys/summary/starts: 3153990a4ade414f501a7e63ab771362 environments/surveys/summary/starts: 3153990a4ade414f501a7e63ab771362
environments/surveys/summary/starts_tooltip: 0a7dd01320490dbbea923053fa1ccad6 environments/surveys/summary/starts_tooltip: 0a7dd01320490dbbea923053fa1ccad6
environments/surveys/summary/survey_reset_successfully: f53db36a28980ef4766215cf13f01e51 environments/surveys/summary/survey_reset_successfully: f53db36a28980ef4766215cf13f01e51
environments/surveys/summary/survey_results: b7d86f636beaee2b4d5746bdda058d07
environments/surveys/summary/this_month: 50845a38865204a97773c44dcd2ebb90 environments/surveys/summary/this_month: 50845a38865204a97773c44dcd2ebb90
environments/surveys/summary/this_quarter: 9c77d94783dff2269c069389122cd7bd environments/surveys/summary/this_quarter: 9c77d94783dff2269c069389122cd7bd
environments/surveys/summary/this_year: 1e69651c2ac722f8ce138f43cf2e02f9 environments/surveys/summary/this_year: 1e69651c2ac722f8ce138f43cf2e02f9
@@ -2027,7 +2052,7 @@ checksums:
environments/workspace/look/advanced_styling_field_description_size: a0d51c3ab7dc56320ecedc2b27917842 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_size_description: ff880ea1beddd1b1ec7416d0b8a69cf3
environments/workspace/look/advanced_styling_field_description_weight: 514680cc7202ad29835c1cbcde3def1c 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_description_weight_description: aa95bc81b5336a548e256bce49350683
environments/workspace/look/advanced_styling_field_font_size: ca44d14429b2175a1b194793b4ab8f6b environments/workspace/look/advanced_styling_field_font_size: ca44d14429b2175a1b194793b4ab8f6b
environments/workspace/look/advanced_styling_field_font_weight: bfef83778146cf40550df9650d8a07da 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: 4ccf3935ad90c88ad4add24f498673ce
@@ -2041,7 +2066,7 @@ checksums:
environments/workspace/look/advanced_styling_field_indicator_bg_description: 7eb3b54a8b331354ec95c0dc1545c620 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_border_radius_description: 0007f1bb572b35d9a3720daeb7a55617
environments/workspace/look/advanced_styling_field_input_font_size_description: 5311f95dcbd083623e35c98ea5374c3b 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_height_description: bb7439d42ec3848a8fa9edb8b001b69a
environments/workspace/look/advanced_styling_field_input_padding_x_description: 10e14296468321c13fda77fd1ba58dfd 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_padding_y_description: 98b4aeff2940516d05ea61bdc1211d0d
environments/workspace/look/advanced_styling_field_input_placeholder_opacity_description: f55a6700884d24014404e58876121ddf environments/workspace/look/advanced_styling_field_input_placeholder_opacity_description: f55a6700884d24014404e58876121ddf
@@ -2050,6 +2075,8 @@ checksums:
environments/workspace/look/advanced_styling_field_input_text_description: 460450df24ea0cc902710118a5000feb 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: 0ceaed10d99ed4ad83cb0934ab970174
environments/workspace/look/advanced_styling_field_option_bg_description: 6cd6ccecbbb9f2f19439d7c682eb67c1 environments/workspace/look/advanced_styling_field_option_bg_description: 6cd6ccecbbb9f2f19439d7c682eb67c1
environments/workspace/look/advanced_styling_field_option_border: aa478eb148515b6a2637fb144ff72028
environments/workspace/look/advanced_styling_field_option_border_description: 8f75b740e8dcb7f6cfeff2e5d5ca7c92
environments/workspace/look/advanced_styling_field_option_border_radius_description: 23f81c25b2681a7c9e2c4f2e7d2e0656 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_font_size_description: 5430fd9b08819972f0a613bf3fa659da
environments/workspace/look/advanced_styling_field_option_label: 2767a5db32742073a01aac16488e93dc environments/workspace/look/advanced_styling_field_option_label: 2767a5db32742073a01aac16488e93dc
@@ -2221,6 +2248,16 @@ checksums:
templates/alignment_and_engagement_survey_question_4_headline: e36be56ce8aad1d0ca04939bea4e39b7 templates/alignment_and_engagement_survey_question_4_headline: e36be56ce8aad1d0ca04939bea4e39b7
templates/alignment_and_engagement_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/alignment_and_engagement_survey_question_4_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee
templates/back: f541015a827e37cb3b1234e56bc2aa3c templates/back: f541015a827e37cb3b1234e56bc2aa3c
templates/block_1: 5e1b4dce0cb70662441b663507a69454
templates/block_2: f50d8aab8b44f168a2ab00526d4f9a2c
templates/block_3: 78d84f8e4763a95710543c5368ce8a41
templates/block_4: 2c346374f245a6821940c061b855ac69
templates/block_5: 975abfc66e8e377478ff691a040dda0b
templates/block_6: 2bd10f1edb210243c5ab459c59e02d30
templates/block_7: 13f0f680c09c96081e125123ad2f6786
templates/block_8: 1be1b18e159e8c8d11d2fb1082ea5d98
templates/block_9: 2da3894d05e4415fa043ba18d11d60e2
templates/block_10: 09a42e99b34b45700e734730acfe37ed
templates/book_interview: 1cc9c72d1c088b28e5dfa5ec7d7b78c4 templates/book_interview: 1cc9c72d1c088b28e5dfa5ec7d7b78c4
templates/build_product_roadmap_description: 6ca163ed3b0095cedcbc11822a0d502a templates/build_product_roadmap_description: 6ca163ed3b0095cedcbc11822a0d502a
templates/build_product_roadmap_name: 8c216b183c3539c0340ce87465a391cc templates/build_product_roadmap_name: 8c216b183c3539c0340ce87465a391cc
@@ -2428,7 +2465,6 @@ checksums:
templates/csat_survey_question_3_headline: 25974b7f1692cad41908fe305830b6c0 templates/csat_survey_question_3_headline: 25974b7f1692cad41908fe305830b6c0
templates/csat_survey_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee templates/csat_survey_question_3_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee
templates/cta_description: bc94a2ddc965b286a8677b0642696c7e templates/cta_description: bc94a2ddc965b286a8677b0642696c7e
templates/custom_survey_block_1_name: 5e1b4dce0cb70662441b663507a69454
templates/custom_survey_description: 0492afdea2ef1bd683eaf48a2bad2caa templates/custom_survey_description: 0492afdea2ef1bd683eaf48a2bad2caa
templates/custom_survey_name: 6fc756927ca9ea22c26368cccd64a67e templates/custom_survey_name: 6fc756927ca9ea22c26368cccd64a67e
templates/custom_survey_question_1_headline: 0abf9d41e0b5c5567c3833fd63048398 templates/custom_survey_question_1_headline: 0abf9d41e0b5c5567c3833fd63048398
@@ -2831,6 +2867,9 @@ checksums:
templates/preview_survey_question_2_choice_2_label: 1af148222f327f28cf0db6513de5989e templates/preview_survey_question_2_choice_2_label: 1af148222f327f28cf0db6513de5989e
templates/preview_survey_question_2_headline: 5cfb173d156555227fbc2c97ad921e72 templates/preview_survey_question_2_headline: 5cfb173d156555227fbc2c97ad921e72
templates/preview_survey_question_2_subheader: 2e652d8acd68d072e5a0ae686c4011c0 templates/preview_survey_question_2_subheader: 2e652d8acd68d072e5a0ae686c4011c0
templates/preview_survey_question_open_text_headline: a9509a47e0456ae98ec3ddac3d6fad2c
templates/preview_survey_question_open_text_placeholder: 37ee9c84f3777b9220d4faec1e1c78ee
templates/preview_survey_question_open_text_subheader: 3c7bf09f3f17b02bc2fbbbdb347a5830
templates/preview_survey_welcome_card_headline: 8778dc41547a2778d0f9482da989fc00 templates/preview_survey_welcome_card_headline: 8778dc41547a2778d0f9482da989fc00
templates/prioritize_features_description: 1eae41fad0e3947f803d8539081e59ec templates/prioritize_features_description: 1eae41fad0e3947f803d8539081e59ec
templates/prioritize_features_name: 4ca59ff1f9c319aaa68c3106d820fd6a templates/prioritize_features_name: 4ca59ff1f9c319aaa68c3106d820fd6a
+2 -1
View File
@@ -63,7 +63,8 @@ export const INVITE_DISABLED = env.INVITE_DISABLED === "1";
export const SLACK_CLIENT_SECRET = env.SLACK_CLIENT_SECRET; export const SLACK_CLIENT_SECRET = env.SLACK_CLIENT_SECRET;
export const SLACK_CLIENT_ID = env.SLACK_CLIENT_ID; export const SLACK_CLIENT_ID = env.SLACK_CLIENT_ID;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize,groups:read`; export const SLACK_REDIRECT_URI = `${WEBAPP_URL}/api/v1/integrations/slack/callback`;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize,groups:read&redirect_uri=${SLACK_REDIRECT_URI}`;
export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID; export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID;
export const GOOGLE_SHEETS_CLIENT_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET; export const GOOGLE_SHEETS_CLIENT_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET;
+99 -8
View File
@@ -1,9 +1,10 @@
import "server-only"; import "server-only";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react"; import { cache as reactCache } from "react";
import { z } from "zod";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { ZId } from "@formbricks/types/common"; import { ZId } from "@formbricks/types/common";
import { TDisplay, TDisplayFilters } from "@formbricks/types/displays"; import { TDisplay, TDisplayFilters, TDisplayWithContact } from "@formbricks/types/displays";
import { DatabaseError } from "@formbricks/types/errors"; import { DatabaseError } from "@formbricks/types/errors";
import { validateInputs } from "../utils/validate"; import { validateInputs } from "../utils/validate";
@@ -23,13 +24,12 @@ export const getDisplayCountBySurveyId = reactCache(
const displayCount = await prisma.display.count({ const displayCount = await prisma.display.count({
where: { where: {
surveyId: surveyId, surveyId: surveyId,
...(filters && ...(filters?.createdAt && {
filters.createdAt && { createdAt: {
createdAt: { gte: filters.createdAt.min,
gte: filters.createdAt.min, lte: filters.createdAt.max,
lte: filters.createdAt.max, },
}, }),
}),
}, },
}); });
return displayCount; return displayCount;
@@ -42,6 +42,97 @@ export const getDisplayCountBySurveyId = reactCache(
} }
); );
export const getDisplaysByContactId = reactCache(
async (contactId: string): Promise<Pick<TDisplay, "id" | "createdAt" | "surveyId">[]> => {
validateInputs([contactId, ZId]);
try {
const displays = await prisma.display.findMany({
where: { contactId },
select: {
id: true,
createdAt: true,
surveyId: true,
},
orderBy: { createdAt: "desc" },
});
return displays;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
throw error;
}
}
);
export const getDisplaysBySurveyIdWithContact = reactCache(
async (surveyId: string, limit?: number, offset?: number): Promise<TDisplayWithContact[]> => {
validateInputs(
[surveyId, ZId],
[limit, z.number().int().min(1).optional()],
[offset, z.number().int().nonnegative().optional()]
);
try {
const displays = await prisma.display.findMany({
where: {
surveyId,
contactId: { not: null },
},
select: {
id: true,
createdAt: true,
surveyId: true,
contact: {
select: {
id: true,
attributes: {
where: {
attributeKey: {
key: { in: ["email", "userId"] },
},
},
select: {
attributeKey: { select: { key: true } },
value: true,
},
},
},
},
},
orderBy: { createdAt: "desc" },
take: limit,
skip: offset,
});
return displays.map((display) => ({
id: display.id,
createdAt: display.createdAt,
surveyId: display.surveyId,
contact: display.contact
? {
id: display.contact.id,
attributes: display.contact.attributes.reduce(
(acc, attr) => {
acc[attr.attributeKey.key] = attr.value;
return acc;
},
{} as Record<string, string>
),
}
: null,
}));
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
throw error;
}
}
);
export const deleteDisplay = async (displayId: string, tx?: Prisma.TransactionClient): Promise<TDisplay> => { export const deleteDisplay = async (displayId: string, tx?: Prisma.TransactionClient): Promise<TDisplay> => {
validateInputs([displayId, ZId]); validateInputs([displayId, ZId]);
try { try {
@@ -0,0 +1,219 @@
import { mockDisplayId, mockSurveyId } from "./__mocks__/data.mock";
import { prisma } from "@/lib/__mocks__/database";
import { Prisma } from "@prisma/client";
import { describe, expect, test, vi } from "vitest";
import { PrismaErrorType } from "@formbricks/database/types/error";
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
import { getDisplaysByContactId, getDisplaysBySurveyIdWithContact } from "../service";
const mockContactId = "clqnj99r9000008lebgf8734j";
const mockDisplaysForContact = [
{
id: mockDisplayId,
createdAt: new Date("2024-01-15T10:00:00Z"),
surveyId: mockSurveyId,
},
{
id: "clqkr5smu000208jy50v6g5k5",
createdAt: new Date("2024-01-14T10:00:00Z"),
surveyId: "clqkr8dlv000308jybb08evgs",
},
];
const mockDisplaysWithContact = [
{
id: mockDisplayId,
createdAt: new Date("2024-01-15T10:00:00Z"),
surveyId: mockSurveyId,
contact: {
id: mockContactId,
attributes: [
{ attributeKey: { key: "email" }, value: "test@example.com" },
{ attributeKey: { key: "userId" }, value: "user-123" },
],
},
},
{
id: "clqkr5smu000208jy50v6g5k5",
createdAt: new Date("2024-01-14T10:00:00Z"),
surveyId: "clqkr8dlv000308jybb08evgs",
contact: {
id: "clqnj99r9000008lebgf8734k",
attributes: [{ attributeKey: { key: "userId" }, value: "user-456" }],
},
},
];
describe("getDisplaysByContactId", () => {
describe("Happy Path", () => {
test("returns displays for a contact ordered by createdAt desc", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue(mockDisplaysForContact as any);
const result = await getDisplaysByContactId(mockContactId);
expect(result).toEqual(mockDisplaysForContact);
expect(prisma.display.findMany).toHaveBeenCalledWith({
where: { contactId: mockContactId },
select: {
id: true,
createdAt: true,
surveyId: true,
},
orderBy: { createdAt: "desc" },
});
});
test("returns empty array when contact has no displays", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue([]);
const result = await getDisplaysByContactId(mockContactId);
expect(result).toEqual([]);
});
});
describe("Sad Path", () => {
test("throws a ValidationError if the contactId is invalid", async () => {
await expect(getDisplaysByContactId("not-a-cuid")).rejects.toThrow(ValidationError);
});
test("throws DatabaseError on PrismaClientKnownRequestError", async () => {
const errToThrow = new Prisma.PrismaClientKnownRequestError("Mock error", {
code: PrismaErrorType.UniqueConstraintViolation,
clientVersion: "0.0.1",
});
vi.mocked(prisma.display.findMany).mockRejectedValue(errToThrow);
await expect(getDisplaysByContactId(mockContactId)).rejects.toThrow(DatabaseError);
});
test("throws generic Error for other exceptions", async () => {
vi.mocked(prisma.display.findMany).mockRejectedValue(new Error("Mock error"));
await expect(getDisplaysByContactId(mockContactId)).rejects.toThrow(Error);
});
});
});
describe("getDisplaysBySurveyIdWithContact", () => {
describe("Happy Path", () => {
test("returns displays with contact attributes transformed", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue(mockDisplaysWithContact as any);
const result = await getDisplaysBySurveyIdWithContact(mockSurveyId, 15, 0);
expect(result).toEqual([
{
id: mockDisplayId,
createdAt: new Date("2024-01-15T10:00:00Z"),
surveyId: mockSurveyId,
contact: {
id: mockContactId,
attributes: { email: "test@example.com", userId: "user-123" },
},
},
{
id: "clqkr5smu000208jy50v6g5k5",
createdAt: new Date("2024-01-14T10:00:00Z"),
surveyId: "clqkr8dlv000308jybb08evgs",
contact: {
id: "clqnj99r9000008lebgf8734k",
attributes: { userId: "user-456" },
},
},
]);
});
test("calls prisma with correct where clause and pagination", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue([]);
await getDisplaysBySurveyIdWithContact(mockSurveyId, 15, 0);
expect(prisma.display.findMany).toHaveBeenCalledWith({
where: {
surveyId: mockSurveyId,
contactId: { not: null },
},
select: {
id: true,
createdAt: true,
surveyId: true,
contact: {
select: {
id: true,
attributes: {
where: {
attributeKey: {
key: { in: ["email", "userId"] },
},
},
select: {
attributeKey: { select: { key: true } },
value: true,
},
},
},
},
},
orderBy: { createdAt: "desc" },
take: 15,
skip: 0,
});
});
test("returns empty array when no displays found", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue([]);
const result = await getDisplaysBySurveyIdWithContact(mockSurveyId);
expect(result).toEqual([]);
});
test("handles display with null contact", async () => {
vi.mocked(prisma.display.findMany).mockResolvedValue([
{
id: mockDisplayId,
createdAt: new Date("2024-01-15T10:00:00Z"),
surveyId: mockSurveyId,
contact: null,
},
] as any);
const result = await getDisplaysBySurveyIdWithContact(mockSurveyId);
expect(result).toEqual([
{
id: mockDisplayId,
createdAt: new Date("2024-01-15T10:00:00Z"),
surveyId: mockSurveyId,
contact: null,
},
]);
});
});
describe("Sad Path", () => {
test("throws a ValidationError if the surveyId is invalid", async () => {
await expect(getDisplaysBySurveyIdWithContact("not-a-cuid")).rejects.toThrow(ValidationError);
});
test("throws DatabaseError on PrismaClientKnownRequestError", async () => {
const errToThrow = new Prisma.PrismaClientKnownRequestError("Mock error", {
code: PrismaErrorType.UniqueConstraintViolation,
clientVersion: "0.0.1",
});
vi.mocked(prisma.display.findMany).mockRejectedValue(errToThrow);
await expect(getDisplaysBySurveyIdWithContact(mockSurveyId)).rejects.toThrow(DatabaseError);
});
test("throws generic Error for other exceptions", async () => {
vi.mocked(prisma.display.findMany).mockRejectedValue(new Error("Mock error"));
await expect(getDisplaysBySurveyIdWithContact(mockSurveyId)).rejects.toThrow(Error);
});
});
});
+48 -26
View File
@@ -1,44 +1,66 @@
import { beforeEach, describe, expect, test, vi } from "vitest"; import { beforeEach, describe, expect, test, vi } from "vitest";
// Mock constants module
const envMock = { const envMock = {
env: { WEBAPP_URL: undefined as string | undefined,
WEBAPP_URL: "http://localhost:3000", VERCEL_URL: undefined as string | undefined,
PUBLIC_URL: undefined as string | undefined, PUBLIC_URL: undefined as string | undefined,
},
}; };
vi.mock("@/lib/env", () => envMock); vi.mock("./env", () => ({
env: envMock,
}));
const loadGetPublicDomain = async () => {
vi.resetModules();
const { getPublicDomain } = await import("./getPublicUrl");
return getPublicDomain;
};
describe("getPublicDomain", () => { describe("getPublicDomain", () => {
beforeEach(() => { beforeEach(() => {
vi.resetModules(); envMock.WEBAPP_URL = undefined;
envMock.VERCEL_URL = undefined;
envMock.PUBLIC_URL = undefined;
}); });
test("should return WEBAPP_URL when PUBLIC_URL is not set", async () => { test("returns trimmed WEBAPP_URL when configured", async () => {
const { getPublicDomain } = await import("./getPublicUrl"); envMock.WEBAPP_URL = " https://app.formbricks.com ";
const domain = getPublicDomain();
expect(domain).toBe("http://localhost:3000"); const getPublicDomain = await loadGetPublicDomain();
expect(getPublicDomain()).toBe("https://app.formbricks.com");
}); });
test("should return PUBLIC_URL when it is set", async () => { test("falls back to VERCEL_URL when WEBAPP_URL is empty", async () => {
envMock.env.PUBLIC_URL = "https://surveys.example.com"; envMock.WEBAPP_URL = " ";
const { getPublicDomain } = await import("./getPublicUrl"); envMock.VERCEL_URL = "preview.formbricks.com";
const domain = getPublicDomain();
expect(domain).toBe("https://surveys.example.com"); const getPublicDomain = await loadGetPublicDomain();
expect(getPublicDomain()).toBe("https://preview.formbricks.com");
}); });
test("should handle empty string PUBLIC_URL by returning WEBAPP_URL", async () => { test("falls back to localhost when WEBAPP_URL and VERCEL_URL are not set", async () => {
envMock.env.PUBLIC_URL = ""; const getPublicDomain = await loadGetPublicDomain();
const { getPublicDomain } = await import("./getPublicUrl");
const domain = getPublicDomain(); expect(getPublicDomain()).toBe("http://localhost:3000");
expect(domain).toBe("http://localhost:3000");
}); });
test("should handle undefined PUBLIC_URL by returning WEBAPP_URL", async () => { test("returns PUBLIC_URL when set", async () => {
envMock.env.PUBLIC_URL = undefined; envMock.WEBAPP_URL = "https://app.formbricks.com";
const { getPublicDomain } = await import("./getPublicUrl"); envMock.PUBLIC_URL = "https://surveys.formbricks.com";
const domain = getPublicDomain();
expect(domain).toBe("http://localhost:3000"); const getPublicDomain = await loadGetPublicDomain();
expect(getPublicDomain()).toBe("https://surveys.formbricks.com");
});
test("falls back to WEBAPP_URL when PUBLIC_URL is empty", async () => {
envMock.WEBAPP_URL = "https://app.formbricks.com";
envMock.PUBLIC_URL = " ";
const getPublicDomain = await loadGetPublicDomain();
expect(getPublicDomain()).toBe("https://app.formbricks.com");
}); });
}); });
+12 -2
View File
@@ -1,8 +1,18 @@
import "server-only"; import "server-only";
import { env } from "./env"; import { env } from "./env";
const WEBAPP_URL = const configuredWebappUrl = env.WEBAPP_URL?.trim() ?? "";
env.WEBAPP_URL ?? (env.VERCEL_URL ? `https://${env.VERCEL_URL}` : "") ?? "http://localhost:3000"; const WEBAPP_URL = (() => {
if (configuredWebappUrl !== "") {
return configuredWebappUrl;
}
if (env.VERCEL_URL) {
return `https://${env.VERCEL_URL}`;
}
return "http://localhost:3000";
})();
/** /**
* Returns the public domain URL * Returns the public domain URL
+6
View File
@@ -0,0 +1,6 @@
/**
* Error codes returned by Google Sheets integration.
* Use these constants when comparing error responses to avoid typos and enable reuse.
*/
export const GOOGLE_SHEET_INTEGRATION_INVALID_GRANT = "invalid_grant";
export const GOOGLE_SHEET_INTEGRATION_INSUFFICIENT_PERMISSION = "insufficient_permission";
+92 -18
View File
@@ -2,7 +2,12 @@ import "server-only";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { z } from "zod"; import { z } from "zod";
import { ZString } from "@formbricks/types/common"; import { ZString } from "@formbricks/types/common";
import { DatabaseError, UnknownError } from "@formbricks/types/errors"; import {
AuthenticationError,
DatabaseError,
OperationNotAllowedError,
UnknownError,
} from "@formbricks/types/errors";
import { import {
TIntegrationGoogleSheets, TIntegrationGoogleSheets,
ZIntegrationGoogleSheets, ZIntegrationGoogleSheets,
@@ -11,8 +16,12 @@ import {
GOOGLE_SHEETS_CLIENT_ID, GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET, GOOGLE_SHEETS_CLIENT_SECRET,
GOOGLE_SHEETS_REDIRECT_URL, GOOGLE_SHEETS_REDIRECT_URL,
GOOGLE_SHEET_MESSAGE_LIMIT,
} from "@/lib/constants"; } from "@/lib/constants";
import { GOOGLE_SHEET_MESSAGE_LIMIT } from "@/lib/constants"; import {
GOOGLE_SHEET_INTEGRATION_INSUFFICIENT_PERMISSION,
GOOGLE_SHEET_INTEGRATION_INVALID_GRANT,
} from "@/lib/googleSheet/constants";
import { createOrUpdateIntegration } from "@/lib/integration/service"; import { createOrUpdateIntegration } from "@/lib/integration/service";
import { truncateText } from "../utils/strings"; import { truncateText } from "../utils/strings";
import { validateInputs } from "../utils/validate"; import { validateInputs } from "../utils/validate";
@@ -81,6 +90,17 @@ export const writeData = async (
} }
}; };
export const validateGoogleSheetsConnection = async (
googleSheetIntegrationData: TIntegrationGoogleSheets
): Promise<void> => {
validateInputs([googleSheetIntegrationData, ZIntegrationGoogleSheets]);
const integrationData = structuredClone(googleSheetIntegrationData);
integrationData.config.data.forEach((data) => {
data.createdAt = new Date(data.createdAt);
});
await authorize(integrationData);
};
export const getSpreadsheetNameById = async ( export const getSpreadsheetNameById = async (
googleSheetIntegrationData: TIntegrationGoogleSheets, googleSheetIntegrationData: TIntegrationGoogleSheets,
spreadsheetId: string spreadsheetId: string
@@ -94,7 +114,17 @@ export const getSpreadsheetNameById = async (
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
sheets.spreadsheets.get({ spreadsheetId }, (err, response) => { sheets.spreadsheets.get({ spreadsheetId }, (err, response) => {
if (err) { if (err) {
reject(new UnknownError(`Error while fetching spreadsheet data: ${err.message}`)); const msg = err.message?.toLowerCase() ?? "";
const isPermissionError =
msg.includes("permission") ||
msg.includes("caller does not have") ||
msg.includes("insufficient permission") ||
msg.includes("access denied");
if (isPermissionError) {
reject(new OperationNotAllowedError(GOOGLE_SHEET_INTEGRATION_INSUFFICIENT_PERMISSION));
} else {
reject(new UnknownError(`Error while fetching spreadsheet data: ${err.message}`));
}
return; return;
} }
const spreadsheetTitle = response.data.properties.title; const spreadsheetTitle = response.data.properties.title;
@@ -109,26 +139,70 @@ export const getSpreadsheetNameById = async (
} }
}; };
const isInvalidGrantError = (error: unknown): boolean => {
const err = error as { message?: string; response?: { data?: { error?: string } } };
return (
typeof err?.message === "string" &&
err.message.toLowerCase().includes(GOOGLE_SHEET_INTEGRATION_INVALID_GRANT)
);
};
/** Buffer in ms before expiry_date to consider token near-expired (5 minutes). */
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
const GOOGLE_TOKENINFO_URL = "https://www.googleapis.com/oauth2/v1/tokeninfo";
/**
* Verifies that the access token is still valid and not revoked (e.g. user removed app access).
* Returns true if token is valid, false if invalid/revoked.
*/
const isAccessTokenValid = async (accessToken: string): Promise<boolean> => {
try {
const res = await fetch(`${GOOGLE_TOKENINFO_URL}?access_token=${encodeURIComponent(accessToken)}`);
return res.ok;
} catch {
return false;
}
};
const authorize = async (googleSheetIntegrationData: TIntegrationGoogleSheets) => { const authorize = async (googleSheetIntegrationData: TIntegrationGoogleSheets) => {
const client_id = GOOGLE_SHEETS_CLIENT_ID; const client_id = GOOGLE_SHEETS_CLIENT_ID;
const client_secret = GOOGLE_SHEETS_CLIENT_SECRET; const client_secret = GOOGLE_SHEETS_CLIENT_SECRET;
const redirect_uri = GOOGLE_SHEETS_REDIRECT_URL; const redirect_uri = GOOGLE_SHEETS_REDIRECT_URL;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri); const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const refresh_token = googleSheetIntegrationData.config.key.refresh_token; const key = googleSheetIntegrationData.config.key;
oAuth2Client.setCredentials({
refresh_token,
});
const { credentials } = await oAuth2Client.refreshAccessToken();
await createOrUpdateIntegration(googleSheetIntegrationData.environmentId, {
type: "googleSheets",
config: {
data: googleSheetIntegrationData.config?.data ?? [],
email: googleSheetIntegrationData.config?.email ?? "",
key: credentials,
},
});
oAuth2Client.setCredentials(credentials); const hasStoredCredentials =
key.access_token && key.expiry_date && key.expiry_date > Date.now() + TOKEN_EXPIRY_BUFFER_MS;
return oAuth2Client; if (hasStoredCredentials && (await isAccessTokenValid(key.access_token))) {
oAuth2Client.setCredentials(key);
return oAuth2Client;
}
oAuth2Client.setCredentials({ refresh_token: key.refresh_token });
try {
const { credentials } = await oAuth2Client.refreshAccessToken();
const mergedCredentials = {
...credentials,
refresh_token: credentials.refresh_token ?? key.refresh_token,
};
await createOrUpdateIntegration(googleSheetIntegrationData.environmentId, {
type: "googleSheets",
config: {
data: googleSheetIntegrationData.config?.data ?? [],
email: googleSheetIntegrationData.config?.email ?? "",
key: mergedCredentials,
},
});
oAuth2Client.setCredentials(mergedCredentials);
return oAuth2Client;
} catch (error) {
if (isInvalidGrantError(error)) {
throw new AuthenticationError(GOOGLE_SHEET_INTEGRATION_INVALID_GRANT);
}
throw error;
}
}; };
+18
View File
@@ -130,84 +130,102 @@ export const appLanguages = [
code: "de-DE", code: "de-DE",
label: { label: {
"en-US": "German", "en-US": "German",
native: "Deutsch",
}, },
}, },
{ {
code: "en-US", code: "en-US",
label: { label: {
"en-US": "English (US)", "en-US": "English (US)",
native: "English (US)",
}, },
}, },
{ {
code: "es-ES", code: "es-ES",
label: { label: {
"en-US": "Spanish", "en-US": "Spanish",
native: "Español",
}, },
}, },
{ {
code: "fr-FR", code: "fr-FR",
label: { label: {
"en-US": "French", "en-US": "French",
native: "Français",
}, },
}, },
{ {
code: "hu-HU", code: "hu-HU",
label: { label: {
"en-US": "Hungarian", "en-US": "Hungarian",
native: "Magyar",
}, },
}, },
{ {
code: "ja-JP", code: "ja-JP",
label: { label: {
"en-US": "Japanese", "en-US": "Japanese",
native: "日本語",
}, },
}, },
{ {
code: "nl-NL", code: "nl-NL",
label: { label: {
"en-US": "Dutch", "en-US": "Dutch",
native: "Nederlands",
}, },
}, },
{ {
code: "pt-BR", code: "pt-BR",
label: { label: {
"en-US": "Portuguese (Brazil)", "en-US": "Portuguese (Brazil)",
native: "Português (Brasil)",
}, },
}, },
{ {
code: "pt-PT", code: "pt-PT",
label: { label: {
"en-US": "Portuguese (Portugal)", "en-US": "Portuguese (Portugal)",
native: "Português (Portugal)",
}, },
}, },
{ {
code: "ro-RO", code: "ro-RO",
label: { label: {
"en-US": "Romanian", "en-US": "Romanian",
native: "Română",
}, },
}, },
{ {
code: "ru-RU", code: "ru-RU",
label: { label: {
"en-US": "Russian", "en-US": "Russian",
native: "Русский",
}, },
}, },
{ {
code: "sv-SE", code: "sv-SE",
label: { label: {
"en-US": "Swedish", "en-US": "Swedish",
native: "Svenska",
}, },
}, },
{ {
code: "zh-Hans-CN", code: "zh-Hans-CN",
label: { label: {
"en-US": "Chinese (Simplified)", "en-US": "Chinese (Simplified)",
native: "简体中文",
}, },
}, },
{ {
code: "zh-Hant-TW", code: "zh-Hant-TW",
label: { label: {
"en-US": "Chinese (Traditional)", "en-US": "Chinese (Traditional)",
native: "繁體中文",
}, },
}, },
]; ];
export const sortedAppLanguages = [...appLanguages].sort((a, b) =>
a.label["en-US"].localeCompare(b.label["en-US"])
);
-1
View File
@@ -397,7 +397,6 @@ export const getResponseDownloadFile = async (
"Survey ID", "Survey ID",
"Formbricks ID (internal)", "Formbricks ID (internal)",
"User ID", "User ID",
"Notes",
"Tags", "Tags",
...metaDataFields, ...metaDataFields,
...elements.flat(), ...elements.flat(),
+6 -2
View File
@@ -60,6 +60,7 @@ export const getSuggestedColors = (brandColor: string = DEFAULT_BRAND_COLOR) =>
// Options (Radio / Checkbox) // Options (Radio / Checkbox)
"optionBgColor.light": inputBg, "optionBgColor.light": inputBg,
"optionLabelColor.light": questionColor, "optionLabelColor.light": questionColor,
"optionBorderColor.light": inputBorder,
// Card // Card
"cardBackgroundColor.light": cardBg, "cardBackgroundColor.light": cardBg,
@@ -138,6 +139,7 @@ export const STYLE_DEFAULTS: TProjectStyling = {
// Options // Options
optionBgColor: { light: _colors["optionBgColor.light"] }, optionBgColor: { light: _colors["optionBgColor.light"] },
optionLabelColor: { light: _colors["optionLabelColor.light"] }, optionLabelColor: { light: _colors["optionLabelColor.light"] },
optionBorderColor: { light: _colors["optionBorderColor.light"] },
optionBorderRadius: 8, optionBorderRadius: 8,
optionPaddingX: 16, optionPaddingX: 16,
optionPaddingY: 16, optionPaddingY: 16,
@@ -169,6 +171,7 @@ export const deriveNewFieldsFromLegacy = (saved: Record<string, unknown>): Recor
const q = light("questionColor"); const q = light("questionColor");
const b = light("brandColor"); const b = light("brandColor");
const i = light("inputColor"); const i = light("inputColor");
const inputBorder = light("inputBorderColor");
return { return {
...(q && !saved.elementHeadlineColor && { elementHeadlineColor: { light: q } }), ...(q && !saved.elementHeadlineColor && { elementHeadlineColor: { light: q } }),
@@ -179,9 +182,9 @@ export const deriveNewFieldsFromLegacy = (saved: Record<string, unknown>): Recor
...(b && !saved.buttonBgColor && { buttonBgColor: { light: b } }), ...(b && !saved.buttonBgColor && { buttonBgColor: { light: b } }),
...(b && !saved.buttonTextColor && { buttonTextColor: { light: isLight(b) ? "#0f172a" : "#ffffff" } }), ...(b && !saved.buttonTextColor && { buttonTextColor: { light: isLight(b) ? "#0f172a" : "#ffffff" } }),
...(i && !saved.optionBgColor && { optionBgColor: { light: i } }), ...(i && !saved.optionBgColor && { optionBgColor: { light: i } }),
...(inputBorder && !saved.optionBorderColor && { optionBorderColor: { light: inputBorder } }),
...(b && !saved.progressIndicatorBgColor && { progressIndicatorBgColor: { light: b } }), ...(b && !saved.progressIndicatorBgColor && { progressIndicatorBgColor: { light: b } }),
...(b && ...(b && !saved.progressTrackBgColor && { progressTrackBgColor: { light: mixColor(b, "#ffffff", 0.8) } }),
!saved.progressTrackBgColor && { progressTrackBgColor: { light: mixColor(b, "#ffffff", 0.8) } }),
}; };
}; };
@@ -211,6 +214,7 @@ export const buildStylingFromBrandColor = (brandColor: string = DEFAULT_BRAND_CO
inputTextColor: { light: colors["inputTextColor.light"] }, inputTextColor: { light: colors["inputTextColor.light"] },
optionBgColor: { light: colors["optionBgColor.light"] }, optionBgColor: { light: colors["optionBgColor.light"] },
optionLabelColor: { light: colors["optionLabelColor.light"] }, optionLabelColor: { light: colors["optionLabelColor.light"] },
optionBorderColor: { light: colors["optionBorderColor.light"] },
cardBackgroundColor: { light: colors["cardBackgroundColor.light"] }, cardBackgroundColor: { light: colors["cardBackgroundColor.light"] },
cardBorderColor: { light: colors["cardBorderColor.light"] }, cardBorderColor: { light: colors["cardBorderColor.light"] },
highlightBorderColor: { light: colors["highlightBorderColor.light"] }, highlightBorderColor: { light: colors["highlightBorderColor.light"] },
+1
View File
@@ -508,6 +508,7 @@ export const updateSurveyInternal = async (
newFollowUps.length > 0 newFollowUps.length > 0
? { ? {
data: newFollowUps.map((followUp) => ({ data: newFollowUps.map((followUp) => ({
id: followUp.id,
name: followUp.name, name: followUp.name,
trigger: followUp.trigger, trigger: followUp.trigger,
action: followUp.action, action: followUp.action,
@@ -0,0 +1,297 @@
import dns from "node:dns";
import { afterEach, describe, expect, test, vi } from "vitest";
import { validateWebhookUrl } from "./validate-webhook-url";
vi.mock("node:dns", () => ({
default: {
resolve: vi.fn(),
resolve6: vi.fn(),
},
}));
const mockResolve = vi.mocked(dns.resolve);
const mockResolve6 = vi.mocked(dns.resolve6);
type DnsCallback = (err: NodeJS.ErrnoException | null, addresses: string[]) => void;
const setupDnsResolution = (ipv4: string[] | null, ipv6: string[] | null = null): void => {
// dns.resolve/resolve6 have overloaded signatures; we only mock the (hostname, callback) form
mockResolve.mockImplementation(((_hostname: string, callback: DnsCallback) => {
if (ipv4) {
callback(null, ipv4);
} else {
callback(new Error("ENOTFOUND"), []);
}
}) as never);
mockResolve6.mockImplementation(((_hostname: string, callback: DnsCallback) => {
if (ipv6) {
callback(null, ipv6);
} else {
callback(new Error("ENOTFOUND"), []);
}
}) as never);
};
afterEach(() => {
vi.clearAllMocks();
});
describe("validateWebhookUrl", () => {
describe("valid public URLs", () => {
test("accepts HTTPS URL resolving to a public IPv4 address", async () => {
setupDnsResolution(["93.184.216.34"]);
await expect(validateWebhookUrl("https://example.com/webhook")).resolves.toBeUndefined();
});
test("accepts HTTP URL resolving to a public IPv4 address", async () => {
setupDnsResolution(["93.184.216.34"]);
await expect(validateWebhookUrl("http://example.com/webhook")).resolves.toBeUndefined();
});
test("accepts URL with port and path segments", async () => {
setupDnsResolution(["93.184.216.34"]);
await expect(validateWebhookUrl("https://example.com:8443/api/v1/webhook")).resolves.toBeUndefined();
});
test("accepts URL resolving to a public IPv6 address", async () => {
setupDnsResolution(null, ["2606:2800:220:1:248:1893:25c8:1946"]);
await expect(validateWebhookUrl("https://example.com/webhook")).resolves.toBeUndefined();
});
test("accepts a public IPv4 address as hostname", async () => {
await expect(validateWebhookUrl("https://93.184.216.34/webhook")).resolves.toBeUndefined();
});
});
describe("URL format validation", () => {
test("rejects a completely malformed string", async () => {
await expect(validateWebhookUrl("not-a-url")).rejects.toThrow("Invalid webhook URL format");
});
test("rejects an empty string", async () => {
await expect(validateWebhookUrl("")).rejects.toThrow("Invalid webhook URL format");
});
});
describe("protocol validation", () => {
test("rejects FTP protocol", async () => {
await expect(validateWebhookUrl("ftp://example.com/file")).rejects.toThrow(
"Webhook URL must use HTTPS or HTTP protocol"
);
});
test("rejects file:// protocol", async () => {
await expect(validateWebhookUrl("file:///etc/passwd")).rejects.toThrow(
"Webhook URL must use HTTPS or HTTP protocol"
);
});
test("rejects javascript: protocol", async () => {
await expect(validateWebhookUrl("javascript:alert(1)")).rejects.toThrow(
"Webhook URL must use HTTPS or HTTP protocol"
);
});
});
describe("blocked hostname validation", () => {
test("rejects localhost", async () => {
await expect(validateWebhookUrl("http://localhost/admin")).rejects.toThrow(
"Webhook URL must not point to localhost or internal services"
);
});
test("rejects localhost.localdomain", async () => {
await expect(validateWebhookUrl("https://localhost.localdomain/path")).rejects.toThrow(
"Webhook URL must not point to localhost or internal services"
);
});
test("rejects metadata.google.internal", async () => {
await expect(validateWebhookUrl("http://metadata.google.internal/computeMetadata/v1/")).rejects.toThrow(
"Webhook URL must not point to localhost or internal services"
);
});
});
describe("private IPv4 literal blocking", () => {
test("rejects 127.0.0.1 (loopback)", async () => {
await expect(validateWebhookUrl("http://127.0.0.1/metadata")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 127.0.0.53 (loopback range)", async () => {
await expect(validateWebhookUrl("http://127.0.0.53/")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 10.0.0.1 (Class A private)", async () => {
await expect(validateWebhookUrl("http://10.0.0.1/internal")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 172.16.0.1 (Class B private)", async () => {
await expect(validateWebhookUrl("http://172.16.0.1/internal")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 172.31.255.255 (Class B private upper bound)", async () => {
await expect(validateWebhookUrl("http://172.31.255.255/internal")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 192.168.1.1 (Class C private)", async () => {
await expect(validateWebhookUrl("http://192.168.1.1/internal")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 169.254.169.254 (AWS/GCP/Azure metadata endpoint)", async () => {
await expect(validateWebhookUrl("http://169.254.169.254/latest/meta-data/")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 0.0.0.0 ('this' network)", async () => {
await expect(validateWebhookUrl("http://0.0.0.0/")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects 100.64.0.1 (CGNAT / shared address space)", async () => {
await expect(validateWebhookUrl("http://100.64.0.1/")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
});
describe("DNS resolution with private IP results", () => {
test("rejects hostname resolving to loopback address", async () => {
setupDnsResolution(["127.0.0.1"]);
await expect(validateWebhookUrl("https://evil.com/steal")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to cloud metadata endpoint IP", async () => {
setupDnsResolution(["169.254.169.254"]);
await expect(validateWebhookUrl("https://attacker.com/ssrf")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to Class A private network", async () => {
setupDnsResolution(["10.0.0.5"]);
await expect(validateWebhookUrl("https://internal.service/api")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to Class C private network", async () => {
setupDnsResolution(["192.168.0.1"]);
await expect(validateWebhookUrl("https://sneaky.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to IPv6 loopback", async () => {
setupDnsResolution(null, ["::1"]);
await expect(validateWebhookUrl("https://sneaky.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to IPv6 link-local", async () => {
setupDnsResolution(null, ["fe80::1"]);
await expect(validateWebhookUrl("https://link-local.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to IPv6 unique local address", async () => {
setupDnsResolution(null, ["fd12:3456:789a::1"]);
await expect(validateWebhookUrl("https://ula.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to IPv4-mapped IPv6 private address (dotted)", async () => {
setupDnsResolution(null, ["::ffff:192.168.1.1"]);
await expect(validateWebhookUrl("https://mapped.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hostname resolving to IPv4-mapped IPv6 private address (hex-encoded)", async () => {
setupDnsResolution(null, ["::ffff:c0a8:0101"]); // 192.168.1.1 in hex
await expect(validateWebhookUrl("https://hex-mapped.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hex-encoded IPv4-mapped loopback (::ffff:7f00:0001)", async () => {
setupDnsResolution(null, ["::ffff:7f00:0001"]); // 127.0.0.1 in hex
await expect(validateWebhookUrl("https://hex-loopback.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects hex-encoded IPv4-mapped metadata endpoint (::ffff:a9fe:a9fe)", async () => {
setupDnsResolution(null, ["::ffff:a9fe:a9fe"]); // 169.254.169.254 in hex
await expect(validateWebhookUrl("https://hex-metadata.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("accepts hex-encoded IPv4-mapped public address", async () => {
setupDnsResolution(null, ["::ffff:5db8:d822"]); // 93.184.216.34 in hex
await expect(validateWebhookUrl("https://hex-public.example.com/webhook")).resolves.toBeUndefined();
});
test("rejects when any resolved IP is private (mixed public + private)", async () => {
setupDnsResolution(["93.184.216.34", "192.168.1.1"]);
await expect(validateWebhookUrl("https://dual.example.com/webhook")).rejects.toThrow(
"Webhook URL must not point to private or internal IP addresses"
);
});
test("rejects unresolvable hostname", async () => {
setupDnsResolution(null, null);
await expect(validateWebhookUrl("https://nonexistent.invalid/path")).rejects.toThrow(
"Could not resolve webhook URL hostname"
);
});
test("rejects with timeout error when DNS resolution hangs", async () => {
vi.useFakeTimers();
mockResolve.mockImplementation((() => {
// never calls callback — simulates a hanging DNS server
}) as never);
const promise = validateWebhookUrl("https://slow-dns.example.com/webhook");
const assertion = expect(promise).rejects.toThrow(
"DNS resolution timed out for webhook URL hostname: slow-dns.example.com"
);
await vi.advanceTimersByTimeAsync(3000);
await assertion;
vi.useRealTimers();
});
});
describe("error type", () => {
test("throws InvalidInputError (not generic Error)", async () => {
await expect(validateWebhookUrl("http://127.0.0.1/")).rejects.toMatchObject({
name: "InvalidInputError",
});
});
});
});
+176
View File
@@ -0,0 +1,176 @@
import "server-only";
import dns from "node:dns";
import { InvalidInputError } from "@formbricks/types/errors";
const BLOCKED_HOSTNAMES = new Set([
"localhost",
"localhost.localdomain",
"ip6-localhost",
"ip6-loopback",
"metadata.google.internal",
]);
const PRIVATE_IPV4_PATTERNS: RegExp[] = [
/^127\./, // 127.0.0.0/8 Loopback
/^10\./, // 10.0.0.0/8 Class A private
/^172\.(1[6-9]|2\d|3[01])\./, // 172.16.0.0/12 Class B private
/^192\.168\./, // 192.168.0.0/16 Class C private
/^169\.254\./, // 169.254.0.0/16 Link-local (AWS/GCP/Azure metadata)
/^0\./, // 0.0.0.0/8 "This" network
/^100\.(6[4-9]|[7-9]\d|1[0-2]\d)\./, // 100.64.0.0/10 Shared address space (RFC 6598)
/^192\.0\.0\./, // 192.0.0.0/24 IETF protocol assignments
/^192\.0\.2\./, // 192.0.2.0/24 TEST-NET-1 (documentation)
/^198\.51\.100\./, // 198.51.100.0/24 TEST-NET-2 (documentation)
/^203\.0\.113\./, // 203.0.113.0/24 TEST-NET-3 (documentation)
/^198\.1[89]\./, // 198.18.0.0/15 Benchmarking
/^224\./, // 224.0.0.0/4 Multicast
/^240\./, // 240.0.0.0/4 Reserved for future use
/^255\.255\.255\.255$/, // Limited broadcast
];
const PRIVATE_IPV6_PREFIXES = [
"::1", // Loopback
"fe80:", // Link-local
"fc", // Unique local address (ULA, fc00::/7 — covers fc00:: through fdff::)
"fd", // Unique local address (ULA, fc00::/7 — covers fc00:: through fdff::)
];
const isPrivateIPv4 = (ip: string): boolean => {
return PRIVATE_IPV4_PATTERNS.some((pattern) => pattern.test(ip));
};
const hexMappedToIPv4 = (hexPart: string): string | null => {
const groups = hexPart.split(":");
if (groups.length !== 2) return null;
const high = Number.parseInt(groups[0], 16);
const low = Number.parseInt(groups[1], 16);
if (Number.isNaN(high) || Number.isNaN(low) || high > 0xffff || low > 0xffff) return null;
return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
};
const isIPv4Mapped = (normalized: string): boolean => {
if (!normalized.startsWith("::ffff:")) return false;
const suffix = normalized.slice(7); // strip "::ffff:"
if (suffix.includes(".")) {
return isPrivateIPv4(suffix);
}
const dotted = hexMappedToIPv4(suffix);
return dotted !== null && isPrivateIPv4(dotted);
};
const isPrivateIPv6 = (ip: string): boolean => {
const normalized = ip.toLowerCase();
if (normalized === "::") return true;
if (isIPv4Mapped(normalized)) return true;
return PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix));
};
const isPrivateIP = (ip: string): boolean => {
return isPrivateIPv4(ip) || isPrivateIPv6(ip);
};
const DNS_TIMEOUT_MS = 3000;
const resolveHostnameToIPs = (hostname: string): Promise<string[]> => {
return new Promise((resolve, reject) => {
let settled = false;
const settle = <T>(fn: (value: T) => void, value: T): void => {
if (settled) return;
settled = true;
clearTimeout(timer);
fn(value);
};
const timer = setTimeout(() => {
settle(reject, new Error(`DNS resolution timed out for hostname: ${hostname}`));
}, DNS_TIMEOUT_MS);
dns.resolve(hostname, (errV4, ipv4Addresses) => {
const ipv4 = errV4 ? [] : ipv4Addresses;
dns.resolve6(hostname, (errV6, ipv6Addresses) => {
const ipv6 = errV6 ? [] : ipv6Addresses;
const allAddresses = [...ipv4, ...ipv6];
if (allAddresses.length === 0) {
settle(reject, new Error(`DNS resolution failed for hostname: ${hostname}`));
} else {
settle(resolve, allAddresses);
}
});
});
});
};
const stripIPv6Brackets = (hostname: string): string => {
if (hostname.startsWith("[") && hostname.endsWith("]")) {
return hostname.slice(1, -1);
}
return hostname;
};
const IPV4_LITERAL = /^\d{1,3}(?:\.\d{1,3}){3}$/;
/**
* Validates a webhook URL to prevent Server-Side Request Forgery (SSRF).
*
* Checks performed:
* 1. URL must be well-formed
* 2. Protocol must be HTTPS or HTTP
* 3. Hostname must not be a known internal name (localhost, metadata endpoints)
* 4. IP literal hostnames are checked directly against private ranges
* 5. Domain hostnames are resolved via DNS; all resulting IPs must be public
*
* @throws {InvalidInputError} when the URL fails any validation check
*/
export const validateWebhookUrl = async (url: string): Promise<void> => {
let parsed: URL;
try {
parsed = new URL(url);
} catch {
throw new InvalidInputError("Invalid webhook URL format");
}
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
throw new InvalidInputError("Webhook URL must use HTTPS or HTTP protocol");
}
const hostname = parsed.hostname;
if (BLOCKED_HOSTNAMES.has(hostname.toLowerCase())) {
throw new InvalidInputError("Webhook URL must not point to localhost or internal services");
}
// Direct IP literal — validate without DNS resolution
const isIPv4Literal = IPV4_LITERAL.test(hostname);
const isIPv6Literal = hostname.startsWith("[");
if (isIPv4Literal || isIPv6Literal) {
const ip = isIPv6Literal ? stripIPv6Brackets(hostname) : hostname;
if (isPrivateIP(ip)) {
throw new InvalidInputError("Webhook URL must not point to private or internal IP addresses");
}
return;
}
// Domain name — resolve DNS and validate every resolved IP
let resolvedIPs: string[];
try {
resolvedIPs = await resolveHostnameToIPs(hostname);
} catch (error) {
const isTimeout = error instanceof Error && error.message.includes("timed out");
throw new InvalidInputError(
isTimeout
? `DNS resolution timed out for webhook URL hostname: ${hostname}`
: `Could not resolve webhook URL hostname: ${hostname}`
);
}
for (const ip of resolvedIPs) {
if (isPrivateIP(ip)) {
throw new InvalidInputError("Webhook URL must not point to private or internal IP addresses");
}
}
};
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Kopieren", "copy": "Kopieren",
"copy_code": "Code kopieren", "copy_code": "Code kopieren",
"copy_link": "Link kopieren", "copy_link": "Link kopieren",
"count_attributes": "{value, plural, one {{value} Attribut} other {{value} Attribute}}", "count_attributes": "{count, plural, one {{count} Attribut} other {{count} Attribute}}",
"count_contacts": "{value, plural, one {{value} Kontakt} other {{value} Kontakte}}", "count_contacts": "{count, plural, one {{count} Kontakt} other {{count} Kontakte}}",
"count_responses": "{value, plural, one {{value} Antwort} other {{value} Antworten}}", "count_members": "{count, plural, one {{count} Mitglied} other {{count} Mitglieder}}",
"count_questions": "{count, plural, one {{count} Frage} other {{count} Fragen}}",
"count_responses": "{count, plural, one {{count} Antwort} other {{count} Antworten}}",
"count_selections": "{count, plural, one {{count} Auswahl} other {{count} Auswahlen}}",
"create_new_organization": "Neue Organisation erstellen", "create_new_organization": "Neue Organisation erstellen",
"create_segment": "Segment erstellen", "create_segment": "Segment erstellen",
"create_survey": "Umfrage erstellen", "create_survey": "Umfrage erstellen",
@@ -191,6 +194,7 @@
"days": "Tage", "days": "Tage",
"default": "Standard", "default": "Standard",
"delete": "Löschen", "delete": "Löschen",
"delete_what": "{deleteWhat} löschen",
"description": "Beschreibung", "description": "Beschreibung",
"dev_env": "Entwicklungsumgebung", "dev_env": "Entwicklungsumgebung",
"development": "Entwicklung", "development": "Entwicklung",
@@ -206,6 +210,8 @@
"download": "Herunterladen", "download": "Herunterladen",
"draft": "Entwurf", "draft": "Entwurf",
"duplicate": "Duplikat", "duplicate": "Duplikat",
"duplicate_copy": "(Kopie)",
"duplicate_copy_number": "(Kopie {copyNumber})",
"e_commerce": "E-Commerce", "e_commerce": "E-Commerce",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"email": "E-Mail", "email": "E-Mail",
@@ -218,13 +224,16 @@
"error": "Fehler", "error": "Fehler",
"error_component_description": "Diese Ressource existiert nicht oder Du hast nicht die notwendigen Rechte, um darauf zuzugreifen.", "error_component_description": "Diese Ressource existiert nicht oder Du hast nicht die notwendigen Rechte, um darauf zuzugreifen.",
"error_component_title": "Fehler beim Laden der Ressourcen", "error_component_title": "Fehler beim Laden der Ressourcen",
"error_loading_data": "Fehler beim Laden der Daten",
"error_rate_limit_description": "Maximale Anzahl an Anfragen erreicht. Bitte später erneut versuchen.", "error_rate_limit_description": "Maximale Anzahl an Anfragen erreicht. Bitte später erneut versuchen.",
"error_rate_limit_title": "Rate Limit Überschritten", "error_rate_limit_title": "Rate Limit Überschritten",
"expand_rows": "Zeilen erweitern", "expand_rows": "Zeilen erweitern",
"failed_to_copy_to_clipboard": "Fehler beim Kopieren in die Zwischenablage", "failed_to_copy_to_clipboard": "Fehler beim Kopieren in die Zwischenablage",
"failed_to_load_organizations": "Fehler beim Laden der Organisationen", "failed_to_load_organizations": "Fehler beim Laden der Organisationen",
"failed_to_load_workspaces": "Projekte konnten nicht geladen werden", "failed_to_load_workspaces": "Projekte konnten nicht geladen werden",
"filter": "Filter",
"finish": "Fertigstellen", "finish": "Fertigstellen",
"first_name": "Vorname",
"follow_these": "Folge diesen", "follow_these": "Folge diesen",
"formbricks_version": "Formbricks Version", "formbricks_version": "Formbricks Version",
"full_name": "Name", "full_name": "Name",
@@ -237,6 +246,7 @@
"hidden_field": "Verstecktes Feld", "hidden_field": "Verstecktes Feld",
"hidden_fields": "Versteckte Felder", "hidden_fields": "Versteckte Felder",
"hide_column": "Spalte ausblenden", "hide_column": "Spalte ausblenden",
"id": "ID",
"image": "Bild", "image": "Bild",
"images": "Bilder", "images": "Bilder",
"import": "Importieren", "import": "Importieren",
@@ -254,6 +264,7 @@
"key": "Schlüssel", "key": "Schlüssel",
"label": "Bezeichnung", "label": "Bezeichnung",
"language": "Sprache", "language": "Sprache",
"last_name": "Nachname",
"learn_more": "Mehr erfahren", "learn_more": "Mehr erfahren",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Helle Überlagerung", "light_overlay": "Helle Überlagerung",
@@ -268,7 +279,6 @@
"look_and_feel": "Darstellung", "look_and_feel": "Darstellung",
"manage": "Verwalten", "manage": "Verwalten",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Mitglied",
"members": "Mitglieder", "members": "Mitglieder",
"members_and_teams": "Mitglieder & Teams", "members_and_teams": "Mitglieder & Teams",
"membership_not_found": "Mitgliedschaft nicht gefunden", "membership_not_found": "Mitgliedschaft nicht gefunden",
@@ -280,6 +290,7 @@
"move_down": "Nach unten bewegen", "move_down": "Nach unten bewegen",
"move_up": "Nach oben bewegen", "move_up": "Nach oben bewegen",
"multiple_languages": "Mehrsprachigkeit", "multiple_languages": "Mehrsprachigkeit",
"my_product": "mein Produkt",
"name": "Name", "name": "Name",
"new": "Neu", "new": "Neu",
"new_version_available": "Formbricks {version} ist da. Jetzt aktualisieren!", "new_version_available": "Formbricks {version} ist da. Jetzt aktualisieren!",
@@ -375,8 +386,6 @@
"select_teams": "Teams auswählen", "select_teams": "Teams auswählen",
"selected": "Ausgewählt", "selected": "Ausgewählt",
"selected_questions": "Ausgewählte Fragen", "selected_questions": "Ausgewählte Fragen",
"selection": "Auswahl",
"selections": "Auswahlen",
"send_test_email": "Test-E-Mail senden", "send_test_email": "Test-E-Mail senden",
"session_not_found": "Sitzung nicht gefunden", "session_not_found": "Sitzung nicht gefunden",
"settings": "Einstellungen", "settings": "Einstellungen",
@@ -428,6 +437,7 @@
"top_right": "Oben rechts", "top_right": "Oben rechts",
"try_again": "Versuch's nochmal", "try_again": "Versuch's nochmal",
"type": "Typ", "type": "Typ",
"unknown_survey": "Unbekannte Umfrage",
"unlock_more_workspaces_with_a_higher_plan": "Schalten Sie mehr Projekte mit einem höheren Tarif frei.", "unlock_more_workspaces_with_a_higher_plan": "Schalten Sie mehr Projekte mit einem höheren Tarif frei.",
"update": "Aktualisierung", "update": "Aktualisierung",
"updated": "Aktualisiert", "updated": "Aktualisiert",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Kontakte aktualisieren", "contacts_table_refresh": "Kontakte aktualisieren",
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert", "contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
"create_attribute": "Attribut erstellen", "create_attribute": "Attribut erstellen",
"create_key": "Schlüssel erstellen",
"create_new_attribute": "Neues Attribut erstellen", "create_new_attribute": "Neues Attribut erstellen",
"create_new_attribute_description": "Erstellen Sie ein neues Attribut für Segmentierungszwecke.", "create_new_attribute_description": "Erstellen Sie ein neues Attribut für Segmentierungszwecke.",
"custom_attributes": "Benutzerdefinierte Attribute", "custom_attributes": "Benutzerdefinierte Attribute",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Anzeigen",
"edit_attribute": "Attribut bearbeiten", "edit_attribute": "Attribut bearbeiten",
"edit_attribute_description": "Aktualisieren Sie die Bezeichnung und Beschreibung für dieses Attribut.", "edit_attribute_description": "Aktualisieren Sie die Bezeichnung und Beschreibung für dieses Attribut.",
"edit_attribute_values": "Attribute bearbeiten", "edit_attribute_values": "Attribute bearbeiten",
@@ -667,6 +677,7 @@
"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_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_date_format": "Ungültiges Datumsformat. Bitte verwende ein gültiges Datum.",
"invalid_number_format": "Ungültiges Zahlenformat. Bitte gib eine gültige Zahl ein.", "invalid_number_format": "Ungültiges Zahlenformat. Bitte gib eine gültige Zahl ein.",
"no_activity_yet": "Noch keine Aktivität",
"no_published_link_surveys_available": "Keine veröffentlichten Link-Umfragen verfügbar. Bitte veröffentliche zuerst eine Link-Umfrage.", "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_published_surveys": "Keine veröffentlichten Umfragen",
"no_responses_found": "Keine Antworten gefunden", "no_responses_found": "Keine Antworten gefunden",
@@ -681,6 +692,8 @@
"select_a_survey": "Wähle eine Umfrage aus", "select_a_survey": "Wähle eine Umfrage aus",
"select_attribute": "Attribut auswählen", "select_attribute": "Attribut auswählen",
"select_attribute_key": "Attributschlüssel auswählen", "select_attribute_key": "Attributschlüssel auswählen",
"survey_viewed": "Umfrage angesehen",
"survey_viewed_at": "Angesehen am",
"system_attributes": "Systemattribute", "system_attributes": "Systemattribute",
"unlock_contacts_description": "Verwalte Kontakte und sende gezielte Umfragen", "unlock_contacts_description": "Verwalte Kontakte und sende gezielte Umfragen",
"unlock_contacts_title": "Kontakte mit einem höheren Plan freischalten", "unlock_contacts_title": "Kontakte mit einem höheren Plan freischalten",
@@ -752,7 +765,12 @@
"link_google_sheet": "Tabelle verlinken", "link_google_sheet": "Tabelle verlinken",
"link_new_sheet": "Neues Blatt verknüpfen", "link_new_sheet": "Neues Blatt verknüpfen",
"no_integrations_yet": "Deine verknüpften Tabellen werden hier angezeigt, sobald Du sie hinzufügst ⏲️", "no_integrations_yet": "Deine verknüpften Tabellen werden hier angezeigt, sobald Du sie hinzufügst ⏲️",
"spreadsheet_url": "Tabellen-URL" "reconnect_button": "Erneut verbinden",
"reconnect_button_description": "Deine Google Sheets-Verbindung ist abgelaufen. Bitte verbinde dich erneut, um weiterhin Antworten zu synchronisieren. Deine bestehenden Tabellen-Links und Daten bleiben erhalten.",
"reconnect_button_tooltip": "Verbinde die Integration erneut, um deinen Zugriff zu aktualisieren. Deine bestehenden Tabellen-Links und Daten bleiben erhalten.",
"spreadsheet_permission_error": "Du hast keine Berechtigung, auf diese Tabelle zuzugreifen. Bitte stelle sicher, dass die Tabelle mit deinem Google-Konto geteilt ist und du Schreibzugriff auf die Tabelle hast.",
"spreadsheet_url": "Tabellen-URL",
"token_expired_error": "Das Google Sheets-Aktualisierungstoken ist abgelaufen oder wurde widerrufen. Bitte verbinde die Integration erneut."
}, },
"include_created_at": "Erstellungsdatum einbeziehen", "include_created_at": "Erstellungsdatum einbeziehen",
"include_hidden_fields": "Versteckte Felder (hidden fields) einbeziehen", "include_hidden_fields": "Versteckte Felder (hidden fields) einbeziehen",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Hey {userName}", "email_customization_preview_email_heading": "Hey {userName}",
"email_customization_preview_email_text": "Dies ist eine E-Mail-Vorschau, um dir zu zeigen, welches Logo in den E-Mails gerendert wird.", "email_customization_preview_email_text": "Dies ist eine E-Mail-Vorschau, um dir zu zeigen, welches Logo in den E-Mails gerendert wird.",
"error_deleting_organization_please_try_again": "Fehler beim Löschen der Organisation. Bitte versuche es erneut.", "error_deleting_organization_please_try_again": "Fehler beim Löschen der Organisation. Bitte versuche es erneut.",
"from_your_organization": "von deiner Organisation", "from_your_organization": "{memberName} aus Ihrer Organisation",
"invitation_sent_once_more": "Einladung nochmal gesendet.", "invitation_sent_once_more": "Einladung nochmal gesendet.",
"invite_deleted_successfully": "Einladung erfolgreich gelöscht", "invite_deleted_successfully": "Einladung erfolgreich gelöscht",
"invite_expires_on": "Einladung läuft ab am {date}", "invite_expires_on": "Einladung läuft ab am {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Platzhalter hinzufügen, falls kein Wert zur Verfügung steht.", "add_fallback_placeholder": "Platzhalter hinzufügen, falls kein Wert zur Verfügung steht.",
"add_hidden_field_id": "Verstecktes Feld ID hinzufügen", "add_hidden_field_id": "Verstecktes Feld ID hinzufügen",
"add_highlight_border": "Rahmen hinzufügen", "add_highlight_border": "Rahmen hinzufügen",
"add_highlight_border_description": "Gilt nur für In-Product-Umfragen.",
"add_logic": "Logik hinzufügen", "add_logic": "Logik hinzufügen",
"add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu", "add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu",
"add_option": "Option hinzufügen", "add_option": "Option hinzufügen",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "'Umfrage geschlossen'-Nachricht anpassen", "adjust_survey_closed_message": "'Umfrage geschlossen'-Nachricht anpassen",
"adjust_survey_closed_message_description": "Ändere die Nachricht, die Besucher sehen, wenn die Umfrage geschlossen ist.", "adjust_survey_closed_message_description": "Ändere die Nachricht, die Besucher sehen, wenn die Umfrage geschlossen ist.",
"adjust_the_theme_in_the": "Passe das Thema an in den", "adjust_the_theme_in_the": "Passe das Thema an in den",
"all_are_true": "alle sind wahr",
"all_other_answers_will_continue_to": "Alle anderen Antworten werden weiterhin", "all_other_answers_will_continue_to": "Alle anderen Antworten werden weiterhin",
"allow_multi_select": "Mehrfachauswahl erlauben", "allow_multi_select": "Mehrfachauswahl erlauben",
"allow_multiple_files": "Mehrere Dateien zulassen", "allow_multiple_files": "Mehrere Dateien zulassen",
"allow_users_to_select_more_than_one_image": "Erlaube Nutzern, mehr als ein Bild auszuwählen", "allow_users_to_select_more_than_one_image": "Erlaube Nutzern, mehr als ein Bild auszuwählen",
"and_launch_surveys_in_your_website_or_app": "und Umfragen auf deiner Website oder App starten.", "and_launch_surveys_in_your_website_or_app": "und Umfragen auf deiner Website oder App starten.",
"animation": "Animation", "animation": "Animation",
"any_is_true": "mindestens eine ist wahr",
"app_survey_description": "Bette eine Umfrage in deine Web-App oder Website ein, um Antworten zu sammeln.", "app_survey_description": "Bette eine Umfrage in deine Web-App oder Website ein, um Antworten zu sammeln.",
"assign": "Zuweisen =", "assign": "Zuweisen =",
"audience": "Publikum", "audience": "Publikum",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Nachverfolgung aktualisiert und wird gespeichert, sobald du die Umfrage speicherst.", "follow_ups_modal_updated_successfull_toast": "Nachverfolgung aktualisiert und wird gespeichert, sobald du die Umfrage speicherst.",
"follow_ups_new": "Neues Follow-up", "follow_ups_new": "Neues Follow-up",
"follow_ups_upgrade_button_text": "Upgrade, um Follow-ups zu aktivieren", "follow_ups_upgrade_button_text": "Upgrade, um Follow-ups zu aktivieren",
"form_styling": "Umfrage Styling",
"formbricks_sdk_is_not_connected": "Formbricks SDK ist nicht verbunden", "formbricks_sdk_is_not_connected": "Formbricks SDK ist nicht verbunden",
"four_points": "4 Punkte", "four_points": "4 Punkte",
"heading": "Überschrift", "heading": "Überschrift",
@@ -1520,7 +1540,7 @@
"option_idx": "Option {choiceIndex}", "option_idx": "Option {choiceIndex}",
"option_used_in_logic_error": "Diese Option wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.", "option_used_in_logic_error": "Diese Option wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.",
"optional": "Optional", "optional": "Optional",
"options": "Optionen", "options": "Optionen*",
"options_used_in_logic_bulk_error": "Die folgenden Optionen werden in der Logik verwendet: {questionIndexes}. Bitte entferne sie zuerst aus der Logik.", "options_used_in_logic_bulk_error": "Die folgenden Optionen werden in der Logik verwendet: {questionIndexes}. Bitte entferne sie zuerst aus der Logik.",
"override_theme_with_individual_styles_for_this_survey": "Styling für diese Umfrage überschreiben.", "override_theme_with_individual_styles_for_this_survey": "Styling für diese Umfrage überschreiben.",
"overwrite_global_waiting_time": "Benutzerdefinierte Abkühlphase festlegen", "overwrite_global_waiting_time": "Benutzerdefinierte Abkühlphase festlegen",
@@ -1545,6 +1565,7 @@
"question_deleted": "Frage gelöscht.", "question_deleted": "Frage gelöscht.",
"question_duplicated": "Frage dupliziert.", "question_duplicated": "Frage dupliziert.",
"question_id_updated": "Frage-ID aktualisiert", "question_id_updated": "Frage-ID aktualisiert",
"question_number": "Frage {number}",
"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_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_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",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Antwort Limits, Weiterleitungen und mehr.", "response_limits_redirections_and_more": "Antwort Limits, Weiterleitungen und mehr.",
"response_options": "Antwortoptionen", "response_options": "Antwortoptionen",
"roundness": "Rundheit", "roundness": "Rundheit",
"roundness_description": "Steuert, wie abgerundet die Kartenecken sind.", "roundness_description": "Steuert, wie abgerundet die Ecken sind.",
"row_used_in_logic_error": "Diese Zeile wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.", "row_used_in_logic_error": "Diese Zeile wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.",
"rows": "Zeilen", "rows": "Zeilen",
"save_and_close": "Speichern & Schließen", "save_and_close": "Speichern & Schließen",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Diese kostenlose und quelloffene Umfrage wurde geschlossen", "survey_completed_subheading": "Diese kostenlose und quelloffene Umfrage wurde geschlossen",
"survey_display_settings": "Einstellungen zur Anzeige der Umfrage", "survey_display_settings": "Einstellungen zur Anzeige der Umfrage",
"survey_placement": "Platzierung der Umfrage", "survey_placement": "Platzierung der Umfrage",
"survey_styling": "Umfrage Styling",
"survey_trigger": "Auslöser der Umfrage", "survey_trigger": "Auslöser der Umfrage",
"switch_multi_language_on_to_get_started": "Aktiviere Mehrsprachigkeit, um loszulegen 👉", "switch_multi_language_on_to_get_started": "Aktiviere Mehrsprachigkeit, um loszulegen 👉",
"target_block_not_found": "Zielblock nicht gefunden", "target_block_not_found": "Zielblock nicht gefunden",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Abkühlphase (umfrageübergreifend)", "waiting_time_across_surveys": "Abkühlphase (umfrageübergreifend)",
"waiting_time_across_surveys_description": "Um Umfragemüdigkeit zu vermeiden, wähle aus, wie diese Umfrage mit der workspace-weiten Abkühlphase interagiert.", "waiting_time_across_surveys_description": "Um Umfragemüdigkeit zu vermeiden, wähle aus, wie diese Umfrage mit der workspace-weiten Abkühlphase interagiert.",
"welcome_message": "Willkommensnachricht", "welcome_message": "Willkommensnachricht",
"when": "Wenn",
"without_a_filter_all_of_your_users_can_be_surveyed": "Ohne Filter können alle deine Nutzer befragt werden.", "without_a_filter_all_of_your_users_can_be_surveyed": "Ohne Filter können alle deine Nutzer befragt werden.",
"you_have_not_created_a_segment_yet": "Du hast noch keinen Segment erstellt.", "you_have_not_created_a_segment_yet": "Du hast noch keinen Segment erstellt.",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Sie müssen zwei oder mehr Sprachen in Ihrem Workspace eingerichtet haben, um mit Übersetzungen zu arbeiten.",
"your_description_here_recall_information_with": "Deine Beschreibung hier. Informationen abrufen mit @", "your_description_here_recall_information_with": "Deine Beschreibung hier. Informationen abrufen mit @",
"your_question_here_recall_information_with": "Deine Frage hier. Informationen abrufen mit @", "your_question_here_recall_information_with": "Deine Frage hier. Informationen abrufen mit @",
"your_web_app": "Deine Web-App", "your_web_app": "Deine Web-App",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Gefilterte Antworten (Excel)", "filtered_responses_excel": "Gefilterte Antworten (Excel)",
"generating_qr_code": "QR-Code wird generiert", "generating_qr_code": "QR-Code wird generiert",
"impressions": "Eindrücke", "impressions": "Eindrücke",
"impressions_identified_only": "Zeigt nur Impressionen von identifizierten Kontakten",
"impressions_tooltip": "Anzahl der Aufrufe der Umfrage.", "impressions_tooltip": "Anzahl der Aufrufe der Umfrage.",
"in_app": { "in_app": {
"connection_description": "Die Umfrage wird den Nutzern Ihrer Website angezeigt, die den unten aufgeführten Kriterien entsprechen", "connection_description": "Die Umfrage wird den Nutzern Ihrer Website angezeigt, die den unten aufgeführten Kriterien entsprechen",
@@ -1989,6 +2012,7 @@
"last_quarter": "Letztes Quartal", "last_quarter": "Letztes Quartal",
"last_year": "Letztes Jahr", "last_year": "Letztes Jahr",
"limit": "Limit", "limit": "Limit",
"no_identified_impressions": "Keine Impressionen von identifizierten Kontakten",
"no_responses_found": "Keine Antworten gefunden", "no_responses_found": "Keine Antworten gefunden",
"other_values_found": "Andere Werte gefunden", "other_values_found": "Andere Werte gefunden",
"overall": "Insgesamt", "overall": "Insgesamt",
@@ -2011,6 +2035,7 @@
"starts": "Startet", "starts": "Startet",
"starts_tooltip": "So oft wurde die Umfrage gestartet.", "starts_tooltip": "So oft wurde die Umfrage gestartet.",
"survey_reset_successfully": "Umfrage erfolgreich zurückgesetzt! {responseCount} Antworten und {displayCount} Anzeigen wurden gelöscht.", "survey_reset_successfully": "Umfrage erfolgreich zurückgesetzt! {responseCount} Antworten und {displayCount} Anzeigen wurden gelöscht.",
"survey_results": "{surveyName}-Ergebnisse",
"this_month": "Dieser Monat", "this_month": "Dieser Monat",
"this_quarter": "Dieses Quartal", "this_quarter": "Dieses Quartal",
"this_year": "Dieses Jahr", "this_year": "Dieses Jahr",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Färbt den gefüllten Teil des Balkens.", "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_border_radius_description": "Rundet die Eingabeecken ab.",
"advanced_styling_field_input_font_size_description": "Skaliert den eingegebenen Text in Eingabefeldern.", "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_height_description": "Steuert die Mindesthöhe der Eingabe.",
"advanced_styling_field_input_padding_x_description": "Fügt links und rechts Abstand hinzu.", "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_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_placeholder_opacity_description": "Blendet den Platzhaltertext aus.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Färbt den eingegebenen Text in Eingabefeldern.", "advanced_styling_field_input_text_description": "Färbt den eingegebenen Text in Eingabefeldern.",
"advanced_styling_field_option_bg": "Hintergrund", "advanced_styling_field_option_bg": "Hintergrund",
"advanced_styling_field_option_bg_description": "Füllt die Optionselemente.", "advanced_styling_field_option_bg_description": "Füllt die Optionselemente.",
"advanced_styling_field_option_border": "Rahmenfarbe",
"advanced_styling_field_option_border_description": "Umrandet Radio- und Checkbox-Optionen.",
"advanced_styling_field_option_border_radius_description": "Rundet die Ecken der Optionen ab.", "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_font_size_description": "Skaliert den Text der Optionsbeschriftung.",
"advanced_styling_field_option_label": "Label-Farbe", "advanced_styling_field_option_label": "Label-Farbe",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Wie kann das Unternehmen seine Vision und strategische Ausrichtung verbessern?", "alignment_and_engagement_survey_question_4_headline": "Wie kann das Unternehmen seine Vision und strategische Ausrichtung verbessern?",
"alignment_and_engagement_survey_question_4_placeholder": "Tippe deine Antwort hier...", "alignment_and_engagement_survey_question_4_placeholder": "Tippe deine Antwort hier...",
"back": "Zurück", "back": "Zurück",
"block_1": "Block 1",
"block_10": "Block 10",
"block_2": "Block 2",
"block_3": "Block 3",
"block_4": "Block 4",
"block_5": "Block 5",
"block_6": "Block 6",
"block_7": "Block 7",
"block_8": "Block 8",
"block_9": "Block 9",
"book_interview": "Interview buchen", "book_interview": "Interview buchen",
"build_product_roadmap_description": "Finde die EINE Sache heraus, die deine Nutzer am meisten wollen, und baue sie.", "build_product_roadmap_description": "Finde die EINE Sache heraus, die deine Nutzer am meisten wollen, und baue sie.",
"build_product_roadmap_name": "Produkt Roadmap erstellen", "build_product_roadmap_name": "Produkt Roadmap erstellen",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Ugh, sorry! Können wir irgendwas tun, um deine Erfahrung zu verbessern?", "csat_survey_question_3_headline": "Ugh, sorry! Können wir irgendwas tun, um deine Erfahrung zu verbessern?",
"csat_survey_question_3_placeholder": "Tippe deine Antwort hier...", "csat_survey_question_3_placeholder": "Tippe deine Antwort hier...",
"cta_description": "Information anzeigen und Benutzer auffordern, eine bestimmte Aktion auszuführen", "cta_description": "Information anzeigen und Benutzer auffordern, eine bestimmte Aktion auszuführen",
"custom_survey_block_1_name": "Block 1",
"custom_survey_description": "Erstelle eine Umfrage ohne Vorlage.", "custom_survey_description": "Erstelle eine Umfrage ohne Vorlage.",
"custom_survey_name": "Eigene Umfrage erstellen", "custom_survey_name": "Eigene Umfrage erstellen",
"custom_survey_question_1_headline": "Was möchtest Du wissen?", "custom_survey_question_1_headline": "Was möchtest Du wissen?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Nein, danke!", "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_headline": "Möchtest Du auf dem Laufenden bleiben?",
"preview_survey_question_2_subheader": "Dies ist eine Beispielbeschreibung.", "preview_survey_question_2_subheader": "Dies ist eine Beispielbeschreibung.",
"preview_survey_question_open_text_headline": "Möchtest Du noch etwas teilen?",
"preview_survey_question_open_text_placeholder": "Tippe deine Antwort hier...",
"preview_survey_question_open_text_subheader": "Dein Feedback hilft uns, besser zu werden.",
"preview_survey_welcome_card_headline": "Willkommen!", "preview_survey_welcome_card_headline": "Willkommen!",
"prioritize_features_description": "Identifiziere die Funktionen, die deine Nutzer am meisten und am wenigsten brauchen.", "prioritize_features_description": "Identifiziere die Funktionen, die deine Nutzer am meisten und am wenigsten brauchen.",
"prioritize_features_name": "Funktionen priorisieren", "prioritize_features_name": "Funktionen priorisieren",
+55 -16
View File
@@ -175,9 +175,12 @@
"copy": "Copy", "copy": "Copy",
"copy_code": "Copy code", "copy_code": "Copy code",
"copy_link": "Copy Link", "copy_link": "Copy Link",
"count_attributes": "{value, plural, one {{value} attribute} other {{value} attributes}}", "count_attributes": "{count, plural, one {{count} attribute} other {{count} attributes}}",
"count_contacts": "{value, plural, one {{value} contact} other {{value} contacts}}", "count_contacts": "{count, plural, one {{count} contact} other {{count} contacts}}",
"count_responses": "{value, plural, one {{value} response} other {{value} responses}}", "count_members": "{count, plural, one {{count} member} other {{count} members}}",
"count_questions": "{count, plural, one {{count} question} other {{count} questions}}",
"count_responses": "{count, plural, one {{count} response} other {{count} responses}}",
"count_selections": "{count, plural, one {{count} selection} other {{count} selections}}",
"create_new_organization": "Create new organization", "create_new_organization": "Create new organization",
"create_segment": "Create segment", "create_segment": "Create segment",
"create_survey": "Create survey", "create_survey": "Create survey",
@@ -191,6 +194,7 @@
"days": "days", "days": "days",
"default": "Default", "default": "Default",
"delete": "Delete", "delete": "Delete",
"delete_what": "Delete {deleteWhat}",
"description": "Description", "description": "Description",
"dev_env": "Dev Environment", "dev_env": "Dev Environment",
"development": "Development", "development": "Development",
@@ -206,6 +210,8 @@
"download": "Download", "download": "Download",
"draft": "Draft", "draft": "Draft",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicate_copy": "(copy)",
"duplicate_copy_number": "(copy {copyNumber})",
"e_commerce": "E-Commerce", "e_commerce": "E-Commerce",
"edit": "Edit", "edit": "Edit",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Error", "error": "Error",
"error_component_description": "This resource does not exist or you do not 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_component_title": "Error loading resources",
"error_loading_data": "Error loading data",
"error_rate_limit_description": "Maximum number of requests reached. Please try again later.", "error_rate_limit_description": "Maximum number of requests reached. Please try again later.",
"error_rate_limit_title": "Rate Limit Exceeded", "error_rate_limit_title": "Rate Limit Exceeded",
"expand_rows": "Expand rows", "expand_rows": "Expand rows",
"failed_to_copy_to_clipboard": "Failed to copy to clipboard", "failed_to_copy_to_clipboard": "Failed to copy to clipboard",
"failed_to_load_organizations": "Failed to load organizations", "failed_to_load_organizations": "Failed to load organizations",
"failed_to_load_workspaces": "Failed to load workspaces", "failed_to_load_workspaces": "Failed to load workspaces",
"filter": "Filter",
"finish": "Finish", "finish": "Finish",
"first_name": "First Name",
"follow_these": "Follow these", "follow_these": "Follow these",
"formbricks_version": "Formbricks Version", "formbricks_version": "Formbricks Version",
"full_name": "Full name", "full_name": "Full name",
@@ -237,6 +246,7 @@
"hidden_field": "Hidden field", "hidden_field": "Hidden field",
"hidden_fields": "Hidden fields", "hidden_fields": "Hidden fields",
"hide_column": "Hide column", "hide_column": "Hide column",
"id": "ID",
"image": "Image", "image": "Image",
"images": "Images", "images": "Images",
"import": "Import", "import": "Import",
@@ -254,6 +264,7 @@
"key": "Key", "key": "Key",
"label": "Label", "label": "Label",
"language": "Language", "language": "Language",
"last_name": "Last Name",
"learn_more": "Learn more", "learn_more": "Learn more",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Light overlay", "light_overlay": "Light overlay",
@@ -268,7 +279,6 @@
"look_and_feel": "Look & Feel", "look_and_feel": "Look & Feel",
"manage": "Manage", "manage": "Manage",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Member",
"members": "Members", "members": "Members",
"members_and_teams": "Members & Teams", "members_and_teams": "Members & Teams",
"membership_not_found": "Membership not found", "membership_not_found": "Membership not found",
@@ -280,6 +290,7 @@
"move_down": "Move down", "move_down": "Move down",
"move_up": "Move up", "move_up": "Move up",
"multiple_languages": "Multiple languages", "multiple_languages": "Multiple languages",
"my_product": "my Product",
"name": "Name", "name": "Name",
"new": "New", "new": "New",
"new_version_available": "Formbricks {version} is here. Upgrade now!", "new_version_available": "Formbricks {version} is here. Upgrade now!",
@@ -375,8 +386,6 @@
"select_teams": "Select teams", "select_teams": "Select teams",
"selected": "Selected", "selected": "Selected",
"selected_questions": "Selected questions", "selected_questions": "Selected questions",
"selection": "Selection",
"selections": "Selections",
"send_test_email": "Send test email", "send_test_email": "Send test email",
"session_not_found": "Session not found", "session_not_found": "Session not found",
"settings": "Settings", "settings": "Settings",
@@ -428,6 +437,7 @@
"top_right": "Top Right", "top_right": "Top Right",
"try_again": "Try again", "try_again": "Try again",
"type": "Type", "type": "Type",
"unknown_survey": "Unknown survey",
"unlock_more_workspaces_with_a_higher_plan": "Unlock more workspaces with a higher plan.", "unlock_more_workspaces_with_a_higher_plan": "Unlock more workspaces with a higher plan.",
"update": "Update", "update": "Update",
"updated": "Updated", "updated": "Updated",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Refresh contacts", "contacts_table_refresh": "Refresh contacts",
"contacts_table_refresh_success": "Contacts refreshed successfully", "contacts_table_refresh_success": "Contacts refreshed successfully",
"create_attribute": "Create attribute", "create_attribute": "Create attribute",
"create_key": "Create Key",
"create_new_attribute": "Create new attribute", "create_new_attribute": "Create new attribute",
"create_new_attribute_description": "Create a new attribute for segmentation purposes.", "create_new_attribute_description": "Create a new attribute for segmentation purposes.",
"custom_attributes": "Custom Attributes", "custom_attributes": "Custom Attributes",
@@ -656,6 +665,7 @@
"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_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 contacts data 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 contacts 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 contacts 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_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 contacts 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.}}",
"displays": "Displays",
"edit_attribute": "Edit attribute", "edit_attribute": "Edit attribute",
"edit_attribute_description": "Update the label and description for this attribute.", "edit_attribute_description": "Update the label and description for this attribute.",
"edit_attribute_values": "Edit attributes", "edit_attribute_values": "Edit attributes",
@@ -667,6 +677,7 @@
"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_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_date_format": "Invalid date format. Please use a valid date.",
"invalid_number_format": "Invalid number format. Please enter a valid number.", "invalid_number_format": "Invalid number format. Please enter a valid number.",
"no_activity_yet": "No activity yet",
"no_published_link_surveys_available": "No published link surveys available. Please publish a link survey first.", "no_published_link_surveys_available": "No published link surveys available. Please publish a link survey first.",
"no_published_surveys": "No published surveys", "no_published_surveys": "No published surveys",
"no_responses_found": "No responses found", "no_responses_found": "No responses found",
@@ -681,6 +692,8 @@
"select_a_survey": "Select a survey", "select_a_survey": "Select a survey",
"select_attribute": "Select Attribute", "select_attribute": "Select Attribute",
"select_attribute_key": "Select attribute key", "select_attribute_key": "Select attribute key",
"survey_viewed": "Survey viewed",
"survey_viewed_at": "Viewed At",
"system_attributes": "System Attributes", "system_attributes": "System Attributes",
"unlock_contacts_description": "Manage contacts and send out targeted surveys", "unlock_contacts_description": "Manage contacts and send out targeted surveys",
"unlock_contacts_title": "Unlock contacts with a higher plan", "unlock_contacts_title": "Unlock contacts with a higher plan",
@@ -752,7 +765,12 @@
"link_google_sheet": "Link Google Sheet", "link_google_sheet": "Link Google Sheet",
"link_new_sheet": "Link new Sheet", "link_new_sheet": "Link new Sheet",
"no_integrations_yet": "Your google sheet integrations will appear here as soon as you add them. ⏲️", "no_integrations_yet": "Your google sheet integrations will appear here as soon as you add them. ⏲️",
"spreadsheet_url": "Spreadsheet URL" "reconnect_button": "Reconnect",
"reconnect_button_description": "Your Google Sheets connection has expired. Please reconnect to continue syncing responses. Your existing spreadsheet links and data will be preserved.",
"reconnect_button_tooltip": "Reconnect the integration to refresh your access. Your existing spreadsheet links and data will be preserved.",
"spreadsheet_permission_error": "You don't have permission to access this spreadsheet. Please ensure the spreadsheet is shared with your Google account and you have write access to the spreadsheet.",
"spreadsheet_url": "Spreadsheet URL",
"token_expired_error": "Google Sheets refresh token has expired or been revoked. Please reconnect the integration."
}, },
"include_created_at": "Include Created At", "include_created_at": "Include Created At",
"include_hidden_fields": "Include Hidden Fields", "include_hidden_fields": "Include Hidden Fields",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Hey {userName}", "email_customization_preview_email_heading": "Hey {userName}",
"email_customization_preview_email_text": "This is an email preview to show you which logo will be rendered in the emails.", "email_customization_preview_email_text": "This is an email preview to show you which logo will be rendered in the emails.",
"error_deleting_organization_please_try_again": "Error deleting organization. Please try again.", "error_deleting_organization_please_try_again": "Error deleting organization. Please try again.",
"from_your_organization": "from your organization", "from_your_organization": "{memberName} from your organization",
"invitation_sent_once_more": "Invitation sent once more.", "invitation_sent_once_more": "Invitation sent once more.",
"invite_deleted_successfully": "Invite deleted successfully", "invite_deleted_successfully": "Invite deleted successfully",
"invite_expires_on": "Invite expires on {date}", "invite_expires_on": "Invite expires on {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Add a placeholder to show if there is no value to recall.", "add_fallback_placeholder": "Add a placeholder to show if there is no value to recall.",
"add_hidden_field_id": "Add hidden field ID", "add_hidden_field_id": "Add hidden field ID",
"add_highlight_border": "Add highlight border", "add_highlight_border": "Add highlight border",
"add_highlight_border_description": "Only applies to in-product surveys.",
"add_logic": "Add logic", "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_option": "Add option",
@@ -1248,12 +1267,14 @@
"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_survey_closed_message_description": "Change the message visitors see when the survey is closed.",
"adjust_the_theme_in_the": "Adjust the theme in the", "adjust_the_theme_in_the": "Adjust the theme in the",
"all_are_true": "all are true",
"all_other_answers_will_continue_to": "All other answers will continue to", "all_other_answers_will_continue_to": "All other answers will continue to",
"allow_multi_select": "Allow multi-select", "allow_multi_select": "Allow multi-select",
"allow_multiple_files": "Allow multiple files", "allow_multiple_files": "Allow multiple files",
"allow_users_to_select_more_than_one_image": "Allow users to select more than one image", "allow_users_to_select_more_than_one_image": "Allow users to select more than one image",
"and_launch_surveys_in_your_website_or_app": "and launch surveys in your website or app.", "and_launch_surveys_in_your_website_or_app": "and launch surveys in your website or app.",
"animation": "Animation", "animation": "Animation",
"any_is_true": "any is true",
"app_survey_description": "Embed a survey in your web app or website to collect responses.", "app_survey_description": "Embed a survey in your web app or website to collect responses.",
"assign": "Assign =", "assign": "Assign =",
"audience": "Audience", "audience": "Audience",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Follow-up updated and will be saved once you save the survey.", "follow_ups_modal_updated_successfull_toast": "Follow-up updated and will be saved once you save the survey.",
"follow_ups_new": "New follow-up", "follow_ups_new": "New follow-up",
"follow_ups_upgrade_button_text": "Upgrade to enable follow-ups", "follow_ups_upgrade_button_text": "Upgrade to enable follow-ups",
"form_styling": "Form styling",
"formbricks_sdk_is_not_connected": "Formbricks SDK is not connected", "formbricks_sdk_is_not_connected": "Formbricks SDK is not connected",
"four_points": "4 points", "four_points": "4 points",
"heading": "Heading", "heading": "Heading",
@@ -1520,7 +1540,7 @@
"option_idx": "Option {choiceIndex}", "option_idx": "Option {choiceIndex}",
"option_used_in_logic_error": "This option is used in logic of question {questionIndex}. Please remove it from logic first.", "option_used_in_logic_error": "This option is used in logic of question {questionIndex}. Please remove it from logic first.",
"optional": "Optional", "optional": "Optional",
"options": "Options", "options": "Options*",
"options_used_in_logic_bulk_error": "The following options are used in logic: {questionIndexes}. Please remove them from logic first.", "options_used_in_logic_bulk_error": "The following options are used in logic: {questionIndexes}. Please remove them from logic first.",
"override_theme_with_individual_styles_for_this_survey": "Override the theme with individual styles for this survey.", "override_theme_with_individual_styles_for_this_survey": "Override the theme with individual styles for this survey.",
"overwrite_global_waiting_time": "Set custom Cooldown Period", "overwrite_global_waiting_time": "Set custom Cooldown Period",
@@ -1545,6 +1565,7 @@
"question_deleted": "Question deleted.", "question_deleted": "Question deleted.",
"question_duplicated": "Question duplicated.", "question_duplicated": "Question duplicated.",
"question_id_updated": "Question ID updated", "question_id_updated": "Question ID updated",
"question_number": "Question {number}",
"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_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_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",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Response limits, redirections and more.", "response_limits_redirections_and_more": "Response limits, redirections and more.",
"response_options": "Response Options", "response_options": "Response Options",
"roundness": "Roundness", "roundness": "Roundness",
"roundness_description": "Controls how rounded the card corners are.", "roundness_description": "Controls how rounded corners are.",
"row_used_in_logic_error": "This row is used in logic of question {questionIndex}. Please remove it from logic first.", "row_used_in_logic_error": "This row is used in logic of question {questionIndex}. Please remove it from logic first.",
"rows": "Rows", "rows": "Rows",
"save_and_close": "Save & Close", "save_and_close": "Save & Close",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "This free & open-source survey has been closed", "survey_completed_subheading": "This free & open-source survey has been closed",
"survey_display_settings": "Survey Display Settings", "survey_display_settings": "Survey Display Settings",
"survey_placement": "Survey Placement", "survey_placement": "Survey Placement",
"survey_styling": "Survey styling",
"survey_trigger": "Survey Trigger", "survey_trigger": "Survey Trigger",
"switch_multi_language_on_to_get_started": "Switch multi-language on to get started 👉", "switch_multi_language_on_to_get_started": "Switch multi-language on to get started 👉",
"target_block_not_found": "Target block not found", "target_block_not_found": "Target block not found",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Cooldown Period (across surveys)", "waiting_time_across_surveys": "Cooldown Period (across surveys)",
"waiting_time_across_surveys_description": "To prevent survey fatigue, choose how this survey interacts with the workspace-wide Cooldown Period.", "waiting_time_across_surveys_description": "To prevent survey fatigue, choose how this survey interacts with the workspace-wide Cooldown Period.",
"welcome_message": "Welcome message", "welcome_message": "Welcome message",
"when": "When",
"without_a_filter_all_of_your_users_can_be_surveyed": "Without a filter, all of your users can be surveyed.", "without_a_filter_all_of_your_users_can_be_surveyed": "Without a filter, all of your users can be surveyed.",
"you_have_not_created_a_segment_yet": "You have not created a segment yet", "you_have_not_created_a_segment_yet": "You have not created a segment yet",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "You need to have two or more languages set up in your workspace to work with translations.",
"your_description_here_recall_information_with": "Your description here. Recall information with @", "your_description_here_recall_information_with": "Your description here. Recall information with @",
"your_question_here_recall_information_with": "Your question here. Recall information with @", "your_question_here_recall_information_with": "Your question here. Recall information with @",
"your_web_app": "Your web app", "your_web_app": "Your web app",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Filtered responses (Excel)", "filtered_responses_excel": "Filtered responses (Excel)",
"generating_qr_code": "Generating QR code", "generating_qr_code": "Generating QR code",
"impressions": "Impressions", "impressions": "Impressions",
"impressions_identified_only": "Only showing impressions from identified contacts",
"impressions_tooltip": "Number of times the survey has been viewed.", "impressions_tooltip": "Number of times the survey has been viewed.",
"in_app": { "in_app": {
"connection_description": "The survey will be shown to users of your website, that match the criteria listed below", "connection_description": "The survey will be shown to users of your website, that match the criteria listed below",
@@ -1989,6 +2012,7 @@
"last_quarter": "Last quarter", "last_quarter": "Last quarter",
"last_year": "Last year", "last_year": "Last year",
"limit": "Limit", "limit": "Limit",
"no_identified_impressions": "No impressions from identified contacts",
"no_responses_found": "No responses found", "no_responses_found": "No responses found",
"other_values_found": "Other values found", "other_values_found": "Other values found",
"overall": "Overall", "overall": "Overall",
@@ -2011,6 +2035,7 @@
"starts": "Starts", "starts": "Starts",
"starts_tooltip": "Number of times the survey has been started.", "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.",
"survey_results": "{surveyName} Results",
"this_month": "This month", "this_month": "This month",
"this_quarter": "This quarter", "this_quarter": "This quarter",
"this_year": "This year", "this_year": "This year",
@@ -2144,7 +2169,7 @@
"advanced_styling_field_description_size": "Description Font Size", "advanced_styling_field_description_size": "Description Font Size",
"advanced_styling_field_description_size_description": "Scales the description text.", "advanced_styling_field_description_size_description": "Scales the description text.",
"advanced_styling_field_description_weight": "Description Font Weight", "advanced_styling_field_description_weight": "Description Font Weight",
"advanced_styling_field_description_weight_description": "Makes description text lighter or bolder.", "advanced_styling_field_description_weight_description": "Makes descr. text lighter or bolder.",
"advanced_styling_field_font_size": "Font Size", "advanced_styling_field_font_size": "Font Size",
"advanced_styling_field_font_weight": "Font Weight", "advanced_styling_field_font_weight": "Font Weight",
"advanced_styling_field_headline_color": "Headline Color", "advanced_styling_field_headline_color": "Headline Color",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Colors the filled portion of the bar.", "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_border_radius_description": "Rounds the input corners.",
"advanced_styling_field_input_font_size_description": "Scales the typed text in inputs.", "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_height_description": "Controls the min. height of the input.",
"advanced_styling_field_input_padding_x_description": "Adds space on the left and right.", "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_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_placeholder_opacity_description": "Fades the placeholder hint text.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colors the typed text in inputs.", "advanced_styling_field_input_text_description": "Colors the typed text in inputs.",
"advanced_styling_field_option_bg": "Background", "advanced_styling_field_option_bg": "Background",
"advanced_styling_field_option_bg_description": "Fills the option items.", "advanced_styling_field_option_bg_description": "Fills the option items.",
"advanced_styling_field_option_border": "Border Color",
"advanced_styling_field_option_border_description": "Outlines radio and checkbox options.",
"advanced_styling_field_option_border_radius_description": "Rounds the option corners.", "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_font_size_description": "Scales the option label text.",
"advanced_styling_field_option_label": "Label Color", "advanced_styling_field_option_label": "Label Color",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "How can the company improve its vision and strategy alignment?", "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", "back": "Back",
"block_1": "Block 1",
"block_10": "Block 10",
"block_2": "Block 2",
"block_3": "Block 3",
"block_4": "Block 4",
"block_5": "Block 5",
"block_6": "Block 6",
"block_7": "Block 7",
"block_8": "Block 8",
"block_9": "Block 9",
"book_interview": "Book interview", "book_interview": "Book interview",
"build_product_roadmap_description": "Identify the ONE thing your users want the most and build it.", "build_product_roadmap_description": "Identify the ONE thing your users want the most and build it.",
"build_product_roadmap_name": "Build Product Roadmap", "build_product_roadmap_name": "Build Product Roadmap",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Ugh, sorry! Is there anything we can do to improve your experience?", "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", "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_description": "Create a survey without template.",
"custom_survey_name": "Start from scratch", "custom_survey_name": "Start from scratch",
"custom_survey_question_1_headline": "What would you like to know?", "custom_survey_question_1_headline": "What would you like to know?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "No, thank you!", "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_headline": "Want to stay in the loop?",
"preview_survey_question_2_subheader": "This is an example description.", "preview_survey_question_2_subheader": "This is an example description.",
"preview_survey_question_open_text_headline": "Anything else you'd like to share?",
"preview_survey_question_open_text_placeholder": "Type your answer here…",
"preview_survey_question_open_text_subheader": "Your feedback helps us improve.",
"preview_survey_welcome_card_headline": "Welcome!", "preview_survey_welcome_card_headline": "Welcome!",
"prioritize_features_description": "Identify features your users need most and least.", "prioritize_features_description": "Identify features your users need most and least.",
"prioritize_features_name": "Prioritize Features", "prioritize_features_name": "Prioritize Features",
+55 -16
View File
@@ -175,9 +175,12 @@
"copy": "Copiar", "copy": "Copiar",
"copy_code": "Copiar código", "copy_code": "Copiar código",
"copy_link": "Copiar enlace", "copy_link": "Copiar enlace",
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}", "count_attributes": "{count, plural, one {{count} atributo} other {{count} atributos}}",
"count_contacts": "{value, plural, one {{value} contacto} other {{value} contactos}}", "count_contacts": "{count, plural, one {{count} contacto} other {{count} contactos}}",
"count_responses": "{value, plural, one {{value} respuesta} other {{value} respuestas}}", "count_members": "{count, plural, one {{count} miembro} other {{count} miembros}}",
"count_questions": "{count, plural, one {{count} pregunta} other {{count} preguntas}}",
"count_responses": "{count, plural, one {{count} respuesta} other {{count} respuestas}}",
"count_selections": "{count, plural, one {{count} selección} other {{count} selecciones}}",
"create_new_organization": "Crear organización nueva", "create_new_organization": "Crear organización nueva",
"create_segment": "Crear segmento", "create_segment": "Crear segmento",
"create_survey": "Crear encuesta", "create_survey": "Crear encuesta",
@@ -191,6 +194,7 @@
"days": "días", "days": "días",
"default": "Predeterminado", "default": "Predeterminado",
"delete": "Eliminar", "delete": "Eliminar",
"delete_what": "Eliminar {deleteWhat}",
"description": "Descripción", "description": "Descripción",
"dev_env": "Entorno de desarrollo", "dev_env": "Entorno de desarrollo",
"development": "Desarrollo", "development": "Desarrollo",
@@ -206,6 +210,8 @@
"download": "Descargar", "download": "Descargar",
"draft": "Borrador", "draft": "Borrador",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicate_copy": "(copia)",
"duplicate_copy_number": "(copia {copyNumber})",
"e_commerce": "Comercio electrónico", "e_commerce": "Comercio electrónico",
"edit": "Editar", "edit": "Editar",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Error", "error": "Error",
"error_component_description": "Este recurso no existe o no tienes los derechos necesarios para acceder a él.", "error_component_description": "Este recurso no existe o no tienes los derechos necesarios para acceder a él.",
"error_component_title": "Error al cargar recursos", "error_component_title": "Error al cargar recursos",
"error_loading_data": "Error al cargar los datos",
"error_rate_limit_description": "Número máximo de solicitudes alcanzado. Por favor, inténtalo de nuevo más tarde.", "error_rate_limit_description": "Número máximo de solicitudes alcanzado. Por favor, inténtalo de nuevo más tarde.",
"error_rate_limit_title": "Límite de frecuencia excedido", "error_rate_limit_title": "Límite de frecuencia excedido",
"expand_rows": "Expandir filas", "expand_rows": "Expandir filas",
"failed_to_copy_to_clipboard": "Error al copiar al portapapeles", "failed_to_copy_to_clipboard": "Error al copiar al portapapeles",
"failed_to_load_organizations": "Error al cargar organizaciones", "failed_to_load_organizations": "Error al cargar organizaciones",
"failed_to_load_workspaces": "Error al cargar los proyectos", "failed_to_load_workspaces": "Error al cargar los proyectos",
"filter": "Filtro",
"finish": "Finalizar", "finish": "Finalizar",
"first_name": "Nombre",
"follow_these": "Sigue estos", "follow_these": "Sigue estos",
"formbricks_version": "Versión de Formbricks", "formbricks_version": "Versión de Formbricks",
"full_name": "Nombre completo", "full_name": "Nombre completo",
@@ -237,6 +246,7 @@
"hidden_field": "Campo oculto", "hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos", "hidden_fields": "Campos ocultos",
"hide_column": "Ocultar columna", "hide_column": "Ocultar columna",
"id": "ID",
"image": "Imagen", "image": "Imagen",
"images": "Imágenes", "images": "Imágenes",
"import": "Importar", "import": "Importar",
@@ -254,6 +264,7 @@
"key": "Clave", "key": "Clave",
"label": "Etiqueta", "label": "Etiqueta",
"language": "Idioma", "language": "Idioma",
"last_name": "Apellido",
"learn_more": "Saber más", "learn_more": "Saber más",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Superposición clara", "light_overlay": "Superposición clara",
@@ -268,7 +279,6 @@
"look_and_feel": "Apariencia", "look_and_feel": "Apariencia",
"manage": "Gestionar", "manage": "Gestionar",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Miembro",
"members": "Miembros", "members": "Miembros",
"members_and_teams": "Miembros y equipos", "members_and_teams": "Miembros y equipos",
"membership_not_found": "Membresía no encontrada", "membership_not_found": "Membresía no encontrada",
@@ -280,6 +290,7 @@
"move_down": "Mover hacia abajo", "move_down": "Mover hacia abajo",
"move_up": "Mover hacia arriba", "move_up": "Mover hacia arriba",
"multiple_languages": "Múltiples idiomas", "multiple_languages": "Múltiples idiomas",
"my_product": "mi producto",
"name": "Nombre", "name": "Nombre",
"new": "Nuevo", "new": "Nuevo",
"new_version_available": "Formbricks {version} está aquí. ¡Actualiza ahora!", "new_version_available": "Formbricks {version} está aquí. ¡Actualiza ahora!",
@@ -375,8 +386,6 @@
"select_teams": "Seleccionar equipos", "select_teams": "Seleccionar equipos",
"selected": "Seleccionado", "selected": "Seleccionado",
"selected_questions": "Preguntas seleccionadas", "selected_questions": "Preguntas seleccionadas",
"selection": "Selección",
"selections": "Selecciones",
"send_test_email": "Enviar correo electrónico de prueba", "send_test_email": "Enviar correo electrónico de prueba",
"session_not_found": "Sesión no encontrada", "session_not_found": "Sesión no encontrada",
"settings": "Ajustes", "settings": "Ajustes",
@@ -428,6 +437,7 @@
"top_right": "Superior derecha", "top_right": "Superior derecha",
"try_again": "Intentar de nuevo", "try_again": "Intentar de nuevo",
"type": "Tipo", "type": "Tipo",
"unknown_survey": "Encuesta desconocida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloquea más proyectos con un plan superior.", "unlock_more_workspaces_with_a_higher_plan": "Desbloquea más proyectos con un plan superior.",
"update": "Actualizar", "update": "Actualizar",
"updated": "Actualizado", "updated": "Actualizado",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Actualizar contactos", "contacts_table_refresh": "Actualizar contactos",
"contacts_table_refresh_success": "Contactos actualizados correctamente", "contacts_table_refresh_success": "Contactos actualizados correctamente",
"create_attribute": "Crear atributo", "create_attribute": "Crear atributo",
"create_key": "Crear clave",
"create_new_attribute": "Crear atributo nuevo", "create_new_attribute": "Crear atributo nuevo",
"create_new_attribute_description": "Crea un atributo nuevo para fines de segmentación.", "create_new_attribute_description": "Crea un atributo nuevo para fines de segmentación.",
"custom_attributes": "Atributos personalizados", "custom_attributes": "Atributos personalizados",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Visualizaciones",
"edit_attribute": "Editar atributo", "edit_attribute": "Editar atributo",
"edit_attribute_description": "Actualiza la etiqueta y la descripción de este atributo.", "edit_attribute_description": "Actualiza la etiqueta y la descripción de este atributo.",
"edit_attribute_values": "Editar atributos", "edit_attribute_values": "Editar atributos",
@@ -667,6 +677,7 @@
"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_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_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.", "invalid_number_format": "Formato de número no válido. Por favor, introduce un número válido.",
"no_activity_yet": "Aún no hay actividad",
"no_published_link_surveys_available": "No hay encuestas de enlace publicadas disponibles. Por favor, publica primero una encuesta de enlace.", "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_published_surveys": "No hay encuestas publicadas",
"no_responses_found": "No se encontraron respuestas", "no_responses_found": "No se encontraron respuestas",
@@ -681,6 +692,8 @@
"select_a_survey": "Selecciona una encuesta", "select_a_survey": "Selecciona una encuesta",
"select_attribute": "Seleccionar atributo", "select_attribute": "Seleccionar atributo",
"select_attribute_key": "Seleccionar clave de atributo", "select_attribute_key": "Seleccionar clave de atributo",
"survey_viewed": "Encuesta vista",
"survey_viewed_at": "Vista el",
"system_attributes": "Atributos del sistema", "system_attributes": "Atributos del sistema",
"unlock_contacts_description": "Gestiona contactos y envía encuestas dirigidas", "unlock_contacts_description": "Gestiona contactos y envía encuestas dirigidas",
"unlock_contacts_title": "Desbloquea contactos con un plan superior", "unlock_contacts_title": "Desbloquea contactos con un plan superior",
@@ -752,7 +765,12 @@
"link_google_sheet": "Vincular Google Sheet", "link_google_sheet": "Vincular Google Sheet",
"link_new_sheet": "Vincular nueva hoja", "link_new_sheet": "Vincular nueva hoja",
"no_integrations_yet": "Tus integraciones de Google Sheet aparecerán aquí tan pronto como las añadas. ⏲️", "no_integrations_yet": "Tus integraciones de Google Sheet aparecerán aquí tan pronto como las añadas. ⏲️",
"spreadsheet_url": "URL de la hoja de cálculo" "reconnect_button": "Reconectar",
"reconnect_button_description": "Tu conexión con Google Sheets ha caducado. Reconecta para continuar sincronizando respuestas. Tus enlaces de hojas de cálculo y datos existentes se conservarán.",
"reconnect_button_tooltip": "Reconecta la integración para actualizar tu acceso. Tus enlaces de hojas de cálculo y datos existentes se conservarán.",
"spreadsheet_permission_error": "No tienes permiso para acceder a esta hoja de cálculo. Asegúrate de que la hoja de cálculo esté compartida con tu cuenta de Google y de que tengas acceso de escritura a la hoja de cálculo.",
"spreadsheet_url": "URL de la hoja de cálculo",
"token_expired_error": "El token de actualización de Google Sheets ha caducado o ha sido revocado. Reconecta la integración."
}, },
"include_created_at": "Incluir fecha de creación", "include_created_at": "Incluir fecha de creación",
"include_hidden_fields": "Incluir campos ocultos", "include_hidden_fields": "Incluir campos ocultos",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Hola {userName}", "email_customization_preview_email_heading": "Hola {userName}",
"email_customization_preview_email_text": "Este es un correo electrónico de vista previa para mostrarte qué logotipo se mostrará en los correos electrónicos.", "email_customization_preview_email_text": "Este es un correo electrónico de vista previa para mostrarte qué logotipo se mostrará en los correos electrónicos.",
"error_deleting_organization_please_try_again": "Error al eliminar la organización. Por favor, inténtalo de nuevo.", "error_deleting_organization_please_try_again": "Error al eliminar la organización. Por favor, inténtalo de nuevo.",
"from_your_organization": "de tu organización", "from_your_organization": "{memberName} de tu organización",
"invitation_sent_once_more": "Invitación enviada una vez más.", "invitation_sent_once_more": "Invitación enviada una vez más.",
"invite_deleted_successfully": "Invitación eliminada correctamente", "invite_deleted_successfully": "Invitación eliminada correctamente",
"invite_expires_on": "La invitación expira el {date}", "invite_expires_on": "La invitación expira el {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Añadir un marcador de posición para mostrar si no hay valor que recuperar.", "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_hidden_field_id": "Añadir ID de campo oculto",
"add_highlight_border": "Añadir borde destacado", "add_highlight_border": "Añadir borde destacado",
"add_highlight_border_description": "Solo se aplica a encuestas dentro del producto.",
"add_logic": "Añadir lógica", "add_logic": "Añadir lógica",
"add_none_of_the_above": "Añadir \"Ninguna de las anteriores\"", "add_none_of_the_above": "Añadir \"Ninguna de las anteriores\"",
"add_option": "Añadir opción", "add_option": "Añadir opción",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Ajustar mensaje 'Encuesta cerrada'", "adjust_survey_closed_message": "Ajustar mensaje 'Encuesta cerrada'",
"adjust_survey_closed_message_description": "Cambiar el mensaje que ven los visitantes cuando la encuesta está cerrada.", "adjust_survey_closed_message_description": "Cambiar el mensaje que ven los visitantes cuando la encuesta está cerrada.",
"adjust_the_theme_in_the": "Ajustar el tema en el", "adjust_the_theme_in_the": "Ajustar el tema en el",
"all_are_true": "todas son verdaderas",
"all_other_answers_will_continue_to": "Todas las demás respuestas continuarán", "all_other_answers_will_continue_to": "Todas las demás respuestas continuarán",
"allow_multi_select": "Permitir selección múltiple", "allow_multi_select": "Permitir selección múltiple",
"allow_multiple_files": "Permitir múltiples archivos", "allow_multiple_files": "Permitir múltiples archivos",
"allow_users_to_select_more_than_one_image": "Permitir a los usuarios seleccionar más de una imagen", "allow_users_to_select_more_than_one_image": "Permitir a los usuarios seleccionar más de una imagen",
"and_launch_surveys_in_your_website_or_app": "y lanzar encuestas en tu sitio web o aplicación.", "and_launch_surveys_in_your_website_or_app": "y lanzar encuestas en tu sitio web o aplicación.",
"animation": "Animación", "animation": "Animación",
"any_is_true": "alguna es verdadera",
"app_survey_description": "Integra una encuesta en tu aplicación web o sitio web para recopilar respuestas.", "app_survey_description": "Integra una encuesta en tu aplicación web o sitio web para recopilar respuestas.",
"assign": "Asignar =", "assign": "Asignar =",
"audience": "Audiencia", "audience": "Audiencia",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Seguimiento actualizado y se guardará cuando guardes la encuesta.", "follow_ups_modal_updated_successfull_toast": "Seguimiento actualizado y se guardará cuando guardes la encuesta.",
"follow_ups_new": "Nuevo seguimiento", "follow_ups_new": "Nuevo seguimiento",
"follow_ups_upgrade_button_text": "Actualiza para habilitar seguimientos", "follow_ups_upgrade_button_text": "Actualiza para habilitar seguimientos",
"form_styling": "Estilo del formulario",
"formbricks_sdk_is_not_connected": "El SDK de Formbricks no está conectado", "formbricks_sdk_is_not_connected": "El SDK de Formbricks no está conectado",
"four_points": "4 puntos", "four_points": "4 puntos",
"heading": "Encabezado", "heading": "Encabezado",
@@ -1520,7 +1540,7 @@
"option_idx": "Opción {choiceIndex}", "option_idx": "Opción {choiceIndex}",
"option_used_in_logic_error": "Esta opción se utiliza en la lógica de la pregunta {questionIndex}. Por favor, elimínala de la lógica primero.", "option_used_in_logic_error": "Esta opción se utiliza en la lógica de la pregunta {questionIndex}. Por favor, elimínala de la lógica primero.",
"optional": "Opcional", "optional": "Opcional",
"options": "Opciones", "options": "Opciones*",
"options_used_in_logic_bulk_error": "Las siguientes opciones se utilizan en la lógica: {questionIndexes}. Por favor, elimínalas de la lógica primero.", "options_used_in_logic_bulk_error": "Las siguientes opciones se utilizan en la lógica: {questionIndexes}. Por favor, elimínalas de la lógica primero.",
"override_theme_with_individual_styles_for_this_survey": "Anular el tema con estilos individuales para esta encuesta.", "override_theme_with_individual_styles_for_this_survey": "Anular el tema con estilos individuales para esta encuesta.",
"overwrite_global_waiting_time": "Establecer periodo de espera personalizado", "overwrite_global_waiting_time": "Establecer periodo de espera personalizado",
@@ -1545,6 +1565,7 @@
"question_deleted": "Pregunta eliminada.", "question_deleted": "Pregunta eliminada.",
"question_duplicated": "Pregunta duplicada.", "question_duplicated": "Pregunta duplicada.",
"question_id_updated": "ID de pregunta actualizado", "question_id_updated": "ID de pregunta actualizado",
"question_number": "Pregunta {number}",
"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_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_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}”",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Límites de respuestas, redirecciones y más.", "response_limits_redirections_and_more": "Límites de respuestas, redirecciones y más.",
"response_options": "Opciones de respuesta", "response_options": "Opciones de respuesta",
"roundness": "Redondez", "roundness": "Redondez",
"roundness_description": "Controla qué tan redondeadas están las esquinas de la tarjeta.", "roundness_description": "Controla qué tan redondeadas están las esquinas.",
"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.", "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", "rows": "Filas",
"save_and_close": "Guardar y cerrar", "save_and_close": "Guardar y cerrar",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Esta encuesta gratuita y de código abierto ha sido cerrada", "survey_completed_subheading": "Esta encuesta gratuita y de código abierto ha sido cerrada",
"survey_display_settings": "Ajustes de visualización de la encuesta", "survey_display_settings": "Ajustes de visualización de la encuesta",
"survey_placement": "Ubicación de la encuesta", "survey_placement": "Ubicación de la encuesta",
"survey_styling": "Estilo del formulario",
"survey_trigger": "Activador de la encuesta", "survey_trigger": "Activador de la encuesta",
"switch_multi_language_on_to_get_started": "Activa el modo multiidioma para comenzar 👉", "switch_multi_language_on_to_get_started": "Activa el modo multiidioma para comenzar 👉",
"target_block_not_found": "Bloque objetivo no encontrado", "target_block_not_found": "Bloque objetivo no encontrado",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Periodo de espera (entre encuestas)", "waiting_time_across_surveys": "Periodo de espera (entre encuestas)",
"waiting_time_across_surveys_description": "Para evitar la fatiga de encuestas, elige cómo interactúa esta encuesta con el periodo de espera general del espacio de trabajo.", "waiting_time_across_surveys_description": "Para evitar la fatiga de encuestas, elige cómo interactúa esta encuesta con el periodo de espera general del espacio de trabajo.",
"welcome_message": "Mensaje de bienvenida", "welcome_message": "Mensaje de bienvenida",
"when": "Cuando",
"without_a_filter_all_of_your_users_can_be_surveyed": "Sin un filtro, todos tus usuarios pueden ser encuestados.", "without_a_filter_all_of_your_users_can_be_surveyed": "Sin un filtro, todos tus usuarios pueden ser encuestados.",
"you_have_not_created_a_segment_yet": "Aún no has creado un segmento", "you_have_not_created_a_segment_yet": "Aún no has creado un segmento",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Necesitas tener dos o más idiomas configurados en tu proyecto para trabajar con traducciones.",
"your_description_here_recall_information_with": "Tu descripción aquí. Recupera información con @", "your_description_here_recall_information_with": "Tu descripción aquí. Recupera información con @",
"your_question_here_recall_information_with": "Tu pregunta aquí. Recupera información con @", "your_question_here_recall_information_with": "Tu pregunta aquí. Recupera información con @",
"your_web_app": "Tu aplicación web", "your_web_app": "Tu aplicación web",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Respuestas filtradas (Excel)", "filtered_responses_excel": "Respuestas filtradas (Excel)",
"generating_qr_code": "Generando código QR", "generating_qr_code": "Generando código QR",
"impressions": "Impresiones", "impressions": "Impresiones",
"impressions_identified_only": "Solo se muestran impresiones de contactos identificados",
"impressions_tooltip": "Número de veces que se ha visto la encuesta.", "impressions_tooltip": "Número de veces que se ha visto la encuesta.",
"in_app": { "in_app": {
"connection_description": "La encuesta se mostrará a los usuarios de tu sitio web que cumplan con los criterios enumerados a continuación", "connection_description": "La encuesta se mostrará a los usuarios de tu sitio web que cumplan con los criterios enumerados a continuación",
@@ -1989,6 +2012,7 @@
"last_quarter": "Último trimestre", "last_quarter": "Último trimestre",
"last_year": "Último año", "last_year": "Último año",
"limit": "Límite", "limit": "Límite",
"no_identified_impressions": "No hay impresiones de contactos identificados",
"no_responses_found": "No se han encontrado respuestas", "no_responses_found": "No se han encontrado respuestas",
"other_values_found": "Otros valores encontrados", "other_values_found": "Otros valores encontrados",
"overall": "General", "overall": "General",
@@ -2011,6 +2035,7 @@
"starts": "Inicios", "starts": "Inicios",
"starts_tooltip": "Número de veces que se ha iniciado la encuesta.", "starts_tooltip": "Número de veces que se ha iniciado la encuesta.",
"survey_reset_successfully": "¡Encuesta restablecida correctamente! Se eliminaron {responseCount} respuestas y {displayCount} visualizaciones.", "survey_reset_successfully": "¡Encuesta restablecida correctamente! Se eliminaron {responseCount} respuestas y {displayCount} visualizaciones.",
"survey_results": "Resultados de {surveyName}",
"this_month": "Este mes", "this_month": "Este mes",
"this_quarter": "Este trimestre", "this_quarter": "Este trimestre",
"this_year": "Este año", "this_year": "Este año",
@@ -2144,7 +2169,7 @@
"advanced_styling_field_description_size": "Tamaño de fuente de la descripción", "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_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": "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_description_weight_description": "Hace el texto de descripción más ligero o más grueso.",
"advanced_styling_field_font_size": "Tamaño de fuente", "advanced_styling_field_font_size": "Tamaño de fuente",
"advanced_styling_field_font_weight": "Grosor de fuente", "advanced_styling_field_font_weight": "Grosor de fuente",
"advanced_styling_field_headline_color": "Color del titular", "advanced_styling_field_headline_color": "Color del titular",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Colorea la porción rellena de la barra.", "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_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_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_height_description": "Controla la altura mínima de la entrada.",
"advanced_styling_field_input_padding_x_description": "Añade espacio a la izquierda y a la derecha.", "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_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_placeholder_opacity_description": "Atenúa el texto de sugerencia del marcador de posición.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colorea el texto escrito en los campos 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": "Fondo",
"advanced_styling_field_option_bg_description": "Rellena los elementos de opción.", "advanced_styling_field_option_bg_description": "Rellena los elementos de opción.",
"advanced_styling_field_option_border": "Color del borde",
"advanced_styling_field_option_border_description": "Delimita las opciones de radio y casillas de verificación.",
"advanced_styling_field_option_border_radius_description": "Redondea las esquinas de las opciones.", "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_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": "Color de la etiqueta",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "¿Cómo puede mejorar la empresa su alineación de visión y estrategia?", "alignment_and_engagement_survey_question_4_headline": "¿Cómo puede mejorar la empresa su alineación de visión y estrategia?",
"alignment_and_engagement_survey_question_4_placeholder": "Escribe tu respuesta aquí...", "alignment_and_engagement_survey_question_4_placeholder": "Escribe tu respuesta aquí...",
"back": "Atrás", "back": "Atrás",
"block_1": "Bloque 1",
"block_10": "Bloque 10",
"block_2": "Bloque 2",
"block_3": "Bloque 3",
"block_4": "Bloque 4",
"block_5": "Bloque 5",
"block_6": "Bloque 6",
"block_7": "Bloque 7",
"block_8": "Bloque 8",
"block_9": "Bloque 9",
"book_interview": "Reservar entrevista", "book_interview": "Reservar entrevista",
"build_product_roadmap_description": "Identifica lo ÚNICO que tus usuarios desean más y constrúyelo.", "build_product_roadmap_description": "Identifica lo ÚNICO que tus usuarios desean más y constrúyelo.",
"build_product_roadmap_name": "Crear hoja de ruta del producto", "build_product_roadmap_name": "Crear hoja de ruta del producto",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Vaya, ¡lo sentimos! ¿Hay algo que podamos hacer para mejorar tu experiencia?", "csat_survey_question_3_headline": "Vaya, ¡lo sentimos! ¿Hay algo que podamos hacer para mejorar tu experiencia?",
"csat_survey_question_3_placeholder": "Escribe tu respuesta aquí...", "csat_survey_question_3_placeholder": "Escribe tu respuesta aquí...",
"cta_description": "Muestra información y anima a los usuarios a realizar una acción específica", "cta_description": "Muestra información y anima a los usuarios a realizar una acción específica",
"custom_survey_block_1_name": "Bloque 1",
"custom_survey_description": "Crea una encuesta sin plantilla.", "custom_survey_description": "Crea una encuesta sin plantilla.",
"custom_survey_name": "Empezar desde cero", "custom_survey_name": "Empezar desde cero",
"custom_survey_question_1_headline": "¿Qué te gustaría saber?", "custom_survey_question_1_headline": "¿Qué te gustaría saber?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "¡No, gracias!", "preview_survey_question_2_choice_2_label": "¡No, gracias!",
"preview_survey_question_2_headline": "¿Quieres estar al tanto?", "preview_survey_question_2_headline": "¿Quieres estar al tanto?",
"preview_survey_question_2_subheader": "Esta es una descripción de ejemplo.", "preview_survey_question_2_subheader": "Esta es una descripción de ejemplo.",
"preview_survey_question_open_text_headline": "¿Hay algo más que te gustaría compartir?",
"preview_survey_question_open_text_placeholder": "Escribe tu respuesta aquí...",
"preview_survey_question_open_text_subheader": "Tus comentarios nos ayudan a mejorar.",
"preview_survey_welcome_card_headline": "¡Bienvenido!", "preview_survey_welcome_card_headline": "¡Bienvenido!",
"prioritize_features_description": "Identifica las funciones que tus usuarios necesitan más y menos.", "prioritize_features_description": "Identifica las funciones que tus usuarios necesitan más y menos.",
"prioritize_features_name": "Priorizar funciones", "prioritize_features_name": "Priorizar funciones",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Copier", "copy": "Copier",
"copy_code": "Copier le code", "copy_code": "Copier le code",
"copy_link": "Copier le lien", "copy_link": "Copier le lien",
"count_attributes": "{value, plural, one {{value} attribut} other {{value} attributs}}", "count_attributes": "{count, plural, one {{count} attribut} other {{count} attributs}}",
"count_contacts": "{value, plural, one {# contact} other {# contacts} }", "count_contacts": "{count, plural, one {{count} contact} other {{count} contacts}}",
"count_responses": "{value, plural, other {# réponses}}", "count_members": "{count, plural, one {{count} membre} other {{count} membres}}",
"count_questions": "{count, plural, one {{count} question} other {{count} questions}}",
"count_responses": "{count, plural, other {# réponses}}",
"count_selections": "{count, plural, one {{count} sélection} other {{count} sélections}}",
"create_new_organization": "Créer une nouvelle organisation", "create_new_organization": "Créer une nouvelle organisation",
"create_segment": "Créer un segment", "create_segment": "Créer un segment",
"create_survey": "Créer un sondage", "create_survey": "Créer un sondage",
@@ -191,6 +194,7 @@
"days": "jours", "days": "jours",
"default": "Par défaut", "default": "Par défaut",
"delete": "Supprimer", "delete": "Supprimer",
"delete_what": "Supprimer {deleteWhat}",
"description": "Description", "description": "Description",
"dev_env": "Environnement de développement", "dev_env": "Environnement de développement",
"development": "Développement", "development": "Développement",
@@ -206,6 +210,8 @@
"download": "Télécharger", "download": "Télécharger",
"draft": "Brouillon", "draft": "Brouillon",
"duplicate": "Dupliquer", "duplicate": "Dupliquer",
"duplicate_copy": "(copie)",
"duplicate_copy_number": "(copie {copyNumber})",
"e_commerce": "E-commerce", "e_commerce": "E-commerce",
"edit": "Modifier", "edit": "Modifier",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Erreur", "error": "Erreur",
"error_component_description": "Cette ressource n'existe pas ou vous n'avez pas les droits nécessaires pour y accéder.", "error_component_description": "Cette ressource n'existe pas ou vous n'avez pas les droits nécessaires pour y accéder.",
"error_component_title": "Erreur de chargement des ressources", "error_component_title": "Erreur de chargement des ressources",
"error_loading_data": "Erreur lors du chargement des données",
"error_rate_limit_description": "Nombre maximal de demandes atteint. Veuillez réessayer plus tard.", "error_rate_limit_description": "Nombre maximal de demandes atteint. Veuillez réessayer plus tard.",
"error_rate_limit_title": "Limite de Taux Dépassée", "error_rate_limit_title": "Limite de Taux Dépassée",
"expand_rows": "Développer les lignes", "expand_rows": "Développer les lignes",
"failed_to_copy_to_clipboard": "Échec de la copie dans le presse-papiers", "failed_to_copy_to_clipboard": "Échec de la copie dans le presse-papiers",
"failed_to_load_organizations": "Échec du chargement des organisations", "failed_to_load_organizations": "Échec du chargement des organisations",
"failed_to_load_workspaces": "Échec du chargement des projets", "failed_to_load_workspaces": "Échec du chargement des projets",
"filter": "Filtre",
"finish": "Terminer", "finish": "Terminer",
"first_name": "Prénom",
"follow_these": "Suivez ceci", "follow_these": "Suivez ceci",
"formbricks_version": "Version de Formbricks", "formbricks_version": "Version de Formbricks",
"full_name": "Nom complet", "full_name": "Nom complet",
@@ -237,6 +246,7 @@
"hidden_field": "Champ caché", "hidden_field": "Champ caché",
"hidden_fields": "Champs cachés", "hidden_fields": "Champs cachés",
"hide_column": "Cacher la colonne", "hide_column": "Cacher la colonne",
"id": "ID",
"image": "Image", "image": "Image",
"images": "Images", "images": "Images",
"import": "Importer", "import": "Importer",
@@ -254,6 +264,7 @@
"key": "Clé", "key": "Clé",
"label": "Étiquette", "label": "Étiquette",
"language": "Langue", "language": "Langue",
"last_name": "Nom de famille",
"learn_more": "En savoir plus", "learn_more": "En savoir plus",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Claire", "light_overlay": "Claire",
@@ -268,7 +279,6 @@
"look_and_feel": "Apparence", "look_and_feel": "Apparence",
"manage": "Gérer", "manage": "Gérer",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Membre",
"members": "Membres", "members": "Membres",
"members_and_teams": "Membres & Équipes", "members_and_teams": "Membres & Équipes",
"membership_not_found": "Abonnement non trouvé", "membership_not_found": "Abonnement non trouvé",
@@ -280,6 +290,7 @@
"move_down": "Déplacer vers le bas", "move_down": "Déplacer vers le bas",
"move_up": "Déplacer vers le haut", "move_up": "Déplacer vers le haut",
"multiple_languages": "Plusieurs langues", "multiple_languages": "Plusieurs langues",
"my_product": "mon produit",
"name": "Nom", "name": "Nom",
"new": "Nouveau", "new": "Nouveau",
"new_version_available": "Formbricks {version} est là. Mettez à jour maintenant !", "new_version_available": "Formbricks {version} est là. Mettez à jour maintenant !",
@@ -375,8 +386,6 @@
"select_teams": "Sélectionner les équipes", "select_teams": "Sélectionner les équipes",
"selected": "Sélectionné", "selected": "Sélectionné",
"selected_questions": "Questions sélectionnées", "selected_questions": "Questions sélectionnées",
"selection": "Sélection",
"selections": "Sélections",
"send_test_email": "Envoyer un e-mail de test", "send_test_email": "Envoyer un e-mail de test",
"session_not_found": "Session non trouvée", "session_not_found": "Session non trouvée",
"settings": "Paramètres", "settings": "Paramètres",
@@ -428,6 +437,7 @@
"top_right": "En haut à droite", "top_right": "En haut à droite",
"try_again": "Réessayer", "try_again": "Réessayer",
"type": "Type", "type": "Type",
"unknown_survey": "Enquête inconnue",
"unlock_more_workspaces_with_a_higher_plan": "Débloquez plus de projets avec un forfait supérieur.", "unlock_more_workspaces_with_a_higher_plan": "Débloquez plus de projets avec un forfait supérieur.",
"update": "Mise à jour", "update": "Mise à jour",
"updated": "Mise à jour", "updated": "Mise à jour",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Actualiser les contacts", "contacts_table_refresh": "Actualiser les contacts",
"contacts_table_refresh_success": "Contacts rafraîchis avec succès", "contacts_table_refresh_success": "Contacts rafraîchis avec succès",
"create_attribute": "Créer un attribut", "create_attribute": "Créer un attribut",
"create_key": "Créer une clé",
"create_new_attribute": "Créer un nouvel attribut", "create_new_attribute": "Créer un nouvel attribut",
"create_new_attribute_description": "Créez un nouvel attribut à des fins de segmentation.", "create_new_attribute_description": "Créez un nouvel attribut à des fins de segmentation.",
"custom_attributes": "Attributs personnalisés", "custom_attributes": "Attributs personnalisés",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Affichages",
"edit_attribute": "Modifier l'attribut", "edit_attribute": "Modifier l'attribut",
"edit_attribute_description": "Mettez à jour l'étiquette et la description de cet attribut.", "edit_attribute_description": "Mettez à jour l'étiquette et la description de cet attribut.",
"edit_attribute_values": "Modifier les attributs", "edit_attribute_values": "Modifier les attributs",
@@ -667,6 +677,7 @@
"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_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_date_format": "Format de date invalide. Merci d'utiliser une date valide.",
"invalid_number_format": "Format de nombre invalide. Veuillez saisir un nombre valide.", "invalid_number_format": "Format de nombre invalide. Veuillez saisir un nombre valide.",
"no_activity_yet": "Aucune activité pour le moment",
"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_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_published_surveys": "Aucune enquête publiée",
"no_responses_found": "Aucune réponse trouvée", "no_responses_found": "Aucune réponse trouvée",
@@ -681,6 +692,8 @@
"select_a_survey": "Sélectionner une enquête", "select_a_survey": "Sélectionner une enquête",
"select_attribute": "Sélectionner un attribut", "select_attribute": "Sélectionner un attribut",
"select_attribute_key": "Sélectionner une clé d'attribut", "select_attribute_key": "Sélectionner une clé d'attribut",
"survey_viewed": "Enquête consultée",
"survey_viewed_at": "Consultée le",
"system_attributes": "Attributs système", "system_attributes": "Attributs système",
"unlock_contacts_description": "Gérer les contacts et envoyer des enquêtes ciblées", "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.", "unlock_contacts_title": "Débloquez des contacts avec un plan supérieur.",
@@ -752,7 +765,12 @@
"link_google_sheet": "Lien Google Sheet", "link_google_sheet": "Lien Google Sheet",
"link_new_sheet": "Lier une nouvelle feuille", "link_new_sheet": "Lier une nouvelle feuille",
"no_integrations_yet": "Vos intégrations Google Sheets apparaîtront ici dès que vous les ajouterez. ⏲️", "no_integrations_yet": "Vos intégrations Google Sheets apparaîtront ici dès que vous les ajouterez. ⏲️",
"spreadsheet_url": "URL de la feuille de calcul" "reconnect_button": "Reconnecter",
"reconnect_button_description": "Votre connexion Google Sheets a expiré. Veuillez vous reconnecter pour continuer à synchroniser les réponses. Vos liens de feuilles de calcul et données existants seront préservés.",
"reconnect_button_tooltip": "Reconnectez l'intégration pour actualiser votre accès. Vos liens de feuilles de calcul et données existants seront préservés.",
"spreadsheet_permission_error": "Vous n'avez pas la permission d'accéder à cette feuille de calcul. Veuillez vous assurer que la feuille de calcul est partagée avec votre compte Google et que vous disposez d'un accès en écriture.",
"spreadsheet_url": "URL de la feuille de calcul",
"token_expired_error": "Le jeton d'actualisation Google Sheets a expiré ou a été révoqué. Veuillez reconnecter l'intégration."
}, },
"include_created_at": "Inclure la date de création", "include_created_at": "Inclure la date de création",
"include_hidden_fields": "Inclure les champs cachés", "include_hidden_fields": "Inclure les champs cachés",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Salut {userName}", "email_customization_preview_email_heading": "Salut {userName}",
"email_customization_preview_email_text": "Cette est une prévisualisation d'e-mail pour vous montrer quel logo sera rendu dans les e-mails.", "email_customization_preview_email_text": "Cette est une prévisualisation d'e-mail pour vous montrer quel logo sera rendu dans les e-mails.",
"error_deleting_organization_please_try_again": "Erreur lors de la suppression de l'organisation. Veuillez réessayer.", "error_deleting_organization_please_try_again": "Erreur lors de la suppression de l'organisation. Veuillez réessayer.",
"from_your_organization": "de votre organisation", "from_your_organization": "{memberName} de votre organisation",
"invitation_sent_once_more": "Invitation envoyée une fois de plus.", "invitation_sent_once_more": "Invitation envoyée une fois de plus.",
"invite_deleted_successfully": "Invitation supprimée avec succès", "invite_deleted_successfully": "Invitation supprimée avec succès",
"invite_expires_on": "L'invitation expire le {date}", "invite_expires_on": "L'invitation expire le {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Ajouter un espace réservé à afficher s'il n'y a pas de valeur à rappeler.", "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_hidden_field_id": "Ajouter un champ caché ID",
"add_highlight_border": "Ajouter une bordure de surlignage", "add_highlight_border": "Ajouter une bordure de surlignage",
"add_highlight_border_description": "S'applique uniquement aux sondages intégrés au produit.",
"add_logic": "Ajouter de la logique", "add_logic": "Ajouter de la logique",
"add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"", "add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"",
"add_option": "Ajouter une option", "add_option": "Ajouter une option",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Ajuster le message \"Sondage fermé\"", "adjust_survey_closed_message": "Ajuster le message \"Sondage fermé\"",
"adjust_survey_closed_message_description": "Modifiez le message que les visiteurs voient lorsque l'enquête est fermée.", "adjust_survey_closed_message_description": "Modifiez le message que les visiteurs voient lorsque l'enquête est fermée.",
"adjust_the_theme_in_the": "Ajustez le thème dans le", "adjust_the_theme_in_the": "Ajustez le thème dans le",
"all_are_true": "toutes sont vraies",
"all_other_answers_will_continue_to": "Toutes les autres réponses continueront à", "all_other_answers_will_continue_to": "Toutes les autres réponses continueront à",
"allow_multi_select": "Autoriser la sélection multiple", "allow_multi_select": "Autoriser la sélection multiple",
"allow_multiple_files": "Autoriser plusieurs fichiers", "allow_multiple_files": "Autoriser plusieurs fichiers",
"allow_users_to_select_more_than_one_image": "Permettre aux utilisateurs de sélectionner plusieurs images", "allow_users_to_select_more_than_one_image": "Permettre aux utilisateurs de sélectionner plusieurs images",
"and_launch_surveys_in_your_website_or_app": "et lancez des enquêtes sur votre site web ou votre application.", "and_launch_surveys_in_your_website_or_app": "et lancez des enquêtes sur votre site web ou votre application.",
"animation": "Animation", "animation": "Animation",
"any_is_true": "au moins une est vraie",
"app_survey_description": "Intégrez une enquête dans votre application web ou votre site web pour collecter des réponses.", "app_survey_description": "Intégrez une enquête dans votre application web ou votre site web pour collecter des réponses.",
"assign": "Attribuer =", "assign": "Attribuer =",
"audience": "Public", "audience": "Public",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "\"Suivi mis à jour et sera enregistré une fois que vous sauvegarderez le sondage.\"", "follow_ups_modal_updated_successfull_toast": "\"Suivi mis à jour et sera enregistré une fois que vous sauvegarderez le sondage.\"",
"follow_ups_new": "Nouveau suivi", "follow_ups_new": "Nouveau suivi",
"follow_ups_upgrade_button_text": "Passez à la version supérieure pour activer les relances", "follow_ups_upgrade_button_text": "Passez à la version supérieure pour activer les relances",
"form_styling": "Style de formulaire",
"formbricks_sdk_is_not_connected": "Le SDK Formbricks n'est pas connecté", "formbricks_sdk_is_not_connected": "Le SDK Formbricks n'est pas connecté",
"four_points": "4 points", "four_points": "4 points",
"heading": "En-tête", "heading": "En-tête",
@@ -1520,7 +1540,7 @@
"option_idx": "Option {choiceIndex}", "option_idx": "Option {choiceIndex}",
"option_used_in_logic_error": "Cette option est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.", "option_used_in_logic_error": "Cette option est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.",
"optional": "Optionnel", "optional": "Optionnel",
"options": "Options", "options": "Options*",
"options_used_in_logic_bulk_error": "Les options suivantes sont utilisées dans la logique: {questionIndexes}. Veuillez d'abord les supprimer de la logique.", "options_used_in_logic_bulk_error": "Les options suivantes sont utilisées dans la logique: {questionIndexes}. Veuillez d'abord les supprimer de la logique.",
"override_theme_with_individual_styles_for_this_survey": "Override the theme with individual styles for this survey.", "override_theme_with_individual_styles_for_this_survey": "Override the theme with individual styles for this survey.",
"overwrite_global_waiting_time": "Définir une période de refroidissement personnalisée", "overwrite_global_waiting_time": "Définir une période de refroidissement personnalisée",
@@ -1545,6 +1565,7 @@
"question_deleted": "Question supprimée.", "question_deleted": "Question supprimée.",
"question_duplicated": "Question dupliquée.", "question_duplicated": "Question dupliquée.",
"question_id_updated": "ID de la question mis à jour", "question_id_updated": "ID de la question mis à jour",
"question_number": "Question {number}",
"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_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_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}”",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Limites de réponse, redirections et plus.", "response_limits_redirections_and_more": "Limites de réponse, redirections et plus.",
"response_options": "Options de réponse", "response_options": "Options de réponse",
"roundness": "Rondeur", "roundness": "Rondeur",
"roundness_description": "Contrôle l'arrondi des coins de la carte.", "roundness_description": "Contrôle l'arrondi des coins.",
"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.", "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", "rows": "Lignes",
"save_and_close": "Enregistrer et fermer", "save_and_close": "Enregistrer et fermer",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Cette enquête gratuite et open-source a été fermé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", "survey_display_settings": "Paramètres d'affichage de l'enquête",
"survey_placement": "Placement de l'enquête", "survey_placement": "Placement de l'enquête",
"survey_styling": "Style de formulaire",
"survey_trigger": "Déclencheur d'enquête", "survey_trigger": "Déclencheur d'enquête",
"switch_multi_language_on_to_get_started": "Activez le mode multilingue pour commencer 👉", "switch_multi_language_on_to_get_started": "Activez le mode multilingue pour commencer 👉",
"target_block_not_found": "Bloc cible non trouvé", "target_block_not_found": "Bloc cible non trouvé",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Période de refroidissement (entre les sondages)", "waiting_time_across_surveys": "Période de refroidissement (entre les sondages)",
"waiting_time_across_surveys_description": "Pour éviter la fatigue liée aux sondages, choisissez comment ce sondage interagit avec la période de refroidissement globale de l'espace de travail.", "waiting_time_across_surveys_description": "Pour éviter la fatigue liée aux sondages, choisissez comment ce sondage interagit avec la période de refroidissement globale de l'espace de travail.",
"welcome_message": "Message de bienvenue", "welcome_message": "Message de bienvenue",
"when": "Quand",
"without_a_filter_all_of_your_users_can_be_surveyed": "Sans filtre, tous vos utilisateurs peuvent être sondés.", "without_a_filter_all_of_your_users_can_be_surveyed": "Sans filtre, tous vos utilisateurs peuvent être sondés.",
"you_have_not_created_a_segment_yet": "Tu n'as pas encore créé de segment.", "you_have_not_created_a_segment_yet": "Tu n'as pas encore créé de segment.",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Vous devez avoir deux langues ou plus configurées dans votre espace de travail pour travailler avec les traductions.",
"your_description_here_recall_information_with": "Votre description ici. Rappelez-vous des informations avec @", "your_description_here_recall_information_with": "Votre description ici. Rappelez-vous des informations avec @",
"your_question_here_recall_information_with": "Votre question ici. Rappelez-vous des informations avec @", "your_question_here_recall_information_with": "Votre question ici. Rappelez-vous des informations avec @",
"your_web_app": "Votre application web", "your_web_app": "Votre application web",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Réponses filtrées (Excel)", "filtered_responses_excel": "Réponses filtrées (Excel)",
"generating_qr_code": "Génération du code QR", "generating_qr_code": "Génération du code QR",
"impressions": "Impressions", "impressions": "Impressions",
"impressions_identified_only": "Affichage uniquement des impressions des contacts identifiés",
"impressions_tooltip": "Nombre de fois que l'enquête a été consultée.", "impressions_tooltip": "Nombre de fois que l'enquête a été consultée.",
"in_app": { "in_app": {
"connection_description": "Le sondage sera affiché aux utilisateurs de votre site web, qui correspondent aux critères listés ci-dessous", "connection_description": "Le sondage sera affiché aux utilisateurs de votre site web, qui correspondent aux critères listés ci-dessous",
@@ -1989,6 +2012,7 @@
"last_quarter": "dernier trimestre", "last_quarter": "dernier trimestre",
"last_year": "l'année dernière", "last_year": "l'année dernière",
"limit": "Limite", "limit": "Limite",
"no_identified_impressions": "Aucune impression des contacts identifiés",
"no_responses_found": "Aucune réponse trouvée", "no_responses_found": "Aucune réponse trouvée",
"other_values_found": "D'autres valeurs trouvées", "other_values_found": "D'autres valeurs trouvées",
"overall": "Globalement", "overall": "Globalement",
@@ -2011,6 +2035,7 @@
"starts": "Commence", "starts": "Commence",
"starts_tooltip": "Nombre de fois que l'enquête a été commencée.", "starts_tooltip": "Nombre de fois que l'enquête a été commencée.",
"survey_reset_successfully": "Réinitialisation du sondage réussie ! {responseCount} réponses et {displayCount} affichages ont été supprimés.", "survey_reset_successfully": "Réinitialisation du sondage réussie ! {responseCount} réponses et {displayCount} affichages ont été supprimés.",
"survey_results": "Résultats de {surveyName}",
"this_month": "Ce mois-ci", "this_month": "Ce mois-ci",
"this_quarter": "Ce trimestre", "this_quarter": "Ce trimestre",
"this_year": "Cette année", "this_year": "Cette année",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Colore la partie remplie de la barre.", "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_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_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_height_description": "Contrôle la hauteur min. du champ de saisie.",
"advanced_styling_field_input_padding_x_description": "Ajoute de l'espace à gauche et à droite.", "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_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_placeholder_opacity_description": "Atténue le texte d'indication du placeholder.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colore le texte saisi dans les champs.", "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": "Arrière-plan",
"advanced_styling_field_option_bg_description": "Remplit les éléments d'option.", "advanced_styling_field_option_bg_description": "Remplit les éléments d'option.",
"advanced_styling_field_option_border": "Couleur de bordure",
"advanced_styling_field_option_border_description": "Contours des options de boutons radio et de cases à cocher.",
"advanced_styling_field_option_border_radius_description": "Arrondit les coins des options.", "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_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": "Couleur de l'étiquette",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Comment l'entreprise peut-elle améliorer l'alignement de sa vision et de sa stratégie ?", "alignment_and_engagement_survey_question_4_headline": "Comment l'entreprise peut-elle améliorer l'alignement de sa vision et de sa stratégie ?",
"alignment_and_engagement_survey_question_4_placeholder": "Entrez votre réponse ici...", "alignment_and_engagement_survey_question_4_placeholder": "Entrez votre réponse ici...",
"back": "Retour", "back": "Retour",
"block_1": "Bloc 1",
"block_10": "Bloc 10",
"block_2": "Bloc 2",
"block_3": "Bloc 3",
"block_4": "Bloc 4",
"block_5": "Bloc 5",
"block_6": "Bloc 6",
"block_7": "Bloc 7",
"block_8": "Bloc 8",
"block_9": "Bloc 9",
"book_interview": "Réserver un entretien", "book_interview": "Réserver un entretien",
"build_product_roadmap_description": "Identifiez la chose UNIQUE que vos utilisateurs désirent le plus et construisez-la.", "build_product_roadmap_description": "Identifiez la chose UNIQUE que vos utilisateurs désirent le plus et construisez-la.",
"build_product_roadmap_name": "Élaborer la feuille de route du produit", "build_product_roadmap_name": "Élaborer la feuille de route du produit",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Ah, désolé ! Y a-t-il quelque chose que nous puissions faire pour améliorer votre expérience ?", "csat_survey_question_3_headline": "Ah, désolé ! Y a-t-il quelque chose que nous puissions faire pour améliorer votre expérience ?",
"csat_survey_question_3_placeholder": "Entrez votre réponse ici...", "csat_survey_question_3_placeholder": "Entrez votre réponse ici...",
"cta_description": "Afficher des informations et inciter les utilisateurs à effectuer une action spécifique", "cta_description": "Afficher des informations et inciter les utilisateurs à effectuer une action spécifique",
"custom_survey_block_1_name": "Bloc 1",
"custom_survey_description": "Créez une enquête sans utiliser de modèle.", "custom_survey_description": "Créez une enquête sans utiliser de modèle.",
"custom_survey_name": "Tout créer moi-même", "custom_survey_name": "Tout créer moi-même",
"custom_survey_question_1_headline": "Que voudriez-vous savoir ?", "custom_survey_question_1_headline": "Que voudriez-vous savoir ?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Non, merci !", "preview_survey_question_2_choice_2_label": "Non, merci !",
"preview_survey_question_2_headline": "Souhaitez-vous être informé ?", "preview_survey_question_2_headline": "Souhaitez-vous être informé ?",
"preview_survey_question_2_subheader": "Ceci est un exemple de description.", "preview_survey_question_2_subheader": "Ceci est un exemple de description.",
"preview_survey_question_open_text_headline": "Autre chose que vous aimeriez partager?",
"preview_survey_question_open_text_placeholder": "Entrez votre réponse ici...",
"preview_survey_question_open_text_subheader": "Vos commentaires nous aident à nous améliorer.",
"preview_survey_welcome_card_headline": "Bienvenue !", "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_description": "Identifiez les fonctionnalités dont vos utilisateurs ont le plus et le moins besoin.",
"prioritize_features_name": "Prioriser les fonctionnalités", "prioritize_features_name": "Prioriser les fonctionnalités",
+57 -18
View File
@@ -175,9 +175,12 @@
"copy": "Másolás", "copy": "Másolás",
"copy_code": "Kód másolása", "copy_code": "Kód másolása",
"copy_link": "Hivatkozás másolása", "copy_link": "Hivatkozás másolása",
"count_attributes": "{value, plural, one {{value} attribútum} other {{value} attribútum}}", "count_attributes": "{count, plural, one {{count} attribútum} other {{count} attribútum}}",
"count_contacts": "{value, plural, one {{value} partner} other {{value} partner}}", "count_contacts": "{count, plural, one {{count} partner} other {{count} partner}}",
"count_responses": "{value, plural, one {{value} válasz} other {{value} válasz}}", "count_members": "{count, plural, one {{count} tag} other {{count} tag}}",
"count_questions": "{count, plural, one {{count} kérdés} other {{count} kérdés}}",
"count_responses": "{count, plural, one {{count} válasz} other {{count} válasz}}",
"count_selections": "{count, plural, one {{count} kijelölés} other {{count} kijelölés}}",
"create_new_organization": "Új szervezet létrehozása", "create_new_organization": "Új szervezet létrehozása",
"create_segment": "Szakasz létrehozása", "create_segment": "Szakasz létrehozása",
"create_survey": "Kérdőív létrehozása", "create_survey": "Kérdőív létrehozása",
@@ -191,6 +194,7 @@
"days": "napok", "days": "napok",
"default": "Alapértelmezett", "default": "Alapértelmezett",
"delete": "Törlés", "delete": "Törlés",
"delete_what": "{deleteWhat} törlése",
"description": "Leírás", "description": "Leírás",
"dev_env": "Fejlesztői környezet", "dev_env": "Fejlesztői környezet",
"development": "Fejlesztés", "development": "Fejlesztés",
@@ -206,6 +210,8 @@
"download": "Letöltés", "download": "Letöltés",
"draft": "Piszkozat", "draft": "Piszkozat",
"duplicate": "Kettőzés", "duplicate": "Kettőzés",
"duplicate_copy": "(másolat)",
"duplicate_copy_number": "({copyNumber}. másolat)",
"e_commerce": "E-kereskedelem", "e_commerce": "E-kereskedelem",
"edit": "Szerkesztés", "edit": "Szerkesztés",
"email": "E-mail", "email": "E-mail",
@@ -218,13 +224,16 @@
"error": "Hiba", "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_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_component_title": "Hiba az erőforrások betöltésekor",
"error_loading_data": "Hiba az adatok betöltése során",
"error_rate_limit_description": "A kérések legnagyobb száma elérve. Próbálja meg később újra.", "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", "error_rate_limit_title": "A sebességkorlát elérve",
"expand_rows": "Sorok kinyitása", "expand_rows": "Sorok kinyitása",
"failed_to_copy_to_clipboard": "Nem sikerült másolni a vágólapra", "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_organizations": "Nem sikerült betölteni a szervezeteket",
"failed_to_load_workspaces": "Nem sikerült a munkaterületek betöltése", "failed_to_load_workspaces": "Nem sikerült a munkaterületek betöltése",
"filter": "Szűrő",
"finish": "Befejezés", "finish": "Befejezés",
"first_name": "Keresztnév",
"follow_these": "Ezek követése", "follow_these": "Ezek követése",
"formbricks_version": "Formbricks verziója", "formbricks_version": "Formbricks verziója",
"full_name": "Teljes név", "full_name": "Teljes név",
@@ -237,6 +246,7 @@
"hidden_field": "Rejtett mező", "hidden_field": "Rejtett mező",
"hidden_fields": "Rejtett mezők", "hidden_fields": "Rejtett mezők",
"hide_column": "Oszlop elrejtése", "hide_column": "Oszlop elrejtése",
"id": "ID",
"image": "Kép", "image": "Kép",
"images": "Képek", "images": "Képek",
"import": "Importálás", "import": "Importálás",
@@ -254,6 +264,7 @@
"key": "Kulcs", "key": "Kulcs",
"label": "Címke", "label": "Címke",
"language": "Nyelv", "language": "Nyelv",
"last_name": "Vezetéknév",
"learn_more": "Tudjon meg többet", "learn_more": "Tudjon meg többet",
"license_expired": "A licenc lejárt", "license_expired": "A licenc lejárt",
"light_overlay": "Világos rávetítés", "light_overlay": "Világos rávetítés",
@@ -268,7 +279,6 @@
"look_and_feel": "Megjelenés", "look_and_feel": "Megjelenés",
"manage": "Kezelés", "manage": "Kezelés",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Tag",
"members": "Tagok", "members": "Tagok",
"members_and_teams": "Tagok és csapatok", "members_and_teams": "Tagok és csapatok",
"membership_not_found": "A tagság nem található", "membership_not_found": "A tagság nem található",
@@ -280,6 +290,7 @@
"move_down": "Mozgatás le", "move_down": "Mozgatás le",
"move_up": "Mozgatás fel", "move_up": "Mozgatás fel",
"multiple_languages": "Több nyelv", "multiple_languages": "Több nyelv",
"my_product": "saját termék",
"name": "Név", "name": "Név",
"new": "Új", "new": "Új",
"new_version_available": "A Formbricks {version} megérkezett. Frissítsen most!", "new_version_available": "A Formbricks {version} megérkezett. Frissítsen most!",
@@ -375,8 +386,6 @@
"select_teams": "Csapatok kiválasztása", "select_teams": "Csapatok kiválasztása",
"selected": "Kiválasztva", "selected": "Kiválasztva",
"selected_questions": "Kiválasztott kérdések", "selected_questions": "Kiválasztott kérdések",
"selection": "Kiválasztás",
"selections": "Kiválasztások",
"send_test_email": "Teszt e-mail küldése", "send_test_email": "Teszt e-mail küldése",
"session_not_found": "A munkamenet nem található", "session_not_found": "A munkamenet nem található",
"settings": "Beállítások", "settings": "Beállítások",
@@ -428,6 +437,7 @@
"top_right": "Jobbra fent", "top_right": "Jobbra fent",
"try_again": "Próbálja újra", "try_again": "Próbálja újra",
"type": "Típus", "type": "Típus",
"unknown_survey": "Ismeretlen kérdőív",
"unlock_more_workspaces_with_a_higher_plan": "Több munkaterület feloldása egy magasabb csomaggal.", "unlock_more_workspaces_with_a_higher_plan": "Több munkaterület feloldása egy magasabb csomaggal.",
"update": "Frissítés", "update": "Frissítés",
"updated": "Frissítve", "updated": "Frissítve",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Partnerek frissítése", "contacts_table_refresh": "Partnerek frissítése",
"contacts_table_refresh_success": "A partnerek sikeresen frissítve", "contacts_table_refresh_success": "A partnerek sikeresen frissítve",
"create_attribute": "Attribútum létrehozása", "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": "Új attribútum létrehozása",
"create_new_attribute_description": "Új attribútum létrehozása szakaszolási célokhoz.", "create_new_attribute_description": "Új attribútum létrehozása szakaszolási célokhoz.",
"custom_attributes": "Egyéni attribútumok", "custom_attributes": "Egyéni attribútumok",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Megjelenítések",
"edit_attribute": "Attribútum szerkesztése", "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_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": "Attribútumok szerkesztése",
@@ -667,6 +677,7 @@
"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_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_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.", "invalid_number_format": "Érvénytelen számformátum. Kérlek, adj meg egy érvényes számot.",
"no_activity_yet": "Még nincs aktivitás",
"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_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_published_surveys": "Nincsenek közzétett kérdőívek",
"no_responses_found": "Nem találhatók válaszok", "no_responses_found": "Nem találhatók válaszok",
@@ -681,6 +692,8 @@
"select_a_survey": "Kérdőív kiválasztása", "select_a_survey": "Kérdőív kiválasztása",
"select_attribute": "Attribútum kiválasztása", "select_attribute": "Attribútum kiválasztása",
"select_attribute_key": "Attribútum kulcs kiválasztása", "select_attribute_key": "Attribútum kulcs kiválasztása",
"survey_viewed": "Kérdőív megtekintve",
"survey_viewed_at": "Megtekintve",
"system_attributes": "Rendszer attribútumok", "system_attributes": "Rendszer attribútumok",
"unlock_contacts_description": "Partnerek kezelése és célzott kérdőívek kiküldése", "unlock_contacts_description": "Partnerek kezelése és célzott kérdőívek kiküldése",
"unlock_contacts_title": "Partnerek feloldása egy magasabb csomaggal", "unlock_contacts_title": "Partnerek feloldása egy magasabb csomaggal",
@@ -752,7 +765,12 @@
"link_google_sheet": "Google Táblázatok összekapcsolása", "link_google_sheet": "Google Táblázatok összekapcsolása",
"link_new_sheet": "Új táblázat ö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. ⏲️", "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" "reconnect_button": "Újrakapcsolódás",
"reconnect_button_description": "A Google Táblázatok kapcsolata lejárt. Kérjük, csatlakozzon újra a válaszok szinkronizálásának folytatásához. A meglévő táblázathivatkozások és adatok megmaradnak.",
"reconnect_button_tooltip": "Csatlakoztassa újra az integrációt a hozzáférés frissítéséhez. A meglévő táblázathivatkozások és adatok megmaradnak.",
"spreadsheet_permission_error": "Nincs jogosultsága a táblázat eléréséhez. Kérjük, győződjön meg arról, hogy a táblázat meg van osztva a Google-fiókjával, és írási jogosultsággal rendelkezik a táblázathoz.",
"spreadsheet_url": "Táblázat URL-e",
"token_expired_error": "A Google Táblázatok frissítési tokenje lejárt vagy visszavonásra került. Kérjük, csatlakoztassa újra az integrációt."
}, },
"include_created_at": "Létrehozva felvétele", "include_created_at": "Létrehozva felvétele",
"include_hidden_fields": "Rejtett mezők felvétele", "include_hidden_fields": "Rejtett mezők felvétele",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Helló {userName}", "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.", "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.", "error_deleting_organization_please_try_again": "Hiba a szervezet törlésekor. Próbálja meg újra.",
"from_your_organization": "a szervezetétől", "from_your_organization": "{memberName} a szervezetből",
"invitation_sent_once_more": "A meghívó még egyszer elküldve.", "invitation_sent_once_more": "A meghívó még egyszer elküldve.",
"invite_deleted_successfully": "A meghívó sikeresen törölve", "invite_deleted_successfully": "A meghívó sikeresen törölve",
"invite_expires_on": "A meghívó lejár ekkor: {date}", "invite_expires_on": "A meghívó lejár ekkor: {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Helykitöltő hozzáadása annak megjelenítéshez, hogy nincs visszahívandó érték.", "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_hidden_field_id": "Rejtett mezőazonosító hozzáadása",
"add_highlight_border": "Kiemelési szegély hozzáadása", "add_highlight_border": "Kiemelési szegély hozzáadása",
"add_highlight_border_description": "Csak a terméken belüli felmérésekre vonatkozik.",
"add_logic": "Logika 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_none_of_the_above": "„A fentiek közül egyik sem” hozzáadása",
"add_option": "Lehetőség hozzáadása", "add_option": "Lehetőség hozzáadása",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "A „Kérdőív lezárva” üzenet módosítása", "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_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:", "adjust_the_theme_in_the": "A téma beállítása ebben:",
"all_are_true": "az összes igaz",
"all_other_answers_will_continue_to": "Az összes többi válasz továbbra is", "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_multi_select": "Több választás engedélyezése",
"allow_multiple_files": "Több fájl 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", "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.", "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ó", "animation": "Animáció",
"any_is_true": "bármelyik igaz",
"app_survey_description": "Egy kérdőív beágyazása a webalkalmazásába vagy webhelyére a válaszok gyűjtéséhez.", "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", "assign": "= hozzárendelése",
"audience": "Közönség", "audience": "Közönség",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "A követés frissítve, és akkor lesz elmentve, ha elmenti 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_new": "Új követés",
"follow_ups_upgrade_button_text": "Magasabb csomagra váltás a követések engedélyezéséhez", "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", "formbricks_sdk_is_not_connected": "A Formbricks SDK nincs csatlakoztatva",
"four_points": "4 pont", "four_points": "4 pont",
"heading": "Címsor", "heading": "Címsor",
@@ -1520,7 +1540,7 @@
"option_idx": "{choiceIndex}. lehetőség", "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.", "option_used_in_logic_error": "Ez a lehetőség használatban van a(z) {questionIndex}. kérdés logikájában. Először távolítsa el a logikából.",
"optional": "Választható", "optional": "Választható",
"options": "Beállítások", "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.", "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.", "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": "Egyéni várakozási időszak beállítása",
@@ -1545,6 +1565,7 @@
"question_deleted": "Kérdés törölve.", "question_deleted": "Kérdés törölve.",
"question_duplicated": "Kérdés megkettőzve.", "question_duplicated": "Kérdés megkettőzve.",
"question_id_updated": "Kérdésazonosító frissítve", "question_id_updated": "Kérdésazonosító frissítve",
"question_number": "{number}. kérdés",
"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_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_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_quota": "Ez a kérdés használatban van a(z) „{quotaName}” kvótában",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Válaszkorlátok, átirányítások és egyebek.", "response_limits_redirections_and_more": "Válaszkorlátok, átirányítások és egyebek.",
"response_options": "Válasz beállításai", "response_options": "Válasz beállításai",
"roundness": "Kerekesség", "roundness": "Kerekesség",
"roundness_description": "Annak vezérlése, hogy a kártya sarkai mennyire legyenek lekerekítve.", "roundness_description": "Szabályozza a sarkok lekerekítését.",
"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.", "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", "rows": "Sorok",
"save_and_close": "Mentés és bezárás", "save_and_close": "Mentés és bezárás",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Ez a szabad és nyílt forráskódú kérdőív le lett zárva", "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_display_settings": "Kérdőív megjelenítésének beállításai",
"survey_placement": "Kérdőív elhelyezése", "survey_placement": "Kérdőív elhelyezése",
"survey_styling": "Űrlap stílusának beállítása",
"survey_trigger": "Kérdőív aktiválója", "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 👉", "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ó", "target_block_not_found": "A célblokk nem található",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Várakozási időszak (kérdőívek között)", "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.", "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", "welcome_message": "Üdvözlő üzenet",
"when": "Amikor",
"without_a_filter_all_of_your_users_can_be_surveyed": "Szűrő nélkül az összes felhasználója megkérdezhető.", "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_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_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_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", "your_web_app": "Saját webalkalmazás",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Szűrt válaszok (Excel)", "filtered_responses_excel": "Szűrt válaszok (Excel)",
"generating_qr_code": "QR-kód előállítása", "generating_qr_code": "QR-kód előállítása",
"impressions": "Benyomások", "impressions": "Benyomások",
"impressions_identified_only": "Csak az azonosított kapcsolatok megjelenítései láthatók",
"impressions_tooltip": "A kérdőív megtekintési alkalmainak száma.", "impressions_tooltip": "A kérdőív megtekintési alkalmainak száma.",
"in_app": { "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_description": "A kérdőív a webhelye azon felhasználóinak lesz megjelenítve, akik megfelelnek az alább felsorolt feltételeknek",
@@ -1989,6 +2012,7 @@
"last_quarter": "Elmúlt negyedév", "last_quarter": "Elmúlt negyedév",
"last_year": "Elmúlt év", "last_year": "Elmúlt év",
"limit": "Korlát", "limit": "Korlát",
"no_identified_impressions": "Nincsenek megjelenítések azonosított kapcsolatoktól",
"no_responses_found": "Nem találhatók válaszok", "no_responses_found": "Nem találhatók válaszok",
"other_values_found": "Más értékek találhatók", "other_values_found": "Más értékek találhatók",
"overall": "Összesen", "overall": "Összesen",
@@ -2011,6 +2035,7 @@
"starts": "Elkezdések", "starts": "Elkezdések",
"starts_tooltip": "A kérdőív elkezdési alkalmainak száma.", "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.", "survey_reset_successfully": "A kérdőív sikeresen visszaállítva. {responseCount} válasz és {displayCount} megjelenítés lett törölve.",
"survey_results": "{surveyName} eredményei",
"this_month": "Ez a hónap", "this_month": "Ez a hónap",
"this_quarter": "Ez a negyedév", "this_quarter": "Ez a negyedév",
"this_year": "Ez az év", "this_year": "Ez az év",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Kiszínezi a sáv kitöltött részét.", "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_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_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_height_description": "Szabályozza a beviteli mező minimális magasságát.",
"advanced_styling_field_input_padding_x_description": "Térközt ad hozzá balra és jobbra.", "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_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_placeholder_opacity_description": "Elhalványítja a helykitöltő súgószöveget.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Kiszínezi a beviteli mezőkbe beírt szöveget.", "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": "Háttér",
"advanced_styling_field_option_bg_description": "Kitölti a választási lehetőség elemeit.", "advanced_styling_field_option_bg_description": "Kitölti a választási lehetőség elemeit.",
"advanced_styling_field_option_border": "Szegély színe",
"advanced_styling_field_option_border_description": "A rádiógomb és jelölőnégyzet opciók körvonalát határozza meg.",
"advanced_styling_field_option_border_radius_description": "Lekerekíti a választási lehetőség sarkait.", "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_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": "Címke színe",
@@ -2376,6 +2403,16 @@
"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_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…", "alignment_and_engagement_survey_question_4_placeholder": "Írja be ide a válaszát…",
"back": "Vissza", "back": "Vissza",
"block_1": "1. blokk",
"block_10": "10. blokk",
"block_2": "2. blokk",
"block_3": "3. blokk",
"block_4": "4. blokk",
"block_5": "5. blokk",
"block_6": "6. blokk",
"block_7": "7. blokk",
"block_8": "8. blokk",
"block_9": "9. blokk",
"book_interview": "Interjú foglalása", "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_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_name": "Termékútiterv összeállítása",
@@ -2439,7 +2476,7 @@
"career_development_survey_question_6_choice_2": "Igazgató", "career_development_survey_question_6_choice_2": "Igazgató",
"career_development_survey_question_6_choice_3": "Vezető 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_4": "Alelnök",
"career_development_survey_question_6_choice_5": "Igazgató", "career_development_survey_question_6_choice_5": "Ügyvezető",
"career_development_survey_question_6_choice_6": "Egyéb", "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_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:", "career_development_survey_question_6_subheader": "Válassza ki a következő lehetőségek egyikét:",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Jaj, bocsánat! Tehetünk valamit, amivel javíthatnánk az élményén?", "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…", "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", "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_description": "Kérdőív létrehozása sablon nélkül.",
"custom_survey_name": "Kezdés a semmiből", "custom_survey_name": "Kezdés a semmiből",
"custom_survey_question_1_headline": "Mit szeretne tudni?", "custom_survey_question_1_headline": "Mit szeretne tudni?",
@@ -2952,7 +2988,7 @@
"onboarding_segmentation": "Beléptetés szakaszolása", "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_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_1": "Alapító",
"onboarding_segmentation_question_1_choice_2": "Igazgató", "onboarding_segmentation_question_1_choice_2": "Ügyvezető",
"onboarding_segmentation_question_1_choice_3": "Termékmenedzser", "onboarding_segmentation_question_1_choice_3": "Termékmenedzser",
"onboarding_segmentation_question_1_choice_4": "Terméktulajdonos", "onboarding_segmentation_question_1_choice_4": "Terméktulajdonos",
"onboarding_segmentation_question_1_choice_5": "Szoftvermérnök", "onboarding_segmentation_question_1_choice_5": "Szoftvermérnök",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Nem, köszönöm!", "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_headline": "Szeretne naprakész maradni?",
"preview_survey_question_2_subheader": "Ez egy példa a leírásra.", "preview_survey_question_2_subheader": "Ez egy példa a leírásra.",
"preview_survey_question_open_text_headline": "Bármi egyéb, amit meg szeretne osztani?",
"preview_survey_question_open_text_placeholder": "Írja be ide a válaszát…",
"preview_survey_question_open_text_subheader": "A visszajelzése segít nekünk a fejlődésben.",
"preview_survey_welcome_card_headline": "Üdvözöljük!", "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_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_name": "Funkciók rangsorolása",
@@ -3020,7 +3059,7 @@
"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_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_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_1": "Alapító",
"product_market_fit_superhuman_question_3_choice_2": "Igazgató", "product_market_fit_superhuman_question_3_choice_2": "Ügyvezető",
"product_market_fit_superhuman_question_3_choice_3": "Termékmenedzser", "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_4": "Terméktulajdonos",
"product_market_fit_superhuman_question_3_choice_5": "Szoftvermérnök", "product_market_fit_superhuman_question_3_choice_5": "Szoftvermérnök",
+53 -14
View File
@@ -175,9 +175,12 @@
"copy": "コピー", "copy": "コピー",
"copy_code": "コードをコピー", "copy_code": "コードをコピー",
"copy_link": "リンクをコピー", "copy_link": "リンクをコピー",
"count_attributes": "{value, plural, other {{value}個の属性}}", "count_attributes": "{count, plural, other {{count}個の属性}}",
"count_contacts": "{count, plural, other {# 件の連絡先}}", "count_contacts": "{count, plural, other {{count}件の連絡先}}",
"count_members": "{count, plural, other {{count}人のメンバー}}",
"count_questions": "{count, plural, other {# 件の質問}}",
"count_responses": "{count, plural, other {# 件の回答}}", "count_responses": "{count, plural, other {# 件の回答}}",
"count_selections": "{count, plural, other {{count}件選択中}}",
"create_new_organization": "新しい組織を作成", "create_new_organization": "新しい組織を作成",
"create_segment": "セグメントを作成", "create_segment": "セグメントを作成",
"create_survey": "フォームを作成", "create_survey": "フォームを作成",
@@ -191,6 +194,7 @@
"days": "日", "days": "日",
"default": "デフォルト", "default": "デフォルト",
"delete": "削除", "delete": "削除",
"delete_what": "{deleteWhat}を削除",
"description": "説明", "description": "説明",
"dev_env": "開発環境", "dev_env": "開発環境",
"development": "開発", "development": "開発",
@@ -206,6 +210,8 @@
"download": "ダウンロード", "download": "ダウンロード",
"draft": "下書き", "draft": "下書き",
"duplicate": "複製", "duplicate": "複製",
"duplicate_copy": "(コピー)",
"duplicate_copy_number": "(コピー {copyNumber})",
"e_commerce": "Eコマース", "e_commerce": "Eコマース",
"edit": "編集", "edit": "編集",
"email": "メールアドレス", "email": "メールアドレス",
@@ -218,13 +224,16 @@
"error": "エラー", "error": "エラー",
"error_component_description": "この リソース は 存在 しない か、アクセス する ための 必要な 権限 が ありません。", "error_component_description": "この リソース は 存在 しない か、アクセス する ための 必要な 権限 が ありません。",
"error_component_title": "リソース の 読み込み エラー", "error_component_title": "リソース の 読み込み エラー",
"error_loading_data": "データの読み込みエラー",
"error_rate_limit_description": "リクエストの最大数に達しました。後でもう一度試してください。", "error_rate_limit_description": "リクエストの最大数に達しました。後でもう一度試してください。",
"error_rate_limit_title": "レート制限を超えました", "error_rate_limit_title": "レート制限を超えました",
"expand_rows": "行を展開", "expand_rows": "行を展開",
"failed_to_copy_to_clipboard": "クリップボードへのコピーに失敗しました", "failed_to_copy_to_clipboard": "クリップボードへのコピーに失敗しました",
"failed_to_load_organizations": "組織の読み込みに失敗しました", "failed_to_load_organizations": "組織の読み込みに失敗しました",
"failed_to_load_workspaces": "ワークスペースの読み込みに失敗しました", "failed_to_load_workspaces": "ワークスペースの読み込みに失敗しました",
"filter": "フィルター",
"finish": "完了", "finish": "完了",
"first_name": "名",
"follow_these": "こちらの手順に従って", "follow_these": "こちらの手順に従って",
"formbricks_version": "Formbricksバージョン", "formbricks_version": "Formbricksバージョン",
"full_name": "氏名", "full_name": "氏名",
@@ -237,6 +246,7 @@
"hidden_field": "非表示フィールド", "hidden_field": "非表示フィールド",
"hidden_fields": "非表示フィールド", "hidden_fields": "非表示フィールド",
"hide_column": "列を非表示", "hide_column": "列を非表示",
"id": "ID",
"image": "画像", "image": "画像",
"images": "画像", "images": "画像",
"import": "インポート", "import": "インポート",
@@ -254,6 +264,7 @@
"key": "キー", "key": "キー",
"label": "ラベル", "label": "ラベル",
"language": "言語", "language": "言語",
"last_name": "姓",
"learn_more": "詳細を見る", "learn_more": "詳細を見る",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "明るいオーバーレイ", "light_overlay": "明るいオーバーレイ",
@@ -268,7 +279,6 @@
"look_and_feel": "デザイン", "look_and_feel": "デザイン",
"manage": "管理", "manage": "管理",
"marketing": "マーケティング", "marketing": "マーケティング",
"member": "メンバー",
"members": "メンバー", "members": "メンバー",
"members_and_teams": "メンバー&チーム", "members_and_teams": "メンバー&チーム",
"membership_not_found": "メンバーシップが見つかりません", "membership_not_found": "メンバーシップが見つかりません",
@@ -280,6 +290,7 @@
"move_down": "下に移動", "move_down": "下に移動",
"move_up": "上に移動", "move_up": "上に移動",
"multiple_languages": "多言語", "multiple_languages": "多言語",
"my_product": "マイプロダクト",
"name": "名前", "name": "名前",
"new": "新規", "new": "新規",
"new_version_available": "Formbricks {version} が利用可能です。今すぐアップグレード!", "new_version_available": "Formbricks {version} が利用可能です。今すぐアップグレード!",
@@ -375,8 +386,6 @@
"select_teams": "チームを選択", "select_teams": "チームを選択",
"selected": "選択済み", "selected": "選択済み",
"selected_questions": "選択した質問", "selected_questions": "選択した質問",
"selection": "選択",
"selections": "選択",
"send_test_email": "テストメールを送信", "send_test_email": "テストメールを送信",
"session_not_found": "セッションが見つかりません", "session_not_found": "セッションが見つかりません",
"settings": "設定", "settings": "設定",
@@ -428,6 +437,7 @@
"top_right": "右上", "top_right": "右上",
"try_again": "もう一度お試しください", "try_again": "もう一度お試しください",
"type": "種類", "type": "種類",
"unknown_survey": "不明なフォーム",
"unlock_more_workspaces_with_a_higher_plan": "上位プランでより多くのワークスペースを利用できます。", "unlock_more_workspaces_with_a_higher_plan": "上位プランでより多くのワークスペースを利用できます。",
"update": "更新", "update": "更新",
"updated": "更新済み", "updated": "更新済み",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "連絡先を更新", "contacts_table_refresh": "連絡先を更新",
"contacts_table_refresh_success": "連絡先を正常に更新しました", "contacts_table_refresh_success": "連絡先を正常に更新しました",
"create_attribute": "属性を作成", "create_attribute": "属性を作成",
"create_key": "キーを作成",
"create_new_attribute": "新しい属性を作成", "create_new_attribute": "新しい属性を作成",
"create_new_attribute_description": "セグメンテーション用の新しい属性を作成します。", "create_new_attribute_description": "セグメンテーション用の新しい属性を作成します。",
"custom_attributes": "カスタム属性", "custom_attributes": "カスタム属性",
@@ -656,6 +665,7 @@
"delete_attribute_confirmation": "{value, plural, one {選択した属性を削除します。この属性に関連付けられたすべてのコンタクトデータは失われます。} other {選択した属性を削除します。これらの属性に関連付けられたすべてのコンタクトデータは失われます。}}", "delete_attribute_confirmation": "{value, plural, one {選択した属性を削除します。この属性に関連付けられたすべてのコンタクトデータは失われます。} other {選択した属性を削除します。これらの属性に関連付けられたすべてのコンタクトデータは失われます。}}",
"delete_contact_confirmation": "これにより、この連絡先に関連付けられているすべてのフォーム回答と連絡先属性が削除されます。この連絡先のデータに基づいたターゲティングとパーソナライゼーションはすべて失われます。", "delete_contact_confirmation": "これにより、この連絡先に関連付けられているすべてのフォーム回答と連絡先属性が削除されます。この連絡先のデータに基づいたターゲティングとパーソナライゼーションはすべて失われます。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {これにより この連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。この連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。この連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。} other {これにより これらの連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。これらの連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。これらの連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。}}", "delete_contact_confirmation_with_quotas": "{value, plural, one {これにより この連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。この連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。この連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。} other {これにより これらの連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。これらの連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。これらの連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。}}",
"displays": "表示回数",
"edit_attribute": "属性を編集", "edit_attribute": "属性を編集",
"edit_attribute_description": "この属性のラベルと説明を更新します。", "edit_attribute_description": "この属性のラベルと説明を更新します。",
"edit_attribute_values": "属性を編集", "edit_attribute_values": "属性を編集",
@@ -667,6 +677,7 @@
"invalid_csv_column_names": "無効なCSV列名: {columns}。新しい属性となる列名は、小文字、数字、アンダースコアのみを含み、文字で始まる必要があります。", "invalid_csv_column_names": "無効なCSV列名: {columns}。新しい属性となる列名は、小文字、数字、アンダースコアのみを含み、文字で始まる必要があります。",
"invalid_date_format": "無効な日付形式です。有効な日付を使用してください。", "invalid_date_format": "無効な日付形式です。有効な日付を使用してください。",
"invalid_number_format": "無効な数値形式です。有効な数値を入力してください。", "invalid_number_format": "無効な数値形式です。有効な数値を入力してください。",
"no_activity_yet": "まだアクティビティがありません",
"no_published_link_surveys_available": "公開されたリンクフォームはありません。まずリンクフォームを公開してください。", "no_published_link_surveys_available": "公開されたリンクフォームはありません。まずリンクフォームを公開してください。",
"no_published_surveys": "公開されたフォームはありません", "no_published_surveys": "公開されたフォームはありません",
"no_responses_found": "回答が見つかりません", "no_responses_found": "回答が見つかりません",
@@ -681,6 +692,8 @@
"select_a_survey": "フォームを選択", "select_a_survey": "フォームを選択",
"select_attribute": "属性を選択", "select_attribute": "属性を選択",
"select_attribute_key": "属性キーを選択", "select_attribute_key": "属性キーを選択",
"survey_viewed": "フォームを閲覧",
"survey_viewed_at": "閲覧日時",
"system_attributes": "システム属性", "system_attributes": "システム属性",
"unlock_contacts_description": "連絡先を管理し、特定のフォームを送信します", "unlock_contacts_description": "連絡先を管理し、特定のフォームを送信します",
"unlock_contacts_title": "上位プランで連絡先をアンロック", "unlock_contacts_title": "上位プランで連絡先をアンロック",
@@ -752,7 +765,12 @@
"link_google_sheet": "スプレッドシートをリンク", "link_google_sheet": "スプレッドシートをリンク",
"link_new_sheet": "新しいシートをリンク", "link_new_sheet": "新しいシートをリンク",
"no_integrations_yet": "Google スプレッドシート連携は、追加するとここに表示されます。⏲️", "no_integrations_yet": "Google スプレッドシート連携は、追加するとここに表示されます。⏲️",
"spreadsheet_url": "スプレッドシートURL" "reconnect_button": "再接続",
"reconnect_button_description": "Google Sheetsの接続が期限切れになりました。回答の同期を続けるには再接続してください。既存のスプレッドシートリンクとデータは保持されます。",
"reconnect_button_tooltip": "統合を再接続してアクセスを更新します。既存のスプレッドシートリンクとデータは保持されます。",
"spreadsheet_permission_error": "このスプレッドシートにアクセスする権限がありません。スプレッドシートがGoogleアカウントと共有されており、書き込みアクセス権があることを確認してください。",
"spreadsheet_url": "スプレッドシートURL",
"token_expired_error": "Google Sheetsのリフレッシュトークンが期限切れになったか、取り消されました。統合を再接続してください。"
}, },
"include_created_at": "作成日時を含める", "include_created_at": "作成日時を含める",
"include_hidden_fields": "非表示フィールドを含める", "include_hidden_fields": "非表示フィールドを含める",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "こんにちは、{userName}さん", "email_customization_preview_email_heading": "こんにちは、{userName}さん",
"email_customization_preview_email_text": "これは、メールに表示されるロゴを確認するためのプレビューメールです。", "email_customization_preview_email_text": "これは、メールに表示されるロゴを確認するためのプレビューメールです。",
"error_deleting_organization_please_try_again": "組織の削除中にエラーが発生しました。もう一度お試しください。", "error_deleting_organization_please_try_again": "組織の削除中にエラーが発生しました。もう一度お試しください。",
"from_your_organization": "あなたの組織から", "from_your_organization": "組織から{memberName}を削除",
"invitation_sent_once_more": "招待状を再度送信しました。", "invitation_sent_once_more": "招待状を再度送信しました。",
"invite_deleted_successfully": "招待を正常に削除しました", "invite_deleted_successfully": "招待を正常に削除しました",
"invite_expires_on": "招待は{date}に期限切れ", "invite_expires_on": "招待は{date}に期限切れ",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "質問がスキップされた場合に表示するプレースホルダーを追加:", "add_fallback_placeholder": "質問がスキップされた場合に表示するプレースホルダーを追加:",
"add_hidden_field_id": "非表示フィールドIDを追加", "add_hidden_field_id": "非表示フィールドIDを追加",
"add_highlight_border": "ハイライトボーダーを追加", "add_highlight_border": "ハイライトボーダーを追加",
"add_highlight_border_description": "プロダクト内サーベイにのみ適用されます。",
"add_logic": "ロジックを追加", "add_logic": "ロジックを追加",
"add_none_of_the_above": "\"いずれも該当しません\" を追加", "add_none_of_the_above": "\"いずれも該当しません\" を追加",
"add_option": "オプションを追加", "add_option": "オプションを追加",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "「フォームはクローズしました」メッセージを調整", "adjust_survey_closed_message": "「フォームはクローズしました」メッセージを調整",
"adjust_survey_closed_message_description": "フォームがクローズしたときに訪問者が見るメッセージを変更します。", "adjust_survey_closed_message_description": "フォームがクローズしたときに訪問者が見るメッセージを変更します。",
"adjust_the_theme_in_the": "テーマを", "adjust_the_theme_in_the": "テーマを",
"all_are_true": "すべてが真である",
"all_other_answers_will_continue_to": "他のすべての回答は引き続き", "all_other_answers_will_continue_to": "他のすべての回答は引き続き",
"allow_multi_select": "複数選択を許可", "allow_multi_select": "複数選択を許可",
"allow_multiple_files": "複数のファイルを許可", "allow_multiple_files": "複数のファイルを許可",
"allow_users_to_select_more_than_one_image": "ユーザーが複数の画像を選択できるようにする", "allow_users_to_select_more_than_one_image": "ユーザーが複数の画像を選択できるようにする",
"and_launch_surveys_in_your_website_or_app": "ウェブサイトやアプリでフォームを公開できます。", "and_launch_surveys_in_your_website_or_app": "ウェブサイトやアプリでフォームを公開できます。",
"animation": "アニメーション", "animation": "アニメーション",
"any_is_true": "いずれかが真",
"app_survey_description": "回答を収集するために、ウェブアプリまたはウェブサイトにフォームを埋め込みます。", "app_survey_description": "回答を収集するために、ウェブアプリまたはウェブサイトにフォームを埋め込みます。",
"assign": "割り当て =", "assign": "割り当て =",
"audience": "オーディエンス", "audience": "オーディエンス",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "フォローアップ が 更新され、 アンケートを 保存すると保存されます。", "follow_ups_modal_updated_successfull_toast": "フォローアップ が 更新され、 アンケートを 保存すると保存されます。",
"follow_ups_new": "新しいフォローアップ", "follow_ups_new": "新しいフォローアップ",
"follow_ups_upgrade_button_text": "フォローアップを有効にするためにアップグレード", "follow_ups_upgrade_button_text": "フォローアップを有効にするためにアップグレード",
"form_styling": "フォームのスタイル",
"formbricks_sdk_is_not_connected": "Formbricks SDKが接続されていません", "formbricks_sdk_is_not_connected": "Formbricks SDKが接続されていません",
"four_points": "4点", "four_points": "4点",
"heading": "見出し", "heading": "見出し",
@@ -1520,7 +1540,7 @@
"option_idx": "オプション {choiceIndex}", "option_idx": "オプション {choiceIndex}",
"option_used_in_logic_error": "このオプションは質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。", "option_used_in_logic_error": "このオプションは質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
"optional": "オプション", "optional": "オプション",
"options": "オプション", "options": "オプション*",
"options_used_in_logic_bulk_error": "以下のオプションはロジックで使用されています:{questionIndexes}。まず、ロジックから削除してください。", "options_used_in_logic_bulk_error": "以下のオプションはロジックで使用されています:{questionIndexes}。まず、ロジックから削除してください。",
"override_theme_with_individual_styles_for_this_survey": "このフォームの個別のスタイルでテーマを上書きします。", "override_theme_with_individual_styles_for_this_survey": "このフォームの個別のスタイルでテーマを上書きします。",
"overwrite_global_waiting_time": "カスタムクールダウン期間を設定", "overwrite_global_waiting_time": "カスタムクールダウン期間を設定",
@@ -1545,6 +1565,7 @@
"question_deleted": "質問を削除しました。", "question_deleted": "質問を削除しました。",
"question_duplicated": "質問を複製しました。", "question_duplicated": "質問を複製しました。",
"question_id_updated": "質問IDを更新しました", "question_id_updated": "質問IDを更新しました",
"question_number": "質問 {number}",
"question_used_in_logic_warning_text": "このブロックの要素はロジックルールで使用されていますが、本当に削除しますか?", "question_used_in_logic_warning_text": "このブロックの要素はロジックルールで使用されていますが、本当に削除しますか?",
"question_used_in_logic_warning_title": "ロジックの不整合", "question_used_in_logic_warning_title": "ロジックの不整合",
"question_used_in_quota": "この質問は“{quotaName}”クォータで使用されています", "question_used_in_quota": "この質問は“{quotaName}”クォータで使用されています",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "回答数の上限、リダイレクトなど。", "response_limits_redirections_and_more": "回答数の上限、リダイレクトなど。",
"response_options": "回答オプション", "response_options": "回答オプション",
"roundness": "丸み", "roundness": "丸み",
"roundness_description": "カードの角の丸みを調整します。", "roundness_description": "角の丸みを調整します。",
"row_used_in_logic_error": "この行は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。", "row_used_in_logic_error": "この行は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
"rows": "行", "rows": "行",
"save_and_close": "保存して閉じる", "save_and_close": "保存して閉じる",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "この無料のオープンソースフォームは閉鎖されました", "survey_completed_subheading": "この無料のオープンソースフォームは閉鎖されました",
"survey_display_settings": "フォーム表示設定", "survey_display_settings": "フォーム表示設定",
"survey_placement": "フォームの配置", "survey_placement": "フォームの配置",
"survey_styling": "フォームのスタイル",
"survey_trigger": "フォームのトリガー", "survey_trigger": "フォームのトリガー",
"switch_multi_language_on_to_get_started": "多言語機能をオンにして開始 👉", "switch_multi_language_on_to_get_started": "多言語機能をオンにして開始 👉",
"target_block_not_found": "対象ブロックが見つかりません", "target_block_not_found": "対象ブロックが見つかりません",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "クールダウン期間(アンケート全体)", "waiting_time_across_surveys": "クールダウン期間(アンケート全体)",
"waiting_time_across_surveys_description": "アンケート疲れを防ぐため、このアンケートがワークスペース全体のクールダウン期間とどのように連動するかを選択してください。", "waiting_time_across_surveys_description": "アンケート疲れを防ぐため、このアンケートがワークスペース全体のクールダウン期間とどのように連動するかを選択してください。",
"welcome_message": "ウェルカムメッセージ", "welcome_message": "ウェルカムメッセージ",
"when": "条件",
"without_a_filter_all_of_your_users_can_be_surveyed": "フィルターがなければ、すべてのユーザーがフォームに回答できます。", "without_a_filter_all_of_your_users_can_be_surveyed": "フィルターがなければ、すべてのユーザーがフォームに回答できます。",
"you_have_not_created_a_segment_yet": "まだセグメントを作成していません", "you_have_not_created_a_segment_yet": "まだセグメントを作成していません",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "翻訳を使用するには、ワークスペースに2つ以上の言語を設定する必要があります。",
"your_description_here_recall_information_with": "ここにあなたの説明。@ で情報を呼び出す", "your_description_here_recall_information_with": "ここにあなたの説明。@ で情報を呼び出す",
"your_question_here_recall_information_with": "ここにあなたの質問。@ で情報を呼び出す", "your_question_here_recall_information_with": "ここにあなたの質問。@ で情報を呼び出す",
"your_web_app": "あなたのウェブアプリ", "your_web_app": "あなたのウェブアプリ",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "フィルター済み回答 (Excel)", "filtered_responses_excel": "フィルター済み回答 (Excel)",
"generating_qr_code": "QRコードを生成中", "generating_qr_code": "QRコードを生成中",
"impressions": "表示回数", "impressions": "表示回数",
"impressions_identified_only": "識別済みコンタクトからのインプレッションのみを表示しています",
"impressions_tooltip": "フォームが表示された回数。", "impressions_tooltip": "フォームが表示された回数。",
"in_app": { "in_app": {
"connection_description": "このフォームは、以下の条件に一致するあなたのウェブサイトのユーザーに表示されます", "connection_description": "このフォームは、以下の条件に一致するあなたのウェブサイトのユーザーに表示されます",
@@ -1989,6 +2012,7 @@
"last_quarter": "前四半期", "last_quarter": "前四半期",
"last_year": "昨年", "last_year": "昨年",
"limit": "制限", "limit": "制限",
"no_identified_impressions": "識別済みコンタクトからのインプレッションはありません",
"no_responses_found": "回答が見つかりません", "no_responses_found": "回答が見つかりません",
"other_values_found": "他の値が見つかりました", "other_values_found": "他の値が見つかりました",
"overall": "全体", "overall": "全体",
@@ -2011,6 +2035,7 @@
"starts": "開始", "starts": "開始",
"starts_tooltip": "フォームが開始された回数。", "starts_tooltip": "フォームが開始された回数。",
"survey_reset_successfully": "フォームを正常にリセットしました!{responseCount} 件の回答と {displayCount} 件の表示が削除されました。", "survey_reset_successfully": "フォームを正常にリセットしました!{responseCount} 件の回答と {displayCount} 件の表示が削除されました。",
"survey_results": "{surveyName}の結果",
"this_month": "今月", "this_month": "今月",
"this_quarter": "今四半期", "this_quarter": "今四半期",
"this_year": "今年", "this_year": "今年",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "バーの塗りつぶし部分に色を付けます。", "advanced_styling_field_indicator_bg_description": "バーの塗りつぶし部分に色を付けます。",
"advanced_styling_field_input_border_radius_description": "入力フィールドの角を丸めます。", "advanced_styling_field_input_border_radius_description": "入力フィールドの角を丸めます。",
"advanced_styling_field_input_font_size_description": "入力フィールド内の入力テキストのサイズを調整します。", "advanced_styling_field_input_font_size_description": "入力フィールド内の入力テキストのサイズを調整します。",
"advanced_styling_field_input_height_description": "入力フィールドの最小の高さを制御します。", "advanced_styling_field_input_height_description": "入力の最小の高さを調整します。",
"advanced_styling_field_input_padding_x_description": "左右にスペースを追加します。", "advanced_styling_field_input_padding_x_description": "左右にスペースを追加します。",
"advanced_styling_field_input_padding_y_description": "上下にスペースを追加します。", "advanced_styling_field_input_padding_y_description": "上下にスペースを追加します。",
"advanced_styling_field_input_placeholder_opacity_description": "プレースホルダーのヒントテキストを薄くします。", "advanced_styling_field_input_placeholder_opacity_description": "プレースホルダーのヒントテキストを薄くします。",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "入力フィールドに入力されたテキストの色を設定します。", "advanced_styling_field_input_text_description": "入力フィールドに入力されたテキストの色を設定します。",
"advanced_styling_field_option_bg": "背景", "advanced_styling_field_option_bg": "背景",
"advanced_styling_field_option_bg_description": "オプション項目を塗りつぶします。", "advanced_styling_field_option_bg_description": "オプション項目を塗りつぶします。",
"advanced_styling_field_option_border": "枠線の色",
"advanced_styling_field_option_border_description": "ラジオボタンとチェックボックスの選択肢の輪郭を設定します。",
"advanced_styling_field_option_border_radius_description": "オプションの角を丸くします。", "advanced_styling_field_option_border_radius_description": "オプションの角を丸くします。",
"advanced_styling_field_option_font_size_description": "オプションラベルのテキストサイズを調整します。", "advanced_styling_field_option_font_size_description": "オプションラベルのテキストサイズを調整します。",
"advanced_styling_field_option_label": "ラベルの色", "advanced_styling_field_option_label": "ラベルの色",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "会社はビジョンと戦略の整合性をどのように改善できますか?", "alignment_and_engagement_survey_question_4_headline": "会社はビジョンと戦略の整合性をどのように改善できますか?",
"alignment_and_engagement_survey_question_4_placeholder": "ここに回答を入力してください...", "alignment_and_engagement_survey_question_4_placeholder": "ここに回答を入力してください...",
"back": "戻る", "back": "戻る",
"block_1": "ブロック 1",
"block_10": "ブロック 10",
"block_2": "ブロック 2",
"block_3": "ブロック 3",
"block_4": "ブロック 4",
"block_5": "ブロック 5",
"block_6": "ブロック 6",
"block_7": "ブロック 7",
"block_8": "ブロック 8",
"block_9": "ブロック 9",
"book_interview": "面談を予約する", "book_interview": "面談を予約する",
"build_product_roadmap_description": "ユーザーが最も望んでいる「たった一つ」のものを特定し、構築する。", "build_product_roadmap_description": "ユーザーが最も望んでいる「たった一つ」のものを特定し、構築する。",
"build_product_roadmap_name": "製品ロードマップの構築", "build_product_roadmap_name": "製品ロードマップの構築",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "申し訳ありません!体験を改善するために何かできることはありますか?", "csat_survey_question_3_headline": "申し訳ありません!体験を改善するために何かできることはありますか?",
"csat_survey_question_3_placeholder": "ここに回答を入力してください...", "csat_survey_question_3_placeholder": "ここに回答を入力してください...",
"cta_description": "情報を表示し、特定の行動を促す", "cta_description": "情報を表示し、特定の行動を促す",
"custom_survey_block_1_name": "ブロック1",
"custom_survey_description": "テンプレートを使わずにアンケートを作成する。", "custom_survey_description": "テンプレートを使わずにアンケートを作成する。",
"custom_survey_name": "最初から始める", "custom_survey_name": "最初から始める",
"custom_survey_question_1_headline": "何を知りたいですか?", "custom_survey_question_1_headline": "何を知りたいですか?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "いいえ、結構です!", "preview_survey_question_2_choice_2_label": "いいえ、結構です!",
"preview_survey_question_2_headline": "最新情報を知りたいですか?", "preview_survey_question_2_headline": "最新情報を知りたいですか?",
"preview_survey_question_2_subheader": "これは説明の例です。", "preview_survey_question_2_subheader": "これは説明の例です。",
"preview_survey_question_open_text_headline": "他に共有したいことはありますか?",
"preview_survey_question_open_text_placeholder": "ここに回答を入力してください...",
"preview_survey_question_open_text_subheader": "あなたのフィードバックは、私たちの改善に役立ちます。",
"preview_survey_welcome_card_headline": "ようこそ!", "preview_survey_welcome_card_headline": "ようこそ!",
"prioritize_features_description": "ユーザーが最も必要とする機能と最も必要としない機能を特定する。", "prioritize_features_description": "ユーザーが最も必要とする機能と最も必要としない機能を特定する。",
"prioritize_features_name": "機能の優先順位付け", "prioritize_features_name": "機能の優先順位付け",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Kopiëren", "copy": "Kopiëren",
"copy_code": "Kopieer code", "copy_code": "Kopieer code",
"copy_link": "Kopieer link", "copy_link": "Kopieer link",
"count_attributes": "{value, plural, one {{value} attribuut} other {{value} attributen}}", "count_attributes": "{count, plural, one {{count} attribuut} other {{count} attributen}}",
"count_contacts": "{value, plural, one {{value} contact} other {{value} contacten}}", "count_contacts": "{count, plural, one {{count} contact} other {{count} contacten}}",
"count_responses": "{value, plural, one {{value} reactie} other {{value} reacties}}", "count_members": "{count, plural, one {{count} lid} other {{count} leden}}",
"count_questions": "{count, plural, one {{count} vraag} other {{count} vragen}}",
"count_responses": "{count, plural, one {{count} reactie} other {{count} reacties}}",
"count_selections": "{count, plural, one {{count} selectie} other {{count} selecties}}",
"create_new_organization": "Creëer een nieuwe organisatie", "create_new_organization": "Creëer een nieuwe organisatie",
"create_segment": "Segment maken", "create_segment": "Segment maken",
"create_survey": "Enquête maken", "create_survey": "Enquête maken",
@@ -191,6 +194,7 @@
"days": "dagen", "days": "dagen",
"default": "Standaard", "default": "Standaard",
"delete": "Verwijderen", "delete": "Verwijderen",
"delete_what": "Verwijder {deleteWhat}",
"description": "Beschrijving", "description": "Beschrijving",
"dev_env": "Ontwikkelomgeving", "dev_env": "Ontwikkelomgeving",
"development": "Ontwikkeling", "development": "Ontwikkeling",
@@ -206,6 +210,8 @@
"download": "Downloaden", "download": "Downloaden",
"draft": "Voorlopige versie", "draft": "Voorlopige versie",
"duplicate": "Duplicaat", "duplicate": "Duplicaat",
"duplicate_copy": "(kopie)",
"duplicate_copy_number": "(kopie {copyNumber})",
"e_commerce": "E-commerce", "e_commerce": "E-commerce",
"edit": "Bewerking", "edit": "Bewerking",
"email": "E-mail", "email": "E-mail",
@@ -218,13 +224,16 @@
"error": "Fout", "error": "Fout",
"error_component_description": "Deze bron bestaat niet of u beschikt niet over de benodigde toegangsrechten.", "error_component_description": "Deze bron bestaat niet of u beschikt niet over de benodigde toegangsrechten.",
"error_component_title": "Fout bij het laden van bronnen", "error_component_title": "Fout bij het laden van bronnen",
"error_loading_data": "Fout bij het laden van gegevens",
"error_rate_limit_description": "Maximaal aantal verzoeken bereikt. Probeer het later opnieuw.", "error_rate_limit_description": "Maximaal aantal verzoeken bereikt. Probeer het later opnieuw.",
"error_rate_limit_title": "Tarieflimiet overschreden", "error_rate_limit_title": "Tarieflimiet overschreden",
"expand_rows": "Vouw rijen uit", "expand_rows": "Vouw rijen uit",
"failed_to_copy_to_clipboard": "Kopiëren naar klembord mislukt", "failed_to_copy_to_clipboard": "Kopiëren naar klembord mislukt",
"failed_to_load_organizations": "Laden van organisaties mislukt", "failed_to_load_organizations": "Laden van organisaties mislukt",
"failed_to_load_workspaces": "Laden van werkruimtes mislukt", "failed_to_load_workspaces": "Laden van werkruimtes mislukt",
"filter": "Filter",
"finish": "Finish", "finish": "Finish",
"first_name": "Voornaam",
"follow_these": "Volg deze", "follow_these": "Volg deze",
"formbricks_version": "Formbricks-versie", "formbricks_version": "Formbricks-versie",
"full_name": "Volledige naam", "full_name": "Volledige naam",
@@ -237,6 +246,7 @@
"hidden_field": "Verborgen veld", "hidden_field": "Verborgen veld",
"hidden_fields": "Verborgen velden", "hidden_fields": "Verborgen velden",
"hide_column": "Kolom verbergen", "hide_column": "Kolom verbergen",
"id": "ID",
"image": "Afbeelding", "image": "Afbeelding",
"images": "Afbeeldingen", "images": "Afbeeldingen",
"import": "Importeren", "import": "Importeren",
@@ -254,6 +264,7 @@
"key": "Sleutel", "key": "Sleutel",
"label": "Label", "label": "Label",
"language": "Taal", "language": "Taal",
"last_name": "Achternaam",
"learn_more": "Meer informatie", "learn_more": "Meer informatie",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Lichte overlay", "light_overlay": "Lichte overlay",
@@ -268,7 +279,6 @@
"look_and_feel": "Kijk & voel", "look_and_feel": "Kijk & voel",
"manage": "Beheren", "manage": "Beheren",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Lid",
"members": "Leden", "members": "Leden",
"members_and_teams": "Leden & teams", "members_and_teams": "Leden & teams",
"membership_not_found": "Lidmaatschap niet gevonden", "membership_not_found": "Lidmaatschap niet gevonden",
@@ -280,6 +290,7 @@
"move_down": "Ga naar beneden", "move_down": "Ga naar beneden",
"move_up": "Ga omhoog", "move_up": "Ga omhoog",
"multiple_languages": "Meerdere talen", "multiple_languages": "Meerdere talen",
"my_product": "mijn product",
"name": "Naam", "name": "Naam",
"new": "Nieuw", "new": "Nieuw",
"new_version_available": "Formbricks {version} is hier. Upgrade nu!", "new_version_available": "Formbricks {version} is hier. Upgrade nu!",
@@ -375,8 +386,6 @@
"select_teams": "Selecteer teams", "select_teams": "Selecteer teams",
"selected": "Gekozen", "selected": "Gekozen",
"selected_questions": "Geselecteerde vragen", "selected_questions": "Geselecteerde vragen",
"selection": "Selectie",
"selections": "Selecties",
"send_test_email": "Test-e-mail verzenden", "send_test_email": "Test-e-mail verzenden",
"session_not_found": "Sessie niet gevonden", "session_not_found": "Sessie niet gevonden",
"settings": "Instellingen", "settings": "Instellingen",
@@ -428,6 +437,7 @@
"top_right": "Rechtsboven", "top_right": "Rechtsboven",
"try_again": "Probeer het opnieuw", "try_again": "Probeer het opnieuw",
"type": "Type", "type": "Type",
"unknown_survey": "Onbekende enquête",
"unlock_more_workspaces_with_a_higher_plan": "Ontgrendel meer werkruimtes met een hoger abonnement.", "unlock_more_workspaces_with_a_higher_plan": "Ontgrendel meer werkruimtes met een hoger abonnement.",
"update": "Update", "update": "Update",
"updated": "Bijgewerkt", "updated": "Bijgewerkt",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Vernieuw contacten", "contacts_table_refresh": "Vernieuw contacten",
"contacts_table_refresh_success": "Contacten zijn vernieuwd", "contacts_table_refresh_success": "Contacten zijn vernieuwd",
"create_attribute": "Attribuut aanmaken", "create_attribute": "Attribuut aanmaken",
"create_key": "Sleutel aanmaken",
"create_new_attribute": "Nieuw attribuut aanmaken", "create_new_attribute": "Nieuw attribuut aanmaken",
"create_new_attribute_description": "Maak een nieuw attribuut aan voor segmentatiedoeleinden.", "create_new_attribute_description": "Maak een nieuw attribuut aan voor segmentatiedoeleinden.",
"custom_attributes": "Aangepaste kenmerken", "custom_attributes": "Aangepaste kenmerken",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Weergaven",
"edit_attribute": "Attribuut bewerken", "edit_attribute": "Attribuut bewerken",
"edit_attribute_description": "Werk het label en de beschrijving voor dit attribuut bij.", "edit_attribute_description": "Werk het label en de beschrijving voor dit attribuut bij.",
"edit_attribute_values": "Attributen bewerken", "edit_attribute_values": "Attributen bewerken",
@@ -667,6 +677,7 @@
"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_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_date_format": "Ongeldig datumformaat. Gebruik een geldige datum.",
"invalid_number_format": "Ongeldig getalformaat. Voer een geldig getal in.", "invalid_number_format": "Ongeldig getalformaat. Voer een geldig getal in.",
"no_activity_yet": "Nog geen activiteit",
"no_published_link_surveys_available": "Geen gepubliceerde link-enquêtes beschikbaar. Publiceer eerst een link-enquête.", "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_published_surveys": "Geen gepubliceerde enquêtes",
"no_responses_found": "Geen reacties gevonden", "no_responses_found": "Geen reacties gevonden",
@@ -681,6 +692,8 @@
"select_a_survey": "Selecteer een enquête", "select_a_survey": "Selecteer een enquête",
"select_attribute": "Selecteer Kenmerk", "select_attribute": "Selecteer Kenmerk",
"select_attribute_key": "Selecteer kenmerksleutel", "select_attribute_key": "Selecteer kenmerksleutel",
"survey_viewed": "Enquête bekeken",
"survey_viewed_at": "Bekeken op",
"system_attributes": "Systeemkenmerken", "system_attributes": "Systeemkenmerken",
"unlock_contacts_description": "Beheer contacten en verstuur gerichte enquêtes", "unlock_contacts_description": "Beheer contacten en verstuur gerichte enquêtes",
"unlock_contacts_title": "Ontgrendel contacten met een hoger abonnement", "unlock_contacts_title": "Ontgrendel contacten met een hoger abonnement",
@@ -752,7 +765,12 @@
"link_google_sheet": "Link Google Spreadsheet", "link_google_sheet": "Link Google Spreadsheet",
"link_new_sheet": "Nieuw blad koppelen", "link_new_sheet": "Nieuw blad koppelen",
"no_integrations_yet": "Uw Google Spreadsheet-integraties verschijnen hier zodra u ze toevoegt. ⏲️", "no_integrations_yet": "Uw Google Spreadsheet-integraties verschijnen hier zodra u ze toevoegt. ⏲️",
"spreadsheet_url": "Spreadsheet-URL" "reconnect_button": "Maak opnieuw verbinding",
"reconnect_button_description": "Je Google Sheets-verbinding is verlopen. Maak opnieuw verbinding om door te gaan met het synchroniseren van antwoorden. Je bestaande spreadsheetlinks en gegevens blijven behouden.",
"reconnect_button_tooltip": "Maak opnieuw verbinding met de integratie om je toegang te vernieuwen. Je bestaande spreadsheetlinks en gegevens blijven behouden.",
"spreadsheet_permission_error": "Je hebt geen toestemming om deze spreadsheet te openen. Zorg ervoor dat de spreadsheet is gedeeld met je Google-account en dat je schrijftoegang hebt tot de spreadsheet.",
"spreadsheet_url": "Spreadsheet-URL",
"token_expired_error": "Het vernieuwingstoken van Google Sheets is verlopen of ingetrokken. Maak opnieuw verbinding met de integratie."
}, },
"include_created_at": "Inclusief gemaakt op", "include_created_at": "Inclusief gemaakt op",
"include_hidden_fields": "Inclusief verborgen velden", "include_hidden_fields": "Inclusief verborgen velden",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Hé {userName}", "email_customization_preview_email_heading": "Hé {userName}",
"email_customization_preview_email_text": "Dit is een e-mailvoorbeeld om u te laten zien welk logo in de e-mails wordt weergegeven.", "email_customization_preview_email_text": "Dit is een e-mailvoorbeeld om u te laten zien welk logo in de e-mails wordt weergegeven.",
"error_deleting_organization_please_try_again": "Fout bij verwijderen van organisatie. Probeer het opnieuw.", "error_deleting_organization_please_try_again": "Fout bij verwijderen van organisatie. Probeer het opnieuw.",
"from_your_organization": "vanuit uw organisatie", "from_your_organization": "{memberName} uit je organisatie",
"invitation_sent_once_more": "Uitnodiging nogmaals verzonden.", "invitation_sent_once_more": "Uitnodiging nogmaals verzonden.",
"invite_deleted_successfully": "Uitnodiging succesvol verwijderd", "invite_deleted_successfully": "Uitnodiging succesvol verwijderd",
"invite_expires_on": "Uitnodiging verloopt op {date}", "invite_expires_on": "Uitnodiging verloopt op {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Voeg een tijdelijke aanduiding toe om aan te geven of er geen waarde is om te onthouden.", "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_hidden_field_id": "Voeg een verborgen veld-ID toe",
"add_highlight_border": "Markeerrand toevoegen", "add_highlight_border": "Markeerrand toevoegen",
"add_highlight_border_description": "Geldt alleen voor in-product enquêtes.",
"add_logic": "Voeg logica toe", "add_logic": "Voeg logica toe",
"add_none_of_the_above": "Voeg 'Geen van bovenstaande' toe", "add_none_of_the_above": "Voeg 'Geen van bovenstaande' toe",
"add_option": "Optie toevoegen", "add_option": "Optie toevoegen",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Pas het bericht 'Enquête gesloten' aan", "adjust_survey_closed_message": "Pas het bericht 'Enquête gesloten' aan",
"adjust_survey_closed_message_description": "Wijzig het bericht dat bezoekers zien wanneer de enquête wordt gesloten.", "adjust_survey_closed_message_description": "Wijzig het bericht dat bezoekers zien wanneer de enquête wordt gesloten.",
"adjust_the_theme_in_the": "Pas het thema aan in de", "adjust_the_theme_in_the": "Pas het thema aan in de",
"all_are_true": "alle zijn waar",
"all_other_answers_will_continue_to": "Alle andere antwoorden blijven hetzelfde", "all_other_answers_will_continue_to": "Alle andere antwoorden blijven hetzelfde",
"allow_multi_select": "Multi-select toestaan", "allow_multi_select": "Multi-select toestaan",
"allow_multiple_files": "Meerdere bestanden toestaan", "allow_multiple_files": "Meerdere bestanden toestaan",
"allow_users_to_select_more_than_one_image": "Sta gebruikers toe meer dan één afbeelding te selecteren", "allow_users_to_select_more_than_one_image": "Sta gebruikers toe meer dan één afbeelding te selecteren",
"and_launch_surveys_in_your_website_or_app": "en start enquêtes op uw website of app.", "and_launch_surveys_in_your_website_or_app": "en start enquêtes op uw website of app.",
"animation": "Animatie", "animation": "Animatie",
"any_is_true": "een is waar",
"app_survey_description": "Sluit een enquête in uw web-app of website in om reacties te verzamelen.", "app_survey_description": "Sluit een enquête in uw web-app of website in om reacties te verzamelen.",
"assign": "Toewijzen =", "assign": "Toewijzen =",
"audience": "Publiek", "audience": "Publiek",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Follow-up bijgewerkt en wordt opgeslagen zodra u de enquête opslaat.", "follow_ups_modal_updated_successfull_toast": "Follow-up bijgewerkt en wordt opgeslagen zodra u de enquête opslaat.",
"follow_ups_new": "Nieuw vervolg", "follow_ups_new": "Nieuw vervolg",
"follow_ups_upgrade_button_text": "Upgrade om follow-ups mogelijk te maken", "follow_ups_upgrade_button_text": "Upgrade om follow-ups mogelijk te maken",
"form_styling": "Vorm styling",
"formbricks_sdk_is_not_connected": "Formbricks SDK is niet verbonden", "formbricks_sdk_is_not_connected": "Formbricks SDK is niet verbonden",
"four_points": "4 punten", "four_points": "4 punten",
"heading": "Rubriek", "heading": "Rubriek",
@@ -1520,7 +1540,7 @@
"option_idx": "Optie {choiceIndex}", "option_idx": "Optie {choiceIndex}",
"option_used_in_logic_error": "Deze optie wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.", "option_used_in_logic_error": "Deze optie wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.",
"optional": "Optioneel", "optional": "Optioneel",
"options": "Opties", "options": "Opties*",
"options_used_in_logic_bulk_error": "De volgende opties worden gebruikt in logica: {questionIndexes}. Verwijder ze eerst uit de logica.", "options_used_in_logic_bulk_error": "De volgende opties worden gebruikt in logica: {questionIndexes}. Verwijder ze eerst uit de logica.",
"override_theme_with_individual_styles_for_this_survey": "Overschrijf het thema met individuele stijlen voor deze enquête.", "override_theme_with_individual_styles_for_this_survey": "Overschrijf het thema met individuele stijlen voor deze enquête.",
"overwrite_global_waiting_time": "Aangepaste afkoelperiode instellen", "overwrite_global_waiting_time": "Aangepaste afkoelperiode instellen",
@@ -1545,6 +1565,7 @@
"question_deleted": "Vraag verwijderd.", "question_deleted": "Vraag verwijderd.",
"question_duplicated": "Vraag dubbel gesteld.", "question_duplicated": "Vraag dubbel gesteld.",
"question_id_updated": "Vraag-ID bijgewerkt", "question_id_updated": "Vraag-ID bijgewerkt",
"question_number": "Vraag {number}",
"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_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_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}”",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Reactielimieten, omleidingen en meer.", "response_limits_redirections_and_more": "Reactielimieten, omleidingen en meer.",
"response_options": "Reactieopties", "response_options": "Reactieopties",
"roundness": "Rondheid", "roundness": "Rondheid",
"roundness_description": "Bepaalt hoe afgerond de kaarthoeken zijn.", "roundness_description": "Bepaalt hoe afgerond de hoeken zijn.",
"row_used_in_logic_error": "Deze rij wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.", "row_used_in_logic_error": "Deze rij wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.",
"rows": "Rijen", "rows": "Rijen",
"save_and_close": "Opslaan en sluiten", "save_and_close": "Opslaan en sluiten",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Deze gratis en open source-enquête is gesloten", "survey_completed_subheading": "Deze gratis en open source-enquête is gesloten",
"survey_display_settings": "Enquêteweergave-instellingen", "survey_display_settings": "Enquêteweergave-instellingen",
"survey_placement": "Enquête plaatsing", "survey_placement": "Enquête plaatsing",
"survey_styling": "Vorm styling",
"survey_trigger": "Enquêtetrigger", "survey_trigger": "Enquêtetrigger",
"switch_multi_language_on_to_get_started": "Schakel meertaligheid in om te beginnen 👉", "switch_multi_language_on_to_get_started": "Schakel meertaligheid in om te beginnen 👉",
"target_block_not_found": "Doelblok niet gevonden", "target_block_not_found": "Doelblok niet gevonden",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Afkoelperiode (voor alle enquêtes)", "waiting_time_across_surveys": "Afkoelperiode (voor alle enquêtes)",
"waiting_time_across_surveys_description": "Om enquêtemoeheid te voorkomen, kies hoe deze enquête omgaat met de workspace-brede afkoelperiode.", "waiting_time_across_surveys_description": "Om enquêtemoeheid te voorkomen, kies hoe deze enquête omgaat met de workspace-brede afkoelperiode.",
"welcome_message": "Welkomstbericht", "welcome_message": "Welkomstbericht",
"when": "Wanneer",
"without_a_filter_all_of_your_users_can_be_surveyed": "Zonder filter kunnen al uw gebruikers worden bevraagd.", "without_a_filter_all_of_your_users_can_be_surveyed": "Zonder filter kunnen al uw gebruikers worden bevraagd.",
"you_have_not_created_a_segment_yet": "U heeft nog geen segment aangemaakt", "you_have_not_created_a_segment_yet": "U heeft nog geen segment aangemaakt",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Je moet twee of meer talen hebben ingesteld in je werkruimte om met vertalingen te kunnen werken.",
"your_description_here_recall_information_with": "Uw beschrijving hier. Roep informatie op met @", "your_description_here_recall_information_with": "Uw beschrijving hier. Roep informatie op met @",
"your_question_here_recall_information_with": "Uw vraag hier. Roep informatie op met @", "your_question_here_recall_information_with": "Uw vraag hier. Roep informatie op met @",
"your_web_app": "Uw web-app", "your_web_app": "Uw web-app",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Gefilterde reacties (Excel)", "filtered_responses_excel": "Gefilterde reacties (Excel)",
"generating_qr_code": "QR-code genereren", "generating_qr_code": "QR-code genereren",
"impressions": "Indrukken", "impressions": "Indrukken",
"impressions_identified_only": "Alleen weergaven van geïdentificeerde contacten worden getoond",
"impressions_tooltip": "Aantal keren dat de enquête is bekeken.", "impressions_tooltip": "Aantal keren dat de enquête is bekeken.",
"in_app": { "in_app": {
"connection_description": "De enquête wordt getoond aan gebruikers van uw website die voldoen aan de onderstaande criteria", "connection_description": "De enquête wordt getoond aan gebruikers van uw website die voldoen aan de onderstaande criteria",
@@ -1989,6 +2012,7 @@
"last_quarter": "Laatste kwartaal", "last_quarter": "Laatste kwartaal",
"last_year": "Vorig jaar", "last_year": "Vorig jaar",
"limit": "Beperken", "limit": "Beperken",
"no_identified_impressions": "Geen weergaven van geïdentificeerde contacten",
"no_responses_found": "Geen reacties gevonden", "no_responses_found": "Geen reacties gevonden",
"other_values_found": "Andere waarden gevonden", "other_values_found": "Andere waarden gevonden",
"overall": "Algemeen", "overall": "Algemeen",
@@ -2011,6 +2035,7 @@
"starts": "Begint", "starts": "Begint",
"starts_tooltip": "Aantal keren dat de enquête is gestart.", "starts_tooltip": "Aantal keren dat de enquête is gestart.",
"survey_reset_successfully": "Enquête opnieuw ingesteld! {responseCount} reacties en {displayCount} displays zijn verwijderd.", "survey_reset_successfully": "Enquête opnieuw ingesteld! {responseCount} reacties en {displayCount} displays zijn verwijderd.",
"survey_results": "Resultaten van {surveyName}",
"this_month": "Deze maand", "this_month": "Deze maand",
"this_quarter": "Dit kwartaal", "this_quarter": "Dit kwartaal",
"this_year": "Dit jaar", "this_year": "Dit jaar",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Kleurt het gevulde deel van de balk.", "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_border_radius_description": "Rondt de invoerhoeken af.",
"advanced_styling_field_input_font_size_description": "Schaalt de getypte tekst in invoervelden.", "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_height_description": "Bepaalt de min. hoogte van het invoerveld.",
"advanced_styling_field_input_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.", "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_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_placeholder_opacity_description": "Vervaagt de tijdelijke aanwijzingstekst.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Kleurt de getypte tekst in invoervelden.", "advanced_styling_field_input_text_description": "Kleurt de getypte tekst in invoervelden.",
"advanced_styling_field_option_bg": "Achtergrond", "advanced_styling_field_option_bg": "Achtergrond",
"advanced_styling_field_option_bg_description": "Vult de optie-items.", "advanced_styling_field_option_bg_description": "Vult de optie-items.",
"advanced_styling_field_option_border": "Randkleur",
"advanced_styling_field_option_border_description": "Omlijnt radio- en checkboxopties.",
"advanced_styling_field_option_border_radius_description": "Rondt de hoeken van opties af.", "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_font_size_description": "Schaalt de tekst van optielabels.",
"advanced_styling_field_option_label": "Labelkleur", "advanced_styling_field_option_label": "Labelkleur",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Hoe kan het bedrijf de afstemming van zijn visie en strategie verbeteren?", "alignment_and_engagement_survey_question_4_headline": "Hoe kan het bedrijf de afstemming van zijn visie en strategie verbeteren?",
"alignment_and_engagement_survey_question_4_placeholder": "Typ hier uw antwoord...", "alignment_and_engagement_survey_question_4_placeholder": "Typ hier uw antwoord...",
"back": "Rug", "back": "Rug",
"block_1": "Blok 1",
"block_10": "Blok 10",
"block_2": "Blok 2",
"block_3": "Blok 3",
"block_4": "Blok 4",
"block_5": "Blok 5",
"block_6": "Blok 6",
"block_7": "Blok 7",
"block_8": "Blok 8",
"block_9": "Blok 9",
"book_interview": "Boek interview", "book_interview": "Boek interview",
"build_product_roadmap_description": "Identificeer het ENE wat uw gebruikers het liefst willen en bouw het.", "build_product_roadmap_description": "Identificeer het ENE wat uw gebruikers het liefst willen en bouw het.",
"build_product_roadmap_name": "Productroadmap opstellen", "build_product_roadmap_name": "Productroadmap opstellen",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Euh, sorry! Kunnen we iets doen om uw ervaring te verbeteren?", "csat_survey_question_3_headline": "Euh, sorry! Kunnen we iets doen om uw ervaring te verbeteren?",
"csat_survey_question_3_placeholder": "Typ hier uw antwoord...", "csat_survey_question_3_placeholder": "Typ hier uw antwoord...",
"cta_description": "Geef informatie weer en vraag gebruikers om een specifieke actie te ondernemen", "cta_description": "Geef informatie weer en vraag gebruikers om een specifieke actie te ondernemen",
"custom_survey_block_1_name": "Blok 1",
"custom_survey_description": "Maak een enquête zonder sjabloon.", "custom_survey_description": "Maak een enquête zonder sjabloon.",
"custom_survey_name": "Begin helemaal opnieuw", "custom_survey_name": "Begin helemaal opnieuw",
"custom_survey_question_1_headline": "Wat zou je willen weten?", "custom_survey_question_1_headline": "Wat zou je willen weten?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Nee, dank je!", "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_headline": "Wil je op de hoogte blijven?",
"preview_survey_question_2_subheader": "Dit is een voorbeeldbeschrijving.", "preview_survey_question_2_subheader": "Dit is een voorbeeldbeschrijving.",
"preview_survey_question_open_text_headline": "Wil je nog iets delen?",
"preview_survey_question_open_text_placeholder": "Typ hier je antwoord...",
"preview_survey_question_open_text_subheader": "Je feedback helpt ons verbeteren.",
"preview_survey_welcome_card_headline": "Welkom!", "preview_survey_welcome_card_headline": "Welkom!",
"prioritize_features_description": "Identificeer functies die uw gebruikers het meest en het minst nodig hebben.", "prioritize_features_description": "Identificeer functies die uw gebruikers het meest en het minst nodig hebben.",
"prioritize_features_name": "Geef prioriteit aan functies", "prioritize_features_name": "Geef prioriteit aan functies",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Copiar", "copy": "Copiar",
"copy_code": "Copiar código", "copy_code": "Copiar código",
"copy_link": "Copiar Link", "copy_link": "Copiar Link",
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}", "count_attributes": "{count, plural, one {{count} atributo} other {{count} atributos}}",
"count_contacts": "{value, plural, one {# contato} other {# contatos} }", "count_contacts": "{count, plural, one {{count} contato} other {{count} contatos}}",
"count_responses": "{value, plural, other {# respostas}}", "count_members": "{count, plural, one {{count} membro} other {{count} membros}}",
"count_questions": "{count, plural, one {{count} pergunta} other {{count} perguntas}}",
"count_responses": "{count, plural, other {# respostas}}",
"count_selections": "{count, plural, one {{count} seleção} other {{count} seleções}}",
"create_new_organization": "Criar nova organização", "create_new_organization": "Criar nova organização",
"create_segment": "Criar segmento", "create_segment": "Criar segmento",
"create_survey": "Criar pesquisa", "create_survey": "Criar pesquisa",
@@ -191,6 +194,7 @@
"days": "dias", "days": "dias",
"default": "Padrão", "default": "Padrão",
"delete": "Apagar", "delete": "Apagar",
"delete_what": "Excluir {deleteWhat}",
"description": "Descrição", "description": "Descrição",
"dev_env": "Ambiente de Desenvolvimento", "dev_env": "Ambiente de Desenvolvimento",
"development": "Desenvolvimento", "development": "Desenvolvimento",
@@ -206,6 +210,8 @@
"download": "baixar", "download": "baixar",
"draft": "Rascunho", "draft": "Rascunho",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicate_copy": "(cópia)",
"duplicate_copy_number": "(cópia {copyNumber})",
"e_commerce": "comércio eletrônico", "e_commerce": "comércio eletrônico",
"edit": "Editar", "edit": "Editar",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Erro", "error": "Erro",
"error_component_description": "Esse recurso não existe ou você não tem permissão para acessá-lo.", "error_component_description": "Esse recurso não existe ou você não tem permissão para acessá-lo.",
"error_component_title": "Erro ao carregar recursos", "error_component_title": "Erro ao carregar recursos",
"error_loading_data": "Erro ao carregar dados",
"error_rate_limit_description": "Número máximo de requisições atingido. Por favor, tente novamente mais tarde.", "error_rate_limit_description": "Número máximo de requisições atingido. Por favor, tente novamente mais tarde.",
"error_rate_limit_title": "Limite de Taxa Excedido", "error_rate_limit_title": "Limite de Taxa Excedido",
"expand_rows": "Expandir linhas", "expand_rows": "Expandir linhas",
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência", "failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
"failed_to_load_organizations": "Falha ao carregar organizações", "failed_to_load_organizations": "Falha ao carregar organizações",
"failed_to_load_workspaces": "Falha ao carregar projetos", "failed_to_load_workspaces": "Falha ao carregar projetos",
"filter": "Filtro",
"finish": "Terminar", "finish": "Terminar",
"first_name": "Primeiro nome",
"follow_these": "Siga esses", "follow_these": "Siga esses",
"formbricks_version": "Versão do Formbricks", "formbricks_version": "Versão do Formbricks",
"full_name": "Nome completo", "full_name": "Nome completo",
@@ -237,6 +246,7 @@
"hidden_field": "Campo oculto", "hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos", "hidden_fields": "Campos ocultos",
"hide_column": "Ocultar coluna", "hide_column": "Ocultar coluna",
"id": "ID",
"image": "imagem", "image": "imagem",
"images": "Imagens", "images": "Imagens",
"import": "importar", "import": "importar",
@@ -254,6 +264,7 @@
"key": "Chave", "key": "Chave",
"label": "Etiqueta", "label": "Etiqueta",
"language": "Língua", "language": "Língua",
"last_name": "Sobrenome",
"learn_more": "Saiba mais", "learn_more": "Saiba mais",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "sobreposição leve", "light_overlay": "sobreposição leve",
@@ -268,7 +279,6 @@
"look_and_feel": "Aparência e Experiência", "look_and_feel": "Aparência e Experiência",
"manage": "gerenciar", "manage": "gerenciar",
"marketing": "marketing", "marketing": "marketing",
"member": "Membros",
"members": "Membros", "members": "Membros",
"members_and_teams": "Membros e equipes", "members_and_teams": "Membros e equipes",
"membership_not_found": "Assinatura não encontrada", "membership_not_found": "Assinatura não encontrada",
@@ -280,6 +290,7 @@
"move_down": "Descer", "move_down": "Descer",
"move_up": "Subir", "move_up": "Subir",
"multiple_languages": "Vários idiomas", "multiple_languages": "Vários idiomas",
"my_product": "meu produto",
"name": "Nome", "name": "Nome",
"new": "Novo", "new": "Novo",
"new_version_available": "Formbricks {version} chegou. Atualize agora!", "new_version_available": "Formbricks {version} chegou. Atualize agora!",
@@ -375,8 +386,6 @@
"select_teams": "Selecionar times", "select_teams": "Selecionar times",
"selected": "Selecionado", "selected": "Selecionado",
"selected_questions": "Perguntas selecionadas", "selected_questions": "Perguntas selecionadas",
"selection": "seleção",
"selections": "seleções",
"send_test_email": "Enviar e-mail de teste", "send_test_email": "Enviar e-mail de teste",
"session_not_found": "Sessão não encontrada", "session_not_found": "Sessão não encontrada",
"settings": "Configurações", "settings": "Configurações",
@@ -428,6 +437,7 @@
"top_right": "Canto Superior Direito", "top_right": "Canto Superior Direito",
"try_again": "Tenta de novo", "try_again": "Tenta de novo",
"type": "Tipo", "type": "Tipo",
"unknown_survey": "Pesquisa desconhecida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.", "unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "atualizar", "update": "atualizar",
"updated": "atualizado", "updated": "atualizado",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Atualizar contatos", "contacts_table_refresh": "Atualizar contatos",
"contacts_table_refresh_success": "Contatos atualizados com sucesso", "contacts_table_refresh_success": "Contatos atualizados com sucesso",
"create_attribute": "Criar atributo", "create_attribute": "Criar atributo",
"create_key": "Criar chave",
"create_new_attribute": "Criar novo atributo", "create_new_attribute": "Criar novo atributo",
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.", "create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
"custom_attributes": "Atributos personalizados", "custom_attributes": "Atributos personalizados",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Exibições",
"edit_attribute": "Editar atributo", "edit_attribute": "Editar atributo",
"edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.", "edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.",
"edit_attribute_values": "Editar atributos", "edit_attribute_values": "Editar atributos",
@@ -667,6 +677,7 @@
"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_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_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.", "invalid_number_format": "Formato de número inválido. Por favor, insira um número válido.",
"no_activity_yet": "Nenhuma atividade ainda",
"no_published_link_surveys_available": "Não há pesquisas de link publicadas disponíveis. Por favor, publique uma pesquisa de link primeiro.", "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_published_surveys": "Sem pesquisas publicadas",
"no_responses_found": "Nenhuma resposta encontrada", "no_responses_found": "Nenhuma resposta encontrada",
@@ -681,6 +692,8 @@
"select_a_survey": "Selecione uma pesquisa", "select_a_survey": "Selecione uma pesquisa",
"select_attribute": "Selecionar Atributo", "select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo", "select_attribute_key": "Selecionar chave de atributo",
"survey_viewed": "Pesquisa visualizada",
"survey_viewed_at": "Visualizada em",
"system_attributes": "Atributos do sistema", "system_attributes": "Atributos do sistema",
"unlock_contacts_description": "Gerencie contatos e envie pesquisas direcionadas", "unlock_contacts_description": "Gerencie contatos e envie pesquisas direcionadas",
"unlock_contacts_title": "Desbloqueie contatos com um plano superior", "unlock_contacts_title": "Desbloqueie contatos com um plano superior",
@@ -752,7 +765,12 @@
"link_google_sheet": "Link da Planilha do Google", "link_google_sheet": "Link da Planilha do Google",
"link_new_sheet": "Vincular nova planilha", "link_new_sheet": "Vincular nova planilha",
"no_integrations_yet": "Suas integrações do Google Sheets vão aparecer aqui assim que você adicioná-las. ⏲️", "no_integrations_yet": "Suas integrações do Google Sheets vão aparecer aqui assim que você adicioná-las. ⏲️",
"spreadsheet_url": "URL da planilha" "reconnect_button": "Reconectar",
"reconnect_button_description": "Sua conexão com o Google Sheets expirou. Reconecte para continuar sincronizando respostas. Seus links de planilhas e dados existentes serão preservados.",
"reconnect_button_tooltip": "Reconecte a integração para atualizar seu acesso. Seus links de planilhas e dados existentes serão preservados.",
"spreadsheet_permission_error": "Você não tem permissão para acessar esta planilha. Certifique-se de que a planilha está compartilhada com sua conta do Google e que você tem acesso de escrita à planilha.",
"spreadsheet_url": "URL da planilha",
"token_expired_error": "O token de atualização do Google Sheets expirou ou foi revogado. Reconecte a integração."
}, },
"include_created_at": "Incluir Data de Criação", "include_created_at": "Incluir Data de Criação",
"include_hidden_fields": "Incluir Campos Ocultos", "include_hidden_fields": "Incluir Campos Ocultos",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Oi {userName}", "email_customization_preview_email_heading": "Oi {userName}",
"email_customization_preview_email_text": "Esta é uma pré-visualização de e-mail para mostrar qual logo será renderizado nos e-mails.", "email_customization_preview_email_text": "Esta é uma pré-visualização de e-mail para mostrar qual logo será renderizado nos e-mails.",
"error_deleting_organization_please_try_again": "Erro ao deletar a organização. Por favor, tente novamente.", "error_deleting_organization_please_try_again": "Erro ao deletar a organização. Por favor, tente novamente.",
"from_your_organization": "da sua organização", "from_your_organization": "{memberName} da sua organização",
"invitation_sent_once_more": "Convite enviado de novo.", "invitation_sent_once_more": "Convite enviado de novo.",
"invite_deleted_successfully": "Convite deletado com sucesso", "invite_deleted_successfully": "Convite deletado com sucesso",
"invite_expires_on": "O convite expira em {date}", "invite_expires_on": "O convite expira em {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Adicionar um texto padrão para mostrar se a pergunta for ignorada:", "add_fallback_placeholder": "Adicionar um texto padrão para mostrar se a pergunta for ignorada:",
"add_hidden_field_id": "Adicionar campo oculto ID", "add_hidden_field_id": "Adicionar campo oculto ID",
"add_highlight_border": "Adicionar borda de destaque", "add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Aplica-se apenas a pesquisas no produto.",
"add_logic": "Adicionar lógica", "add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"", "add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"",
"add_option": "Adicionar opção", "add_option": "Adicionar opção",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Ajustar mensagem 'Pesquisa Encerrada''", "adjust_survey_closed_message": "Ajustar mensagem 'Pesquisa Encerrada''",
"adjust_survey_closed_message_description": "Mude a mensagem que os visitantes veem quando a pesquisa está fechada.", "adjust_survey_closed_message_description": "Mude a mensagem que os visitantes veem quando a pesquisa está fechada.",
"adjust_the_theme_in_the": "Ajuste o tema no", "adjust_the_theme_in_the": "Ajuste o tema no",
"all_are_true": "todas são verdadeiras",
"all_other_answers_will_continue_to": "Todas as outras respostas continuarão a", "all_other_answers_will_continue_to": "Todas as outras respostas continuarão a",
"allow_multi_select": "Permitir seleção múltipla", "allow_multi_select": "Permitir seleção múltipla",
"allow_multiple_files": "Permitir vários arquivos", "allow_multiple_files": "Permitir vários arquivos",
"allow_users_to_select_more_than_one_image": "Permitir que os usuários selecionem mais de uma imagem", "allow_users_to_select_more_than_one_image": "Permitir que os usuários selecionem mais de uma imagem",
"and_launch_surveys_in_your_website_or_app": "e lançar pesquisas no seu site ou app.", "and_launch_surveys_in_your_website_or_app": "e lançar pesquisas no seu site ou app.",
"animation": "animação", "animation": "animação",
"any_is_true": "qualquer uma é verdadeira",
"app_survey_description": "Embuta uma pesquisa no seu app ou site para coletar respostas.", "app_survey_description": "Embuta uma pesquisa no seu app ou site para coletar respostas.",
"assign": "atribuir =", "assign": "atribuir =",
"audience": "Público", "audience": "Público",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Acompanhamento atualizado e será salvo assim que você salvar a pesquisa.", "follow_ups_modal_updated_successfull_toast": "Acompanhamento atualizado e será salvo assim que você salvar a pesquisa.",
"follow_ups_new": "Novo acompanhamento", "follow_ups_new": "Novo acompanhamento",
"follow_ups_upgrade_button_text": "Atualize para habilitar os Acompanhamentos", "follow_ups_upgrade_button_text": "Atualize para habilitar os Acompanhamentos",
"form_styling": "Estilização de Formulários",
"formbricks_sdk_is_not_connected": "O SDK do Formbricks não está conectado", "formbricks_sdk_is_not_connected": "O SDK do Formbricks não está conectado",
"four_points": "4 pontos", "four_points": "4 pontos",
"heading": "Título", "heading": "Título",
@@ -1520,7 +1540,7 @@
"option_idx": "Opção {choiceIndex}", "option_idx": "Opção {choiceIndex}",
"option_used_in_logic_error": "Esta opção é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "option_used_in_logic_error": "Esta opção é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"optional": "Opcional", "optional": "Opcional",
"options": "Opções", "options": "Opções*",
"options_used_in_logic_bulk_error": "As seguintes opções são usadas na lógica: {questionIndexes}. Por favor, remova-as da lógica primeiro.", "options_used_in_logic_bulk_error": "As seguintes opções são usadas na lógica: {questionIndexes}. Por favor, remova-as da lógica primeiro.",
"override_theme_with_individual_styles_for_this_survey": "Substitua o tema com estilos individuais para essa pesquisa.", "override_theme_with_individual_styles_for_this_survey": "Substitua o tema com estilos individuais para essa pesquisa.",
"overwrite_global_waiting_time": "Definir período de espera personalizado", "overwrite_global_waiting_time": "Definir período de espera personalizado",
@@ -1545,6 +1565,7 @@
"question_deleted": "Pergunta deletada.", "question_deleted": "Pergunta deletada.",
"question_duplicated": "Pergunta duplicada.", "question_duplicated": "Pergunta duplicada.",
"question_id_updated": "ID da pergunta atualizado", "question_id_updated": "ID da pergunta atualizado",
"question_number": "Pergunta {number}",
"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_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_logic_warning_title": "Inconsistência de lógica",
"question_used_in_quota": "Esta pergunta está sendo usada na cota \"{quotaName}\"", "question_used_in_quota": "Esta pergunta está sendo usada na cota \"{quotaName}\"",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.", "response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta", "response_options": "Opções de Resposta",
"roundness": "Circularidade", "roundness": "Circularidade",
"roundness_description": "Controla o arredondamento dos cantos do cartão.", "roundness_description": "Controla o arredondamento dos cantos.",
"row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"rows": "linhas", "rows": "linhas",
"save_and_close": "Salvar e Fechar", "save_and_close": "Salvar e Fechar",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Essa pesquisa gratuita e de código aberto foi encerrada", "survey_completed_subheading": "Essa pesquisa gratuita e de código aberto foi encerrada",
"survey_display_settings": "Configurações de Exibição da Pesquisa", "survey_display_settings": "Configurações de Exibição da Pesquisa",
"survey_placement": "Posicionamento da Pesquisa", "survey_placement": "Posicionamento da Pesquisa",
"survey_styling": "Estilização de Formulários",
"survey_trigger": "Gatilho de Pesquisa", "survey_trigger": "Gatilho de Pesquisa",
"switch_multi_language_on_to_get_started": "Ative o modo multilíngue para começar 👉", "switch_multi_language_on_to_get_started": "Ative o modo multilíngue para começar 👉",
"target_block_not_found": "Bloco de destino não encontrado", "target_block_not_found": "Bloco de destino não encontrado",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Período de espera (entre pesquisas)", "waiting_time_across_surveys": "Período de espera (entre pesquisas)",
"waiting_time_across_surveys_description": "Para evitar fadiga de pesquisas, escolha como esta pesquisa interage com o período de espera geral do workspace.", "waiting_time_across_surveys_description": "Para evitar fadiga de pesquisas, escolha como esta pesquisa interage com o período de espera geral do workspace.",
"welcome_message": "Mensagem de boas-vindas", "welcome_message": "Mensagem de boas-vindas",
"when": "Quando",
"without_a_filter_all_of_your_users_can_be_surveyed": "Sem um filtro, todos os seus usuários podem ser pesquisados.", "without_a_filter_all_of_your_users_can_be_surveyed": "Sem um filtro, todos os seus usuários podem ser pesquisados.",
"you_have_not_created_a_segment_yet": "Você ainda não criou um segmento.", "you_have_not_created_a_segment_yet": "Você ainda não criou um segmento.",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Você precisa ter dois ou mais idiomas configurados em seu espaço de trabalho para trabalhar com traduções.",
"your_description_here_recall_information_with": "Sua descrição aqui. Lembre-se de informações com @", "your_description_here_recall_information_with": "Sua descrição aqui. Lembre-se de informações com @",
"your_question_here_recall_information_with": "Sua pergunta aqui. Lembre-se de informações com @", "your_question_here_recall_information_with": "Sua pergunta aqui. Lembre-se de informações com @",
"your_web_app": "Sua aplicação web", "your_web_app": "Sua aplicação web",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Respostas filtradas (Excel)", "filtered_responses_excel": "Respostas filtradas (Excel)",
"generating_qr_code": "Gerando código QR", "generating_qr_code": "Gerando código QR",
"impressions": "Impressões", "impressions": "Impressões",
"impressions_identified_only": "Mostrando apenas impressões de contatos identificados",
"impressions_tooltip": "Número de vezes que a pesquisa foi visualizada.", "impressions_tooltip": "Número de vezes que a pesquisa foi visualizada.",
"in_app": { "in_app": {
"connection_description": "A pesquisa será exibida para usuários do seu site, que atendam aos critérios listados abaixo", "connection_description": "A pesquisa será exibida para usuários do seu site, que atendam aos critérios listados abaixo",
@@ -1989,6 +2012,7 @@
"last_quarter": "Último trimestre", "last_quarter": "Último trimestre",
"last_year": "Último ano", "last_year": "Último ano",
"limit": "Limite", "limit": "Limite",
"no_identified_impressions": "Nenhuma impressão de contatos identificados",
"no_responses_found": "Nenhuma resposta encontrada", "no_responses_found": "Nenhuma resposta encontrada",
"other_values_found": "Outros valores encontrados", "other_values_found": "Outros valores encontrados",
"overall": "No geral", "overall": "No geral",
@@ -2011,6 +2035,7 @@
"starts": "começa", "starts": "começa",
"starts_tooltip": "Número de vezes que a pesquisa foi iniciada.", "starts_tooltip": "Número de vezes que a pesquisa foi iniciada.",
"survey_reset_successfully": "Pesquisa redefinida com sucesso! {responseCount} respostas e {displayCount} exibições foram deletadas.", "survey_reset_successfully": "Pesquisa redefinida com sucesso! {responseCount} respostas e {displayCount} exibições foram deletadas.",
"survey_results": "Resultados de {surveyName}",
"this_month": "Este mês", "this_month": "Este mês",
"this_quarter": "Este trimestre", "this_quarter": "Este trimestre",
"this_year": "Este ano", "this_year": "Este ano",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.", "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_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_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_height_description": "Controla a altura mínima da entrada.",
"advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.", "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_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_placeholder_opacity_description": "Esmaece o texto de dica do placeholder.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos 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": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.", "advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border": "Cor da borda",
"advanced_styling_field_option_border_description": "Contorna as opções de botões de rádio e caixas de seleção.",
"advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.", "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_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": "Cor do rótulo",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Como a empresa pode melhorar sua visão e direcionamento estratégico?", "alignment_and_engagement_survey_question_4_headline": "Como a empresa pode melhorar sua visão e direcionamento estratégico?",
"alignment_and_engagement_survey_question_4_placeholder": "Digite sua resposta aqui...", "alignment_and_engagement_survey_question_4_placeholder": "Digite sua resposta aqui...",
"back": "voltar", "back": "voltar",
"block_1": "Bloco 1",
"block_10": "Bloco 10",
"block_2": "Bloco 2",
"block_3": "Bloco 3",
"block_4": "Bloco 4",
"block_5": "Bloco 5",
"block_6": "Bloco 6",
"block_7": "Bloco 7",
"block_8": "Bloco 8",
"block_9": "Bloco 9",
"book_interview": "Marcar entrevista", "book_interview": "Marcar entrevista",
"build_product_roadmap_description": "Identifique a ÚNICA coisa que seus usuários mais querem e construa isso.", "build_product_roadmap_description": "Identifique a ÚNICA coisa que seus usuários mais querem e construa isso.",
"build_product_roadmap_name": "Construir Roteiro do Produto", "build_product_roadmap_name": "Construir Roteiro do Produto",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Ah, foi mal! Tem algo que a gente possa fazer pra melhorar sua experiência?", "csat_survey_question_3_headline": "Ah, foi mal! Tem algo que a gente possa fazer pra melhorar sua experiência?",
"csat_survey_question_3_placeholder": "Digite sua resposta aqui...", "csat_survey_question_3_placeholder": "Digite sua resposta aqui...",
"cta_description": "Mostrar informações e pedir para os usuários tomarem uma ação específica", "cta_description": "Mostrar informações e pedir para os usuários tomarem uma ação específica",
"custom_survey_block_1_name": "Bloco 1",
"custom_survey_description": "Crie uma pesquisa sem modelo.", "custom_survey_description": "Crie uma pesquisa sem modelo.",
"custom_survey_name": "Começar do zero", "custom_survey_name": "Começar do zero",
"custom_survey_question_1_headline": "O que você gostaria de saber?", "custom_survey_question_1_headline": "O que você gostaria de saber?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Não, obrigado!", "preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer ficar por dentro?", "preview_survey_question_2_headline": "Quer ficar por dentro?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.", "preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_question_open_text_headline": "Tem mais alguma coisa que você gostaria de compartilhar?",
"preview_survey_question_open_text_placeholder": "Digite sua resposta aqui...",
"preview_survey_question_open_text_subheader": "Seu feedback nos ajuda a melhorar.",
"preview_survey_welcome_card_headline": "Bem-vindo!", "preview_survey_welcome_card_headline": "Bem-vindo!",
"prioritize_features_description": "Identifique os recursos que seus usuários mais e menos precisam.", "prioritize_features_description": "Identifique os recursos que seus usuários mais e menos precisam.",
"prioritize_features_name": "Priorizar Funcionalidades", "prioritize_features_name": "Priorizar Funcionalidades",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Copiar", "copy": "Copiar",
"copy_code": "Copiar código", "copy_code": "Copiar código",
"copy_link": "Copiar Link", "copy_link": "Copiar Link",
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}", "count_attributes": "{count, plural, one {{count} atributo} other {{count} atributos}}",
"count_contacts": "{value, plural, one {# contacto} other {# contactos} }", "count_contacts": "{count, plural, one {{count} contacto} other {{count} contactos}}",
"count_responses": "{value, plural, other {# respostas}}", "count_members": "{count, plural, one {{count} membro} other {{count} membros}}",
"count_questions": "{count, plural, one {{count} pergunta} other {{count} perguntas}}",
"count_responses": "{count, plural, other {# respostas}}",
"count_selections": "{count, plural, one {{count} seleção} other {{count} seleções}}",
"create_new_organization": "Criar nova organização", "create_new_organization": "Criar nova organização",
"create_segment": "Criar segmento", "create_segment": "Criar segmento",
"create_survey": "Criar inquérito", "create_survey": "Criar inquérito",
@@ -191,6 +194,7 @@
"days": "dias", "days": "dias",
"default": "Padrão", "default": "Padrão",
"delete": "Eliminar", "delete": "Eliminar",
"delete_what": "Eliminar {deleteWhat}",
"description": "Descrição", "description": "Descrição",
"dev_env": "Ambiente de Desenvolvimento", "dev_env": "Ambiente de Desenvolvimento",
"development": "Desenvolvimento", "development": "Desenvolvimento",
@@ -206,6 +210,8 @@
"download": "Transferir", "download": "Transferir",
"draft": "Rascunho", "draft": "Rascunho",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicate_copy": "(cópia)",
"duplicate_copy_number": "(cópia {copyNumber})",
"e_commerce": "Comércio Eletrónico", "e_commerce": "Comércio Eletrónico",
"edit": "Editar", "edit": "Editar",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Erro", "error": "Erro",
"error_component_description": "Este recurso não existe ou não tem os direitos necessários para aceder a ele.", "error_component_description": "Este recurso não existe ou não tem os direitos necessários para aceder a ele.",
"error_component_title": "Erro ao carregar recursos", "error_component_title": "Erro ao carregar recursos",
"error_loading_data": "Erro ao carregar dados",
"error_rate_limit_description": "Número máximo de pedidos alcançado. Por favor, tente novamente mais tarde.", "error_rate_limit_description": "Número máximo de pedidos alcançado. Por favor, tente novamente mais tarde.",
"error_rate_limit_title": "Limite de Taxa Excedido", "error_rate_limit_title": "Limite de Taxa Excedido",
"expand_rows": "Expandir linhas", "expand_rows": "Expandir linhas",
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência", "failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
"failed_to_load_organizations": "Falha ao carregar organizações", "failed_to_load_organizations": "Falha ao carregar organizações",
"failed_to_load_workspaces": "Falha ao carregar projetos", "failed_to_load_workspaces": "Falha ao carregar projetos",
"filter": "Filtro",
"finish": "Concluir", "finish": "Concluir",
"first_name": "Primeiro nome",
"follow_these": "Siga estes", "follow_these": "Siga estes",
"formbricks_version": "Versão do Formbricks", "formbricks_version": "Versão do Formbricks",
"full_name": "Nome completo", "full_name": "Nome completo",
@@ -237,6 +246,7 @@
"hidden_field": "Campo oculto", "hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos", "hidden_fields": "Campos ocultos",
"hide_column": "Ocultar coluna", "hide_column": "Ocultar coluna",
"id": "ID",
"image": "Imagem", "image": "Imagem",
"images": "Imagens", "images": "Imagens",
"import": "Importar", "import": "Importar",
@@ -254,6 +264,7 @@
"key": "Chave", "key": "Chave",
"label": "Etiqueta", "label": "Etiqueta",
"language": "Idioma", "language": "Idioma",
"last_name": "Apelido",
"learn_more": "Saiba mais", "learn_more": "Saiba mais",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Sobreposição leve", "light_overlay": "Sobreposição leve",
@@ -268,7 +279,6 @@
"look_and_feel": "Aparência e Sensação", "look_and_feel": "Aparência e Sensação",
"manage": "Gerir", "manage": "Gerir",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Membro",
"members": "Membros", "members": "Membros",
"members_and_teams": "Membros e equipas", "members_and_teams": "Membros e equipas",
"membership_not_found": "Associação não encontrada", "membership_not_found": "Associação não encontrada",
@@ -280,6 +290,7 @@
"move_down": "Mover para baixo", "move_down": "Mover para baixo",
"move_up": "Mover para cima", "move_up": "Mover para cima",
"multiple_languages": "Várias línguas", "multiple_languages": "Várias línguas",
"my_product": "o meu produto",
"name": "Nome", "name": "Nome",
"new": "Novo", "new": "Novo",
"new_version_available": "Formbricks {version} está aqui. Atualize agora!", "new_version_available": "Formbricks {version} está aqui. Atualize agora!",
@@ -375,8 +386,6 @@
"select_teams": "Selecionar equipas", "select_teams": "Selecionar equipas",
"selected": "Selecionado", "selected": "Selecionado",
"selected_questions": "Perguntas selecionadas", "selected_questions": "Perguntas selecionadas",
"selection": "Seleção",
"selections": "Seleções",
"send_test_email": "Enviar email de teste", "send_test_email": "Enviar email de teste",
"session_not_found": "Sessão não encontrada", "session_not_found": "Sessão não encontrada",
"settings": "Configurações", "settings": "Configurações",
@@ -428,6 +437,7 @@
"top_right": "Superior Direito", "top_right": "Superior Direito",
"try_again": "Tente novamente", "try_again": "Tente novamente",
"type": "Tipo", "type": "Tipo",
"unknown_survey": "Inquérito desconhecido",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.", "unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "Atualizar", "update": "Atualizar",
"updated": "Atualizado", "updated": "Atualizado",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Atualizar contactos", "contacts_table_refresh": "Atualizar contactos",
"contacts_table_refresh_success": "Contactos atualizados com sucesso", "contacts_table_refresh_success": "Contactos atualizados com sucesso",
"create_attribute": "Criar atributo", "create_attribute": "Criar atributo",
"create_key": "Criar chave",
"create_new_attribute": "Criar novo atributo", "create_new_attribute": "Criar novo atributo",
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.", "create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
"custom_attributes": "Atributos personalizados", "custom_attributes": "Atributos personalizados",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Visualizações",
"edit_attribute": "Editar atributo", "edit_attribute": "Editar atributo",
"edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.", "edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.",
"edit_attribute_values": "Editar atributos", "edit_attribute_values": "Editar atributos",
@@ -667,6 +677,7 @@
"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_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_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.", "invalid_number_format": "Formato de número inválido. Por favor, introduz um número válido.",
"no_activity_yet": "Ainda sem atividade",
"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_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_published_surveys": "Sem inquéritos publicados",
"no_responses_found": "Nenhuma resposta encontrada", "no_responses_found": "Nenhuma resposta encontrada",
@@ -681,6 +692,8 @@
"select_a_survey": "Selecione um inquérito", "select_a_survey": "Selecione um inquérito",
"select_attribute": "Selecionar Atributo", "select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo", "select_attribute_key": "Selecionar chave de atributo",
"survey_viewed": "Inquérito visualizado",
"survey_viewed_at": "Visualizado em",
"system_attributes": "Atributos do sistema", "system_attributes": "Atributos do sistema",
"unlock_contacts_description": "Gerir contactos e enviar inquéritos direcionados", "unlock_contacts_description": "Gerir contactos e enviar inquéritos direcionados",
"unlock_contacts_title": "Desbloqueie os contactos com um plano superior", "unlock_contacts_title": "Desbloqueie os contactos com um plano superior",
@@ -752,7 +765,12 @@
"link_google_sheet": "Ligar Folha do Google", "link_google_sheet": "Ligar Folha do Google",
"link_new_sheet": "Ligar nova Folha", "link_new_sheet": "Ligar nova Folha",
"no_integrations_yet": "As suas integrações com o Google Sheets aparecerão aqui assim que as adicionar. ⏲️", "no_integrations_yet": "As suas integrações com o Google Sheets aparecerão aqui assim que as adicionar. ⏲️",
"spreadsheet_url": "URL da folha de cálculo" "reconnect_button": "Reconectar",
"reconnect_button_description": "A tua ligação ao Google Sheets expirou. Por favor, reconecta para continuar a sincronizar respostas. As tuas ligações de folhas de cálculo e dados existentes serão preservados.",
"reconnect_button_tooltip": "Reconecta a integração para atualizar o teu acesso. As tuas ligações de folhas de cálculo e dados existentes serão preservados.",
"spreadsheet_permission_error": "Não tens permissão para aceder a esta folha de cálculo. Por favor, certifica-te de que a folha de cálculo está partilhada com a tua conta Google e que tens acesso de escrita à folha de cálculo.",
"spreadsheet_url": "URL da folha de cálculo",
"token_expired_error": "O token de atualização do Google Sheets expirou ou foi revogado. Por favor, reconecta a integração."
}, },
"include_created_at": "Incluir Criado Em", "include_created_at": "Incluir Criado Em",
"include_hidden_fields": "Incluir Campos Ocultos", "include_hidden_fields": "Incluir Campos Ocultos",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Olá {userName}", "email_customization_preview_email_heading": "Olá {userName}",
"email_customization_preview_email_text": "Esta é uma pré-visualização de email para mostrar qual logotipo será exibido nos emails.", "email_customization_preview_email_text": "Esta é uma pré-visualização de email para mostrar qual logotipo será exibido nos emails.",
"error_deleting_organization_please_try_again": "Erro ao eliminar a organização. Por favor, tente novamente.", "error_deleting_organization_please_try_again": "Erro ao eliminar a organização. Por favor, tente novamente.",
"from_your_organization": "da sua organização", "from_your_organization": "{memberName} da sua organização",
"invitation_sent_once_more": "Convite enviado mais uma vez.", "invitation_sent_once_more": "Convite enviado mais uma vez.",
"invite_deleted_successfully": "Convite eliminado com sucesso", "invite_deleted_successfully": "Convite eliminado com sucesso",
"invite_expires_on": "O convite expira em {date}", "invite_expires_on": "O convite expira em {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Adicionar um espaço reservado para mostrar se não houver valor para recordar.", "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_hidden_field_id": "Adicionar ID do campo oculto",
"add_highlight_border": "Adicionar borda de destaque", "add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Aplica-se apenas a inquéritos no produto.",
"add_logic": "Adicionar lógica", "add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"", "add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"",
"add_option": "Adicionar opção", "add_option": "Adicionar opção",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Ajustar mensagem de 'Inquérito Fechado'", "adjust_survey_closed_message": "Ajustar mensagem de 'Inquérito Fechado'",
"adjust_survey_closed_message_description": "Alterar a mensagem que os visitantes veem quando o inquérito está fechado.", "adjust_survey_closed_message_description": "Alterar a mensagem que os visitantes veem quando o inquérito está fechado.",
"adjust_the_theme_in_the": "Ajustar o tema no", "adjust_the_theme_in_the": "Ajustar o tema no",
"all_are_true": "todas são verdadeiras",
"all_other_answers_will_continue_to": "Todas as outras respostas continuarão a", "all_other_answers_will_continue_to": "Todas as outras respostas continuarão a",
"allow_multi_select": "Permitir seleção múltipla", "allow_multi_select": "Permitir seleção múltipla",
"allow_multiple_files": "Permitir vários ficheiros", "allow_multiple_files": "Permitir vários ficheiros",
"allow_users_to_select_more_than_one_image": "Permitir aos utilizadores selecionar mais do que uma imagem", "allow_users_to_select_more_than_one_image": "Permitir aos utilizadores selecionar mais do que uma imagem",
"and_launch_surveys_in_your_website_or_app": "e lance inquéritos no seu site ou aplicação.", "and_launch_surveys_in_your_website_or_app": "e lance inquéritos no seu site ou aplicação.",
"animation": "Animação", "animation": "Animação",
"any_is_true": "qualquer uma é verdadeira",
"app_survey_description": "Incorpore um inquérito na sua aplicação web ou site para recolher respostas.", "app_survey_description": "Incorpore um inquérito na sua aplicação web ou site para recolher respostas.",
"assign": "Atribuir =", "assign": "Atribuir =",
"audience": "Público", "audience": "Público",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Seguimento atualizado e será guardado assim que guardar o questionário.", "follow_ups_modal_updated_successfull_toast": "Seguimento atualizado e será guardado assim que guardar o questionário.",
"follow_ups_new": "Novo acompanhamento", "follow_ups_new": "Novo acompanhamento",
"follow_ups_upgrade_button_text": "Atualize para ativar os acompanhamentos", "follow_ups_upgrade_button_text": "Atualize para ativar os acompanhamentos",
"form_styling": "Estilo do formulário",
"formbricks_sdk_is_not_connected": "O SDK do Formbricks não está conectado", "formbricks_sdk_is_not_connected": "O SDK do Formbricks não está conectado",
"four_points": "4 pontos", "four_points": "4 pontos",
"heading": "Cabeçalho", "heading": "Cabeçalho",
@@ -1520,7 +1540,7 @@
"option_idx": "Opção {choiceIndex}", "option_idx": "Opção {choiceIndex}",
"option_used_in_logic_error": "Esta opção é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "option_used_in_logic_error": "Esta opção é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"optional": "Opcional", "optional": "Opcional",
"options": "Opções", "options": "Opções*",
"options_used_in_logic_bulk_error": "As seguintes opções são usadas na lógica: {questionIndexes}. Por favor, remova-as da lógica primeiro.", "options_used_in_logic_bulk_error": "As seguintes opções são usadas na lógica: {questionIndexes}. Por favor, remova-as da lógica primeiro.",
"override_theme_with_individual_styles_for_this_survey": "Substituir o tema com estilos individuais para este inquérito.", "override_theme_with_individual_styles_for_this_survey": "Substituir o tema com estilos individuais para este inquérito.",
"overwrite_global_waiting_time": "Definir período de espera personalizado", "overwrite_global_waiting_time": "Definir período de espera personalizado",
@@ -1545,6 +1565,7 @@
"question_deleted": "Pergunta eliminada.", "question_deleted": "Pergunta eliminada.",
"question_duplicated": "Pergunta duplicada.", "question_duplicated": "Pergunta duplicada.",
"question_id_updated": "ID da pergunta atualizado", "question_id_updated": "ID da pergunta atualizado",
"question_number": "Pergunta {number}",
"question_used_in_logic_warning_text": "Os elementos deste bloco são utilizados numa regra de lógica, tem a certeza de que pretende eliminá-lo?", "question_used_in_logic_warning_text": "Os elementos deste bloco são utilizados numa regra de lógica, tem a certeza de que pretende eliminá-lo?",
"question_used_in_logic_warning_title": "Inconsistência de lógica", "question_used_in_logic_warning_title": "Inconsistência de lógica",
"question_used_in_quota": "Esta pergunta está a ser usada na quota \"{quotaName}\"", "question_used_in_quota": "Esta pergunta está a ser usada na quota \"{quotaName}\"",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.", "response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta", "response_options": "Opções de Resposta",
"roundness": "Arredondamento", "roundness": "Arredondamento",
"roundness_description": "Controla o arredondamento dos cantos do cartão.", "roundness_description": "Controla o arredondamento dos cantos.",
"row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.", "row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"rows": "Linhas", "rows": "Linhas",
"save_and_close": "Guardar e Fechar", "save_and_close": "Guardar e Fechar",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Este inquérito gratuito e de código aberto foi encerrado", "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", "survey_display_settings": "Configurações de Exibição do Inquérito",
"survey_placement": "Colocação do Inquérito", "survey_placement": "Colocação do Inquérito",
"survey_styling": "Estilo do formulário",
"survey_trigger": "Desencadeador de Inquérito", "survey_trigger": "Desencadeador de Inquérito",
"switch_multi_language_on_to_get_started": "Ative o modo multilingue para começar 👉", "switch_multi_language_on_to_get_started": "Ative o modo multilingue para começar 👉",
"target_block_not_found": "Bloco de destino não encontrado", "target_block_not_found": "Bloco de destino não encontrado",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Período de espera (entre inquéritos)", "waiting_time_across_surveys": "Período de espera (entre inquéritos)",
"waiting_time_across_surveys_description": "Para prevenir fadiga de inquéritos, escolha como este inquérito interage com o período de espera geral do espaço de trabalho.", "waiting_time_across_surveys_description": "Para prevenir fadiga de inquéritos, escolha como este inquérito interage com o período de espera geral do espaço de trabalho.",
"welcome_message": "Mensagem de boas-vindas", "welcome_message": "Mensagem de boas-vindas",
"when": "Quando",
"without_a_filter_all_of_your_users_can_be_surveyed": "Sem um filtro, todos os seus utilizadores podem ser pesquisados.", "without_a_filter_all_of_your_users_can_be_surveyed": "Sem um filtro, todos os seus utilizadores podem ser pesquisados.",
"you_have_not_created_a_segment_yet": "Ainda não criou um segmento", "you_have_not_created_a_segment_yet": "Ainda não criou um segmento",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Precisa de ter dois ou mais idiomas configurados no seu espaço de trabalho para trabalhar com traduções.",
"your_description_here_recall_information_with": "A sua descrição aqui. Recorde a informação com @", "your_description_here_recall_information_with": "A sua descrição aqui. Recorde a informação com @",
"your_question_here_recall_information_with": "A sua pergunta aqui. Recorde a informação com @", "your_question_here_recall_information_with": "A sua pergunta aqui. Recorde a informação com @",
"your_web_app": "A sua aplicação web", "your_web_app": "A sua aplicação web",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Respostas filtradas (Excel)", "filtered_responses_excel": "Respostas filtradas (Excel)",
"generating_qr_code": "A gerar código QR", "generating_qr_code": "A gerar código QR",
"impressions": "Impressões", "impressions": "Impressões",
"impressions_identified_only": "A mostrar apenas impressões de contactos identificados",
"impressions_tooltip": "Número de vezes que o inquérito foi visualizado.", "impressions_tooltip": "Número de vezes que o inquérito foi visualizado.",
"in_app": { "in_app": {
"connection_description": "O questionário será exibido aos utilizadores do seu website que correspondam aos critérios listados abaixo", "connection_description": "O questionário será exibido aos utilizadores do seu website que correspondam aos critérios listados abaixo",
@@ -1989,6 +2012,7 @@
"last_quarter": "Último trimestre", "last_quarter": "Último trimestre",
"last_year": "Ano passado", "last_year": "Ano passado",
"limit": "Limite", "limit": "Limite",
"no_identified_impressions": "Sem impressões de contactos identificados",
"no_responses_found": "Nenhuma resposta encontrada", "no_responses_found": "Nenhuma resposta encontrada",
"other_values_found": "Outros valores encontrados", "other_values_found": "Outros valores encontrados",
"overall": "Geral", "overall": "Geral",
@@ -2011,6 +2035,7 @@
"starts": "Começa", "starts": "Começa",
"starts_tooltip": "Número de vezes que o inquérito foi iniciado.", "starts_tooltip": "Número de vezes que o inquérito foi iniciado.",
"survey_reset_successfully": "Inquérito reiniciado com sucesso! {responseCount} respostas e {displayCount} exibições foram eliminadas.", "survey_reset_successfully": "Inquérito reiniciado com sucesso! {responseCount} respostas e {displayCount} exibições foram eliminadas.",
"survey_results": "Resultados de {surveyName}",
"this_month": "Este mês", "this_month": "Este mês",
"this_quarter": "Este trimestre", "this_quarter": "Este trimestre",
"this_year": "Este ano", "this_year": "Este ano",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.", "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_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_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_height_description": "Controla a altura mínima da entrada.",
"advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.", "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_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_placeholder_opacity_description": "Atenua o texto de sugestão do placeholder.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos 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": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.", "advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border": "Cor do contorno",
"advanced_styling_field_option_border_description": "Contorna as opções de botões de rádio e caixas de seleção.",
"advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.", "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_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": "Cor da etiqueta",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Como pode a empresa melhorar o alinhamento da sua visão e estratégia?", "alignment_and_engagement_survey_question_4_headline": "Como pode a empresa melhorar o alinhamento da sua visão e estratégia?",
"alignment_and_engagement_survey_question_4_placeholder": "Escreva a sua resposta aqui...", "alignment_and_engagement_survey_question_4_placeholder": "Escreva a sua resposta aqui...",
"back": "Voltar", "back": "Voltar",
"block_1": "Bloco 1",
"block_10": "Bloco 10",
"block_2": "Bloco 2",
"block_3": "Bloco 3",
"block_4": "Bloco 4",
"block_5": "Bloco 5",
"block_6": "Bloco 6",
"block_7": "Bloco 7",
"block_8": "Bloco 8",
"block_9": "Bloco 9",
"book_interview": "Agendar entrevista", "book_interview": "Agendar entrevista",
"build_product_roadmap_description": "Identifique a ÚNICA coisa que os seus utilizadores mais querem e construa-a.", "build_product_roadmap_description": "Identifique a ÚNICA coisa que os seus utilizadores mais querem e construa-a.",
"build_product_roadmap_name": "Construir Roteiro do Produto", "build_product_roadmap_name": "Construir Roteiro do Produto",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Oh, desculpe! Há algo que possamos fazer para melhorar a sua experiência?", "csat_survey_question_3_headline": "Oh, desculpe! Há algo que possamos fazer para melhorar a sua experiência?",
"csat_survey_question_3_placeholder": "Escreva a sua resposta aqui...", "csat_survey_question_3_placeholder": "Escreva a sua resposta aqui...",
"cta_description": "Exibir informações e solicitar aos utilizadores que tomem uma ação específica", "cta_description": "Exibir informações e solicitar aos utilizadores que tomem uma ação específica",
"custom_survey_block_1_name": "Bloco 1",
"custom_survey_description": "Crie um inquérito sem modelo.", "custom_survey_description": "Crie um inquérito sem modelo.",
"custom_survey_name": "Começar do zero", "custom_survey_name": "Começar do zero",
"custom_survey_question_1_headline": "O que gostaria de saber?", "custom_survey_question_1_headline": "O que gostaria de saber?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Não, obrigado!", "preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer manter-se atualizado?", "preview_survey_question_2_headline": "Quer manter-se atualizado?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.", "preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_question_open_text_headline": "Mais alguma coisa que gostaria de partilhar?",
"preview_survey_question_open_text_placeholder": "Escreva a sua resposta aqui...",
"preview_survey_question_open_text_subheader": "O seu feedback ajuda-nos a melhorar.",
"preview_survey_welcome_card_headline": "Bem-vindo!", "preview_survey_welcome_card_headline": "Bem-vindo!",
"prioritize_features_description": "Identifique as funcionalidades que os seus utilizadores precisam mais e menos.", "prioritize_features_description": "Identifique as funcionalidades que os seus utilizadores precisam mais e menos.",
"prioritize_features_name": "Priorizar Funcionalidades", "prioritize_features_name": "Priorizar Funcionalidades",
+53 -14
View File
@@ -175,9 +175,12 @@
"copy": "Copiază", "copy": "Copiază",
"copy_code": "Copiază codul", "copy_code": "Copiază codul",
"copy_link": "Copiază legătura", "copy_link": "Copiază legătura",
"count_attributes": "{value, plural, one {{value} atribut} few {{value} atribute} other {{value} de atribute}}", "count_attributes": "{count, plural, one {{count} atribut} few {{count} atribute} other {{count} de atribute}}",
"count_contacts": "{value, plural, one {# contact} other {# contacte} }", "count_contacts": "{count, plural, one {{count} contact} few {{count} contacte} other {{count} de contacte}}",
"count_responses": "{value, plural, one {# răspuns} other {# răspunsuri} }", "count_members": "{count, plural, one {{count} membru} few {{count} membri} other {{count} de membri}}",
"count_questions": "{count, plural, one {# întrebare} few {# întrebări} other {# de întrebări}}",
"count_responses": "{count, plural, one {# răspuns} other {# răspunsuri} }",
"count_selections": "{count, plural, one {{count} selecție} few {{count} selecții} other {{count} de selecții}}",
"create_new_organization": "Creează organizație nouă", "create_new_organization": "Creează organizație nouă",
"create_segment": "Creați segment", "create_segment": "Creați segment",
"create_survey": "Creează sondaj", "create_survey": "Creează sondaj",
@@ -191,6 +194,7 @@
"days": "zile", "days": "zile",
"default": "Implicit", "default": "Implicit",
"delete": "Șterge", "delete": "Șterge",
"delete_what": "Șterge {deleteWhat}",
"description": "Descriere", "description": "Descriere",
"dev_env": "Mediu de dezvoltare", "dev_env": "Mediu de dezvoltare",
"development": "Dezvoltare", "development": "Dezvoltare",
@@ -206,6 +210,8 @@
"download": "Descărcare", "download": "Descărcare",
"draft": "Schiță", "draft": "Schiță",
"duplicate": "Duplicități", "duplicate": "Duplicități",
"duplicate_copy": "(copie)",
"duplicate_copy_number": "(copie {copyNumber})",
"e_commerce": "Comerț electronic", "e_commerce": "Comerț electronic",
"edit": "Editare", "edit": "Editare",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Eroare", "error": "Eroare",
"error_component_description": "Această resursă nu există sau nu aveți drepturile necesare pentru a o accesa.", "error_component_description": "Această resursă nu există sau nu aveți drepturile necesare pentru a o accesa.",
"error_component_title": "Eroare la încărcarea resurselor", "error_component_title": "Eroare la încărcarea resurselor",
"error_loading_data": "Eroare la încărcarea datelor",
"error_rate_limit_description": "Numărul maxim de cereri atins. Vă rugăm să încercați din nou mai târziu.", "error_rate_limit_description": "Numărul maxim de cereri atins. Vă rugăm să încercați din nou mai târziu.",
"error_rate_limit_title": "Limită de cereri depășită", "error_rate_limit_title": "Limită de cereri depășită",
"expand_rows": "Extinde rândurile", "expand_rows": "Extinde rândurile",
"failed_to_copy_to_clipboard": "Nu s-a reușit copierea în clipboard", "failed_to_copy_to_clipboard": "Nu s-a reușit copierea în clipboard",
"failed_to_load_organizations": "Nu s-a reușit încărcarea organizațiilor", "failed_to_load_organizations": "Nu s-a reușit încărcarea organizațiilor",
"failed_to_load_workspaces": "Nu s-au putut încărca workspaces", "failed_to_load_workspaces": "Nu s-au putut încărca workspaces",
"filter": "Filtru",
"finish": "Finalizează", "finish": "Finalizează",
"first_name": "Prenume",
"follow_these": "Urmați acestea", "follow_these": "Urmați acestea",
"formbricks_version": "Versiunea Formbricks", "formbricks_version": "Versiunea Formbricks",
"full_name": "Nume complet", "full_name": "Nume complet",
@@ -237,6 +246,7 @@
"hidden_field": "Câmp ascuns", "hidden_field": "Câmp ascuns",
"hidden_fields": "Câmpuri ascunse", "hidden_fields": "Câmpuri ascunse",
"hide_column": "Ascunde coloana", "hide_column": "Ascunde coloana",
"id": "ID",
"image": "Imagine", "image": "Imagine",
"images": "Imagini", "images": "Imagini",
"import": "Import", "import": "Import",
@@ -254,6 +264,7 @@
"key": "Cheie", "key": "Cheie",
"label": "Etichetă", "label": "Etichetă",
"language": "Limba", "language": "Limba",
"last_name": "Nume de familie",
"learn_more": "Află mai multe", "learn_more": "Află mai multe",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Suprapunere ușoară", "light_overlay": "Suprapunere ușoară",
@@ -268,7 +279,6 @@
"look_and_feel": "Aspect și Comportament", "look_and_feel": "Aspect și Comportament",
"manage": "Gestionați", "manage": "Gestionați",
"marketing": "Marketing", "marketing": "Marketing",
"member": "Membru",
"members": "Membri", "members": "Membri",
"members_and_teams": "Membri și echipe", "members_and_teams": "Membri și echipe",
"membership_not_found": "Apartenența nu a fost găsită", "membership_not_found": "Apartenența nu a fost găsită",
@@ -280,6 +290,7 @@
"move_down": "Mută în jos", "move_down": "Mută în jos",
"move_up": "Mută sus", "move_up": "Mută sus",
"multiple_languages": "Mai multe limbi", "multiple_languages": "Mai multe limbi",
"my_product": "produsul meu",
"name": "Nume", "name": "Nume",
"new": "Nou", "new": "Nou",
"new_version_available": "Formbricks {version} este disponibil. Actualizați acum!", "new_version_available": "Formbricks {version} este disponibil. Actualizați acum!",
@@ -375,8 +386,6 @@
"select_teams": "Selectați echipele", "select_teams": "Selectați echipele",
"selected": "Selectat", "selected": "Selectat",
"selected_questions": "Întrebări selectate", "selected_questions": "Întrebări selectate",
"selection": "Selecție",
"selections": "Selecții",
"send_test_email": "Trimite email de test", "send_test_email": "Trimite email de test",
"session_not_found": "Sesiune inexistentă", "session_not_found": "Sesiune inexistentă",
"settings": "Setări", "settings": "Setări",
@@ -428,6 +437,7 @@
"top_right": "Dreapta Sus", "top_right": "Dreapta Sus",
"try_again": "Încearcă din nou", "try_again": "Încearcă din nou",
"type": "Tip", "type": "Tip",
"unknown_survey": "Chestionar necunoscut",
"unlock_more_workspaces_with_a_higher_plan": "Deblochează mai multe workspaces cu un plan superior.", "unlock_more_workspaces_with_a_higher_plan": "Deblochează mai multe workspaces cu un plan superior.",
"update": "Actualizare", "update": "Actualizare",
"updated": "Actualizat", "updated": "Actualizat",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Reîmprospătare contacte", "contacts_table_refresh": "Reîmprospătare contacte",
"contacts_table_refresh_success": "Contactele au fost actualizate cu succes", "contacts_table_refresh_success": "Contactele au fost actualizate cu succes",
"create_attribute": "Creează atribut", "create_attribute": "Creează atribut",
"create_key": "Creează cheie",
"create_new_attribute": "Creează atribut nou", "create_new_attribute": "Creează atribut nou",
"create_new_attribute_description": "Creează un atribut nou pentru segmentare.", "create_new_attribute_description": "Creează un atribut nou pentru segmentare.",
"custom_attributes": "Atribute personalizate", "custom_attributes": "Atribute personalizate",
@@ -656,6 +665,7 @@
"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_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": "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.} }", "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.} }",
"displays": "Afișări",
"edit_attribute": "Editează atributul", "edit_attribute": "Editează atributul",
"edit_attribute_description": "Actualizează eticheta și descrierea acestui atribut.", "edit_attribute_description": "Actualizează eticheta și descrierea acestui atribut.",
"edit_attribute_values": "Editează atributele", "edit_attribute_values": "Editează atributele",
@@ -667,6 +677,7 @@
"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_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_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.", "invalid_number_format": "Format de număr invalid. Te rugăm să introduci un număr valid.",
"no_activity_yet": "Nicio activitate încă",
"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_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_published_surveys": "Nu există sondaje publicate",
"no_responses_found": "Nu s-au găsit răspunsuri", "no_responses_found": "Nu s-au găsit răspunsuri",
@@ -681,6 +692,8 @@
"select_a_survey": "Selectați un sondaj", "select_a_survey": "Selectați un sondaj",
"select_attribute": "Selectează atributul", "select_attribute": "Selectează atributul",
"select_attribute_key": "Selectează cheia atributului", "select_attribute_key": "Selectează cheia atributului",
"survey_viewed": "Chestionar vizualizat",
"survey_viewed_at": "Vizualizat la",
"system_attributes": "Atribute de sistem", "system_attributes": "Atribute de sistem",
"unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite", "unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite",
"unlock_contacts_title": "Deblocați contactele cu un plan superior.", "unlock_contacts_title": "Deblocați contactele cu un plan superior.",
@@ -752,7 +765,12 @@
"link_google_sheet": "Leagă Google Sheet", "link_google_sheet": "Leagă Google Sheet",
"link_new_sheet": "Leagă un nou Sheet", "link_new_sheet": "Leagă un nou Sheet",
"no_integrations_yet": "Integrațiile tale Google Sheet vor apărea aici de îndată ce le vei adăuga. ⏲️", "no_integrations_yet": "Integrațiile tale Google Sheet vor apărea aici de îndată ce le vei adăuga. ⏲️",
"spreadsheet_url": "URL foaie de calcul" "reconnect_button": "Reconectează",
"reconnect_button_description": "Conexiunea ta cu Google Sheets a expirat. Te rugăm să te reconectezi pentru a continua sincronizarea răspunsurilor. Linkurile și datele existente din foile de calcul vor fi păstrate.",
"reconnect_button_tooltip": "Reconectează integrarea pentru a-ți reîmprospăta accesul. Linkurile și datele existente din foile de calcul vor fi păstrate.",
"spreadsheet_permission_error": "Nu ai permisiunea de a accesa această foaie de calcul. Asigură-te că foaia de calcul este partajată cu contul tău Google și că ai acces de scriere la aceasta.",
"spreadsheet_url": "URL foaie de calcul",
"token_expired_error": "Tokenul de reîmprospătare Google Sheets a expirat sau a fost revocat. Te rugăm să reconectezi integrarea."
}, },
"include_created_at": "Include data creării", "include_created_at": "Include data creării",
"include_hidden_fields": "Include câmpuri ascunse", "include_hidden_fields": "Include câmpuri ascunse",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Salut {userName}", "email_customization_preview_email_heading": "Salut {userName}",
"email_customization_preview_email_text": "Acesta este o previzualizare a e-mailului pentru a vă arăta ce logo va fi afișat în e-mailurile.", "email_customization_preview_email_text": "Acesta este o previzualizare a e-mailului pentru a vă arăta ce logo va fi afișat în e-mailurile.",
"error_deleting_organization_please_try_again": "Eroare la ștergerea organizației. Vă rugăm să încercați din nou.", "error_deleting_organization_please_try_again": "Eroare la ștergerea organizației. Vă rugăm să încercați din nou.",
"from_your_organization": "din organizația ta", "from_your_organization": "{memberName} din organizația ta",
"invitation_sent_once_more": "Invitație trimisă din nou.", "invitation_sent_once_more": "Invitație trimisă din nou.",
"invite_deleted_successfully": "Invitație ștearsă cu succes", "invite_deleted_successfully": "Invitație ștearsă cu succes",
"invite_expires_on": "Invitația expiră pe {date}", "invite_expires_on": "Invitația expiră pe {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Adaugă un placeholder pentru a afișa dacă nu există valoare de reamintit", "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_hidden_field_id": "Adăugați ID câmp ascuns",
"add_highlight_border": "Adaugă bordură evidențiată", "add_highlight_border": "Adaugă bordură evidențiată",
"add_highlight_border_description": "Se aplică doar sondajelor din produs.",
"add_logic": "Adaugă logică", "add_logic": "Adaugă logică",
"add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"", "add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"",
"add_option": "Adăugați opțiune", "add_option": "Adăugați opțiune",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Ajustați mesajul 'Sondaj Închis'", "adjust_survey_closed_message": "Ajustați mesajul 'Sondaj Închis'",
"adjust_survey_closed_message_description": "Schimbați mesajul pe care îl văd vizitatorii atunci când sondajul este închis.", "adjust_survey_closed_message_description": "Schimbați mesajul pe care îl văd vizitatorii atunci când sondajul este închis.",
"adjust_the_theme_in_the": "Ajustați tema în", "adjust_the_theme_in_the": "Ajustați tema în",
"all_are_true": "toate sunt adevărate",
"all_other_answers_will_continue_to": "Toate celelalte răspunsuri vor continua să", "all_other_answers_will_continue_to": "Toate celelalte răspunsuri vor continua să",
"allow_multi_select": "Permite selectare multiplă", "allow_multi_select": "Permite selectare multiplă",
"allow_multiple_files": "Permite fișiere multiple", "allow_multiple_files": "Permite fișiere multiple",
"allow_users_to_select_more_than_one_image": "Permite utilizatorilor să selecteze mai mult de o imagine", "allow_users_to_select_more_than_one_image": "Permite utilizatorilor să selecteze mai mult de o imagine",
"and_launch_surveys_in_your_website_or_app": "și lansați chestionare pe site-ul sau în aplicația dvs.", "and_launch_surveys_in_your_website_or_app": "și lansați chestionare pe site-ul sau în aplicația dvs.",
"animation": "Animație", "animation": "Animație",
"any_is_true": "oricare este adevărată",
"app_survey_description": "Incorporați un chestionar în aplicația web sau pe site-ul dvs. pentru a colecta răspunsuri.", "app_survey_description": "Incorporați un chestionar în aplicația web sau pe site-ul dvs. pentru a colecta răspunsuri.",
"assign": "Atribuire =", "assign": "Atribuire =",
"audience": "Public", "audience": "Public",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Urmărirea a fost actualizată și va fi salvată odată ce salvați sondajul.", "follow_ups_modal_updated_successfull_toast": "Urmărirea a fost actualizată și va fi salvată odată ce salvați sondajul.",
"follow_ups_new": "Follow-up nou", "follow_ups_new": "Follow-up nou",
"follow_ups_upgrade_button_text": "Actualizați pentru a activa urmărările", "follow_ups_upgrade_button_text": "Actualizați pentru a activa urmărările",
"form_styling": "Stilizare formular",
"formbricks_sdk_is_not_connected": "SDK Formbricks nu este conectat", "formbricks_sdk_is_not_connected": "SDK Formbricks nu este conectat",
"four_points": "4 puncte", "four_points": "4 puncte",
"heading": "Titlu", "heading": "Titlu",
@@ -1520,7 +1540,7 @@
"option_idx": "Opțiunea {choiceIndex}", "option_idx": "Opțiunea {choiceIndex}",
"option_used_in_logic_error": "Această opțiune este folosită în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.", "option_used_in_logic_error": "Această opțiune este folosită în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.",
"optional": "Opțional", "optional": "Opțional",
"options": "Opțiuni", "options": "Opțiuni*",
"options_used_in_logic_bulk_error": "Următoarele opțiuni sunt folosite în logică: {questionIndexes}. Vă rugăm să le eliminați din logică mai întâi.", "options_used_in_logic_bulk_error": "Următoarele opțiuni sunt folosite în logică: {questionIndexes}. Vă rugăm să le eliminați din logică mai întâi.",
"override_theme_with_individual_styles_for_this_survey": "Suprascrie tema cu stiluri individuale pentru acest sondaj.", "override_theme_with_individual_styles_for_this_survey": "Suprascrie tema cu stiluri individuale pentru acest sondaj.",
"overwrite_global_waiting_time": "Setează perioadă de răcire personalizată", "overwrite_global_waiting_time": "Setează perioadă de răcire personalizată",
@@ -1545,6 +1565,7 @@
"question_deleted": "Întrebare ștearsă.", "question_deleted": "Întrebare ștearsă.",
"question_duplicated": "Întrebare duplicată.", "question_duplicated": "Întrebare duplicată.",
"question_id_updated": "ID întrebare actualizat", "question_id_updated": "ID întrebare actualizat",
"question_number": "Întrebarea {number}",
"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_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_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}”",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.", "response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.",
"response_options": "Opțiuni răspuns", "response_options": "Opțiuni răspuns",
"roundness": "Rotunjire", "roundness": "Rotunjire",
"roundness_description": "Controlează cât de rotunjite sunt colțurile cardului.", "roundness_description": "Controlează cât de rotunjite sunt colțurile.",
"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.", "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", "rows": "Rânduri",
"save_and_close": "Salvează & Închide", "save_and_close": "Salvează & Închide",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Acest sondaj gratuit și open-source a fost închis", "survey_completed_subheading": "Acest sondaj gratuit și open-source a fost închis",
"survey_display_settings": "Setări de afișare a sondajului", "survey_display_settings": "Setări de afișare a sondajului",
"survey_placement": "Amplasarea sondajului", "survey_placement": "Amplasarea sondajului",
"survey_styling": "Stilizare formular",
"survey_trigger": "Declanșator sondaj", "survey_trigger": "Declanșator sondaj",
"switch_multi_language_on_to_get_started": "Activați opțiunea multi-limbă pentru a începe 👉", "switch_multi_language_on_to_get_started": "Activați opțiunea multi-limbă pentru a începe 👉",
"target_block_not_found": "Blocul țintă nu a fost găsit", "target_block_not_found": "Blocul țintă nu a fost găsit",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Perioadă de răcire (între sondaje)", "waiting_time_across_surveys": "Perioadă de răcire (între sondaje)",
"waiting_time_across_surveys_description": "Pentru a preveni oboseala cauzată de sondaje, alege cum interacționează acest sondaj cu perioada de răcire la nivel de workspace.", "waiting_time_across_surveys_description": "Pentru a preveni oboseala cauzată de sondaje, alege cum interacționează acest sondaj cu perioada de răcire la nivel de workspace.",
"welcome_message": "Mesaj de bun venit", "welcome_message": "Mesaj de bun venit",
"when": "Când",
"without_a_filter_all_of_your_users_can_be_surveyed": "Fără un filtru, toți utilizatorii pot fi chestionați.", "without_a_filter_all_of_your_users_can_be_surveyed": "Fără un filtru, toți utilizatorii pot fi chestionați.",
"you_have_not_created_a_segment_yet": "Nu ai creat încă un segment", "you_have_not_created_a_segment_yet": "Nu ai creat încă un segment",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Trebuie să aveți cel puțin două limbi configurate în spațiul de lucru pentru a putea lucra cu traduceri.",
"your_description_here_recall_information_with": "Descrierea ta aici. Reamintiți informațiile cu @", "your_description_here_recall_information_with": "Descrierea ta aici. Reamintiți informațiile cu @",
"your_question_here_recall_information_with": "Întrebarea ta aici. Reamintiți informațiile cu @", "your_question_here_recall_information_with": "Întrebarea ta aici. Reamintiți informațiile cu @",
"your_web_app": "Aplicația dumneavoastră web", "your_web_app": "Aplicația dumneavoastră web",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Răspunsuri filtrate (Excel)", "filtered_responses_excel": "Răspunsuri filtrate (Excel)",
"generating_qr_code": "Se generează codul QR", "generating_qr_code": "Se generează codul QR",
"impressions": "Impresii", "impressions": "Impresii",
"impressions_identified_only": "Se afișează doar impresiile de la contactele identificate",
"impressions_tooltip": "Număr de ori când sondajul a fost vizualizat.", "impressions_tooltip": "Număr de ori când sondajul a fost vizualizat.",
"in_app": { "in_app": {
"connection_description": "Sondajul va fi afișat utilizatorilor site-ului dvs. web, care îndeplinesc criteriile enumerate mai jos", "connection_description": "Sondajul va fi afișat utilizatorilor site-ului dvs. web, care îndeplinesc criteriile enumerate mai jos",
@@ -1989,6 +2012,7 @@
"last_quarter": "Ultimul trimestru", "last_quarter": "Ultimul trimestru",
"last_year": "Anul trecut", "last_year": "Anul trecut",
"limit": "Limită", "limit": "Limită",
"no_identified_impressions": "Nicio impresie de la contactele identificate",
"no_responses_found": "Nu s-au găsit răspunsuri", "no_responses_found": "Nu s-au găsit răspunsuri",
"other_values_found": "Alte valori găsite", "other_values_found": "Alte valori găsite",
"overall": "General", "overall": "General",
@@ -2011,6 +2035,7 @@
"starts": "Începuturi", "starts": "Începuturi",
"starts_tooltip": "Număr de ori când sondajul a fost început.", "starts_tooltip": "Număr de ori când sondajul a fost început.",
"survey_reset_successfully": "Resetarea chestionarului realizată cu succes! Au fost șterse {responseCount} răspunsuri și {displayCount} afișări.", "survey_reset_successfully": "Resetarea chestionarului realizată cu succes! Au fost șterse {responseCount} răspunsuri și {displayCount} afișări.",
"survey_results": "Rezultatele {surveyName}",
"this_month": "Luna aceasta", "this_month": "Luna aceasta",
"this_quarter": "Trimestrul acesta", "this_quarter": "Trimestrul acesta",
"this_year": "Anul acesta", "this_year": "Anul acesta",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Colorează textul introdus în câmpuri.", "advanced_styling_field_input_text_description": "Colorează textul introdus în câmpuri.",
"advanced_styling_field_option_bg": "Fundal", "advanced_styling_field_option_bg": "Fundal",
"advanced_styling_field_option_bg_description": "Umple elementele de opțiune.", "advanced_styling_field_option_bg_description": "Umple elementele de opțiune.",
"advanced_styling_field_option_border": "Culoare contur",
"advanced_styling_field_option_border_description": "Evidențiază opțiunile radio și checkbox.",
"advanced_styling_field_option_border_radius_description": "Rotunjește colțurile opțiunilor.", "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_font_size_description": "Redimensionează textul etichetei opțiunii.",
"advanced_styling_field_option_label": "Culoare etichetă", "advanced_styling_field_option_label": "Culoare etichetă",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Cum poate îmbunătăți compania alinierea viziunii și strategiei sale?", "alignment_and_engagement_survey_question_4_headline": "Cum poate îmbunătăți compania alinierea viziunii și strategiei sale?",
"alignment_and_engagement_survey_question_4_placeholder": "Tastează răspunsul aici...", "alignment_and_engagement_survey_question_4_placeholder": "Tastează răspunsul aici...",
"back": "Înapoi", "back": "Înapoi",
"block_1": "Blocul 1",
"block_10": "Blocul 10",
"block_2": "Blocul 2",
"block_3": "Blocul 3",
"block_4": "Blocul 4",
"block_5": "Blocul 5",
"block_6": "Blocul 6",
"block_7": "Blocul 7",
"block_8": "Blocul 8",
"block_9": "Blocul 9",
"book_interview": "Rezervă interviu", "book_interview": "Rezervă interviu",
"build_product_roadmap_description": "Identificați acel UN lucru pe care îl doresc cel mai mult utilizatorii și construiți-l.", "build_product_roadmap_description": "Identificați acel UN lucru pe care îl doresc cel mai mult utilizatorii și construiți-l.",
"build_product_roadmap_name": "Crearea foii de parcurs a produsului", "build_product_roadmap_name": "Crearea foii de parcurs a produsului",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Of, îmi pare rău! Există ceva ce putem face pentru a-ți îmbunătăți experiența?", "csat_survey_question_3_headline": "Of, îmi pare rău! Există ceva ce putem face pentru a-ți îmbunătăți experiența?",
"csat_survey_question_3_placeholder": "Tastează răspunsul aici...", "csat_survey_question_3_placeholder": "Tastează răspunsul aici...",
"cta_description": "Afișează informații și solicită utilizatorilor să ia o acțiune specifică", "cta_description": "Afișează informații și solicită utilizatorilor să ia o acțiune specifică",
"custom_survey_block_1_name": "Bloc 1",
"custom_survey_description": "Creează un sondaj fără șablon.", "custom_survey_description": "Creează un sondaj fără șablon.",
"custom_survey_name": "Începe de la zero", "custom_survey_name": "Începe de la zero",
"custom_survey_question_1_headline": "Ce ați dori să știți?", "custom_survey_question_1_headline": "Ce ați dori să știți?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Nu, mulţumesc!", "preview_survey_question_2_choice_2_label": "Nu, mulţumesc!",
"preview_survey_question_2_headline": "Vrei să fii în temă?", "preview_survey_question_2_headline": "Vrei să fii în temă?",
"preview_survey_question_2_subheader": "Aceasta este o descriere exemplu.", "preview_survey_question_2_subheader": "Aceasta este o descriere exemplu.",
"preview_survey_question_open_text_headline": "Mai vrei să împărtășești ceva?",
"preview_survey_question_open_text_placeholder": "Tastează răspunsul aici...",
"preview_survey_question_open_text_subheader": "Feedbackul tău ne ajută să ne îmbunătățim.",
"preview_survey_welcome_card_headline": "Bun venit!", "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_description": "Identificați caracteristicile de care utilizatorii dumneavoastră au cel mai mult și cel mai puțin nevoie.",
"prioritize_features_name": "Prioritizați caracteristicile", "prioritize_features_name": "Prioritizați caracteristicile",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "Копировать", "copy": "Копировать",
"copy_code": "Скопировать код", "copy_code": "Скопировать код",
"copy_link": "Скопировать ссылку", "copy_link": "Скопировать ссылку",
"count_attributes": "{value, plural, one {{value} атрибут} few {{value} атрибута} many {{value} атрибутов} other {{value} атрибута}}", "count_attributes": "{count, plural, one {{count} атрибут} few {{count} атрибута} many {{count} атрибутов} other {{count} атрибута}}",
"count_contacts": "{value, plural, one {{value} контакт} few {{value} контакта} many {{value} контактов} other {{value} контактов}}", "count_contacts": "{count, plural, one {{count} контакт} few {{count} контакта} many {{count} контактов} other {{count} контакта}}",
"count_responses": "{value, plural, one {{value} ответ} few {{value} ответа} many {{value} ответов} other {{value} ответов}}", "count_members": "{count, plural, one {{count} участник} few {{count} участника} many {{count} участников} other {{count} участника}}",
"count_questions": "{count, plural, one {{count} вопрос} few {{count} вопроса} many {{count} вопросов} other {{count} вопросов}}",
"count_responses": "{count, plural, one {{count} ответ} few {{count} ответа} many {{count} ответов} other {{count} ответов}}",
"count_selections": "{count, plural, one {{count} выбран} few {{count} выбрано} many {{count} выбрано} other {{count} выбрано}}",
"create_new_organization": "Создать новую организацию", "create_new_organization": "Создать новую организацию",
"create_segment": "Создать сегмент", "create_segment": "Создать сегмент",
"create_survey": "Создать опрос", "create_survey": "Создать опрос",
@@ -191,6 +194,7 @@
"days": "дни", "days": "дни",
"default": "По умолчанию", "default": "По умолчанию",
"delete": "Удалить", "delete": "Удалить",
"delete_what": "Удалить {deleteWhat}",
"description": "Описание", "description": "Описание",
"dev_env": "Dev Environment", "dev_env": "Dev Environment",
"development": "Разработка", "development": "Разработка",
@@ -206,6 +210,8 @@
"download": "Скачать", "download": "Скачать",
"draft": "Черновик", "draft": "Черновик",
"duplicate": "Дублировать", "duplicate": "Дублировать",
"duplicate_copy": "(копия)",
"duplicate_copy_number": "(копия {copyNumber})",
"e_commerce": "E-Commerce", "e_commerce": "E-Commerce",
"edit": "Редактировать", "edit": "Редактировать",
"email": "Email", "email": "Email",
@@ -218,13 +224,16 @@
"error": "Ошибка", "error": "Ошибка",
"error_component_description": "Этот ресурс не существует или у вас нет необходимых прав для доступа к нему.", "error_component_description": "Этот ресурс не существует или у вас нет необходимых прав для доступа к нему.",
"error_component_title": "Ошибка загрузки ресурсов", "error_component_title": "Ошибка загрузки ресурсов",
"error_loading_data": "Ошибка загрузки данных",
"error_rate_limit_description": "Достигнуто максимальное количество запросов. Пожалуйста, попробуйте позже.", "error_rate_limit_description": "Достигнуто максимальное количество запросов. Пожалуйста, попробуйте позже.",
"error_rate_limit_title": "Превышен лимит запросов", "error_rate_limit_title": "Превышен лимит запросов",
"expand_rows": "Развернуть строки", "expand_rows": "Развернуть строки",
"failed_to_copy_to_clipboard": "Не удалось скопировать в буфер обмена", "failed_to_copy_to_clipboard": "Не удалось скопировать в буфер обмена",
"failed_to_load_organizations": "Не удалось загрузить организации", "failed_to_load_organizations": "Не удалось загрузить организации",
"failed_to_load_workspaces": "Не удалось загрузить рабочие пространства", "failed_to_load_workspaces": "Не удалось загрузить рабочие пространства",
"filter": "Фильтр",
"finish": "Завершить", "finish": "Завершить",
"first_name": "Имя",
"follow_these": "Выполните следующие действия", "follow_these": "Выполните следующие действия",
"formbricks_version": "Версия Formbricks", "formbricks_version": "Версия Formbricks",
"full_name": "Полное имя", "full_name": "Полное имя",
@@ -237,6 +246,7 @@
"hidden_field": "Скрытое поле", "hidden_field": "Скрытое поле",
"hidden_fields": "Скрытые поля", "hidden_fields": "Скрытые поля",
"hide_column": "Скрыть столбец", "hide_column": "Скрыть столбец",
"id": "ID",
"image": "Изображение", "image": "Изображение",
"images": "Изображения", "images": "Изображения",
"import": "Импорт", "import": "Импорт",
@@ -254,6 +264,7 @@
"key": "Ключ", "key": "Ключ",
"label": "Метка", "label": "Метка",
"language": "Язык", "language": "Язык",
"last_name": "Фамилия",
"learn_more": "Подробнее", "learn_more": "Подробнее",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Светлый оверлей", "light_overlay": "Светлый оверлей",
@@ -268,7 +279,6 @@
"look_and_feel": "Внешний вид", "look_and_feel": "Внешний вид",
"manage": "Управление", "manage": "Управление",
"marketing": "Маркетинг", "marketing": "Маркетинг",
"member": "Участник",
"members": "Участники", "members": "Участники",
"members_and_teams": "Участники и команды", "members_and_teams": "Участники и команды",
"membership_not_found": "Участие не найдено", "membership_not_found": "Участие не найдено",
@@ -280,6 +290,7 @@
"move_down": "Переместить вниз", "move_down": "Переместить вниз",
"move_up": "Переместить вверх", "move_up": "Переместить вверх",
"multiple_languages": "Несколько языков", "multiple_languages": "Несколько языков",
"my_product": "мой продукт",
"name": "Имя", "name": "Имя",
"new": "Новый", "new": "Новый",
"new_version_available": "Formbricks {version} уже здесь. Обновитесь сейчас!", "new_version_available": "Formbricks {version} уже здесь. Обновитесь сейчас!",
@@ -375,8 +386,6 @@
"select_teams": "Выбрать команды", "select_teams": "Выбрать команды",
"selected": "Выбрано", "selected": "Выбрано",
"selected_questions": "Выбранные вопросы", "selected_questions": "Выбранные вопросы",
"selection": "Выбор",
"selections": "Выборы",
"send_test_email": "Отправить тестовое письмо", "send_test_email": "Отправить тестовое письмо",
"session_not_found": "Сессия не найдена", "session_not_found": "Сессия не найдена",
"settings": "Настройки", "settings": "Настройки",
@@ -428,6 +437,7 @@
"top_right": "Вверху справа", "top_right": "Вверху справа",
"try_again": "Попробуйте ещё раз", "try_again": "Попробуйте ещё раз",
"type": "Тип", "type": "Тип",
"unknown_survey": "Неизвестный опрос",
"unlock_more_workspaces_with_a_higher_plan": "Откройте больше рабочих пространств с более высоким тарифом.", "unlock_more_workspaces_with_a_higher_plan": "Откройте больше рабочих пространств с более высоким тарифом.",
"update": "Обновить", "update": "Обновить",
"updated": "Обновлено", "updated": "Обновлено",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Обновить контакты", "contacts_table_refresh": "Обновить контакты",
"contacts_table_refresh_success": "Контакты успешно обновлены", "contacts_table_refresh_success": "Контакты успешно обновлены",
"create_attribute": "Создать атрибут", "create_attribute": "Создать атрибут",
"create_key": "Создать ключ",
"create_new_attribute": "Создать новый атрибут", "create_new_attribute": "Создать новый атрибут",
"create_new_attribute_description": "Создайте новый атрибут для целей сегментации.", "create_new_attribute_description": "Создайте новый атрибут для целей сегментации.",
"custom_attributes": "Пользовательские атрибуты", "custom_attributes": "Пользовательские атрибуты",
@@ -656,6 +665,7 @@
"delete_attribute_confirmation": "{value, plural, one {Будет удалён выбранный атрибут. Все данные контактов, связанные с этим атрибутом, будут потеряны.} few {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} many {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} other {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.}}", "delete_attribute_confirmation": "{value, plural, one {Будет удалён выбранный атрибут. Все данные контактов, связанные с этим атрибутом, будут потеряны.} few {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} many {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} other {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.}}",
"delete_contact_confirmation": "Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны.", "delete_contact_confirmation": "Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны.",
"delete_contact_confirmation_with_quotas": "{value, plural, one {Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны. Если у этого контакта есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} few {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} many {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} other {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.}}", "delete_contact_confirmation_with_quotas": "{value, plural, one {Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны. Если у этого контакта есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} few {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} many {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} other {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.}}",
"displays": "Показы",
"edit_attribute": "Редактировать атрибут", "edit_attribute": "Редактировать атрибут",
"edit_attribute_description": "Обновите метку и описание для этого атрибута.", "edit_attribute_description": "Обновите метку и описание для этого атрибута.",
"edit_attribute_values": "Редактировать атрибуты", "edit_attribute_values": "Редактировать атрибуты",
@@ -667,6 +677,7 @@
"invalid_csv_column_names": "Недопустимые имена столбцов в CSV: {columns}. Имена столбцов, которые станут новыми атрибутами, должны содержать только строчные буквы, цифры и подчёркивания, а также начинаться с буквы.", "invalid_csv_column_names": "Недопустимые имена столбцов в CSV: {columns}. Имена столбцов, которые станут новыми атрибутами, должны содержать только строчные буквы, цифры и подчёркивания, а также начинаться с буквы.",
"invalid_date_format": "Неверный формат даты. Пожалуйста, используйте корректную дату.", "invalid_date_format": "Неверный формат даты. Пожалуйста, используйте корректную дату.",
"invalid_number_format": "Неверный формат числа. Пожалуйста, введите корректное число.", "invalid_number_format": "Неверный формат числа. Пожалуйста, введите корректное число.",
"no_activity_yet": "Пока нет активности",
"no_published_link_surveys_available": "Нет доступных опубликованных опросов-ссылок. Пожалуйста, сначала опубликуйте опрос-ссылку.", "no_published_link_surveys_available": "Нет доступных опубликованных опросов-ссылок. Пожалуйста, сначала опубликуйте опрос-ссылку.",
"no_published_surveys": "Нет опубликованных опросов", "no_published_surveys": "Нет опубликованных опросов",
"no_responses_found": "Ответы не найдены", "no_responses_found": "Ответы не найдены",
@@ -681,6 +692,8 @@
"select_a_survey": "Выберите опрос", "select_a_survey": "Выберите опрос",
"select_attribute": "Выберите атрибут", "select_attribute": "Выберите атрибут",
"select_attribute_key": "Выберите ключ атрибута", "select_attribute_key": "Выберите ключ атрибута",
"survey_viewed": "Опрос просмотрен",
"survey_viewed_at": "Просмотрено",
"system_attributes": "Системные атрибуты", "system_attributes": "Системные атрибуты",
"unlock_contacts_description": "Управляйте контактами и отправляйте целевые опросы", "unlock_contacts_description": "Управляйте контактами и отправляйте целевые опросы",
"unlock_contacts_title": "Откройте доступ к контактам с более высоким тарифом", "unlock_contacts_title": "Откройте доступ к контактам с более высоким тарифом",
@@ -752,7 +765,12 @@
"link_google_sheet": "Связать с Google Sheet", "link_google_sheet": "Связать с Google Sheet",
"link_new_sheet": "Связать с новой таблицей", "link_new_sheet": "Связать с новой таблицей",
"no_integrations_yet": "Ваши интеграции с Google Sheet появятся здесь, как только вы их добавите. ⏲️", "no_integrations_yet": "Ваши интеграции с Google Sheet появятся здесь, как только вы их добавите. ⏲️",
"spreadsheet_url": "URL таблицы" "reconnect_button": "Переподключить",
"reconnect_button_description": "Срок действия подключения к Google Sheets истёк. Пожалуйста, переподключись, чтобы продолжить синхронизацию ответов. Все существующие ссылки на таблицы и данные будут сохранены.",
"reconnect_button_tooltip": "Переподключи интеграцию, чтобы обновить доступ. Все существующие ссылки на таблицы и данные будут сохранены.",
"spreadsheet_permission_error": "У тебя нет доступа к этой таблице. Убедись, что таблица открыта для твоего Google-аккаунта и у тебя есть права на запись.",
"spreadsheet_url": "URL таблицы",
"token_expired_error": "Срок действия токена обновления Google Sheets истёк или он был отозван. Пожалуйста, переподключи интеграцию."
}, },
"include_created_at": "Включить дату создания", "include_created_at": "Включить дату создания",
"include_hidden_fields": "Включить скрытые поля", "include_hidden_fields": "Включить скрытые поля",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "Привет, {userName}", "email_customization_preview_email_heading": "Привет, {userName}",
"email_customization_preview_email_text": "Это предварительный просмотр письма, чтобы показать, какой логотип будет отображаться в письмах.", "email_customization_preview_email_text": "Это предварительный просмотр письма, чтобы показать, какой логотип будет отображаться в письмах.",
"error_deleting_organization_please_try_again": "Ошибка при удалении организации. Пожалуйста, попробуйте ещё раз.", "error_deleting_organization_please_try_again": "Ошибка при удалении организации. Пожалуйста, попробуйте ещё раз.",
"from_your_organization": "из вашей организации", "from_your_organization": "{memberName} из вашей организации",
"invitation_sent_once_more": "Приглашение отправлено ещё раз.", "invitation_sent_once_more": "Приглашение отправлено ещё раз.",
"invite_deleted_successfully": "Приглашение успешно удалено", "invite_deleted_successfully": "Приглашение успешно удалено",
"invite_expires_on": "Приглашение истекает {date}", "invite_expires_on": "Приглашение истекает {date}",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Добавить плейсхолдер, который будет показан, если нет значения для отображения.", "add_fallback_placeholder": "Добавить плейсхолдер, который будет показан, если нет значения для отображения.",
"add_hidden_field_id": "Добавить скрытый ID поля", "add_hidden_field_id": "Добавить скрытый ID поля",
"add_highlight_border": "Добавить выделяющую рамку", "add_highlight_border": "Добавить выделяющую рамку",
"add_highlight_border_description": "Применяется только к опросам внутри продукта.",
"add_logic": "Добавить логику", "add_logic": "Добавить логику",
"add_none_of_the_above": "Добавить вариант «Ничего из вышеперечисленного»", "add_none_of_the_above": "Добавить вариант «Ничего из вышеперечисленного»",
"add_option": "Добавить вариант", "add_option": "Добавить вариант",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Изменить сообщение «Опрос закрыт»", "adjust_survey_closed_message": "Изменить сообщение «Опрос закрыт»",
"adjust_survey_closed_message_description": "Измените сообщение, которое видят посетители, когда опрос закрыт.", "adjust_survey_closed_message_description": "Измените сообщение, которое видят посетители, когда опрос закрыт.",
"adjust_the_theme_in_the": "Настройте тему в", "adjust_the_theme_in_the": "Настройте тему в",
"all_are_true": "все условия выполняются",
"all_other_answers_will_continue_to": "Все остальные ответы будут продолжать", "all_other_answers_will_continue_to": "Все остальные ответы будут продолжать",
"allow_multi_select": "Разрешить множественный выбор", "allow_multi_select": "Разрешить множественный выбор",
"allow_multiple_files": "Разрешить несколько файлов", "allow_multiple_files": "Разрешить несколько файлов",
"allow_users_to_select_more_than_one_image": "Разрешить пользователям выбирать более одного изображения", "allow_users_to_select_more_than_one_image": "Разрешить пользователям выбирать более одного изображения",
"and_launch_surveys_in_your_website_or_app": "и запускать опросы на вашем сайте или в приложении.", "and_launch_surveys_in_your_website_or_app": "и запускать опросы на вашем сайте или в приложении.",
"animation": "Анимация", "animation": "Анимация",
"any_is_true": "выполняется хотя бы одно условие",
"app_survey_description": "Встраивайте опрос в ваше веб-приложение или сайт для сбора ответов.", "app_survey_description": "Встраивайте опрос в ваше веб-приложение или сайт для сбора ответов.",
"assign": "Назначить =", "assign": "Назначить =",
"audience": "Аудитория", "audience": "Аудитория",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Фоллоу-ап обновлён и будет сохранён после сохранения опроса.", "follow_ups_modal_updated_successfull_toast": "Фоллоу-ап обновлён и будет сохранён после сохранения опроса.",
"follow_ups_new": "Новый фоллоу-ап", "follow_ups_new": "Новый фоллоу-ап",
"follow_ups_upgrade_button_text": "Обновите тариф для активации фоллоу-апов", "follow_ups_upgrade_button_text": "Обновите тариф для активации фоллоу-апов",
"form_styling": "Оформление формы",
"formbricks_sdk_is_not_connected": "Formbricks SDK не подключён", "formbricks_sdk_is_not_connected": "Formbricks SDK не подключён",
"four_points": "4 балла", "four_points": "4 балла",
"heading": "Заголовок", "heading": "Заголовок",
@@ -1520,7 +1540,7 @@
"option_idx": "Вариант {choiceIndex}", "option_idx": "Вариант {choiceIndex}",
"option_used_in_logic_error": "Этот вариант используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите его из логики.", "option_used_in_logic_error": "Этот вариант используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите его из логики.",
"optional": "Необязательно", "optional": "Необязательно",
"options": "Варианты", "options": "Варианты*",
"options_used_in_logic_bulk_error": "Следующие варианты используются в логике: {questionIndexes}. Пожалуйста, сначала удалите их из логики.", "options_used_in_logic_bulk_error": "Следующие варианты используются в логике: {questionIndexes}. Пожалуйста, сначала удалите их из логики.",
"override_theme_with_individual_styles_for_this_survey": "Переопределить тему индивидуальными стилями для этого опроса.", "override_theme_with_individual_styles_for_this_survey": "Переопределить тему индивидуальными стилями для этого опроса.",
"overwrite_global_waiting_time": "Установить свой период ожидания", "overwrite_global_waiting_time": "Установить свой период ожидания",
@@ -1545,6 +1565,7 @@
"question_deleted": "Вопрос удалён.", "question_deleted": "Вопрос удалён.",
"question_duplicated": "Вопрос дублирован.", "question_duplicated": "Вопрос дублирован.",
"question_id_updated": "ID вопроса обновлён", "question_id_updated": "ID вопроса обновлён",
"question_number": "Вопрос {number}",
"question_used_in_logic_warning_text": "Элементы из этого блока используются в правиле логики. Вы уверены, что хотите удалить его?", "question_used_in_logic_warning_text": "Элементы из этого блока используются в правиле логики. Вы уверены, что хотите удалить его?",
"question_used_in_logic_warning_title": "Несогласованность логики", "question_used_in_logic_warning_title": "Несогласованность логики",
"question_used_in_quota": "Этот вопрос используется в квоте «{quotaName}»", "question_used_in_quota": "Этот вопрос используется в квоте «{quotaName}»",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Лимиты ответов, перенаправления и другое.", "response_limits_redirections_and_more": "Лимиты ответов, перенаправления и другое.",
"response_options": "Параметры ответа", "response_options": "Параметры ответа",
"roundness": "Скругление", "roundness": "Скругление",
"roundness_description": "Определяет степень скругления углов карточки.", "roundness_description": "Определяет степень скругления углов.",
"row_used_in_logic_error": "Эта строка используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите её из логики.", "row_used_in_logic_error": "Эта строка используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите её из логики.",
"rows": "Строки", "rows": "Строки",
"save_and_close": "Сохранить и закрыть", "save_and_close": "Сохранить и закрыть",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Этот бесплатный и открытый опрос был закрыт", "survey_completed_subheading": "Этот бесплатный и открытый опрос был закрыт",
"survey_display_settings": "Настройки отображения опроса", "survey_display_settings": "Настройки отображения опроса",
"survey_placement": "Размещение опроса", "survey_placement": "Размещение опроса",
"survey_styling": "Оформление формы",
"survey_trigger": "Триггер опроса", "survey_trigger": "Триггер опроса",
"switch_multi_language_on_to_get_started": "Включите многоязычный режим, чтобы начать 👉", "switch_multi_language_on_to_get_started": "Включите многоязычный режим, чтобы начать 👉",
"target_block_not_found": "Целевой блок не найден", "target_block_not_found": "Целевой блок не найден",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Период ожидания (между опросами)", "waiting_time_across_surveys": "Период ожидания (между опросами)",
"waiting_time_across_surveys_description": "Чтобы избежать усталости от опросов, выберите, как этот опрос взаимодействует с общим периодом ожидания в рабочем пространстве.", "waiting_time_across_surveys_description": "Чтобы избежать усталости от опросов, выберите, как этот опрос взаимодействует с общим периодом ожидания в рабочем пространстве.",
"welcome_message": "Приветственное сообщение", "welcome_message": "Приветственное сообщение",
"when": "Когда",
"without_a_filter_all_of_your_users_can_be_surveyed": "Без фильтра все ваши пользователи могут быть опрошены.", "without_a_filter_all_of_your_users_can_be_surveyed": "Без фильтра все ваши пользователи могут быть опрошены.",
"you_have_not_created_a_segment_yet": "Вы ещё не создали сегмент", "you_have_not_created_a_segment_yet": "Вы ещё не создали сегмент",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Для работы с переводами необходимо настроить два или более языков в рабочем пространстве.",
"your_description_here_recall_information_with": "Ваша инструкция здесь. Вспомните информацию с помощью @", "your_description_here_recall_information_with": "Ваша инструкция здесь. Вспомните информацию с помощью @",
"your_question_here_recall_information_with": "Ваш вопрос здесь. Вспомните информацию с помощью @", "your_question_here_recall_information_with": "Ваш вопрос здесь. Вспомните информацию с помощью @",
"your_web_app": "Ваше веб-приложение", "your_web_app": "Ваше веб-приложение",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Отфильтрованные ответы (Excel)", "filtered_responses_excel": "Отфильтрованные ответы (Excel)",
"generating_qr_code": "Генерация QR-кода", "generating_qr_code": "Генерация QR-кода",
"impressions": "Просмотры", "impressions": "Просмотры",
"impressions_identified_only": "Показаны только показы от идентифицированных контактов",
"impressions_tooltip": "Количество раз, когда опрос был просмотрен.", "impressions_tooltip": "Количество раз, когда опрос был просмотрен.",
"in_app": { "in_app": {
"connection_description": "Опрос будет показан пользователям вашего сайта, которые соответствуют указанным ниже критериям", "connection_description": "Опрос будет показан пользователям вашего сайта, которые соответствуют указанным ниже критериям",
@@ -1989,6 +2012,7 @@
"last_quarter": "Прошлый квартал", "last_quarter": "Прошлый квартал",
"last_year": "Прошлый год", "last_year": "Прошлый год",
"limit": "Лимит", "limit": "Лимит",
"no_identified_impressions": "Нет показов от идентифицированных контактов",
"no_responses_found": "Ответы не найдены", "no_responses_found": "Ответы не найдены",
"other_values_found": "Найдены другие значения", "other_values_found": "Найдены другие значения",
"overall": "В целом", "overall": "В целом",
@@ -2011,6 +2035,7 @@
"starts": "Запуски", "starts": "Запуски",
"starts_tooltip": "Количество запусков опроса.", "starts_tooltip": "Количество запусков опроса.",
"survey_reset_successfully": "Опрос успешно сброшен! {responseCount} ответов и {displayCount} показов были удалены.", "survey_reset_successfully": "Опрос успешно сброшен! {responseCount} ответов и {displayCount} показов были удалены.",
"survey_results": "Результаты {surveyName}",
"this_month": "В этом месяце", "this_month": "В этом месяце",
"this_quarter": "В этом квартале", "this_quarter": "В этом квартале",
"this_year": "В этом году", "this_year": "В этом году",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Задаёт цвет заполненной части полосы.", "advanced_styling_field_indicator_bg_description": "Задаёт цвет заполненной части полосы.",
"advanced_styling_field_input_border_radius_description": "Скругляет углы полей ввода.", "advanced_styling_field_input_border_radius_description": "Скругляет углы полей ввода.",
"advanced_styling_field_input_font_size_description": "Масштабирует введённый текст в полях ввода.", "advanced_styling_field_input_font_size_description": "Масштабирует введённый текст в полях ввода.",
"advanced_styling_field_input_height_description": "Определяет минимальную высоту поля ввода.", "advanced_styling_field_input_height_description": "Управляет минимальной высотой поля ввода.",
"advanced_styling_field_input_padding_x_description": "Добавляет отступы слева и справа.", "advanced_styling_field_input_padding_x_description": "Добавляет отступы слева и справа.",
"advanced_styling_field_input_padding_y_description": "Добавляет пространство сверху и снизу.", "advanced_styling_field_input_padding_y_description": "Добавляет пространство сверху и снизу.",
"advanced_styling_field_input_placeholder_opacity_description": "Делает текст подсказки менее заметным.", "advanced_styling_field_input_placeholder_opacity_description": "Делает текст подсказки менее заметным.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Задаёт цвет введённого текста в полях.", "advanced_styling_field_input_text_description": "Задаёт цвет введённого текста в полях.",
"advanced_styling_field_option_bg": "Фон", "advanced_styling_field_option_bg": "Фон",
"advanced_styling_field_option_bg_description": "Заливает фон элементов опций.", "advanced_styling_field_option_bg_description": "Заливает фон элементов опций.",
"advanced_styling_field_option_border": "Цвет границы",
"advanced_styling_field_option_border_description": "Обводка для вариантов radio и checkbox.",
"advanced_styling_field_option_border_radius_description": "Скругляет углы опций.", "advanced_styling_field_option_border_radius_description": "Скругляет углы опций.",
"advanced_styling_field_option_font_size_description": "Изменяет размер текста метки опции.", "advanced_styling_field_option_font_size_description": "Изменяет размер текста метки опции.",
"advanced_styling_field_option_label": "Цвет метки", "advanced_styling_field_option_label": "Цвет метки",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Как компания может улучшить согласованность видения и стратегии?", "alignment_and_engagement_survey_question_4_headline": "Как компания может улучшить согласованность видения и стратегии?",
"alignment_and_engagement_survey_question_4_placeholder": "Введите ваш ответ здесь...", "alignment_and_engagement_survey_question_4_placeholder": "Введите ваш ответ здесь...",
"back": "Назад", "back": "Назад",
"block_1": "Блок 1",
"block_10": "Блок 10",
"block_2": "Блок 2",
"block_3": "Блок 3",
"block_4": "Блок 4",
"block_5": "Блок 5",
"block_6": "Блок 6",
"block_7": "Блок 7",
"block_8": "Блок 8",
"block_9": "Блок 9",
"book_interview": "Записаться на интервью", "book_interview": "Записаться на интервью",
"build_product_roadmap_description": "Определите ОДНУ вещь, которую ваши пользователи хотят больше всего, и реализуйте её.", "build_product_roadmap_description": "Определите ОДНУ вещь, которую ваши пользователи хотят больше всего, и реализуйте её.",
"build_product_roadmap_name": "Построение продуктовой дорожной карты", "build_product_roadmap_name": "Построение продуктовой дорожной карты",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Ой, извините! Что мы можем сделать, чтобы улучшить ваш опыт?", "csat_survey_question_3_headline": "Ой, извините! Что мы можем сделать, чтобы улучшить ваш опыт?",
"csat_survey_question_3_placeholder": "Введите ваш ответ здесь...", "csat_survey_question_3_placeholder": "Введите ваш ответ здесь...",
"cta_description": "Показывайте информацию и побуждайте пользователей к определённому действию", "cta_description": "Показывайте информацию и побуждайте пользователей к определённому действию",
"custom_survey_block_1_name": "Блок 1",
"custom_survey_description": "Создайте опрос без шаблона.", "custom_survey_description": "Создайте опрос без шаблона.",
"custom_survey_name": "Начать с нуля", "custom_survey_name": "Начать с нуля",
"custom_survey_question_1_headline": "Что вы хотели бы узнать?", "custom_survey_question_1_headline": "Что вы хотели бы узнать?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Нет, спасибо!", "preview_survey_question_2_choice_2_label": "Нет, спасибо!",
"preview_survey_question_2_headline": "Хотите быть в курсе событий?", "preview_survey_question_2_headline": "Хотите быть в курсе событий?",
"preview_survey_question_2_subheader": "Это пример описания.", "preview_survey_question_2_subheader": "Это пример описания.",
"preview_survey_question_open_text_headline": "Есть ли ещё что-то, чем хочешь поделиться?",
"preview_survey_question_open_text_placeholder": "Введи свой ответ здесь...",
"preview_survey_question_open_text_subheader": "Твой отзыв помогает нам становиться лучше.",
"preview_survey_welcome_card_headline": "Добро пожаловать!", "preview_survey_welcome_card_headline": "Добро пожаловать!",
"prioritize_features_description": "Определите, какие функции наиболее и наименее важны для ваших пользователей.", "prioritize_features_description": "Определите, какие функции наиболее и наименее важны для ваших пользователей.",
"prioritize_features_name": "Приоритизация функций", "prioritize_features_name": "Приоритизация функций",
+53 -14
View File
@@ -175,9 +175,12 @@
"copy": "Kopiera", "copy": "Kopiera",
"copy_code": "Kopiera kod", "copy_code": "Kopiera kod",
"copy_link": "Kopiera länk", "copy_link": "Kopiera länk",
"count_attributes": "{value, plural, one {{value} attribut} other {{value} attribut}}", "count_attributes": "{count, plural, one {{count} attribut} other {{count} attribut}}",
"count_contacts": "{value, plural, one {{value} kontakt} other {{value} kontakter}}", "count_contacts": "{count, plural, one {{count} kontakt} other {{count} kontakter}}",
"count_responses": "{value, plural, one {{value} svar} other {{value} svar}}", "count_members": "{count, plural, one {{count} medlem} other {{count} medlemmar}}",
"count_questions": "{count, plural, one {{count} fråga} other {{count} frågor}}",
"count_responses": "{count, plural, one {{count} svar} other {{count} svar}}",
"count_selections": "{count, plural, one {{count} val} other {{count} val}}",
"create_new_organization": "Skapa ny organisation", "create_new_organization": "Skapa ny organisation",
"create_segment": "Skapa segment", "create_segment": "Skapa segment",
"create_survey": "Skapa enkät", "create_survey": "Skapa enkät",
@@ -191,6 +194,7 @@
"days": "dagar", "days": "dagar",
"default": "Standard", "default": "Standard",
"delete": "Ta bort", "delete": "Ta bort",
"delete_what": "Ta bort {deleteWhat}",
"description": "Beskrivning", "description": "Beskrivning",
"dev_env": "Utvecklingsmiljö", "dev_env": "Utvecklingsmiljö",
"development": "Utveckling", "development": "Utveckling",
@@ -206,6 +210,8 @@
"download": "Ladda ner", "download": "Ladda ner",
"draft": "Utkast", "draft": "Utkast",
"duplicate": "Duplicera", "duplicate": "Duplicera",
"duplicate_copy": "(kopia)",
"duplicate_copy_number": "(kopia {copyNumber})",
"e_commerce": "E-handel", "e_commerce": "E-handel",
"edit": "Redigera", "edit": "Redigera",
"email": "E-post", "email": "E-post",
@@ -218,13 +224,16 @@
"error": "Fel", "error": "Fel",
"error_component_description": "Denna resurs finns inte eller så har du inte de nödvändiga rättigheterna för att komma åt den.", "error_component_description": "Denna resurs finns inte eller så har du inte de nödvändiga rättigheterna för att komma åt den.",
"error_component_title": "Fel vid laddning av resurser", "error_component_title": "Fel vid laddning av resurser",
"error_loading_data": "Fel vid inläsning av data",
"error_rate_limit_description": "Maximalt antal förfrågningar har nåtts. Försök igen senare.", "error_rate_limit_description": "Maximalt antal förfrågningar har nåtts. Försök igen senare.",
"error_rate_limit_title": "Begränsningsgräns överskriden", "error_rate_limit_title": "Begränsningsgräns överskriden",
"expand_rows": "Visa rader", "expand_rows": "Visa rader",
"failed_to_copy_to_clipboard": "Misslyckades att kopiera till urklipp", "failed_to_copy_to_clipboard": "Misslyckades att kopiera till urklipp",
"failed_to_load_organizations": "Misslyckades att ladda organisationer", "failed_to_load_organizations": "Misslyckades att ladda organisationer",
"failed_to_load_workspaces": "Det gick inte att ladda arbetsytor", "failed_to_load_workspaces": "Det gick inte att ladda arbetsytor",
"filter": "Filter",
"finish": "Slutför", "finish": "Slutför",
"first_name": "Förnamn",
"follow_these": "Följ dessa", "follow_these": "Följ dessa",
"formbricks_version": "Formbricks-version", "formbricks_version": "Formbricks-version",
"full_name": "Fullständigt namn", "full_name": "Fullständigt namn",
@@ -237,6 +246,7 @@
"hidden_field": "Dolt fält", "hidden_field": "Dolt fält",
"hidden_fields": "Dolda fält", "hidden_fields": "Dolda fält",
"hide_column": "Dölj kolumn", "hide_column": "Dölj kolumn",
"id": "ID",
"image": "Bild", "image": "Bild",
"images": "Bilder", "images": "Bilder",
"import": "Importera", "import": "Importera",
@@ -254,6 +264,7 @@
"key": "Nyckel", "key": "Nyckel",
"label": "Etikett", "label": "Etikett",
"language": "Språk", "language": "Språk",
"last_name": "Efternamn",
"learn_more": "Läs mer", "learn_more": "Läs mer",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "Ljust överlägg", "light_overlay": "Ljust överlägg",
@@ -268,7 +279,6 @@
"look_and_feel": "Utseende", "look_and_feel": "Utseende",
"manage": "Hantera", "manage": "Hantera",
"marketing": "Marknadsföring", "marketing": "Marknadsföring",
"member": "Medlem",
"members": "Medlemmar", "members": "Medlemmar",
"members_and_teams": "Medlemmar och team", "members_and_teams": "Medlemmar och team",
"membership_not_found": "Medlemskap hittades inte", "membership_not_found": "Medlemskap hittades inte",
@@ -280,6 +290,7 @@
"move_down": "Flytta ner", "move_down": "Flytta ner",
"move_up": "Flytta upp", "move_up": "Flytta upp",
"multiple_languages": "Flera språk", "multiple_languages": "Flera språk",
"my_product": "min produkt",
"name": "Namn", "name": "Namn",
"new": "Ny", "new": "Ny",
"new_version_available": "Formbricks {version} är här. Uppgradera nu!", "new_version_available": "Formbricks {version} är här. Uppgradera nu!",
@@ -375,8 +386,6 @@
"select_teams": "Välj team", "select_teams": "Välj team",
"selected": "Vald", "selected": "Vald",
"selected_questions": "Valda frågor", "selected_questions": "Valda frågor",
"selection": "Urval",
"selections": "Urval",
"send_test_email": "Skicka testmeddelande", "send_test_email": "Skicka testmeddelande",
"session_not_found": "Session hittades inte", "session_not_found": "Session hittades inte",
"settings": "Inställningar", "settings": "Inställningar",
@@ -428,6 +437,7 @@
"top_right": "Övre höger", "top_right": "Övre höger",
"try_again": "Försök igen", "try_again": "Försök igen",
"type": "Typ", "type": "Typ",
"unknown_survey": "Okänd enkät",
"unlock_more_workspaces_with_a_higher_plan": "Lås upp fler arbetsytor med ett högre abonnemang.", "unlock_more_workspaces_with_a_higher_plan": "Lås upp fler arbetsytor med ett högre abonnemang.",
"update": "Uppdatera", "update": "Uppdatera",
"updated": "Uppdaterad", "updated": "Uppdaterad",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "Uppdatera kontakter", "contacts_table_refresh": "Uppdatera kontakter",
"contacts_table_refresh_success": "Kontakter uppdaterade", "contacts_table_refresh_success": "Kontakter uppdaterade",
"create_attribute": "Skapa attribut", "create_attribute": "Skapa attribut",
"create_key": "Skapa nyckel",
"create_new_attribute": "Skapa nytt attribut", "create_new_attribute": "Skapa nytt attribut",
"create_new_attribute_description": "Skapa ett nytt attribut för segmenteringsändamål.", "create_new_attribute_description": "Skapa ett nytt attribut för segmenteringsändamål.",
"custom_attributes": "Anpassade attribut", "custom_attributes": "Anpassade attribut",
@@ -656,6 +665,7 @@
"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_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": "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.}}", "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.}}",
"displays": "Visningar",
"edit_attribute": "Redigera attribut", "edit_attribute": "Redigera attribut",
"edit_attribute_description": "Uppdatera etikett och beskrivning för detta attribut.", "edit_attribute_description": "Uppdatera etikett och beskrivning för detta attribut.",
"edit_attribute_values": "Redigera attribut", "edit_attribute_values": "Redigera attribut",
@@ -667,6 +677,7 @@
"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_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_date_format": "Ogiltigt datumformat. Ange ett giltigt datum.",
"invalid_number_format": "Ogiltigt nummerformat. Ange ett giltigt nummer.", "invalid_number_format": "Ogiltigt nummerformat. Ange ett giltigt nummer.",
"no_activity_yet": "Ingen aktivitet än",
"no_published_link_surveys_available": "Inga publicerade länkenkäter tillgängliga. Vänligen publicera en länkenkät först.", "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_published_surveys": "Inga publicerade enkäter",
"no_responses_found": "Inga svar hittades", "no_responses_found": "Inga svar hittades",
@@ -681,6 +692,8 @@
"select_a_survey": "Välj en enkät", "select_a_survey": "Välj en enkät",
"select_attribute": "Välj attribut", "select_attribute": "Välj attribut",
"select_attribute_key": "Välj attributnyckel", "select_attribute_key": "Välj attributnyckel",
"survey_viewed": "Enkät visad",
"survey_viewed_at": "Visad kl.",
"system_attributes": "Systemattribut", "system_attributes": "Systemattribut",
"unlock_contacts_description": "Hantera kontakter och skicka ut riktade enkäter", "unlock_contacts_description": "Hantera kontakter och skicka ut riktade enkäter",
"unlock_contacts_title": "Lås upp kontakter med en högre plan", "unlock_contacts_title": "Lås upp kontakter med en högre plan",
@@ -752,7 +765,12 @@
"link_google_sheet": "Länka Google Kalkylark", "link_google_sheet": "Länka Google Kalkylark",
"link_new_sheet": "Länka nytt kalkylark", "link_new_sheet": "Länka nytt kalkylark",
"no_integrations_yet": "Dina Google Kalkylark-integrationer visas här så snart du lägger till dem. ⏲️", "no_integrations_yet": "Dina Google Kalkylark-integrationer visas här så snart du lägger till dem. ⏲️",
"spreadsheet_url": "Kalkylblads-URL" "reconnect_button": "Återanslut",
"reconnect_button_description": "Din Google Sheets-anslutning har gått ut. Återanslut för att fortsätta synkronisera svar. Dina befintliga kalkylarkslänkar och data kommer att sparas.",
"reconnect_button_tooltip": "Återanslut integrationen för att uppdatera din åtkomst. Dina befintliga kalkylarkslänkar och data kommer att sparas.",
"spreadsheet_permission_error": "Du har inte behörighet att komma åt det här kalkylarket. Kontrollera att kalkylarket är delat med ditt Google-konto och att du har skrivrättigheter till kalkylarket.",
"spreadsheet_url": "Kalkylblads-URL",
"token_expired_error": "Google Sheets refresh token har gått ut eller återkallats. Återanslut integrationen."
}, },
"include_created_at": "Inkludera Skapad vid", "include_created_at": "Inkludera Skapad vid",
"include_hidden_fields": "Inkludera dolda fält", "include_hidden_fields": "Inkludera dolda fält",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "Lägg till en platshållare att visa om det inte finns något värde att återkalla.", "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_hidden_field_id": "Lägg till dolt fält-ID",
"add_highlight_border": "Lägg till markerad kant", "add_highlight_border": "Lägg till markerad kant",
"add_highlight_border_description": "Gäller bara för undersökningar i produkten.",
"add_logic": "Lägg till logik", "add_logic": "Lägg till logik",
"add_none_of_the_above": "Lägg till \"Inget av ovanstående\"", "add_none_of_the_above": "Lägg till \"Inget av ovanstående\"",
"add_option": "Lägg till alternativ", "add_option": "Lägg till alternativ",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "Justera meddelande för 'Enkät stängd'", "adjust_survey_closed_message": "Justera meddelande för 'Enkät stängd'",
"adjust_survey_closed_message_description": "Ändra meddelandet besökare ser när enkäten är stängd.", "adjust_survey_closed_message_description": "Ändra meddelandet besökare ser när enkäten är stängd.",
"adjust_the_theme_in_the": "Justera temat i", "adjust_the_theme_in_the": "Justera temat i",
"all_are_true": "alla är sanna",
"all_other_answers_will_continue_to": "Alla andra svar fortsätter till", "all_other_answers_will_continue_to": "Alla andra svar fortsätter till",
"allow_multi_select": "Tillåt flerval", "allow_multi_select": "Tillåt flerval",
"allow_multiple_files": "Tillåt flera filer", "allow_multiple_files": "Tillåt flera filer",
"allow_users_to_select_more_than_one_image": "Tillåt användare att välja mer än en bild", "allow_users_to_select_more_than_one_image": "Tillåt användare att välja mer än en bild",
"and_launch_surveys_in_your_website_or_app": "och starta enkäter på din webbplats eller i din app.", "and_launch_surveys_in_your_website_or_app": "och starta enkäter på din webbplats eller i din app.",
"animation": "Animering", "animation": "Animering",
"any_is_true": "någon är sann",
"app_survey_description": "Bädda in en enkät i din webbapp eller webbplats för att samla in svar.", "app_survey_description": "Bädda in en enkät i din webbapp eller webbplats för att samla in svar.",
"assign": "Tilldela =", "assign": "Tilldela =",
"audience": "Målgrupp", "audience": "Målgrupp",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "Uppföljning uppdaterad och sparas när du sparar enkäten.", "follow_ups_modal_updated_successfull_toast": "Uppföljning uppdaterad och sparas när du sparar enkäten.",
"follow_ups_new": "Ny uppföljning", "follow_ups_new": "Ny uppföljning",
"follow_ups_upgrade_button_text": "Uppgradera för att aktivera uppföljningar", "follow_ups_upgrade_button_text": "Uppgradera för att aktivera uppföljningar",
"form_styling": "Formulärstil",
"formbricks_sdk_is_not_connected": "Formbricks SDK är inte anslutet", "formbricks_sdk_is_not_connected": "Formbricks SDK är inte anslutet",
"four_points": "4 poäng", "four_points": "4 poäng",
"heading": "Rubrik", "heading": "Rubrik",
@@ -1520,7 +1540,7 @@
"option_idx": "Alternativ {choiceIndex}", "option_idx": "Alternativ {choiceIndex}",
"option_used_in_logic_error": "Detta alternativ används i logiken för fråga {questionIndex}. Vänligen ta bort det från logiken först.", "option_used_in_logic_error": "Detta alternativ används i logiken för fråga {questionIndex}. Vänligen ta bort det från logiken först.",
"optional": "Valfritt", "optional": "Valfritt",
"options": "Alternativ", "options": "Alternativ*",
"options_used_in_logic_bulk_error": "Följande alternativ används i logiken: {questionIndexes}. Vänligen ta bort dem från logiken först.", "options_used_in_logic_bulk_error": "Följande alternativ används i logiken: {questionIndexes}. Vänligen ta bort dem från logiken först.",
"override_theme_with_individual_styles_for_this_survey": "Åsidosätt temat med individuella stilar för denna enkät.", "override_theme_with_individual_styles_for_this_survey": "Åsidosätt temat med individuella stilar för denna enkät.",
"overwrite_global_waiting_time": "Ange anpassad väntetid", "overwrite_global_waiting_time": "Ange anpassad väntetid",
@@ -1545,6 +1565,7 @@
"question_deleted": "Fråga borttagen.", "question_deleted": "Fråga borttagen.",
"question_duplicated": "Fråga duplicerad.", "question_duplicated": "Fråga duplicerad.",
"question_id_updated": "Fråge-ID uppdaterat", "question_id_updated": "Fråge-ID uppdaterat",
"question_number": "Fråga {number}",
"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_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_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}”",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "Svarsgränser, omdirigeringar och mer.", "response_limits_redirections_and_more": "Svarsgränser, omdirigeringar och mer.",
"response_options": "Svarsalternativ", "response_options": "Svarsalternativ",
"roundness": "Rundhet", "roundness": "Rundhet",
"roundness_description": "Styr hur rundade kortets hörn är.", "roundness_description": "Styr hur rundade hörnen ä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.", "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", "rows": "Rader",
"save_and_close": "Spara och stäng", "save_and_close": "Spara och stäng",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "Denna gratis och öppenkällkodsenkät har stängts", "survey_completed_subheading": "Denna gratis och öppenkällkodsenkät har stängts",
"survey_display_settings": "Visningsinställningar för enkät", "survey_display_settings": "Visningsinställningar för enkät",
"survey_placement": "Enkätplacering", "survey_placement": "Enkätplacering",
"survey_styling": "Formulärstil",
"survey_trigger": "Enkätutlösare", "survey_trigger": "Enkätutlösare",
"switch_multi_language_on_to_get_started": "Slå på flerspråkighet för att komma igång 👉", "switch_multi_language_on_to_get_started": "Slå på flerspråkighet för att komma igång 👉",
"target_block_not_found": "Målblock hittades inte", "target_block_not_found": "Målblock hittades inte",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "Väntetid (mellan enkäter)", "waiting_time_across_surveys": "Väntetid (mellan enkäter)",
"waiting_time_across_surveys_description": "För att undvika enkättrötthet, välj hur denna enkät ska förhålla sig till arbetsytans gemensamma väntetid.", "waiting_time_across_surveys_description": "För att undvika enkättrötthet, välj hur denna enkät ska förhålla sig till arbetsytans gemensamma väntetid.",
"welcome_message": "Välkomstmeddelande", "welcome_message": "Välkomstmeddelande",
"when": "När",
"without_a_filter_all_of_your_users_can_be_surveyed": "Utan ett filter kan alla dina användare enkäteras.", "without_a_filter_all_of_your_users_can_be_surveyed": "Utan ett filter kan alla dina användare enkäteras.",
"you_have_not_created_a_segment_yet": "Du har inte skapat ett segment ännu", "you_have_not_created_a_segment_yet": "Du har inte skapat ett segment ännu",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "Du måste ha två eller fler språk inställda i din arbetsyta för att kunna arbeta med översättningar.",
"your_description_here_recall_information_with": "Din beskrivning här. Återkalla information med @", "your_description_here_recall_information_with": "Din beskrivning här. Återkalla information med @",
"your_question_here_recall_information_with": "Din fråga här. Återkalla information med @", "your_question_here_recall_information_with": "Din fråga här. Återkalla information med @",
"your_web_app": "Din webbapp", "your_web_app": "Din webbapp",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "Filtrerade svar (Excel)", "filtered_responses_excel": "Filtrerade svar (Excel)",
"generating_qr_code": "Genererar QR-kod", "generating_qr_code": "Genererar QR-kod",
"impressions": "Visningar", "impressions": "Visningar",
"impressions_identified_only": "Visar bara visningar från identifierade kontakter",
"impressions_tooltip": "Antal gånger enkäten har visats.", "impressions_tooltip": "Antal gånger enkäten har visats.",
"in_app": { "in_app": {
"connection_description": "Enkäten kommer att visas för användare på din webbplats som matchar kriterierna nedan", "connection_description": "Enkäten kommer att visas för användare på din webbplats som matchar kriterierna nedan",
@@ -1989,6 +2012,7 @@
"last_quarter": "Senaste kvartalet", "last_quarter": "Senaste kvartalet",
"last_year": "Senaste året", "last_year": "Senaste året",
"limit": "Gräns", "limit": "Gräns",
"no_identified_impressions": "Inga visningar från identifierade kontakter",
"no_responses_found": "Inga svar hittades", "no_responses_found": "Inga svar hittades",
"other_values_found": "Andra värden hittades", "other_values_found": "Andra värden hittades",
"overall": "Övergripande", "overall": "Övergripande",
@@ -2011,6 +2035,7 @@
"starts": "Starter", "starts": "Starter",
"starts_tooltip": "Antal gånger enkäten har startats.", "starts_tooltip": "Antal gånger enkäten har startats.",
"survey_reset_successfully": "Enkät återställd! {responseCount} svar och {displayCount} visningar togs bort.", "survey_reset_successfully": "Enkät återställd! {responseCount} svar och {displayCount} visningar togs bort.",
"survey_results": "Resultat för {surveyName}",
"this_month": "Denna månad", "this_month": "Denna månad",
"this_quarter": "Detta kvartal", "this_quarter": "Detta kvartal",
"this_year": "Detta år", "this_year": "Detta år",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "Färglägger den fyllda delen av stapeln.", "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_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_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_height_description": "Styr 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_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_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_placeholder_opacity_description": "Tonar ut platshållartexten.",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "Färgar den inmatade texten i fälten.", "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": "Bakgrund",
"advanced_styling_field_option_bg_description": "Fyller alternativraderna.", "advanced_styling_field_option_bg_description": "Fyller alternativraderna.",
"advanced_styling_field_option_border": "Kantfärg",
"advanced_styling_field_option_border_description": "Markerar radio- och kryssrutealternativ.",
"advanced_styling_field_option_border_radius_description": "Rundar hörnen på alternativen.", "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_font_size_description": "Skalar textstorleken på alternativetiketten.",
"advanced_styling_field_option_label": "Etikettfärg", "advanced_styling_field_option_label": "Etikettfärg",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "Hur kan företaget förbättra sin vision och strategiöverensstämmelse?", "alignment_and_engagement_survey_question_4_headline": "Hur kan företaget förbättra sin vision och strategiöverensstämmelse?",
"alignment_and_engagement_survey_question_4_placeholder": "Skriv ditt svar här...", "alignment_and_engagement_survey_question_4_placeholder": "Skriv ditt svar här...",
"back": "Tillbaka", "back": "Tillbaka",
"block_1": "Block 1",
"block_10": "Block 10",
"block_2": "Block 2",
"block_3": "Block 3",
"block_4": "Block 4",
"block_5": "Block 5",
"block_6": "Block 6",
"block_7": "Block 7",
"block_8": "Block 8",
"block_9": "Block 9",
"book_interview": "Boka intervju", "book_interview": "Boka intervju",
"build_product_roadmap_description": "Identifiera det EN sak dina användare vill ha mest och bygg den.", "build_product_roadmap_description": "Identifiera det EN sak dina användare vill ha mest och bygg den.",
"build_product_roadmap_name": "Bygg produktroadmap", "build_product_roadmap_name": "Bygg produktroadmap",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "Aj, förlåt! Finns det något vi kan göra för att förbättra din upplevelse?", "csat_survey_question_3_headline": "Aj, förlåt! Finns det något vi kan göra för att förbättra din upplevelse?",
"csat_survey_question_3_placeholder": "Skriv ditt svar här...", "csat_survey_question_3_placeholder": "Skriv ditt svar här...",
"cta_description": "Visa information och uppmana användare att vidta en specifik åtgärd", "cta_description": "Visa information och uppmana användare att vidta en specifik åtgärd",
"custom_survey_block_1_name": "Block 1",
"custom_survey_description": "Skapa en enkät utan mall.", "custom_survey_description": "Skapa en enkät utan mall.",
"custom_survey_name": "Börja från början", "custom_survey_name": "Börja från början",
"custom_survey_question_1_headline": "Vad vill du veta?", "custom_survey_question_1_headline": "Vad vill du veta?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "Nej, tack!", "preview_survey_question_2_choice_2_label": "Nej, tack!",
"preview_survey_question_2_headline": "Vill du hållas uppdaterad?", "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_question_2_subheader": "Det här är ett exempel på en beskrivning.",
"preview_survey_question_open_text_headline": "Något mer du vill dela med dig av?",
"preview_survey_question_open_text_placeholder": "Skriv ditt svar här...",
"preview_survey_question_open_text_subheader": "Din feedback hjälper oss att bli bättre.",
"preview_survey_welcome_card_headline": "Välkommen!", "preview_survey_welcome_card_headline": "Välkommen!",
"prioritize_features_description": "Identifiera vilka funktioner dina användare behöver mest och minst.", "prioritize_features_description": "Identifiera vilka funktioner dina användare behöver mest och minst.",
"prioritize_features_name": "Prioritera funktioner", "prioritize_features_name": "Prioritera funktioner",
+54 -15
View File
@@ -175,9 +175,12 @@
"copy": "复制", "copy": "复制",
"copy_code": "复制 代码", "copy_code": "复制 代码",
"copy_link": "复制 链接", "copy_link": "复制 链接",
"count_attributes": "{value, plural, one {{value} 个属性} other {{value} 个属性}}", "count_attributes": "{count, plural, other {{count} 个属性}}",
"count_contacts": "{value, plural, other {{value} 联系人} }", "count_contacts": "{count, plural, other {{count} 联系人}}",
"count_responses": "{value, plural, other {{value} 回复} }", "count_members": "{count, plural, one {{count} 位成员} other {{count} 位成员}}",
"count_questions": "{count, plural, other {{count} 个问题} }",
"count_responses": "{count, plural, other {{count} 回复} }",
"count_selections": "{count, plural, other {已选择{count}项}}",
"create_new_organization": "创建 新的 组织", "create_new_organization": "创建 新的 组织",
"create_segment": "创建 细分", "create_segment": "创建 细分",
"create_survey": "创建 调查", "create_survey": "创建 调查",
@@ -191,6 +194,7 @@
"days": "天", "days": "天",
"default": "默认", "default": "默认",
"delete": "删除", "delete": "删除",
"delete_what": "删除{deleteWhat}",
"description": "描述", "description": "描述",
"dev_env": "开发 环境", "dev_env": "开发 环境",
"development": "开发环境", "development": "开发环境",
@@ -206,6 +210,8 @@
"download": "下载", "download": "下载",
"draft": "草稿", "draft": "草稿",
"duplicate": "复制", "duplicate": "复制",
"duplicate_copy": "(副本)",
"duplicate_copy_number": "(副本 {copyNumber}",
"e_commerce": "电子商务", "e_commerce": "电子商务",
"edit": "编辑", "edit": "编辑",
"email": "邮箱", "email": "邮箱",
@@ -218,13 +224,16 @@
"error": "错误", "error": "错误",
"error_component_description": "这个资源不存在或您没有权限访问它。", "error_component_description": "这个资源不存在或您没有权限访问它。",
"error_component_title": "错误 加载 资源", "error_component_title": "错误 加载 资源",
"error_loading_data": "数据加载出错",
"error_rate_limit_description": "请求 达到 最大 上限 , 请 稍后 再试 。", "error_rate_limit_description": "请求 达到 最大 上限 , 请 稍后 再试 。",
"error_rate_limit_title": "速率 限制 超过", "error_rate_limit_title": "速率 限制 超过",
"expand_rows": "展开 行", "expand_rows": "展开 行",
"failed_to_copy_to_clipboard": "复制到剪贴板失败", "failed_to_copy_to_clipboard": "复制到剪贴板失败",
"failed_to_load_organizations": "加载组织失败", "failed_to_load_organizations": "加载组织失败",
"failed_to_load_workspaces": "加载工作区失败", "failed_to_load_workspaces": "加载工作区失败",
"filter": "筛选",
"finish": "完成", "finish": "完成",
"first_name": "名字",
"follow_these": "遵循 这些", "follow_these": "遵循 这些",
"formbricks_version": "Formbricks 版本", "formbricks_version": "Formbricks 版本",
"full_name": "全名", "full_name": "全名",
@@ -237,6 +246,7 @@
"hidden_field": "隐藏 字段", "hidden_field": "隐藏 字段",
"hidden_fields": "隐藏 字段", "hidden_fields": "隐藏 字段",
"hide_column": "隐藏 列", "hide_column": "隐藏 列",
"id": "ID",
"image": "图片", "image": "图片",
"images": "图片", "images": "图片",
"import": "导入", "import": "导入",
@@ -254,6 +264,7 @@
"key": "键", "key": "键",
"label": "标签", "label": "标签",
"language": "语言", "language": "语言",
"last_name": "姓",
"learn_more": "了解 更多", "learn_more": "了解 更多",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "浅色遮罩层", "light_overlay": "浅色遮罩层",
@@ -268,7 +279,6 @@
"look_and_feel": "外观 & 感觉", "look_and_feel": "外观 & 感觉",
"manage": "管理", "manage": "管理",
"marketing": "市场营销", "marketing": "市场营销",
"member": "成员",
"members": "成员", "members": "成员",
"members_and_teams": "成员和团队", "members_and_teams": "成员和团队",
"membership_not_found": "未找到会员资格", "membership_not_found": "未找到会员资格",
@@ -280,6 +290,7 @@
"move_down": "下移", "move_down": "下移",
"move_up": "上移", "move_up": "上移",
"multiple_languages": "多种 语言", "multiple_languages": "多种 语言",
"my_product": "我的产品",
"name": "名称", "name": "名称",
"new": "新建", "new": "新建",
"new_version_available": "Formbricks {version} 在 这里。立即 升级!", "new_version_available": "Formbricks {version} 在 这里。立即 升级!",
@@ -375,8 +386,6 @@
"select_teams": "选择 团队", "select_teams": "选择 团队",
"selected": "已选择", "selected": "已选择",
"selected_questions": "选择的问题", "selected_questions": "选择的问题",
"selection": "选择",
"selections": "选择",
"send_test_email": "发送 测试 电子邮件", "send_test_email": "发送 测试 电子邮件",
"session_not_found": "会话 未找到", "session_not_found": "会话 未找到",
"settings": "设置", "settings": "设置",
@@ -428,6 +437,7 @@
"top_right": "右上", "top_right": "右上",
"try_again": "再试一次", "try_again": "再试一次",
"type": "类型", "type": "类型",
"unknown_survey": "未知调查",
"unlock_more_workspaces_with_a_higher_plan": "升级套餐以解锁更多工作区。", "unlock_more_workspaces_with_a_higher_plan": "升级套餐以解锁更多工作区。",
"update": "更新", "update": "更新",
"updated": "已更新", "updated": "已更新",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "刷新 联系人", "contacts_table_refresh": "刷新 联系人",
"contacts_table_refresh_success": "联系人 已成功刷新", "contacts_table_refresh_success": "联系人 已成功刷新",
"create_attribute": "创建属性", "create_attribute": "创建属性",
"create_key": "创建键",
"create_new_attribute": "创建新属性", "create_new_attribute": "创建新属性",
"create_new_attribute_description": "为细分目的创建新属性。", "create_new_attribute_description": "为细分目的创建新属性。",
"custom_attributes": "自定义属性", "custom_attributes": "自定义属性",
@@ -656,6 +665,7 @@
"delete_attribute_confirmation": "{value, plural, one {这将删除所选属性。与该属性相关的任何联系人数据都将丢失。} other {这将删除所选属性。与这些属性相关的任何联系人数据都将丢失。}}", "delete_attribute_confirmation": "{value, plural, one {这将删除所选属性。与该属性相关的任何联系人数据都将丢失。} other {这将删除所选属性。与这些属性相关的任何联系人数据都将丢失。}}",
"delete_contact_confirmation": "这将删除与此联系人相关的所有调查问卷回复和联系人属性。基于此联系人数据的任何定位和个性化将会丢失。", "delete_contact_confirmation": "这将删除与此联系人相关的所有调查问卷回复和联系人属性。基于此联系人数据的任何定位和个性化将会丢失。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {这将删除与此联系人相关的所有调查回复和联系人属性。基于此联系人数据的任何定位和个性化将丢失。如果此联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。} other {这将删除与这些联系人相关的所有调查回复和联系人属性。基于这些联系人数据的任何定位和个性化将丢失。如果这些联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。}}", "delete_contact_confirmation_with_quotas": "{value, plural, one {这将删除与此联系人相关的所有调查回复和联系人属性。基于此联系人数据的任何定位和个性化将丢失。如果此联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。} other {这将删除与这些联系人相关的所有调查回复和联系人属性。基于这些联系人数据的任何定位和个性化将丢失。如果这些联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。}}",
"displays": "展示次数",
"edit_attribute": "编辑属性", "edit_attribute": "编辑属性",
"edit_attribute_description": "更新此属性的标签和描述。", "edit_attribute_description": "更新此属性的标签和描述。",
"edit_attribute_values": "编辑属性", "edit_attribute_values": "编辑属性",
@@ -667,6 +677,7 @@
"invalid_csv_column_names": "无效的 CSV 列名:{columns}。作为新属性的列名只能包含小写字母、数字和下划线,并且必须以字母开头。", "invalid_csv_column_names": "无效的 CSV 列名:{columns}。作为新属性的列名只能包含小写字母、数字和下划线,并且必须以字母开头。",
"invalid_date_format": "日期格式无效。请使用有效日期。", "invalid_date_format": "日期格式无效。请使用有效日期。",
"invalid_number_format": "数字格式无效。请输入有效的数字。", "invalid_number_format": "数字格式无效。请输入有效的数字。",
"no_activity_yet": "暂无活动",
"no_published_link_surveys_available": "没有可用的已发布链接调查。请先发布一个链接调查。", "no_published_link_surveys_available": "没有可用的已发布链接调查。请先发布一个链接调查。",
"no_published_surveys": "没有已发布的调查", "no_published_surveys": "没有已发布的调查",
"no_responses_found": "未找到 响应", "no_responses_found": "未找到 响应",
@@ -681,6 +692,8 @@
"select_a_survey": "选择一个调查", "select_a_survey": "选择一个调查",
"select_attribute": "选择 属性", "select_attribute": "选择 属性",
"select_attribute_key": "选择属性键", "select_attribute_key": "选择属性键",
"survey_viewed": "已查看调查",
"survey_viewed_at": "查看时间",
"system_attributes": "系统属性", "system_attributes": "系统属性",
"unlock_contacts_description": "管理 联系人 并 发送 定向 调查", "unlock_contacts_description": "管理 联系人 并 发送 定向 调查",
"unlock_contacts_title": "通过 更 高级 划解锁 联系人", "unlock_contacts_title": "通过 更 高级 划解锁 联系人",
@@ -752,7 +765,12 @@
"link_google_sheet": "链接 Google 表格", "link_google_sheet": "链接 Google 表格",
"link_new_sheet": "链接 新 表格", "link_new_sheet": "链接 新 表格",
"no_integrations_yet": "您的 Google Sheet 集成会在您 添加 后 出现在这里。 ⏲️", "no_integrations_yet": "您的 Google Sheet 集成会在您 添加 后 出现在这里。 ⏲️",
"spreadsheet_url": "电子表格 URL" "reconnect_button": "重新连接",
"reconnect_button_description": "你的 Google Sheets 连接已过期。请重新连接以继续同步回复。你现有的表格链接和数据会被保留。",
"reconnect_button_tooltip": "重新连接集成以刷新你的访问权限。你现有的表格链接和数据会被保留。",
"spreadsheet_permission_error": "你没有权限访问此表格。请确保该表格已与你的 Google 账号共享,并且你拥有该表格的编辑权限。",
"spreadsheet_url": "电子表格 URL",
"token_expired_error": "Google Sheets 的刷新令牌已过期或被撤销。请重新连接集成。"
}, },
"include_created_at": "包括 创建 于", "include_created_at": "包括 创建 于",
"include_hidden_fields": "包括 隐藏 字段", "include_hidden_fields": "包括 隐藏 字段",
@@ -1067,7 +1085,7 @@
"email_customization_preview_email_heading": "嘿 {userName}", "email_customization_preview_email_heading": "嘿 {userName}",
"email_customization_preview_email_text": "这 是 一封 电子邮件 预览,展示 哪个 徽标 将在 电子邮件 中 渲染。", "email_customization_preview_email_text": "这 是 一封 电子邮件 预览,展示 哪个 徽标 将在 电子邮件 中 渲染。",
"error_deleting_organization_please_try_again": "删除 组织时 出错 。 请重试 。", "error_deleting_organization_please_try_again": "删除 组织时 出错 。 请重试 。",
"from_your_organization": "来自你的组织", "from_your_organization": "来自您组织的{memberName}",
"invitation_sent_once_more": "再次发送邀请。", "invitation_sent_once_more": "再次发送邀请。",
"invite_deleted_successfully": "邀请 删除 成功", "invite_deleted_successfully": "邀请 删除 成功",
"invite_expires_on": "邀请将于 {date} 过期", "invite_expires_on": "邀请将于 {date} 过期",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "添加 占位符 显示 如果 没有 值以 回忆", "add_fallback_placeholder": "添加 占位符 显示 如果 没有 值以 回忆",
"add_hidden_field_id": "添加 隐藏 字段 ID", "add_hidden_field_id": "添加 隐藏 字段 ID",
"add_highlight_border": "添加 高亮 边框", "add_highlight_border": "添加 高亮 边框",
"add_highlight_border_description": "仅适用于产品内调查。",
"add_logic": "添加逻辑", "add_logic": "添加逻辑",
"add_none_of_the_above": "添加 “以上 都 不 是”", "add_none_of_the_above": "添加 “以上 都 不 是”",
"add_option": "添加 选项", "add_option": "添加 选项",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "调整 \"调查 关闭\" 消息", "adjust_survey_closed_message": "调整 \"调查 关闭\" 消息",
"adjust_survey_closed_message_description": "更改 访客 看到 调查 关闭 时 的 消息。", "adjust_survey_closed_message_description": "更改 访客 看到 调查 关闭 时 的 消息。",
"adjust_the_theme_in_the": "调整主题在", "adjust_the_theme_in_the": "调整主题在",
"all_are_true": "全部为真",
"all_other_answers_will_continue_to": "所有其他答案将继续", "all_other_answers_will_continue_to": "所有其他答案将继续",
"allow_multi_select": "允许 多选", "allow_multi_select": "允许 多选",
"allow_multiple_files": "允许 多 个 文件", "allow_multiple_files": "允许 多 个 文件",
"allow_users_to_select_more_than_one_image": "允许 用户 选择 多于 一个 图片", "allow_users_to_select_more_than_one_image": "允许 用户 选择 多于 一个 图片",
"and_launch_surveys_in_your_website_or_app": "并 在 你 的 网站 或 应用 中 启动 问卷 。", "and_launch_surveys_in_your_website_or_app": "并 在 你 的 网站 或 应用 中 启动 问卷 。",
"animation": "动画", "animation": "动画",
"any_is_true": "任一为真",
"app_survey_description": "在 你的 网络 应用 或 网站 中 嵌入 问卷 收集 反馈 。", "app_survey_description": "在 你的 网络 应用 或 网站 中 嵌入 问卷 收集 反馈 。",
"assign": "指派 =", "assign": "指派 =",
"audience": "受众", "audience": "受众",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "后续 操作 已 更新, 并且 在 你 保存 调查 后 将 被 保存。", "follow_ups_modal_updated_successfull_toast": "后续 操作 已 更新, 并且 在 你 保存 调查 后 将 被 保存。",
"follow_ups_new": "新的跟进", "follow_ups_new": "新的跟进",
"follow_ups_upgrade_button_text": "升级 以启用 跟进", "follow_ups_upgrade_button_text": "升级 以启用 跟进",
"form_styling": "表单 样式",
"formbricks_sdk_is_not_connected": "Formbricks SDK 未连接", "formbricks_sdk_is_not_connected": "Formbricks SDK 未连接",
"four_points": "4 分", "four_points": "4 分",
"heading": "标题", "heading": "标题",
@@ -1520,7 +1540,7 @@
"option_idx": "选项 {choiceIndex}", "option_idx": "选项 {choiceIndex}",
"option_used_in_logic_error": "\"这个 选项 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"", "option_used_in_logic_error": "\"这个 选项 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
"optional": "可选", "optional": "可选",
"options": "选项", "options": "选项*",
"options_used_in_logic_bulk_error": "以下选项在逻辑中被使用:{questionIndexes}。请先从逻辑中删除它们。", "options_used_in_logic_bulk_error": "以下选项在逻辑中被使用:{questionIndexes}。请先从逻辑中删除它们。",
"override_theme_with_individual_styles_for_this_survey": "使用 个性化 样式 替代 这份 问卷 的 主题。", "override_theme_with_individual_styles_for_this_survey": "使用 个性化 样式 替代 这份 问卷 的 主题。",
"overwrite_global_waiting_time": "自定义冷却期", "overwrite_global_waiting_time": "自定义冷却期",
@@ -1545,6 +1565,7 @@
"question_deleted": "问题 已删除", "question_deleted": "问题 已删除",
"question_duplicated": "问题重复。", "question_duplicated": "问题重复。",
"question_id_updated": "问题 ID 更新", "question_id_updated": "问题 ID 更新",
"question_number": "第 {number} 题",
"question_used_in_logic_warning_text": "此区块中的元素已被用于逻辑规则,您确定要删除吗?", "question_used_in_logic_warning_text": "此区块中的元素已被用于逻辑规则,您确定要删除吗?",
"question_used_in_logic_warning_title": "逻辑不一致", "question_used_in_logic_warning_title": "逻辑不一致",
"question_used_in_quota": "此问题正在被“{quotaName}”配额使用", "question_used_in_quota": "此问题正在被“{quotaName}”配额使用",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "响应 限制 、 重定向 和 更多 。", "response_limits_redirections_and_more": "响应 限制 、 重定向 和 更多 。",
"response_options": "响应 选项", "response_options": "响应 选项",
"roundness": "圆度", "roundness": "圆度",
"roundness_description": "控制卡片角的圆润程度。", "roundness_description": "控制圆角的弧度。",
"row_used_in_logic_error": "\"这个 行 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"", "row_used_in_logic_error": "\"这个 行 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
"rows": "行", "rows": "行",
"save_and_close": "保存 和 关闭", "save_and_close": "保存 和 关闭",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "此 免费 & 开源 调查 已 关闭", "survey_completed_subheading": "此 免费 & 开源 调查 已 关闭",
"survey_display_settings": "调查显示设置", "survey_display_settings": "调查显示设置",
"survey_placement": "调查 放置", "survey_placement": "调查 放置",
"survey_styling": "表单 样式",
"survey_trigger": "调查 触发", "survey_trigger": "调查 触发",
"switch_multi_language_on_to_get_started": "开启多语言以开始使用 👉", "switch_multi_language_on_to_get_started": "开启多语言以开始使用 👉",
"target_block_not_found": "未找到目标区块", "target_block_not_found": "未找到目标区块",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "冷却期(跨问卷)", "waiting_time_across_surveys": "冷却期(跨问卷)",
"waiting_time_across_surveys_description": "为防止问卷疲劳,请选择此问卷与工作区冷却期的交互方式。", "waiting_time_across_surveys_description": "为防止问卷疲劳,请选择此问卷与工作区冷却期的交互方式。",
"welcome_message": "欢迎 信息", "welcome_message": "欢迎 信息",
"when": "当",
"without_a_filter_all_of_your_users_can_be_surveyed": "没有 过滤器 时 ,所有 用户 都可以 被 调查 。", "without_a_filter_all_of_your_users_can_be_surveyed": "没有 过滤器 时 ,所有 用户 都可以 被 调查 。",
"you_have_not_created_a_segment_yet": "您 还没有 创建 段落", "you_have_not_created_a_segment_yet": "您 还没有 创建 段落",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "要使用翻译功能,您的工作区需设置两种或以上语言。",
"your_description_here_recall_information_with": "在此输入描述。 调用信息与 @", "your_description_here_recall_information_with": "在此输入描述。 调用信息与 @",
"your_question_here_recall_information_with": "在此输入你的问题。 调用信息与 @", "your_question_here_recall_information_with": "在此输入你的问题。 调用信息与 @",
"your_web_app": "您的 网页应用", "your_web_app": "您的 网页应用",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "过滤 反馈 Excel", "filtered_responses_excel": "过滤 反馈 Excel",
"generating_qr_code": "正在生成二维码", "generating_qr_code": "正在生成二维码",
"impressions": "印象", "impressions": "印象",
"impressions_identified_only": "仅显示已识别联系人的展示次数",
"impressions_tooltip": "调查 被 查看 的 次数", "impressions_tooltip": "调查 被 查看 的 次数",
"in_app": { "in_app": {
"connection_description": "调查将显示给符合以下条件的您网站用户", "connection_description": "调查将显示给符合以下条件的您网站用户",
@@ -1989,6 +2012,7 @@
"last_quarter": "上季度", "last_quarter": "上季度",
"last_year": "去年", "last_year": "去年",
"limit": "限额", "limit": "限额",
"no_identified_impressions": "没有已识别联系人的展示次数",
"no_responses_found": "未找到响应", "no_responses_found": "未找到响应",
"other_values_found": "找到其他值", "other_values_found": "找到其他值",
"overall": "整体", "overall": "整体",
@@ -2011,6 +2035,7 @@
"starts": "开始", "starts": "开始",
"starts_tooltip": "调查 被 开始 的 次数", "starts_tooltip": "调查 被 开始 的 次数",
"survey_reset_successfully": "调查已重置成功!{responseCount} 个 反馈 和 {displayCount} 个 显示 已删除。", "survey_reset_successfully": "调查已重置成功!{responseCount} 个 反馈 和 {displayCount} 个 显示 已删除。",
"survey_results": "{surveyName} 结果",
"this_month": "本月", "this_month": "本月",
"this_quarter": "本季度", "this_quarter": "本季度",
"this_year": "今年", "this_year": "今年",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "设置进度条已填充部分的颜色。", "advanced_styling_field_indicator_bg_description": "设置进度条已填充部分的颜色。",
"advanced_styling_field_input_border_radius_description": "设置输入框圆角。", "advanced_styling_field_input_border_radius_description": "设置输入框圆角。",
"advanced_styling_field_input_font_size_description": "调整输入框内文字大小。", "advanced_styling_field_input_font_size_description": "调整输入框内文字大小。",
"advanced_styling_field_input_height_description": "设置输入框的最小高度。", "advanced_styling_field_input_height_description": "控制输入框的最小高度。",
"advanced_styling_field_input_padding_x_description": "增加输入框左右间距。", "advanced_styling_field_input_padding_x_description": "增加输入框左右间距。",
"advanced_styling_field_input_padding_y_description": "为输入框上下添加间距。", "advanced_styling_field_input_padding_y_description": "为输入框上下添加间距。",
"advanced_styling_field_input_placeholder_opacity_description": "调整占位提示文字的透明度。", "advanced_styling_field_input_placeholder_opacity_description": "调整占位提示文字的透明度。",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "设置输入框内已输入文字的颜色。", "advanced_styling_field_input_text_description": "设置输入框内已输入文字的颜色。",
"advanced_styling_field_option_bg": "背景色", "advanced_styling_field_option_bg": "背景色",
"advanced_styling_field_option_bg_description": "设置选项项的背景色。", "advanced_styling_field_option_bg_description": "设置选项项的背景色。",
"advanced_styling_field_option_border": "边框颜色",
"advanced_styling_field_option_border_description": "为单选框和复选框选项添加轮廓。",
"advanced_styling_field_option_border_radius_description": "设置选项的圆角。", "advanced_styling_field_option_border_radius_description": "设置选项的圆角。",
"advanced_styling_field_option_font_size_description": "调整选项标签文字的大小。", "advanced_styling_field_option_font_size_description": "调整选项标签文字的大小。",
"advanced_styling_field_option_label": "标签颜色", "advanced_styling_field_option_label": "标签颜色",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "公司 如何 改进 其 愿景 与 战略 的 协同?", "alignment_and_engagement_survey_question_4_headline": "公司 如何 改进 其 愿景 与 战略 的 协同?",
"alignment_and_engagement_survey_question_4_placeholder": "输入您的答案...", "alignment_and_engagement_survey_question_4_placeholder": "输入您的答案...",
"back": "返回", "back": "返回",
"block_1": "第 1 块",
"block_10": "第 10 块",
"block_2": "第 2 块",
"block_3": "第 3 块",
"block_4": "第 4 块",
"block_5": "第 5 块",
"block_6": "第 6 块",
"block_7": "第 7 块",
"block_8": "第 8 块",
"block_9": "第 9 块",
"book_interview": "预约 面试", "book_interview": "预约 面试",
"build_product_roadmap_description": "识别 用户 最 想要 的 一个 东西 并 构建 它 。", "build_product_roadmap_description": "识别 用户 最 想要 的 一个 东西 并 构建 它 。",
"build_product_roadmap_name": "构建 产品 路线图", "build_product_roadmap_name": "构建 产品 路线图",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "糟糕, 对不起!我们可以做些什么来改善您的体验?", "csat_survey_question_3_headline": "糟糕, 对不起!我们可以做些什么来改善您的体验?",
"csat_survey_question_3_placeholder": "在此输入您的答案...", "csat_survey_question_3_placeholder": "在此输入您的答案...",
"cta_description": "显示 信息 并 提示用户采取 特定行动", "cta_description": "显示 信息 并 提示用户采取 特定行动",
"custom_survey_block_1_name": "模块 1",
"custom_survey_description": "创建 一个 没有 模板 的 调查。", "custom_survey_description": "创建 一个 没有 模板 的 调查。",
"custom_survey_name": "从零开始", "custom_survey_name": "从零开始",
"custom_survey_question_1_headline": "你 想 知道 什么?", "custom_survey_question_1_headline": "你 想 知道 什么?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "不,谢谢!", "preview_survey_question_2_choice_2_label": "不,谢谢!",
"preview_survey_question_2_headline": "想 了解 最新信息吗?", "preview_survey_question_2_headline": "想 了解 最新信息吗?",
"preview_survey_question_2_subheader": "这是一个示例描述。", "preview_survey_question_2_subheader": "这是一个示例描述。",
"preview_survey_question_open_text_headline": "还有什么想和我们分享的吗?",
"preview_survey_question_open_text_placeholder": "请在这里输入你的答案...",
"preview_survey_question_open_text_subheader": "你的反馈能帮助我们改进。",
"preview_survey_welcome_card_headline": "欢迎!", "preview_survey_welcome_card_headline": "欢迎!",
"prioritize_features_description": "确定 用户 最 需要 和 最 不 需要 的 功能。", "prioritize_features_description": "确定 用户 最 需要 和 最 不 需要 的 功能。",
"prioritize_features_name": "优先 功能", "prioritize_features_name": "优先 功能",
+53 -14
View File
@@ -175,9 +175,12 @@
"copy": "複製", "copy": "複製",
"copy_code": "複製程式碼", "copy_code": "複製程式碼",
"copy_link": "複製連結", "copy_link": "複製連結",
"count_attributes": "{value, plural, one {{value} 個屬性} other {{value} 個屬性}}", "count_attributes": "{count, plural, other {{count} 個屬性}}",
"count_contacts": "{value, plural, other {{value} 聯絡人} }", "count_contacts": "{count, plural, other {{count} 聯絡人}}",
"count_responses": "{value, plural, other {{value} 回應} }", "count_members": "{count, plural, one {{count} 位成員} other {{count} 位成員}}",
"count_questions": "{count, plural, other {{count} 個問題}}",
"count_responses": "{count, plural, other {{count} 回應} }",
"count_selections": "{count, plural, one {{count} 個選項} other {{count} 個選項}}",
"create_new_organization": "建立新組織", "create_new_organization": "建立新組織",
"create_segment": "建立區隔", "create_segment": "建立區隔",
"create_survey": "建立問卷", "create_survey": "建立問卷",
@@ -191,6 +194,7 @@
"days": "天", "days": "天",
"default": "預設", "default": "預設",
"delete": "刪除", "delete": "刪除",
"delete_what": "刪除{deleteWhat}",
"description": "描述", "description": "描述",
"dev_env": "開發環境", "dev_env": "開發環境",
"development": "開發", "development": "開發",
@@ -206,6 +210,8 @@
"download": "下載", "download": "下載",
"draft": "草稿", "draft": "草稿",
"duplicate": "複製", "duplicate": "複製",
"duplicate_copy": "(複製)",
"duplicate_copy_number": "(複製 {copyNumber}",
"e_commerce": "電子商務", "e_commerce": "電子商務",
"edit": "編輯", "edit": "編輯",
"email": "電子郵件", "email": "電子郵件",
@@ -218,13 +224,16 @@
"error": "錯誤", "error": "錯誤",
"error_component_description": "此資源不存在或您沒有存取權限。", "error_component_description": "此資源不存在或您沒有存取權限。",
"error_component_title": "載入資源錯誤", "error_component_title": "載入資源錯誤",
"error_loading_data": "載入資料時發生錯誤",
"error_rate_limit_description": "已達 到最大 請求 次數。請 稍後 再試。", "error_rate_limit_description": "已達 到最大 請求 次數。請 稍後 再試。",
"error_rate_limit_title": "限流超過", "error_rate_limit_title": "限流超過",
"expand_rows": "展開列", "expand_rows": "展開列",
"failed_to_copy_to_clipboard": "無法複製到剪貼簿", "failed_to_copy_to_clipboard": "無法複製到剪貼簿",
"failed_to_load_organizations": "無法載入組織", "failed_to_load_organizations": "無法載入組織",
"failed_to_load_workspaces": "載入工作區失敗", "failed_to_load_workspaces": "載入工作區失敗",
"filter": "篩選",
"finish": "完成", "finish": "完成",
"first_name": "名字",
"follow_these": "按照這些步驟", "follow_these": "按照這些步驟",
"formbricks_version": "Formbricks 版本", "formbricks_version": "Formbricks 版本",
"full_name": "全名", "full_name": "全名",
@@ -237,6 +246,7 @@
"hidden_field": "隱藏欄位", "hidden_field": "隱藏欄位",
"hidden_fields": "隱藏欄位", "hidden_fields": "隱藏欄位",
"hide_column": "隱藏欄位", "hide_column": "隱藏欄位",
"id": "ID",
"image": "圖片", "image": "圖片",
"images": "圖片", "images": "圖片",
"import": "匯入", "import": "匯入",
@@ -254,6 +264,7 @@
"key": "金鑰", "key": "金鑰",
"label": "標籤", "label": "標籤",
"language": "語言", "language": "語言",
"last_name": "姓氏",
"learn_more": "瞭解更多", "learn_more": "瞭解更多",
"license_expired": "License Expired", "license_expired": "License Expired",
"light_overlay": "淺色覆蓋", "light_overlay": "淺色覆蓋",
@@ -268,7 +279,6 @@
"look_and_feel": "外觀與風格", "look_and_feel": "外觀與風格",
"manage": "管理", "manage": "管理",
"marketing": "行銷", "marketing": "行銷",
"member": "成員",
"members": "成員", "members": "成員",
"members_and_teams": "成員與團隊", "members_and_teams": "成員與團隊",
"membership_not_found": "找不到成員資格", "membership_not_found": "找不到成員資格",
@@ -280,6 +290,7 @@
"move_down": "下移", "move_down": "下移",
"move_up": "上移", "move_up": "上移",
"multiple_languages": "多種語言", "multiple_languages": "多種語言",
"my_product": "我的產品",
"name": "名稱", "name": "名稱",
"new": "新增", "new": "新增",
"new_version_available": "Formbricks '{'version'}' 已推出。立即升級!", "new_version_available": "Formbricks '{'version'}' 已推出。立即升級!",
@@ -375,8 +386,6 @@
"select_teams": "選擇 團隊", "select_teams": "選擇 團隊",
"selected": "已選取", "selected": "已選取",
"selected_questions": "選取的問題", "selected_questions": "選取的問題",
"selection": "選取",
"selections": "選取",
"send_test_email": "發送測試電子郵件", "send_test_email": "發送測試電子郵件",
"session_not_found": "找不到工作階段", "session_not_found": "找不到工作階段",
"settings": "設定", "settings": "設定",
@@ -428,6 +437,7 @@
"top_right": "右上", "top_right": "右上",
"try_again": "再試一次", "try_again": "再試一次",
"type": "類型", "type": "類型",
"unknown_survey": "未知問卷",
"unlock_more_workspaces_with_a_higher_plan": "升級方案以解鎖更多工作區。", "unlock_more_workspaces_with_a_higher_plan": "升級方案以解鎖更多工作區。",
"update": "更新", "update": "更新",
"updated": "已更新", "updated": "已更新",
@@ -645,7 +655,6 @@
"contacts_table_refresh": "重新整理聯絡人", "contacts_table_refresh": "重新整理聯絡人",
"contacts_table_refresh_success": "聯絡人已成功重新整理", "contacts_table_refresh_success": "聯絡人已成功重新整理",
"create_attribute": "建立屬性", "create_attribute": "建立屬性",
"create_key": "建立金鑰",
"create_new_attribute": "建立新屬性", "create_new_attribute": "建立新屬性",
"create_new_attribute_description": "建立新屬性以進行分群用途。", "create_new_attribute_description": "建立新屬性以進行分群用途。",
"custom_attributes": "自訂屬性", "custom_attributes": "自訂屬性",
@@ -656,6 +665,7 @@
"delete_attribute_confirmation": "{value, plural, one {這將刪除所選屬性。與此屬性相關的聯絡人資料將會遺失。} other {這將刪除所選屬性。與這些屬性相關的聯絡人資料將會遺失。}}", "delete_attribute_confirmation": "{value, plural, one {這將刪除所選屬性。與此屬性相關的聯絡人資料將會遺失。} other {這將刪除所選屬性。與這些屬性相關的聯絡人資料將會遺失。}}",
"delete_contact_confirmation": "這將刪除與此聯繫人相關的所有調查回應和聯繫屬性。任何基於此聯繫人數據的定位和個性化將會丟失。", "delete_contact_confirmation": "這將刪除與此聯繫人相關的所有調查回應和聯繫屬性。任何基於此聯繫人數據的定位和個性化將會丟失。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {這將刪除與這個 contact 相關的所有調查響應和聯繫人屬性。基於這個 contact 數據的任何定向和個性化功能將會丟失。如果這個 contact 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。} other {這將刪除與這些 contacts 相關的所有調查響應和聯繫人屬性。基於這些 contacts 數據的任何定向和個性化功能將會丟失。如果這些 contacts 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。}}", "delete_contact_confirmation_with_quotas": "{value, plural, one {這將刪除與這個 contact 相關的所有調查響應和聯繫人屬性。基於這個 contact 數據的任何定向和個性化功能將會丟失。如果這個 contact 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。} other {這將刪除與這些 contacts 相關的所有調查響應和聯繫人屬性。基於這些 contacts 數據的任何定向和個性化功能將會丟失。如果這些 contacts 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。}}",
"displays": "顯示次數",
"edit_attribute": "編輯屬性", "edit_attribute": "編輯屬性",
"edit_attribute_description": "更新此屬性的標籤與描述。", "edit_attribute_description": "更新此屬性的標籤與描述。",
"edit_attribute_values": "編輯屬性", "edit_attribute_values": "編輯屬性",
@@ -667,6 +677,7 @@
"invalid_csv_column_names": "無效的 CSV 欄位名稱:{columns}。作為新屬性的欄位名稱只能包含小寫字母、數字和底線,且必須以字母開頭。", "invalid_csv_column_names": "無效的 CSV 欄位名稱:{columns}。作為新屬性的欄位名稱只能包含小寫字母、數字和底線,且必須以字母開頭。",
"invalid_date_format": "日期格式無效。請使用有效的日期。", "invalid_date_format": "日期格式無效。請使用有效的日期。",
"invalid_number_format": "數字格式無效。請輸入有效的數字。", "invalid_number_format": "數字格式無效。請輸入有效的數字。",
"no_activity_yet": "尚無活動",
"no_published_link_surveys_available": "沒有可用的已發佈連結問卷。請先發佈一個連結問卷。", "no_published_link_surveys_available": "沒有可用的已發佈連結問卷。請先發佈一個連結問卷。",
"no_published_surveys": "沒有已發佈的問卷", "no_published_surveys": "沒有已發佈的問卷",
"no_responses_found": "找不到回應", "no_responses_found": "找不到回應",
@@ -681,6 +692,8 @@
"select_a_survey": "選擇問卷", "select_a_survey": "選擇問卷",
"select_attribute": "選取屬性", "select_attribute": "選取屬性",
"select_attribute_key": "選取屬性鍵值", "select_attribute_key": "選取屬性鍵值",
"survey_viewed": "已查看問卷",
"survey_viewed_at": "查看時間",
"system_attributes": "系統屬性", "system_attributes": "系統屬性",
"unlock_contacts_description": "管理聯絡人並發送目標問卷", "unlock_contacts_description": "管理聯絡人並發送目標問卷",
"unlock_contacts_title": "使用更高等級的方案解鎖聯絡人", "unlock_contacts_title": "使用更高等級的方案解鎖聯絡人",
@@ -752,7 +765,12 @@
"link_google_sheet": "連結 Google 試算表", "link_google_sheet": "連結 Google 試算表",
"link_new_sheet": "連結新試算表", "link_new_sheet": "連結新試算表",
"no_integrations_yet": "您的 Google 試算表整合將在您新增後立即顯示在此處。⏲️", "no_integrations_yet": "您的 Google 試算表整合將在您新增後立即顯示在此處。⏲️",
"spreadsheet_url": "試算表網址" "reconnect_button": "重新連線",
"reconnect_button_description": "你的 Google Sheets 連線已過期。請重新連線以繼續同步回應。你現有的試算表連結和資料都會被保留。",
"reconnect_button_tooltip": "重新連線整合以刷新存取權限。你現有的試算表連結和資料都會被保留。",
"spreadsheet_permission_error": "你沒有權限存取這個試算表。請確認該試算表已與你的 Google 帳戶分享,且你擁有寫入權限。",
"spreadsheet_url": "試算表網址",
"token_expired_error": "Google Sheets 的刷新權杖已過期或被撤銷。請重新連線整合。"
}, },
"include_created_at": "包含建立於", "include_created_at": "包含建立於",
"include_hidden_fields": "包含隱藏欄位", "include_hidden_fields": "包含隱藏欄位",
@@ -1232,6 +1250,7 @@
"add_fallback_placeholder": "新增 預設 以顯示是否沒 有 值 可 回憶 。", "add_fallback_placeholder": "新增 預設 以顯示是否沒 有 值 可 回憶 。",
"add_hidden_field_id": "新增隱藏欄位 ID", "add_hidden_field_id": "新增隱藏欄位 ID",
"add_highlight_border": "新增醒目提示邊框", "add_highlight_border": "新增醒目提示邊框",
"add_highlight_border_description": "僅適用於產品內調查。",
"add_logic": "新增邏輯", "add_logic": "新增邏輯",
"add_none_of_the_above": "新增 \"以上皆非\"", "add_none_of_the_above": "新增 \"以上皆非\"",
"add_option": "新增選項", "add_option": "新增選項",
@@ -1248,12 +1267,14 @@
"adjust_survey_closed_message": "調整「問卷已關閉」訊息", "adjust_survey_closed_message": "調整「問卷已關閉」訊息",
"adjust_survey_closed_message_description": "變更訪客在問卷關閉時看到的訊息。", "adjust_survey_closed_message_description": "變更訪客在問卷關閉時看到的訊息。",
"adjust_the_theme_in_the": "在", "adjust_the_theme_in_the": "在",
"all_are_true": "全部為真",
"all_other_answers_will_continue_to": "所有其他答案將繼續", "all_other_answers_will_continue_to": "所有其他答案將繼續",
"allow_multi_select": "允許多重選取", "allow_multi_select": "允許多重選取",
"allow_multiple_files": "允許上傳多個檔案", "allow_multiple_files": "允許上傳多個檔案",
"allow_users_to_select_more_than_one_image": "允許使用者選取多張圖片", "allow_users_to_select_more_than_one_image": "允許使用者選取多張圖片",
"and_launch_surveys_in_your_website_or_app": "並在您的網站或應用程式中啟動問卷。", "and_launch_surveys_in_your_website_or_app": "並在您的網站或應用程式中啟動問卷。",
"animation": "動畫", "animation": "動畫",
"any_is_true": "任一為真",
"app_survey_description": "將問卷嵌入您的 Web 應用程式或網站中以收集回應。", "app_survey_description": "將問卷嵌入您的 Web 應用程式或網站中以收集回應。",
"assign": "等於 =", "assign": "等於 =",
"audience": "受眾", "audience": "受眾",
@@ -1430,7 +1451,6 @@
"follow_ups_modal_updated_successfull_toast": "後續 動作 已 更新 並 將 在 你 儲存 調查 後 儲存", "follow_ups_modal_updated_successfull_toast": "後續 動作 已 更新 並 將 在 你 儲存 調查 後 儲存",
"follow_ups_new": "新增後續追蹤", "follow_ups_new": "新增後續追蹤",
"follow_ups_upgrade_button_text": "升級以啟用後續追蹤", "follow_ups_upgrade_button_text": "升級以啟用後續追蹤",
"form_styling": "表單樣式設定",
"formbricks_sdk_is_not_connected": "Formbricks SDK 未連線", "formbricks_sdk_is_not_connected": "Formbricks SDK 未連線",
"four_points": "4 分", "four_points": "4 分",
"heading": "標題", "heading": "標題",
@@ -1520,7 +1540,7 @@
"option_idx": "選項 '{'choiceIndex'}'", "option_idx": "選項 '{'choiceIndex'}'",
"option_used_in_logic_error": "此選項用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。", "option_used_in_logic_error": "此選項用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
"optional": "選填", "optional": "選填",
"options": "選項", "options": "選項*",
"options_used_in_logic_bulk_error": "以下選項已用於邏輯中:{questionIndexes}。請先從邏輯中移除它們。", "options_used_in_logic_bulk_error": "以下選項已用於邏輯中:{questionIndexes}。請先從邏輯中移除它們。",
"override_theme_with_individual_styles_for_this_survey": "使用此問卷的個別樣式覆寫主題。", "override_theme_with_individual_styles_for_this_survey": "使用此問卷的個別樣式覆寫主題。",
"overwrite_global_waiting_time": "自訂冷卻期", "overwrite_global_waiting_time": "自訂冷卻期",
@@ -1545,6 +1565,7 @@
"question_deleted": "問題已刪除。", "question_deleted": "問題已刪除。",
"question_duplicated": "問題已複製。", "question_duplicated": "問題已複製。",
"question_id_updated": "問題 ID 已更新", "question_id_updated": "問題 ID 已更新",
"question_number": "第 {number} 題",
"question_used_in_logic_warning_text": "此區塊中的元素已用於邏輯規則,確定要刪除嗎?", "question_used_in_logic_warning_text": "此區塊中的元素已用於邏輯規則,確定要刪除嗎?",
"question_used_in_logic_warning_title": "邏輯不一致", "question_used_in_logic_warning_title": "邏輯不一致",
"question_used_in_quota": "此問題正被使用於「{quotaName}」配額中", "question_used_in_quota": "此問題正被使用於「{quotaName}」配額中",
@@ -1603,7 +1624,7 @@
"response_limits_redirections_and_more": "回應限制、重新導向等。", "response_limits_redirections_and_more": "回應限制、重新導向等。",
"response_options": "回應選項", "response_options": "回應選項",
"roundness": "圓角", "roundness": "圓角",
"roundness_description": "調整卡片邊角的圓度。", "roundness_description": "調整邊角的圓潤程度。",
"row_used_in_logic_error": "此 row 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。", "row_used_in_logic_error": "此 row 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
"rows": "列", "rows": "列",
"save_and_close": "儲存並關閉", "save_and_close": "儲存並關閉",
@@ -1649,6 +1670,7 @@
"survey_completed_subheading": "此免費且開源的問卷已關閉", "survey_completed_subheading": "此免費且開源的問卷已關閉",
"survey_display_settings": "問卷顯示設定", "survey_display_settings": "問卷顯示設定",
"survey_placement": "問卷位置", "survey_placement": "問卷位置",
"survey_styling": "表單樣式設定",
"survey_trigger": "問卷觸發器", "survey_trigger": "問卷觸發器",
"switch_multi_language_on_to_get_started": "請開啟多語言功能以開始使用 👉", "switch_multi_language_on_to_get_started": "請開啟多語言功能以開始使用 👉",
"target_block_not_found": "找不到目標區塊", "target_block_not_found": "找不到目標區塊",
@@ -1737,9 +1759,9 @@
"waiting_time_across_surveys": "冷卻期(跨問卷)", "waiting_time_across_surveys": "冷卻期(跨問卷)",
"waiting_time_across_surveys_description": "為避免問卷疲勞,請選擇此問卷如何與工作區的冷卻期互動。", "waiting_time_across_surveys_description": "為避免問卷疲勞,請選擇此問卷如何與工作區的冷卻期互動。",
"welcome_message": "歡迎訊息", "welcome_message": "歡迎訊息",
"when": "當",
"without_a_filter_all_of_your_users_can_be_surveyed": "如果沒有篩選器,則可以調查您的所有使用者。", "without_a_filter_all_of_your_users_can_be_surveyed": "如果沒有篩選器,則可以調查您的所有使用者。",
"you_have_not_created_a_segment_yet": "您尚未建立區隔", "you_have_not_created_a_segment_yet": "您尚未建立區隔",
"you_need_to_have_two_or_more_languages_set_up_in_your_workspace_to_work_with_translations": "您必須在工作區中設定兩種或以上語言,才能進行翻譯作業。",
"your_description_here_recall_information_with": "您的描述在這裡。使用 @ 回憶資訊", "your_description_here_recall_information_with": "您的描述在這裡。使用 @ 回憶資訊",
"your_question_here_recall_information_with": "您的問題在這裡。使用 @ 回憶資訊", "your_question_here_recall_information_with": "您的問題在這裡。使用 @ 回憶資訊",
"your_web_app": "您的 Web 應用程式", "your_web_app": "您的 Web 應用程式",
@@ -1947,6 +1969,7 @@
"filtered_responses_excel": "篩選回應 (Excel)", "filtered_responses_excel": "篩選回應 (Excel)",
"generating_qr_code": "正在生成 QR code", "generating_qr_code": "正在生成 QR code",
"impressions": "曝光數", "impressions": "曝光數",
"impressions_identified_only": "僅顯示已識別聯絡人的曝光次數",
"impressions_tooltip": "問卷已檢視的次數。", "impressions_tooltip": "問卷已檢視的次數。",
"in_app": { "in_app": {
"connection_description": "調查將顯示給符合以下列出條件的網站用戶", "connection_description": "調查將顯示給符合以下列出條件的網站用戶",
@@ -1989,6 +2012,7 @@
"last_quarter": "上一季", "last_quarter": "上一季",
"last_year": "去年", "last_year": "去年",
"limit": "限制", "limit": "限制",
"no_identified_impressions": "沒有來自已識別聯絡人的曝光次數",
"no_responses_found": "找不到回應", "no_responses_found": "找不到回應",
"other_values_found": "找到其他值", "other_values_found": "找到其他值",
"overall": "整體", "overall": "整體",
@@ -2011,6 +2035,7 @@
"starts": "開始次數", "starts": "開始次數",
"starts_tooltip": "問卷已開始的次數。", "starts_tooltip": "問卷已開始的次數。",
"survey_reset_successfully": "調查 重置 成功!{responseCount} 條回應和 {displayCount} 個顯示被刪除。", "survey_reset_successfully": "調查 重置 成功!{responseCount} 條回應和 {displayCount} 個顯示被刪除。",
"survey_results": "{surveyName} 結果",
"this_month": "本月", "this_month": "本月",
"this_quarter": "本季", "this_quarter": "本季",
"this_year": "今年", "this_year": "今年",
@@ -2158,7 +2183,7 @@
"advanced_styling_field_indicator_bg_description": "設定進度條已填滿部分的顏色。", "advanced_styling_field_indicator_bg_description": "設定進度條已填滿部分的顏色。",
"advanced_styling_field_input_border_radius_description": "調整輸入框的圓角。", "advanced_styling_field_input_border_radius_description": "調整輸入框的圓角。",
"advanced_styling_field_input_font_size_description": "調整輸入框內輸入文字的大小。", "advanced_styling_field_input_font_size_description": "調整輸入框內輸入文字的大小。",
"advanced_styling_field_input_height_description": "設定輸入欄的最小高度。", "advanced_styling_field_input_height_description": "控制輸入欄的最小高度。",
"advanced_styling_field_input_padding_x_description": "在左右兩側增加間距。", "advanced_styling_field_input_padding_x_description": "在左右兩側增加間距。",
"advanced_styling_field_input_padding_y_description": "在上方和下方增加間距。", "advanced_styling_field_input_padding_y_description": "在上方和下方增加間距。",
"advanced_styling_field_input_placeholder_opacity_description": "讓提示文字變得更淡。", "advanced_styling_field_input_placeholder_opacity_description": "讓提示文字變得更淡。",
@@ -2167,6 +2192,8 @@
"advanced_styling_field_input_text_description": "設定輸入文字的顏色。", "advanced_styling_field_input_text_description": "設定輸入文字的顏色。",
"advanced_styling_field_option_bg": "背景", "advanced_styling_field_option_bg": "背景",
"advanced_styling_field_option_bg_description": "填滿選項項目背景。", "advanced_styling_field_option_bg_description": "填滿選項項目背景。",
"advanced_styling_field_option_border": "邊框顏色",
"advanced_styling_field_option_border_description": "為單選框和核取方塊選項加上外框。",
"advanced_styling_field_option_border_radius_description": "讓選項的邊角變圓。", "advanced_styling_field_option_border_radius_description": "讓選項的邊角變圓。",
"advanced_styling_field_option_font_size_description": "調整選項標籤文字的大小。", "advanced_styling_field_option_font_size_description": "調整選項標籤文字的大小。",
"advanced_styling_field_option_label": "標籤顏色", "advanced_styling_field_option_label": "標籤顏色",
@@ -2376,6 +2403,16 @@
"alignment_and_engagement_survey_question_4_headline": "公司如何改善其願景和策略一致性?", "alignment_and_engagement_survey_question_4_headline": "公司如何改善其願景和策略一致性?",
"alignment_and_engagement_survey_question_4_placeholder": "在此輸入您的答案...", "alignment_and_engagement_survey_question_4_placeholder": "在此輸入您的答案...",
"back": "返回", "back": "返回",
"block_1": "區塊 1",
"block_10": "區塊 10",
"block_2": "區塊 2",
"block_3": "區塊 3",
"block_4": "區塊 4",
"block_5": "區塊 5",
"block_6": "區塊 6",
"block_7": "區塊 7",
"block_8": "區塊 8",
"block_9": "區塊 9",
"book_interview": "預訂面試", "book_interview": "預訂面試",
"build_product_roadmap_description": "找出您的使用者最想要的一件事,然後建立它。", "build_product_roadmap_description": "找出您的使用者最想要的一件事,然後建立它。",
"build_product_roadmap_name": "建立產品路線圖", "build_product_roadmap_name": "建立產品路線圖",
@@ -2583,7 +2620,6 @@
"csat_survey_question_3_headline": "唉,抱歉!我們是否有任何可以改善您體驗的地方?", "csat_survey_question_3_headline": "唉,抱歉!我們是否有任何可以改善您體驗的地方?",
"csat_survey_question_3_placeholder": "在此輸入您的答案...", "csat_survey_question_3_placeholder": "在此輸入您的答案...",
"cta_description": "顯示資訊並提示使用者採取特定操作", "cta_description": "顯示資訊並提示使用者採取特定操作",
"custom_survey_block_1_name": "區塊 1",
"custom_survey_description": "建立沒有範本的問卷。", "custom_survey_description": "建立沒有範本的問卷。",
"custom_survey_name": "從頭開始", "custom_survey_name": "從頭開始",
"custom_survey_question_1_headline": "您想瞭解什麼?", "custom_survey_question_1_headline": "您想瞭解什麼?",
@@ -2986,6 +3022,9 @@
"preview_survey_question_2_choice_2_label": "不用了,謝謝!", "preview_survey_question_2_choice_2_label": "不用了,謝謝!",
"preview_survey_question_2_headline": "想要緊跟最新動態嗎?", "preview_survey_question_2_headline": "想要緊跟最新動態嗎?",
"preview_survey_question_2_subheader": "這是一個範例說明。", "preview_survey_question_2_subheader": "這是一個範例說明。",
"preview_survey_question_open_text_headline": "還有什麼想和我們分享的嗎?",
"preview_survey_question_open_text_placeholder": "在此輸入您的答案...",
"preview_survey_question_open_text_subheader": "您的回饋能幫助我們進步。",
"preview_survey_welcome_card_headline": "歡迎!", "preview_survey_welcome_card_headline": "歡迎!",
"prioritize_features_description": "找出您的使用者最需要和最不需要的功能。", "prioritize_features_description": "找出您的使用者最需要和最不需要的功能。",
"prioritize_features_name": "優先排序功能", "prioritize_features_name": "優先排序功能",
@@ -42,7 +42,7 @@ export const SingleResponseCardBody = ({
return ( return (
<span <span
key={index} key={index}
className="mr-0.5 ml-0.5 rounded-md border border-slate-200 bg-slate-50 px-1 py-0.5 text-sm first:ml-0"> className="ml-0.5 mr-0.5 rounded-md border border-slate-200 bg-slate-50 px-1 py-0.5 text-sm first:ml-0">
@{part} @{part}
</span> </span>
); );
@@ -1,6 +1,8 @@
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import { z } from "zod"; import { z } from "zod";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { InvalidInputError } from "@formbricks/types/errors";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
import { import {
mockedPrismaWebhookUpdateReturn, mockedPrismaWebhookUpdateReturn,
prismaNotFoundError, prismaNotFoundError,
@@ -18,6 +20,10 @@ vi.mock("@formbricks/database", () => ({
}, },
})); }));
vi.mock("@/lib/utils/validate-webhook-url", () => ({
validateWebhookUrl: vi.fn().mockResolvedValue(undefined),
}));
describe("getWebhook", () => { describe("getWebhook", () => {
test("returns ok if webhook is found", async () => { test("returns ok if webhook is found", async () => {
vi.mocked(prisma.webhook.findUnique).mockResolvedValueOnce({ id: "123" }); vi.mocked(prisma.webhook.findUnique).mockResolvedValueOnce({ id: "123" });
@@ -63,6 +69,44 @@ describe("updateWebhook", () => {
} }
}); });
test("calls validateWebhookUrl when URL is provided", async () => {
vi.mocked(prisma.webhook.update).mockResolvedValueOnce(mockedPrismaWebhookUpdateReturn);
await updateWebhook("123", mockedWebhookUpdateReturn);
expect(validateWebhookUrl).toHaveBeenCalledWith("https://example.com");
});
test("returns bad_request and skips Prisma update when URL fails SSRF validation", async () => {
vi.mocked(validateWebhookUrl).mockRejectedValueOnce(
new InvalidInputError("Webhook URL must not point to private or internal IP addresses")
);
const result = await updateWebhook("123", mockedWebhookUpdateReturn);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.type).toBe("bad_request");
expect(result.error.details[0].field).toBe("url");
}
expect(prisma.webhook.update).not.toHaveBeenCalled();
});
test("returns internal_server_error when validateWebhookUrl throws an unexpected error", async () => {
vi.mocked(validateWebhookUrl).mockRejectedValueOnce(new Error("unexpected DNS failure"));
const result = await updateWebhook("123", mockedWebhookUpdateReturn);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.type).toBe("internal_server_error");
expect(result.error.details[0].field).toBe("url");
}
expect(prisma.webhook.update).not.toHaveBeenCalled();
});
test("returns not_found if record does not exist", async () => { test("returns not_found if record does not exist", async () => {
vi.mocked(prisma.webhook.update).mockRejectedValueOnce(prismaNotFoundError); vi.mocked(prisma.webhook.update).mockRejectedValueOnce(prismaNotFoundError);
const result = await updateWebhook("999", mockedWebhookUpdateReturn); const result = await updateWebhook("999", mockedWebhookUpdateReturn);
@@ -3,6 +3,8 @@ import { z } from "zod";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { PrismaErrorType } from "@formbricks/database/types/error"; import { PrismaErrorType } from "@formbricks/database/types/error";
import { Result, err, ok } from "@formbricks/types/error-handlers"; import { Result, err, ok } from "@formbricks/types/error-handlers";
import { InvalidInputError } from "@formbricks/types/errors";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
import { ZWebhookUpdateSchema } from "@/modules/api/v2/management/webhooks/[webhookId]/types/webhooks"; import { ZWebhookUpdateSchema } from "@/modules/api/v2/management/webhooks/[webhookId]/types/webhooks";
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
@@ -34,6 +36,23 @@ export const updateWebhook = async (
webhookId: string, webhookId: string,
webhookInput: z.infer<typeof ZWebhookUpdateSchema> webhookInput: z.infer<typeof ZWebhookUpdateSchema>
): Promise<Result<Webhook, ApiErrorResponseV2>> => { ): Promise<Result<Webhook, ApiErrorResponseV2>> => {
if (webhookInput.url) {
try {
await validateWebhookUrl(webhookInput.url);
} catch (error) {
if (error instanceof InvalidInputError) {
return err({
type: "bad_request",
details: [{ field: "url", issue: error.message }],
});
}
return err({
type: "internal_server_error",
details: [{ field: "url", issue: "Webhook URL validation failed unexpectedly" }],
});
}
}
try { try {
const updatedWebhook = await prisma.webhook.update({ const updatedWebhook = await prisma.webhook.update({
where: { where: {
@@ -1,6 +1,8 @@
import { WebhookSource } from "@prisma/client"; import { WebhookSource } from "@prisma/client";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { InvalidInputError } from "@formbricks/types/errors";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
import { TGetWebhooksFilter, TWebhookInput } from "@/modules/api/v2/management/webhooks/types/webhooks"; import { TGetWebhooksFilter, TWebhookInput } from "@/modules/api/v2/management/webhooks/types/webhooks";
import { createWebhook, getWebhooks } from "../webhook"; import { createWebhook, getWebhooks } from "../webhook";
@@ -15,6 +17,10 @@ vi.mock("@formbricks/database", () => ({
}, },
})); }));
vi.mock("@/lib/utils/validate-webhook-url", () => ({
validateWebhookUrl: vi.fn().mockResolvedValue(undefined),
}));
describe("getWebhooks", () => { describe("getWebhooks", () => {
const environmentId = "env1"; const environmentId = "env1";
const params = { const params = {
@@ -89,6 +95,44 @@ describe("createWebhook", () => {
} }
}); });
test("calls validateWebhookUrl with the provided URL", async () => {
vi.mocked(prisma.webhook.create).mockResolvedValueOnce(createdWebhook);
await createWebhook(inputWebhook);
expect(validateWebhookUrl).toHaveBeenCalledWith("http://example.com");
});
test("returns bad_request and skips Prisma create when URL fails SSRF validation", async () => {
vi.mocked(validateWebhookUrl).mockRejectedValueOnce(
new InvalidInputError("Webhook URL must not point to private or internal IP addresses")
);
const result = await createWebhook(inputWebhook);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.type).toEqual("bad_request");
expect(result.error.details[0].field).toEqual("url");
}
expect(prisma.webhook.create).not.toHaveBeenCalled();
});
test("returns internal_server_error when validateWebhookUrl throws an unexpected error", async () => {
vi.mocked(validateWebhookUrl).mockRejectedValueOnce(new Error("unexpected DNS failure"));
const result = await createWebhook(inputWebhook);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.type).toEqual("internal_server_error");
expect(result.error.details[0].field).toEqual("url");
}
expect(prisma.webhook.create).not.toHaveBeenCalled();
});
test("returns error when creation fails", async () => { test("returns error when creation fails", async () => {
vi.mocked(prisma.webhook.create).mockRejectedValueOnce(new Error("Creation failed")); vi.mocked(prisma.webhook.create).mockRejectedValueOnce(new Error("Creation failed"));
@@ -1,7 +1,9 @@
import { Prisma, Webhook } from "@prisma/client"; import { Prisma, Webhook } from "@prisma/client";
import { prisma } from "@formbricks/database"; import { prisma } from "@formbricks/database";
import { Result, err, ok } from "@formbricks/types/error-handlers"; import { Result, err, ok } from "@formbricks/types/error-handlers";
import { InvalidInputError } from "@formbricks/types/errors";
import { generateWebhookSecret } from "@/lib/crypto"; import { generateWebhookSecret } from "@/lib/crypto";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";
import { getWebhooksQuery } from "@/modules/api/v2/management/webhooks/lib/utils"; import { getWebhooksQuery } from "@/modules/api/v2/management/webhooks/lib/utils";
import { TGetWebhooksFilter, TWebhookInput } from "@/modules/api/v2/management/webhooks/types/webhooks"; import { TGetWebhooksFilter, TWebhookInput } from "@/modules/api/v2/management/webhooks/types/webhooks";
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
@@ -49,6 +51,21 @@ export const getWebhooks = async (
export const createWebhook = async (webhook: TWebhookInput): Promise<Result<Webhook, ApiErrorResponseV2>> => { export const createWebhook = async (webhook: TWebhookInput): Promise<Result<Webhook, ApiErrorResponseV2>> => {
const { environmentId, name, url, source, triggers, surveyIds } = webhook; const { environmentId, name, url, source, triggers, surveyIds } = webhook;
try {
await validateWebhookUrl(url);
} catch (error) {
if (error instanceof InvalidInputError) {
return err({
type: "bad_request",
details: [{ field: "url", issue: error.message }],
});
}
return err({
type: "internal_server_error",
details: [{ field: "url", issue: "Webhook URL validation failed unexpectedly" }],
});
}
try { try {
const secret = generateWebhookSecret(); const secret = generateWebhookSecret();
@@ -15,7 +15,7 @@ export const EmailChangeWithoutVerificationSuccessPage = async () => {
} }
return ( return (
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-50"> <div className="flex min-h-screen bg-gradient-radial from-slate-200 to-slate-50">
<FormWrapper> <FormWrapper>
<h1 className="leading-2 mb-4 text-center font-bold"> <h1 className="leading-2 mb-4 text-center font-bold">
{t("auth.email-change.email_change_success")} {t("auth.email-change.email_change_success")}
@@ -60,7 +60,7 @@ export const ForgotPasswordForm = () => {
onChange={(e) => field.onChange(e)} onChange={(e) => field.onChange(e)}
autoComplete="email" autoComplete="email"
required required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm" className="block w-full rounded-md border-slate-300 shadow-sm focus:border-brand-dark focus:ring-brand-dark sm:text-sm"
/> />
</FormControl> </FormControl>
{error?.message && <FormError className="text-left">{error.message}</FormError>} {error?.message && <FormError className="text-left">{error.message}</FormError>}
@@ -1,8 +1,10 @@
import { Invite } from "@prisma/client"; import { Invite } from "@prisma/client";
import { TUserLocale } from "@formbricks/types/user"; import { TUserLocale } from "@formbricks/types/user";
export interface InviteWithCreator export interface InviteWithCreator extends Pick<
extends Pick<Invite, "id" | "expiresAt" | "organizationId" | "role" | "teamIds"> { Invite,
"id" | "expiresAt" | "organizationId" | "role" | "teamIds"
> {
creator: { creator: {
name: string | null; name: string | null;
email: string; email: string;
+1 -1
View File
@@ -24,7 +24,7 @@ export const AuthLayout = async ({ children }: { children: React.ReactNode }) =>
<Toaster /> <Toaster />
<div className="min-h-screen bg-slate-50"> <div className="min-h-screen bg-slate-50">
<div className="isolate bg-white"> <div className="isolate bg-white">
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-50">{children}</div> <div className="flex min-h-screen bg-gradient-radial from-slate-200 to-slate-50">{children}</div>
</div> </div>
</div> </div>
</> </>
@@ -195,6 +195,7 @@ describe("authOptions", () => {
vi.mocked(applyIPRateLimit).mockRejectedValue( vi.mocked(applyIPRateLimit).mockRejectedValue(
new Error("Maximum number of requests reached. Please try again later.") new Error("Maximum number of requests reached. Please try again later.")
); );
const findUniqueSpy = vi.spyOn(prisma.user, "findUnique");
const credentials = { email: mockUser.email, password: mockPassword }; const credentials = { email: mockUser.email, password: mockPassword };
@@ -202,7 +203,7 @@ describe("authOptions", () => {
"Maximum number of requests reached. Please try again later." "Maximum number of requests reached. Please try again later."
); );
expect(prisma.user.findUnique).not.toHaveBeenCalled(); expect(findUniqueSpy).not.toHaveBeenCalled();
}); });
test("should use correct rate limit configuration", async () => { test("should use correct rate limit configuration", async () => {
@@ -281,6 +282,7 @@ describe("authOptions", () => {
vi.mocked(applyIPRateLimit).mockRejectedValue( vi.mocked(applyIPRateLimit).mockRejectedValue(
new Error("Maximum number of requests reached. Please try again later.") new Error("Maximum number of requests reached. Please try again later.")
); );
const findUniqueSpy = vi.spyOn(prisma.user, "findUnique");
const credentials = { token: "sometoken" }; const credentials = { token: "sometoken" };
@@ -288,7 +290,7 @@ describe("authOptions", () => {
"Maximum number of requests reached. Please try again later." "Maximum number of requests reached. Please try again later."
); );
expect(prisma.user.findUnique).not.toHaveBeenCalled(); expect(findUniqueSpy).not.toHaveBeenCalled();
}); });
}); });
}); });
@@ -184,7 +184,7 @@ export const LoginForm = ({
value={field.value} value={field.value}
onChange={(email) => field.onChange(email)} onChange={(email) => field.onChange(email)}
placeholder="work@email.com" placeholder="work@email.com"
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm" className="block w-full rounded-md border-slate-300 shadow-sm focus:border-brand-dark focus:ring-brand-dark sm:text-sm"
/> />
{error?.message && <FormError className="text-left">{error.message}</FormError>} {error?.message && <FormError className="text-left">{error.message}</FormError>}
</div> </div>
@@ -207,7 +207,7 @@ export const LoginForm = ({
aria-label="password" aria-label="password"
aria-required="true" aria-required="true"
required required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 pr-8 shadow-sm sm:text-sm" className="block w-full rounded-md border-slate-300 pr-8 shadow-sm focus:border-brand-dark focus:ring-brand-dark sm:text-sm"
value={field.value} value={field.value}
onChange={(password) => field.onChange(password)} onChange={(password) => field.onChange(password)}
/> />
@@ -221,7 +221,7 @@ export const LoginForm = ({
<div className="ml-1 text-right transition-all duration-500 ease-in-out"> <div className="ml-1 text-right transition-all duration-500 ease-in-out">
<Link <Link
href="/auth/forgot-password" href="/auth/forgot-password"
className="hover:text-brand-dark text-xs text-slate-500"> className="text-xs text-slate-500 hover:text-brand-dark">
{t("auth.login.forgot_your_password")} {t("auth.login.forgot_your_password")}
</Link> </Link>
</div> </div>
@@ -222,7 +222,7 @@ export const SignupForm = ({
placeholder="*******" placeholder="*******"
aria-placeholder="password" aria-placeholder="password"
required required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md shadow-sm sm:text-sm" className="block w-full rounded-md shadow-sm focus:border-brand-dark focus:ring-brand-dark sm:text-sm"
/> />
{error?.message && <FormError className="text-left">{error.message}</FormError>} {error?.message && <FormError className="text-left">{error.message}</FormError>}
</div> </div>
@@ -6,7 +6,7 @@ export const VerifyEmailChangePage = async ({ searchParams }) => {
const { token } = await searchParams; const { token } = await searchParams;
return ( return (
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-50"> <div className="flex min-h-screen bg-gradient-radial from-slate-200 to-slate-50">
<FormWrapper> <FormWrapper>
<EmailChangeSignIn token={token} /> <EmailChangeSignIn token={token} />
<BackToLoginButton /> <BackToLoginButton />
@@ -138,7 +138,7 @@ export const PricingTable = ({
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex w-full"> <div className="flex w-full">
<h2 className="mr-2 mb-3 inline-flex w-full text-2xl font-bold text-slate-700"> <h2 className="mb-3 mr-2 inline-flex w-full text-2xl font-bold text-slate-700">
{t("environments.settings.billing.current_plan")}:{" "} {t("environments.settings.billing.current_plan")}:{" "}
<span className="capitalize">{organization.billing.plan}</span> <span className="capitalize">{organization.billing.plan}</span>
{cancellingOn && ( {cancellingOn && (
@@ -203,7 +203,7 @@ export const PricingTable = ({
<div <div
className={cn( className={cn(
"relative mx-8 mb-8 flex flex-col gap-4", "relative mx-8 mb-8 flex flex-col gap-4",
peopleUnlimitedCheck && "mt-4 mb-0 flex-row pb-0" peopleUnlimitedCheck && "mb-0 mt-4 flex-row pb-0"
)}> )}>
<p className="text-md font-semibold text-slate-700"> <p className="text-md font-semibold text-slate-700">
{t("environments.settings.billing.monthly_identified_users")} {t("environments.settings.billing.monthly_identified_users")}
@@ -226,7 +226,7 @@ export const PricingTable = ({
<div <div
className={cn( className={cn(
"relative mx-8 flex flex-col gap-4 pb-6", "relative mx-8 flex flex-col gap-4 pb-6",
projectsUnlimitedCheck && "mt-4 mb-0 flex-row pb-0" projectsUnlimitedCheck && "mb-0 mt-4 flex-row pb-0"
)}> )}>
<p className="text-md font-semibold text-slate-700">{t("common.workspaces")}</p> <p className="text-md font-semibold text-slate-700">{t("common.workspaces")}</p>
{organization.billing.limits.projects && ( {organization.billing.limits.projects && (
@@ -264,7 +264,7 @@ export const PricingTable = ({
</button> </button>
<button <button
aria-pressed={planPeriod === "yearly"} aria-pressed={planPeriod === "yearly"}
className={`flex-1 items-center rounded-md py-0.5 pr-2 pl-4 text-center whitespace-nowrap ${ className={`flex-1 items-center whitespace-nowrap rounded-md py-0.5 pl-4 pr-2 text-center ${
planPeriod === "yearly" ? "bg-slate-200 font-semibold" : "bg-transparent" planPeriod === "yearly" ? "bg-slate-200 font-semibold" : "bg-transparent"
}`} }`}
onClick={() => handleMonthlyToggle("yearly")}> onClick={() => handleMonthlyToggle("yearly")}>
@@ -276,7 +276,7 @@ export const PricingTable = ({
</div> </div>
<div className="relative mx-auto grid max-w-md grid-cols-1 gap-y-8 lg:mx-0 lg:-mb-14 lg:max-w-none lg:grid-cols-3"> <div className="relative mx-auto grid max-w-md grid-cols-1 gap-y-8 lg:mx-0 lg:-mb-14 lg:max-w-none lg:grid-cols-3">
<div <div
className="hidden lg:absolute lg:inset-x-px lg:top-4 lg:bottom-0 lg:block lg:rounded-xl lg:rounded-t-2xl lg:border lg:border-slate-200 lg:bg-slate-100 lg:pb-8 lg:ring-1 lg:ring-white/10" className="hidden lg:absolute lg:inset-x-px lg:bottom-0 lg:top-4 lg:block lg:rounded-xl lg:rounded-t-2xl lg:border lg:border-slate-200 lg:bg-slate-100 lg:pb-8 lg:ring-1 lg:ring-white/10"
aria-hidden="true" aria-hidden="true"
/> />
{getCloudPricingData(t).plans.map((plan) => ( {getCloudPricingData(t).plans.map((plan) => (

Some files were not shown because too many files have changed in this diff Show More