Compare commits

...

72 Commits

Author SHA1 Message Date
Johannes
5854bd635f add translations 2026-03-02 15:46:37 -03:00
Johannes
25ebb6411c feat: enhance AI settings management in organization
Updated organization AI settings to include separate toggles for smart tools and data analysis features. Refactored related components and tests to accommodate the new settings structure, ensuring a more granular control over AI functionalities. Adjusted localization strings for clarity on AI features.
2026-03-02 15:43:26 -03:00
Dhruwang
421a732875 feat: refactor organization update actions for name and AI settings
Updated the organization update actions to separate the handling of organization name and AI settings. Introduced `updateOrganizationNameAction` and `updateOrganizationAISettingsAction` for more specific updates. Adjusted related components to utilize the new actions accordingly.
2026-03-02 14:13:02 +05:30
Dhruwang
03ec8603bb feat: add Formbricks AI toggle to organization settings
Made-with: Cursor
2026-03-02 14:07:35 +05:30
Dhruwang Jariwala
345b282733 chore: sync epic dashboards with main (#7398) 2026-03-02 13:48:04 +05:30
Dhruwang
c7c30a9d58 Merge branch 'epic/dashboards' of https://github.com/formbricks/formbricks into sync/epic-dashboards-with-main 2026-03-02 12:48:41 +05:30
Dhruwang
08510659de chore: merge main into sync branch to update epic/dashboards 2026-03-02 12:45:58 +05:30
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
Dhruwang Jariwala
f8fa29d56e feat: charts ui (#7332)
Co-authored-by: TheodorTomas <theodortomas@gmail.com>
2026-02-26 12:21:48 +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
Theodór Tómas
ff6176df0a chore: sync epic/dashboards with main (#7368) 2026-02-26 15:24:39 +07:00
TheodorTomas
d0f4228b45 chore: resolve merge conflicts syncing main into epic/dashboards
- segments.ts: take main's sequential WHERE clause building to prevent pool saturation
- package.json: update zod to 3.25.76 from main
- pnpm-lock.yaml: resolve zod version references, keep @suspensive/react from epic
2026-02-26 13:46:39 +07: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
Dhruwang Jariwala
3a802810e3 feat: Charts list page with demo create/edit and real delete/duplicate (#7353)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: TheodorTomas <theodortomas@gmail.com>
2026-02-25 11:20:34 +00:00
Dhruwang Jariwala
fbbf917093 chore: sync dashboard epic (#7351)
Signed-off-by: gulshank0 <gulshanbahadur002@gmail.com>
Co-authored-by: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
Co-authored-by: Bhagya Amarasinghe <b.sithumini@yahoo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Chowdhury Tafsir Ahmed Siddiki <ctafsiras@gmail.com>
Co-authored-by: neila <40727091+neila@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
Co-authored-by: Balázs Úr <balazs@urbalazs.hu>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Gulshan <gulshanbahadur002@gmail.com>
2026-02-25 13:24:51 +05:30
Dhruwang
a7e42bfd29 Merge main into epic/dashboards to sync with latest changes 2026-02-25 12:11:43 +05:30
Dhruwang
562fdec899 Merge branch 'epic/dashboards' of https://github.com/formbricks/formbricks into epic/dashboards 2026-02-25 12:08:10 +05:30
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
Theodór Tómas
d670d5de31 feat: (dashboards) listing page (#7330) 2026-02-23 20:26:03 +07: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
Theodór Tómas
5ccb4af249 feat: (dashboards) crud charts/dashboard server actions (#7307)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-23 11:44:26 +05:30
Anshuman Pandey
219883266c fix: add bool support (#7323) 2026-02-20 15:30:40 +00:00
Theodór Tómas
62aa186a81 chore: merge main into dashboard epic (#7321)
Co-authored-by: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
Co-authored-by: Bhagya Amarasinghe <b.sithumini@yahoo.com>
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Chowdhury Tafsir Ahmed Siddiki <ctafsiras@gmail.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: neila <40727091+neila@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-20 12:22:54 +00:00
Dhruwang
cb094761ca Merge branch 'main' of https://github.com/formbricks/formbricks into epic/dashboards 2026-02-20 17:38:35 +05:30
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
Theodór Tómas
f35e54f21d feat: (dashboards) adding analysis tab to sidebar along with placeholder pages (#7311) 2026-02-20 09:58:26 +05:30
Anshuman Pandey
4fadc54b4e fix: fixes storage resolution issues (#7310) 2026-02-19 14:03:19 +00:00
Dhruwang Jariwala
f4ac9a8292 fix: always validate only responseData fields in client/management APIs (#7292) (#7296)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 08:56:42 +00:00
Anshuman Pandey
7c8a7606b7 fix: fixes the no segment in draft surveys bug (#7290) 2026-02-19 08:16:18 +00:00
Anshuman Pandey
225217330b fix: adds dataType filter in bc code (#7294) 2026-02-19 07:47:58 +00:00
Dhruwang Jariwala
589c04a530 fix: allow CTA elements to proceed when marked required (#1415) (#7293)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 06:56:03 +00:00
Theodór Tómas
f49f40610b feat: add Cube.js dev setup and analytics client (#7287) 2026-02-18 21:10:52 +07:00
Theodór Tómas
9e754bad9c feat: add Chart, Dashboard, DashboardWidget schema and migration (#7286) 2026-02-18 21:10:36 +07:00
Dhruwang
4dcf6fda40 fix: code rabbit feedback 2026-02-18 18:44:24 +05:30
Anshuman Pandey
aa538a3a51 fix: better query in the backwards compatible code (#7288) 2026-02-18 13:00:19 +00:00
Dhruwang
1b8ccd7199 feat: add JSON type definitions for Chart and Dashboard fields
Add Zod schemas and TypeScript types for ChartQuery, ChartConfig,
WidgetLayout. ChartQuery mirrors Cube.js REST API query format.
Register types with prisma-json-types-generator.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 18:17:44 +05:30
Dhruwang
4f9088559f feat: add Cube.js dev setup and analytics client
- Add Cube container to docker-compose.dev.yml (pinned v1.3.21)
- Add Cube server config (cube/cube.js) and FeedbackRecords schema
- Add @cubejs-client/core dependency and singleton client in EE module
- Add CUBEJS_API_URL and CUBEJS_API_TOKEN to .env.example

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 18:05:47 +05:30
Dhruwang
18550f1d11 feat: link Chart and Dashboard createdBy to User
- Add creator relation on Chart and Dashboard to User
- Add createdBy foreign key constraints in migration (ON DELETE SET NULL)
- Mirror Survey pattern for createdBy user tracking

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 17:26:42 +05:30
Dhruwang
881cd31f74 feat: add Chart, Dashboard, DashboardWidget schema and migration
- Add Prisma models for Chart, Dashboard, DashboardWidget
- ChartType: area, bar, line, pie, big_number only
- Remove DashboardStatus and WidgetType (widgets are always charts)
- DashboardWidget requires chartId, remove content/type fields

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 17:24:48 +05:30
Dhruwang
e00405dca2 feat: add Chart, Dashboard, and DashboardWidget schema and migration
- Add Prisma models for Chart, Dashboard, DashboardWidget
- Add ChartType, DashboardStatus, WidgetType enums
- Add migration 20260128111722 for charts and dashboards tables

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 17:21:34 +05:30
Anshuman Pandey
817e108ff5 docs: adds migration docs (#7281)
Co-authored-by: Bhagya Amarasinghe <b.sithumini@yahoo.com>
2026-02-17 17:01:46 +01:00
Theodór Tómas
33542d0c54 fix: default preview colors (#7277)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-17 11:28:58 +00:00
Matti Nannt
f37d22f13d docs: align rate limiting docs with current code enforcement (#7267)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-02-17 07:42:53 +00:00
Anshuman Pandey
202ae903ac chore: makes rate limit config const (#7274) 2026-02-17 06:49:56 +00:00
Dhruwang Jariwala
6ab5cc367c fix: reduced default height of input (#7259) 2026-02-17 05:11:29 +00:00
Theodór Tómas
21559045ba fix: input placeholder color (#7265) 2026-02-17 05:11:01 +00:00
Theodór Tómas
d7c57a7a48 fix: disabling cache in dev (#7269) 2026-02-17 04:44:22 +00:00
Chowdhury Tafsir Ahmed Siddiki
11b2ef4788 docs: remove stale 'coming soon' placeholders (#7254) 2026-02-16 13:21:12 +00:00
Theodór Tómas
6fefd51cce fix: suggest colors has better succes copy (#7258) 2026-02-16 13:18:46 +00:00
Theodór Tómas
65af826222 fix: matrix table preview (#7257)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-16 13:18:17 +00:00
Anshuman Pandey
12eb54c653 fix: fixes number being passed into string attribute (#7255) 2026-02-16 11:18:59 +00:00
Dhruwang Jariwala
5aa1427e64 fix: input combobx height (#7256) 2026-02-16 10:03:23 +00:00
257 changed files with 15862 additions and 1535 deletions

View File

@@ -229,5 +229,24 @@ REDIS_URL=redis://localhost:6379
# AUDIT_LOG_GET_USER_IP=0
# Cube.js Analytics (optional — only needed for the analytics/dashboard feature)
# Required when running the Cube service (docker-compose.dev.yml). Generate with: openssl rand -hex 32
# Use the same value for CUBEJS_API_TOKEN so the client can authenticate.
# CUBEJS_API_SECRET=
# URL where the Cube.js instance is running
# CUBEJS_API_URL=http://localhost:4000
# API token sent with each Cube.js request; must match CUBEJS_API_SECRET when CUBEJS_DEV_MODE is off
# CUBEJS_API_TOKEN=
#
# Cube connects to the Hub DB. When using docker-compose.dev.yml with the hub network,
# use the container name and internal port. Hub credentials: formbricks/formbricks_dev, db: hub
# CUBEJS_DB_HOST=formbricks_hub_postgres
# CUBEJS_DB_PORT=5432
# CUBEJS_DB_NAME=hub
# CUBEJS_DB_USER=formbricks
# CUBEJS_DB_PASS=formbricks_dev
#
# Alternative (when not on same Docker network): host.docker.internal and port 5433
# Lingo.dev API key for translation generation
LINGODOTDEV_API_KEY=your_api_key_here

View File

@@ -6,19 +6,9 @@ permissions:
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "apps/web/**/*.ts"
- "apps/web/**/*.tsx"
- "apps/web/locales/**/*.json"
- "scan-translations.ts"
push:
branches:
- main
paths:
- "apps/web/**/*.ts"
- "apps/web/**/*.tsx"
- "apps/web/locales/**/*.json"
- "scan-translations.ts"
jobs:
validate-translations:
@@ -33,30 +23,38 @@ jobs:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check for relevant changes
id: changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
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
if: steps.changes.outputs.translations == 'true'
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
node-version: 22.x
- name: Install pnpm
if: steps.changes.outputs.translations == 'true'
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Install dependencies
if: steps.changes.outputs.translations == 'true'
run: pnpm install --config.platform=linux --config.architecture=x64
- name: Validate translation keys
run: |
echo ""
echo "🔍 Validating translation keys..."
echo ""
pnpm run scan-translations
if: steps.changes.outputs.translations == 'true'
run: pnpm run scan-translations
- name: Summary
if: success()
run: |
echo ""
echo "✅ Translation validation completed successfully!"
echo ""
- name: Skip (no translation-related changes)
if: steps.changes.outputs.translations != 'true'
run: echo "No translation-related files changed — skipping validation."

View File

@@ -1,40 +1 @@
# Load environment variables from .env files
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
pnpm lint-staged

View File

@@ -32,6 +32,7 @@ The `@formbricks/surveys` package is pre-compiled (Vite → UMD + ESM) and the b
TypeScript, React, and Prisma are the primary languages. Use the shared ESLint presets (`@formbricks/eslint-config`) and Prettier preset (110-char width, semicolons, double quotes, sorted import groups). Two-space indentation is standard; prefer `PascalCase` for React components and folders under `modules/`, `camelCase` for functions/variables, and `SCREAMING_SNAKE_CASE` only for constants. When adding mocks, place them inside `__mocks__` so import ordering stays stable.
We are using SonarQube to identify code smells and security hotspots.
Always mark React component props as `Readonly<>` (e.g., `({ children }: Readonly<MyProps>)`).
## Architecture & Patterns

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
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
RUN chown nextjs:nextjs ./packages/database/schema.prisma && chmod 644 ./packages/database/schema.prisma

View File

@@ -228,7 +228,7 @@ export const ProjectSettings = ({
</FormProvider>
</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 && (
<Image
src={logoUrl}
@@ -239,18 +239,16 @@ export const ProjectSettings = ({
/>
)}
<p className="text-sm text-slate-400">{t("common.preview")}</p>
<div className="z-0 h-3/4 w-3/4">
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={previewSurvey(projectName || "my Product", t)}
styling={previewStyling}
isBrandingEnabled={false}
languageCode="default"
onFileUpload={async (file) => file.name}
autoFocus={false}
/>
</div>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={previewSurvey(projectName || t("common.my_product"), t)}
styling={previewStyling}
isBrandingEnabled={false}
languageCode="default"
onFileUpload={async (file) => file.name}
autoFocus={false}
/>
</div>
<CreateTeamModal
open={createTeamModalOpen}

View File

@@ -0,0 +1,8 @@
import { ChartsListPage } from "@/modules/ee/analysis/charts/components/charts-list-page";
const ChartsPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
const { environmentId } = await props.params;
return <ChartsListPage environmentId={environmentId} />;
};
export default ChartsPage;

View File

@@ -0,0 +1,11 @@
const DashboardDetailPage = async (props: Readonly<{ params: Promise<{ dashboardId: string }> }>) => {
const { dashboardId } = await props.params;
return (
<div className="flex items-center justify-center py-12 text-sm text-slate-500">
Dashboard detail for {dashboardId} will appear here.
</div>
);
};
export default DashboardDetailPage;

View File

@@ -0,0 +1,8 @@
import { DashboardsListPage } from "@/modules/ee/analysis/dashboards/pages/dashboards-list-page";
const DashboardsPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
const { environmentId } = await props.params;
return <DashboardsListPage environmentId={environmentId} />;
};
export default DashboardsPage;

View File

@@ -0,0 +1,8 @@
import { redirect } from "next/navigation";
const AnalysisPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
const { environmentId } = await props.params;
return redirect(`/environments/${environmentId}/analysis/dashboards`);
};
export default AnalysisPage;

View File

@@ -2,6 +2,7 @@
import {
ArrowUpRightIcon,
ChartBar,
ChevronRightIcon,
Cog,
LogOutIcon,
@@ -114,6 +115,13 @@ export const MainNavigation = ({
pathname?.includes("/segments") ||
pathname?.includes("/attributes"),
},
{
name: t("common.analysis"),
href: `/environments/${environment.id}/analysis`,
icon: ChartBar,
isActive: pathname?.includes("/analysis"),
isHidden: false,
},
{
name: t("common.configuration"),
href: `/environments/${environment.id}/workspace/general`,
@@ -188,7 +196,7 @@ export const MainNavigation = ({
size="icon"
onClick={toggleSidebar}
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 ? (
<PanelLeftOpenIcon strokeWidth={1.5} />

View File

@@ -81,7 +81,7 @@ export const OrganizationBreadcrumb = ({
getOrganizationsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => {
if (result?.data) {
// 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);
} else {
// Handle server errors or validation errors

View File

@@ -82,7 +82,7 @@ export const ProjectBreadcrumb = ({
getProjectsForSwitcherAction({ organizationId: currentOrganizationId }).then((result) => {
if (result?.data) {
// 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);
} else {
// Handle server errors or validation errors

View File

@@ -30,7 +30,7 @@ export const NotificationSwitch = ({
const isChecked =
notificationType === "unsubscribedOrganizationIds"
? !notificationSettings.unsubscribedOrganizationIds?.includes(surveyOrProjectOrOrganizationId)
: notificationSettings[notificationType][surveyOrProjectOrOrganizationId] === true;
: notificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId] === true;
const handleSwitchChange = async () => {
setIsLoading(true);
@@ -49,8 +49,11 @@ export const NotificationSwitch = ({
];
}
} else {
updatedNotificationSettings[notificationType][surveyOrProjectOrOrganizationId] =
!updatedNotificationSettings[notificationType][surveyOrProjectOrOrganizationId];
updatedNotificationSettings[notificationType] = {
...updatedNotificationSettings[notificationType],
[surveyOrProjectOrOrganizationId]:
!updatedNotificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId],
};
}
const updatedNotificationSettingsActionResponse = await updateNotificationSettingsAction({
@@ -78,7 +81,7 @@ export const NotificationSwitch = ({
) {
switch (notificationType) {
case "alert":
if (notificationSettings[notificationType][surveyOrProjectOrOrganizationId] === true) {
if (notificationSettings[notificationType]?.[surveyOrProjectOrOrganizationId] === true) {
handleSwitchChange();
toast.success(
t(

View File

@@ -27,7 +27,7 @@ export const updateOrganizationNameAction = authenticatedActionClient
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: Record<string, any>;
parsedInput: z.infer<typeof ZUpdateOrganizationNameAction>;
}) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
@@ -51,6 +51,49 @@ export const updateOrganizationNameAction = authenticatedActionClient
)
);
const ZUpdateOrganizationAISettingsAction = z.object({
organizationId: ZId,
data: ZOrganizationUpdateInput.pick({ isAISmartToolsEnabled: true, isAIDataAnalysisEnabled: true }),
});
export const updateOrganizationAISettingsAction = authenticatedActionClient
.schema(ZUpdateOrganizationAISettingsAction)
.action(
withAuditLogging(
"updated",
"organization",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZUpdateOrganizationAISettingsAction>;
}) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: parsedInput.organizationId,
access: [
{
type: "organization",
schema: ZOrganizationUpdateInput.pick({
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
}),
data: parsedInput.data,
roles: ["owner", "manager"],
},
],
});
ctx.auditLoggingCtx.organizationId = parsedInput.organizationId;
const oldObject = await getOrganization(parsedInput.organizationId);
const result = await updateOrganization(parsedInput.organizationId, parsedInput.data);
ctx.auditLoggingCtx.oldObject = oldObject;
ctx.auditLoggingCtx.newObject = result;
return result;
}
)
);
const ZDeleteOrganizationAction = z.object({
organizationId: ZId,
});

View File

@@ -0,0 +1,101 @@
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { TOrganization } from "@formbricks/types/organizations";
import { updateOrganizationAISettingsAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions";
import { getAccessFlags } from "@/lib/membership/utils";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
interface AISettingsToggleProps {
organization: TOrganization;
membershipRole?: TOrganizationRole;
}
export const AISettingsToggle = ({ organization, membershipRole }: Readonly<AISettingsToggleProps>) => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const router = useRouter();
const { isOwner, isManager } = getAccessFlags(membershipRole);
const canEdit = isOwner || isManager;
const handleToggle = async (
field: "isAISmartToolsEnabled" | "isAIDataAnalysisEnabled",
checked: boolean
) => {
setIsLoading(true);
try {
const response = await updateOrganizationAISettingsAction({
organizationId: organization.id,
data: { [field]: checked },
});
if (response?.data) {
toast.success(t("environments.settings.general.ai_settings_updated_successfully"));
router.refresh();
} else {
const errorMessage = getFormattedErrorMessage(response);
toast.error(errorMessage);
}
} catch {
toast.error(t("common.something_went_wrong"));
} finally {
setIsLoading(false);
}
};
return (
<div className="space-y-4">
<div className="flex items-start space-x-2">
<Switch
id="ai-smart-tools-toggle"
className="mt-0.5"
checked={organization.isAISmartToolsEnabled}
disabled={isLoading || !canEdit}
onCheckedChange={(checked) => handleToggle("isAISmartToolsEnabled", checked)}
/>
<div>
<Label htmlFor="ai-smart-tools-toggle">
{t("environments.settings.general.ai_smart_tools_enabled")}
</Label>
<p className="text-xs text-slate-500">
{t("environments.settings.general.ai_smart_tools_enabled_description")}
</p>
</div>
</div>
<div className="flex items-start space-x-2">
<Switch
id="ai-data-analysis-toggle"
className="mt-0.5"
checked={organization.isAIDataAnalysisEnabled}
disabled={isLoading || !canEdit}
onCheckedChange={(checked) => handleToggle("isAIDataAnalysisEnabled", checked)}
/>
<div>
<Label htmlFor="ai-data-analysis-toggle">
{t("environments.settings.general.ai_data_analysis_enabled")}
</Label>
<p className="text-xs text-slate-500">
{t("environments.settings.general.ai_data_analysis_enabled_description")}
</p>
</div>
</div>
{!canEdit && (
<Alert variant="warning">
<AlertDescription>
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
</AlertDescription>
</Alert>
)}
</div>
);
};

View File

@@ -9,7 +9,9 @@ import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { IdBadge } from "@/modules/ui/components/id-badge";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import packageJson from "@/package.json";
import { SettingsCard } from "../../components/SettingsCard";
import { AISettingsToggle } from "./components/AISettingsToggle";
import { DeleteOrganization } from "./components/DeleteOrganization";
import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm";
import { SecurityListTip } from "./components/SecurityListTip";
@@ -59,6 +61,11 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
membershipRole={currentUserMembership?.role}
/>
</SettingsCard>
<SettingsCard
title={t("environments.settings.general.ai_enabled")}
description={t("environments.settings.general.ai_enabled_description")}>
<AISettingsToggle organization={organization} membershipRole={currentUserMembership?.role} />
</SettingsCard>
<EmailCustomizationSettings
organization={organization}
hasWhiteLabelPermission={hasWhiteLabelPermission}
@@ -81,7 +88,10 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
</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>
);
};

View File

@@ -4,6 +4,7 @@ import { revalidatePath } from "next/cache";
import { z } from "zod";
import { ZId } from "@formbricks/types/common";
import { ZResponseFilterCriteria } from "@formbricks/types/responses";
import { getDisplaysBySurveyIdWithContact } from "@/lib/display/service";
import { getResponseCountBySurveyId, getResponses } from "@/lib/response/service";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
@@ -106,3 +107,31 @@ export const getResponseCountAction = authenticatedActionClient
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);
});

View File

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

View File

@@ -10,8 +10,8 @@ interface SummaryMetadataProps {
surveySummary: TSurveySummary["meta"];
quotasCount: number;
isLoading: boolean;
tab: "dropOffs" | "quotas" | undefined;
setTab: React.Dispatch<React.SetStateAction<"dropOffs" | "quotas" | undefined>>;
tab: "dropOffs" | "quotas" | "impressions" | undefined;
setTab: React.Dispatch<React.SetStateAction<"dropOffs" | "quotas" | "impressions" | undefined>>;
isQuotasAllowed: boolean;
}
@@ -53,7 +53,7 @@ export const SummaryMetadata = ({
const { t } = useTranslation();
const dropoffCountValue = dropOffCount === 0 ? <span>-</span> : dropOffCount;
const handleTabChange = (val: "dropOffs" | "quotas") => {
const handleTabChange = (val: "dropOffs" | "quotas" | "impressions") => {
const change = tab === val ? undefined : val;
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`,
isQuotasAllowed && quotasCount > 0 && "2xl:grid-cols-6"
)}>
<StatCard
<InteractiveCard
key="impressions"
tab="impressions"
label={t("environments.surveys.summary.impressions")}
percentage={null}
value={displayCount === 0 ? <span>-</span> : displayCount}
tooltipText={t("environments.surveys.summary.impressions_tooltip")}
isLoading={isLoading}
onClick={() => handleTabChange("impressions")}
isActive={tab === "impressions"}
/>
<StatCard
label={t("environments.surveys.summary.starts")}

View File

@@ -1,21 +1,31 @@
"use client";
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 { TSurvey, TSurveySummary } from "@formbricks/types/surveys/types";
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 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 { SummaryImpressions } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryImpressions";
import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter";
import { getFormattedFilters } from "@/app/lib/surveys/surveys";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { replaceHeadlineRecall } from "@/lib/utils/recall";
import { QuotasSummary } from "@/modules/ee/quotas/components/quotas-summary";
import { SummaryList } from "./SummaryList";
import { SummaryMetadata } from "./SummaryMetadata";
const DISPLAYS_PER_PAGE = 15;
const defaultSurveySummary: TSurveySummary = {
meta: {
completedPercentage: 0,
@@ -51,17 +61,76 @@ export const SummaryPage = ({
initialSurveySummary,
isQuotasAllowed,
}: SummaryPageProps) => {
const { t } = useTranslation();
const searchParams = useSearchParams();
const [surveySummary, setSurveySummary] = useState<TSurveySummary>(
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 { 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
useEffect(() => {
// If we have initial data and no filters are applied, don't fetch
@@ -121,6 +190,18 @@ export const SummaryPage = ({
setTab={setTab}
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} />}
{isQuotasAllowed && tab === "quotas" && <QuotasSummary quotas={surveySummary.quotas} />}
<div className="flex gap-1.5">

View File

@@ -4,9 +4,9 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { BaseCard } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/base-card";
interface InteractiveCardProps {
tab: "dropOffs" | "quotas";
tab: "dropOffs" | "quotas" | "impressions";
label: string;
percentage: number;
percentage: number | null;
value: React.ReactNode;
tooltipText: string;
isLoading: boolean;

View File

@@ -352,7 +352,7 @@ export const AnonymousLinksTab = ({
},
{
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",
},
]}
/>

View File

@@ -241,7 +241,7 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
<Popover open={isOpen} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<PopoverTriggerButton isOpen={isOpen}>
Filter <b>{activeFilterCount > 0 && `(${activeFilterCount})`}</b>
{t("common.filter")} <b>{activeFilterCount > 0 && `(${activeFilterCount})`}</b>
</PopoverTriggerButton>
</PopoverTrigger>
<PopoverContent
@@ -329,7 +329,7 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
</div>
{i !== filterValue.filter.length - 1 && (
<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" />
</div>
)}

View File

@@ -1,12 +1,49 @@
"use server";
import { z } from "zod";
import { ZIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
import { getSpreadsheetNameById } from "@/lib/googleSheet/service";
import { ZId } from "@formbricks/types/common";
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 { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
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({
googleSheetIntegration: ZIntegrationGoogleSheets,
environmentId: z.string(),

View File

@@ -20,6 +20,10 @@ import {
isValidGoogleSheetsUrl,
} from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/util";
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 { recallToHeadline } from "@/lib/utils/recall";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
@@ -118,6 +122,17 @@ export const AddIntegrationModal = ({
resetForm();
}, [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 () => {
try {
if (!isValidGoogleSheetsUrl(spreadsheetUrl)) {
@@ -129,6 +144,7 @@ export const AddIntegrationModal = ({
if (selectedElements.length === 0) {
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
}
setIsLinkingSheet(true);
const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl);
const spreadsheetNameResponse = await getSpreadsheetNameByIdAction({
googleSheetIntegration,
@@ -137,13 +153,11 @@ export const AddIntegrationModal = ({
});
if (!spreadsheetNameResponse?.data) {
const errorMessage = getFormattedErrorMessage(spreadsheetNameResponse);
throw new Error(errorMessage);
showErrorMessageToast(spreadsheetNameResponse);
return;
}
const spreadsheetName = spreadsheetNameResponse.data;
setIsLinkingSheet(true);
integrationData.spreadsheetId = spreadsheetId;
integrationData.spreadsheetName = spreadsheetName;
integrationData.surveyId = selectedSurvey.id;
@@ -280,7 +294,7 @@ export const AddIntegrationModal = ({
<div className="space-y-4">
<div>
<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">
{surveyElements.map((question) => (
<div key={question.id} className="my-1 flex items-center space-x-2">

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { TEnvironment } from "@formbricks/types/environment";
import {
TIntegrationGoogleSheets,
@@ -8,9 +8,11 @@ import {
} from "@formbricks/types/integration/google-sheet";
import { TSurvey } from "@formbricks/types/surveys/types";
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 { authorize } from "@/app/(app)/environments/[environmentId]/workspace/integrations/google-sheets/lib/google";
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 { AddIntegrationModal } from "./AddIntegrationModal";
@@ -35,10 +37,23 @@ export const GoogleSheetWrapper = ({
googleSheetIntegration ? googleSheetIntegration.config?.key : false
);
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [showReconnectButton, setShowReconnectButton] = useState<boolean>(false);
const [selectedIntegration, setSelectedIntegration] = useState<
(TIntegrationGoogleSheetsConfigData & { index: number }) | 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 () => {
authorize(environment.id, webAppUrl).then((url: string) => {
if (url) {
@@ -64,6 +79,8 @@ export const GoogleSheetWrapper = ({
setOpenAddIntegrationModal={setIsModalOpen}
setIsConnected={setIsConnected}
setSelectedIntegration={setSelectedIntegration}
showReconnectButton={showReconnectButton}
handleGoogleAuthorization={handleGoogleAuthorization}
locale={locale}
/>
</>

View File

@@ -1,6 +1,6 @@
"use client";
import { Trash2Icon } from "lucide-react";
import { RefreshCcwIcon, Trash2Icon } from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast";
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 { timeSince } from "@/lib/time";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertButton, AlertDescription } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
import { EmptyState } from "@/modules/ui/components/empty-state";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
interface ManageIntegrationProps {
googleSheetIntegration: TIntegrationGoogleSheets;
setOpenAddIntegrationModal: (v: boolean) => void;
setIsConnected: (v: boolean) => void;
setSelectedIntegration: (v: (TIntegrationGoogleSheetsConfigData & { index: number }) | null) => void;
showReconnectButton: boolean;
handleGoogleAuthorization: () => void;
locale: TUserLocale;
}
@@ -29,6 +33,8 @@ export const ManageIntegration = ({
setOpenAddIntegrationModal,
setIsConnected,
setSelectedIntegration,
showReconnectButton,
handleGoogleAuthorization,
locale,
}: ManageIntegrationProps) => {
const { t } = useTranslation();
@@ -68,7 +74,17 @@ export const ManageIntegration = ({
return (
<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">
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
<span className="text-slate-500">
@@ -77,6 +93,19 @@ export const ManageIntegration = ({
})}
</span>
</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
onClick={() => {
setSelectedIntegration(null);

View File

@@ -21,6 +21,7 @@ import { getElementsFromBlocks } from "@/lib/survey/utils";
import { getFormattedDateTimeString } from "@/lib/utils/datetime";
import { parseRecallInfo } from "@/lib/utils/recall";
import { truncateText } from "@/lib/utils/strings";
import { resolveStorageUrlAuto } from "@/modules/storage/utils";
const convertMetaObjectToString = (metadata: TResponseMeta): string => {
let result: string[] = [];
@@ -256,10 +257,16 @@ const processElementResponse = (
const selectedChoiceIds = responseValue as string[];
return element.choices
.filter((choice) => selectedChoiceIds.includes(choice.id))
.map((choice) => choice.imageUrl)
.map((choice) => resolveStorageUrlAuto(choice.imageUrl))
.join("\n");
}
if (element.type === TSurveyElementTypeEnum.FileUpload && Array.isArray(responseValue)) {
return responseValue
.map((url) => (typeof url === "string" ? resolveStorageUrlAuto(url) : url))
.join("; ");
}
return processResponseData(responseValue);
};
@@ -368,7 +375,7 @@ const buildNotionPayloadProperties = (
responses[resp] = (pictureElement as any)?.choices
.filter((choice) => selectedChoiceIds.includes(choice.id))
.map((choice) => choice.imageUrl);
.map((choice) => resolveStorageUrlAuto(choice.imageUrl));
}
});

View File

@@ -18,6 +18,7 @@ import { convertDatesInObject } from "@/lib/time";
import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler";
import { TAuditStatus, UNKNOWN_DATA } from "@/modules/ee/audit-logs/types/audit-log";
import { sendResponseFinishedEmail } from "@/modules/email";
import { resolveStorageUrlsInObject } from "@/modules/storage/utils";
import { sendFollowUpsForResponse } from "@/modules/survey/follow-ups/lib/follow-ups";
import { FollowUpSendError } from "@/modules/survey/follow-ups/types/follow-up";
import { handleIntegrations } from "./lib/handleIntegrations";
@@ -95,12 +96,15 @@ export const POST = async (request: Request) => {
]);
};
const resolvedResponseData = resolveStorageUrlsInObject(response.data);
const webhookPromises = webhooks.map((webhook) => {
const body = JSON.stringify({
webhookId: webhook.id,
event,
data: {
...response,
data: resolvedResponseData,
survey: {
title: survey.name,
type: survey.type,

View File

@@ -1,5 +1,6 @@
import { google } from "googleapis";
import { getServerSession } from "next-auth";
import { TIntegrationGoogleSheetsConfig } from "@formbricks/types/integration/google-sheet";
import { responses } from "@/app/lib/api/response";
import {
GOOGLE_SHEETS_CLIENT_ID,
@@ -8,7 +9,7 @@ import {
WEBAPP_URL,
} from "@/lib/constants";
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";
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");
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
let key;
let userEmail;
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;
if (!code) {
return Response.redirect(
`${WEBAPP_URL}/environments/${environmentId}/workspace/integrations/google-sheets`
);
}
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 = {
type: "googleSheets" as "googleSheets",
type: integrationType,
environment: environmentId,
config: {
key,
data: [],
data: existingConfig?.data ?? [],
email: userEmail,
},
};

View File

@@ -10,6 +10,7 @@ import {
TJsEnvironmentStateSurvey,
} from "@formbricks/types/js";
import { validateInputs } from "@/lib/utils/validate";
import { resolveStorageUrlsInObject } from "@/modules/storage/utils";
import { transformPrismaSurvey } from "@/modules/survey/lib/utils";
/**
@@ -177,14 +178,14 @@ export const getEnvironmentStateData = async (environmentId: string): Promise<En
overlay: environmentData.project.overlay,
placement: environmentData.project.placement,
inAppSurveyBranding: environmentData.project.inAppSurveyBranding,
styling: environmentData.project.styling,
styling: resolveStorageUrlsInObject(environmentData.project.styling),
},
},
organization: {
id: environmentData.project.organization.id,
billing: environmentData.project.organization.billing,
},
surveys: transformedSurveys,
surveys: resolveStorageUrlsInObject(transformedSurveys),
actionClasses: environmentData.actionClasses as TJsEnvironmentStateActionClass[],
};
} catch (error) {

View File

@@ -82,7 +82,8 @@ const mockOrganization: TOrganization = {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
const mockSurveys: TSurvey[] = [

View File

@@ -44,13 +44,10 @@ const validateResponse = (
...responseUpdateInput.data,
};
const isFinished = responseUpdateInput.finished ?? false;
const validationErrors = validateResponseData(
survey.blocks,
mergedData,
responseUpdateInput.language ?? response.language ?? "en",
isFinished,
survey.questions
);

View File

@@ -41,7 +41,6 @@ const validateResponse = (responseInputData: TResponseInput, survey: TSurvey) =>
survey.blocks,
responseInputData.data,
responseInputData.language ?? "en",
responseInputData.finished,
survey.questions
);

View File

@@ -6,7 +6,7 @@ import {
} from "@formbricks/types/integration/slack";
import { responses } from "@/app/lib/api/response";
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 { createOrUpdateIntegration, getIntegrationByType } from "@/lib/integration/service";
@@ -56,6 +56,7 @@ export const GET = withV1ApiWrapper({
code,
client_id: SLACK_CLIENT_ID,
client_secret: SLACK_CLIENT_SECRET,
redirect_uri: SLACK_REDIRECT_URI,
};
const formBody: string[] = [];
for (const property in formData) {

View File

@@ -10,7 +10,7 @@ import { deleteResponse, getResponse } from "@/lib/response/service";
import { getSurvey } from "@/lib/survey/service";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import { resolveStorageUrlsInObject, validateFileUploads } from "@/modules/storage/utils";
import { updateResponseWithQuotaEvaluation } from "./lib/response";
async function fetchAndAuthorizeResponse(
@@ -57,7 +57,10 @@ export const GET = withV1ApiWrapper({
}
return {
response: responses.successResponse(result.response),
response: responses.successResponse({
...result.response,
data: resolveStorageUrlsInObject(result.response.data),
}),
};
} catch (error) {
return {
@@ -146,7 +149,6 @@ export const PUT = withV1ApiWrapper({
result.survey.blocks,
responseUpdate.data,
responseUpdate.language ?? "en",
responseUpdate.finished,
result.survey.questions
);
@@ -190,7 +192,7 @@ export const PUT = withV1ApiWrapper({
}
return {
response: responses.successResponse(updated),
response: responses.successResponse({ ...updated, data: resolveStorageUrlsInObject(updated.data) }),
};
} catch (error) {
return {

View File

@@ -9,7 +9,7 @@ import { sendToPipeline } from "@/app/lib/pipelines";
import { getSurvey } from "@/lib/survey/service";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import { resolveStorageUrlsInObject, validateFileUploads } from "@/modules/storage/utils";
import {
createResponseWithQuotaEvaluation,
getResponses,
@@ -54,7 +54,9 @@ export const GET = withV1ApiWrapper({
allResponses.push(...environmentResponses);
}
return {
response: responses.successResponse(allResponses),
response: responses.successResponse(
allResponses.map((r) => ({ ...r, data: resolveStorageUrlsInObject(r.data) }))
),
};
} catch (error) {
if (error instanceof DatabaseError) {
@@ -155,7 +157,6 @@ export const POST = withV1ApiWrapper({
surveyResult.survey.blocks,
responseInput.data,
responseInput.language ?? "en",
responseInput.finished,
surveyResult.survey.questions
);

View File

@@ -16,6 +16,7 @@ import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib
import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { getSurvey, updateSurvey } from "@/lib/survey/service";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { resolveStorageUrlsInObject } from "@/modules/storage/utils";
const fetchAndAuthorizeSurvey = async (
surveyId: string,
@@ -58,16 +59,18 @@ export const GET = withV1ApiWrapper({
if (shouldTransformToQuestions) {
return {
response: responses.successResponse({
...result.survey,
questions: transformBlocksToQuestions(result.survey.blocks, result.survey.endings),
blocks: [],
}),
response: responses.successResponse(
resolveStorageUrlsInObject({
...result.survey,
questions: transformBlocksToQuestions(result.survey.blocks, result.survey.endings),
blocks: [],
})
),
};
}
return {
response: responses.successResponse(result.survey),
response: responses.successResponse(resolveStorageUrlsInObject(result.survey)),
};
} catch (error) {
return {
@@ -202,12 +205,12 @@ export const PUT = withV1ApiWrapper({
};
return {
response: responses.successResponse(surveyWithQuestions),
response: responses.successResponse(resolveStorageUrlsInObject(surveyWithQuestions)),
};
}
return {
response: responses.successResponse(updatedSurvey),
response: responses.successResponse(resolveStorageUrlsInObject(updatedSurvey)),
};
} catch (error) {
return {

View File

@@ -43,7 +43,8 @@ const mockOrganization: TOrganization = {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
const mockFollowUp: TSurveyCreateInputWithEnvironmentId["followUps"][number] = {

View File

@@ -14,6 +14,7 @@ import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib
import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { createSurvey } from "@/lib/survey/service";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { resolveStorageUrlsInObject } from "@/modules/storage/utils";
import { getSurveys } from "./lib/surveys";
export const GET = withV1ApiWrapper({
@@ -55,7 +56,7 @@ export const GET = withV1ApiWrapper({
});
return {
response: responses.successResponse(surveysWithQuestions),
response: responses.successResponse(resolveStorageUrlsInObject(surveysWithQuestions)),
};
} catch (error) {
if (error instanceof DatabaseError) {

View File

@@ -112,7 +112,6 @@ export const POST = async (request: Request, context: Context): Promise<Response
survey.blocks,
responseInputData.data,
responseInputData.language ?? "en",
responseInputData.finished,
survey.questions
);

View File

@@ -4854,6 +4854,17 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => {
}),
isDraft: true,
},
{
...buildOpenTextElement({
id: "preview-open-text-01",
headline: t("templates.preview_survey_question_open_text_headline"),
subheader: t("templates.preview_survey_question_open_text_subheader"),
placeholder: t("templates.preview_survey_question_open_text_placeholder"),
inputType: "text",
required: false,
}),
isDraft: true,
},
],
buttonLabel: createI18nString(t("templates.next"), []),
backButtonLabel: createI18nString(t("templates.preview_survey_question_2_back_button_label"), []),

View File

@@ -257,6 +257,7 @@ describe("endpoint-validator", () => {
expect(isAuthProtectedRoute("/api/v1/client/test")).toBe(false);
expect(isAuthProtectedRoute("/")).toBe(false);
expect(isAuthProtectedRoute("/s/survey123")).toBe(false);
expect(isAuthProtectedRoute("/p/pretty-url")).toBe(false);
expect(isAuthProtectedRoute("/c/jwt-token")).toBe(false);
expect(isAuthProtectedRoute("/health")).toBe(false);
});
@@ -312,6 +313,19 @@ describe("endpoint-validator", () => {
expect(isPublicDomainRoute("/c")).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", () => {
expect(isPublicDomainRoute("/api/v1/client/something")).toBe(true);
@@ -375,6 +389,8 @@ describe("endpoint-validator", () => {
expect(isAdminDomainRoute("/s/survey-id-with-dashes")).toBe(false);
expect(isAdminDomainRoute("/c/jwt-token")).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/v2/client/other")).toBe(false);
});
@@ -390,6 +406,7 @@ describe("endpoint-validator", () => {
test("should allow public routes on public domain", () => {
expect(isRouteAllowedForDomain("/s/survey123", 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/v2/client/other", 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("/c/jwt-token", 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/v2/client/other", false)).toBe(false);
});
@@ -440,6 +459,8 @@ describe("endpoint-validator", () => {
test("should handle paths with query parameters and fragments", () => {
expect(isRouteAllowedForDomain("/s/survey123?param=value", 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", false)).toBe(true);
});
@@ -450,6 +471,7 @@ describe("endpoint-validator", () => {
describe("URL parsing edge cases", () => {
test("should handle paths with query parameters", () => {
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("/environments/123?tab=settings")).toBe(false);
expect(isAuthProtectedRoute("/environments/123?tab=overview")).toBe(true);
@@ -458,12 +480,14 @@ describe("endpoint-validator", () => {
test("should handle paths with fragments", () => {
expect(isPublicDomainRoute("/s/survey123#section")).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(isAuthProtectedRoute("/organizations/456#settings")).toBe(true);
});
test("should handle trailing slashes", () => {
expect(isPublicDomainRoute("/s/survey123/")).toBe(true);
expect(isPublicDomainRoute("/p/pretty123/")).toBe(true);
expect(isPublicDomainRoute("/api/v1/client/test/")).toBe(true);
expect(isManagementApiRoute("/api/v1/management/test/")).toEqual({
isManagementApi: true,
@@ -478,6 +502,9 @@ describe("endpoint-validator", () => {
expect(isPublicDomainRoute("/s/survey123/preview")).toBe(true);
expect(isPublicDomainRoute("/s/survey123/embed")).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", () => {
@@ -529,6 +556,7 @@ describe("endpoint-validator", () => {
test("should handle special characters in survey IDs", () => {
expect(isPublicDomainRoute("/s/survey-123_test.v2")).toBe(true);
expect(isPublicDomainRoute("/c/jwt.token.with.dots")).toBe(true);
expect(isPublicDomainRoute("/p/pretty-123_test.v2")).toBe(true);
});
});
@@ -536,6 +564,7 @@ describe("endpoint-validator", () => {
test("should properly validate malicious or injection-like URLs", () => {
// SQL injection-like attempts
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({
isManagementApi: true,
authenticationMethod: AuthenticationMethod.ApiKey,
@@ -543,10 +572,12 @@ describe("endpoint-validator", () => {
// Path traversal attempts
expect(isPublicDomainRoute("/s/../../../etc/passwd")).toBe(true); // Still matches pattern
expect(isPublicDomainRoute("/p/../../../etc/passwd")).toBe(true);
expect(isAuthProtectedRoute("/environments/../../../etc/passwd")).toBe(true);
// XSS-like attempts
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({
isClientSideApi: true,
isRateLimited: true,
@@ -556,6 +587,7 @@ describe("endpoint-validator", () => {
test("should handle URL encoding", () => {
expect(isPublicDomainRoute("/s/survey%20123")).toBe(true);
expect(isPublicDomainRoute("/c/jwt%2Etoken")).toBe(true);
expect(isPublicDomainRoute("/p/pretty%20123")).toBe(true);
expect(isAuthProtectedRoute("/environments%2F123")).toBe(true);
expect(isManagementApiRoute("/api/v1/management/test%20route")).toEqual({
isManagementApi: true,
@@ -591,6 +623,7 @@ describe("endpoint-validator", () => {
// These should not match due to case sensitivity
expect(isPublicDomainRoute("/S/survey123")).toBe(false);
expect(isPublicDomainRoute("/C/jwt-token")).toBe(false);
expect(isPublicDomainRoute("/P/pretty123")).toBe(false);
expect(isClientSideApiRoute("/API/V1/CLIENT/test")).toEqual({
isClientSideApi: false,
isRateLimited: true,

View File

@@ -7,6 +7,7 @@ const PUBLIC_ROUTES = {
SURVEY_ROUTES: [
/^\/s\/[^/]+/, // /s/[surveyId] - survey pages
/^\/c\/[^/]+/, // /c/[jwt] - contact survey pages
/^\/p\/[^/]+/, // /p/[prettyUrl] - pretty URL pages
],
// API routes accessible from public domain

View File

@@ -106,6 +106,7 @@ checksums:
common/allow: 3e39cc5940255e6bff0fea95c817dd43
common/allow_users_to_exit_by_clicking_outside_the_survey: 1c09db6e85214f1b1c3d4774c4c5cd56
common/an_unknown_error_occurred_while_deleting_table_items: 06be3fd128aeb51eed4fba9a079ecee2
common/analysis: 409bac6215382c47e59f5039cc4cdcdd
common/and: dc75b95c804b16dc617a5f16f7393bca
common/and_response_limit_of: 05be41a1d7e8dafa4aa012dcba77f5d4
common/anonymous: 77b5222e710cc1dae073dae32309f8ed
@@ -122,6 +123,8 @@ checksums:
common/bottom_right: aaef9a70ef795affc806c6d1853d8373
common/cancel: 2e2a849c2223911717de8caa2c71bade
common/centered_modal: 982ff411cb7e91e30300c2ed56b7e507
common/chart: 6f4d9c56e45ceb8fc22d2f74454cd813
common/charts: 1da4564d89264c89de4ed28d7451b43e
common/choices: 8a7a77a71ec6eebc363c5dc0f8490a4d
common/choose_environment: 5762cd499529815fc3e6a7feea39f90b
common/choose_organization: a8f5db68012323bfbb1a0ad0fb194603
@@ -151,6 +154,7 @@ checksums:
common/count_attributes: 042fba9baffef5afe2c24f13d4f50697
common/count_contacts: b1c413a4b06961b71b6aeee95d6775d7
common/count_responses: 690118a456c01c5b4d437ae82b50b131
common/create: 757ccd28dd533ff3a933355273c1e32a
common/create_new_organization: 51dae7b33143686ee218abf5bea764a5
common/create_segment: 9d8291cd4d778b53b73bbc84fd91c181
common/create_survey: 1cfbba08d34876566d84b2960054a987
@@ -160,6 +164,8 @@ checksums:
common/created_by: 6775c2fa7d495fea48f1ad816daea93b
common/customer_success: 2b0c99a5f57e1d16cf0a998f9bb116c4
common/dark_overlay: 173e84b526414dbc70dbf9737e443b60
common/dashboard: c9380ea68c8c76ea451bd9613329a07c
common/dashboards: 4bc47e48559a6b688684dcb7ac4babc9
common/date: 56f41c5d30a76295bb087b20b7bee4c3
common/days: c95fe8aedde21a0b5653dbd0b3c58b48
common/default: d9c6dc5c412fe94143dfd1d332ec81d4
@@ -191,13 +197,16 @@ checksums:
common/error: 3c95bcb32c2104b99a46f5b3dd015248
common/error_component_description: fa9eee04f864c3fe6e6681f716caa015
common/error_component_title: ae68fa341a143aaa13a5ea30dd57a63e
common/error_loading_data: aaeffbfe4a2c2145442a57de524494be
common/error_rate_limit_description: 37791a33a947204662ee9c6544e90f51
common/error_rate_limit_title: 23ac9419e267e610e1bfd38e1dc35dc0
common/expand_rows: b6e06327cb8718dfd6651720843e4dad
common/failed_to_copy_to_clipboard: de836a7d628d36c832809252f188f784
common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e
common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4
common/filter: 626325a05e4c8800f7ede7012b0cadaf
common/finish: ffa7a10f71182b48fefed7135bee24fa
common/first_name: cf040a5d6a9fd696be400380cc99f54b
common/follow_these: 3a730b242bb17a3f95e01bf0dae86885
common/formbricks_version: d9967c797f3e49ca0cae78bc0ebd19cb
common/full_name: f45991923345e8322c9ff8cd6b7e2b16
@@ -209,7 +218,9 @@ checksums:
common/hidden: fa290c6ada5869d744ed35e9cca64699
common/hidden_field: 3ed5c58d0ed359e558cdf7bd33606d2d
common/hidden_fields: 3de6cfd308293a826cb8679fd1d49972
common/hide: a6088b934651055bb27314d111be510b
common/hide_column: 23ce94db148f2d8e4a0923defead6cf1
common/id: c8886d38aeea2ed5f785aba4fc96784b
common/image: 048ba7a239de0fbd883ade8558415830
common/images: 9305827c28694866f49db42b4c51831f
common/import: 348b8ab981de5b7f1fca6d7302263bbd
@@ -227,6 +238,7 @@ checksums:
common/key: 3d1065ab98a1c2f1210507fd5c7bf515
common/label: a5c71bf158481233f8215dbd38cc196b
common/language: 277fd1a41cc237a437cd1d5e4a80463b
common/last_name: 2c9a7de7738ca007ba9023c385149c26
common/learn_more: e598091d132f890c37a6d4ed94f6d794
common/license_expired: 7af13535e320e4197989472c01387d2c
common/light_overlay: 0499907ea7b8405f4267b117998b5a78
@@ -253,6 +265,7 @@ checksums:
common/move_down: 4f4de55743043355ad4a839aff2c48ff
common/move_up: 69f25b205c677abdb26cbb69d97cd10b
common/multiple_languages: 7d8ddd4b40d32fcd7bd6f7bac6485b1f
common/my_product: ad022177062f9ef6e9acf33b13e889aa
common/name: 9368b5a047572b6051f334af5aa76819
common/new: 126d036fae5fb6b629728ecb97e6195b
common/new_version_available: 399ddfc4232712e18ddab2587356b3dc
@@ -276,6 +289,7 @@ checksums:
common/on: 1929bcf2fba8003c043b446a851bcb4f
common/only_one_file_allowed: 171be177f2e96c4bb4c4a47b3bf6c8c9
common/only_owners_managers_and_manage_access_members_can_perform_this_action: 3c16fc506e871935f6183793e73b6709
common/open_options: a4578c0afbfdf4a76d5952a53085b72a
common/option_id: ed21d97b8ab035ba89fb3f5f073229bd
common/option_ids: e68c25215ce81ea7ad82ff7be0a0bf2d
common/optional: 396fb9a0472daf401c392bdc3e248943
@@ -401,6 +415,7 @@ checksums:
common/top_right: 241f95c923846911aaf13af6109333e5
common/try_again: 33dd8820e743e35a66e6977f69e9d3b5
common/type: f04471a7ddac844b9ad145eb9911ef75
common/unknown_survey: dd8f6985e17ccf19fac1776e18b2c498
common/unlock_more_workspaces_with_a_higher_plan: fe1590075b855bb4306c9388b65143b0
common/update: 079fc039262fd31b10532929685c2d1b
common/updated: 8aa8ff2dc2977ca4b269e80a513100b4
@@ -417,6 +432,7 @@ checksums:
common/variables: ffd3eec5497af36d7b4e4185bad1313a
common/verified_email: d4a9e5e47d622c6ef2fede44233076c7
common/video: 8050c90e4289b105a0780f0fdda6ff66
common/view: 36a9b5e3dc153c036d320460d72a03c3
common/warning: 6618da2c7e5e93bb4ea0e16d29ab8c4c
common/we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable: f29f2e0286195dab170b9806bcd74fc9
common/webhook: 70f95b2c27f2c3840b500fcaf79ee83c
@@ -574,6 +590,170 @@ checksums:
environments/actions/you_can_track_code_action_anywhere_in_your_app_using: 3c0bbf160b8ddbeef142403103b70554
environments/actions/your_survey_would_be_shown_on_this_url: 766fdeeb52d170c156af5d035a1f8c37
environments/actions/your_survey_would_not_be_shown: af44fe160f449ff9557ebe5d3686832d
environments/analysis/charts/OR: 0208d355f231c386b19390f0bea41b95
environments/analysis/charts/add_chart_to_dashboard: c2a517ada86cdda60e49bec655ca9a6d
environments/analysis/charts/add_chart_to_dashboard_description: 08980a1849757e9aec21fca5881c6be4
environments/analysis/charts/add_filter: ed5d8e9bfcb05cd1e10e4c403befbae6
environments/analysis/charts/add_to_dashboard: 9941c3d30895bb8e25ce8d4e03d33a08
environments/analysis/charts/advanced_chart_builder_config_prompt: c2fe2c1a076f27d3ae62a4db75474b0a
environments/analysis/charts/ai_query_placeholder: 24c3d18f514cb3a9953f04c3b04503a2
environments/analysis/charts/ai_query_section_description: 66d06342f29bf6658793403856521fd7
environments/analysis/charts/ai_query_section_title: c0e450a47af7c2a516b77f73cf54db1b
environments/analysis/charts/apply_changes: ed3da8072dbd27dc0c959777cdcbebf3
environments/analysis/charts/chart: 6f4d9c56e45ceb8fc22d2f74454cd813
environments/analysis/charts/chart_added_to_dashboard: 7bc429ab605cb89a9232c26be008cc00
environments/analysis/charts/chart_builder_choose_chart_type: 1376de2dcafac573a2df9e4c007b0ec8
environments/analysis/charts/chart_data: 6739a9576b357a58d73ff0c9bf8db0e4
environments/analysis/charts/chart_data_tab: b7b46ab6ce9606032c8f81f6f6afbb9b
environments/analysis/charts/chart_deleted_successfully: 79148f471cd9acc2c8d0d033fb85437e
environments/analysis/charts/chart_deletion_error: 267eb65c168e726075d7cea678dd32e0
environments/analysis/charts/chart_duplicated_successfully: 755c4ce5bf533764d549a53c33e32165
environments/analysis/charts/chart_duplication_error: 90d7166c85188b52f821c9d9f53ff8c4
environments/analysis/charts/chart_name: cdb36e2f121a7b9c28298e15ab8218dc
environments/analysis/charts/chart_name_placeholder: 7370d4f88f27aea337ba1c36465c3f8b
environments/analysis/charts/chart_preview: 1b7faae244d31e43f758f50b94132413
environments/analysis/charts/chart_render_error: 01e9ece0c86a1fedf301afa0dbbf6aeb
environments/analysis/charts/chart_saved_successfully: 2489c853c0b36790e3592ac6ea31cc61
environments/analysis/charts/chart_type_area: 535754c6425f045f17e1dcb551840c93
environments/analysis/charts/chart_type_bar: c11d460595d3ddfe8efd67ac068574c5
environments/analysis/charts/chart_type_big_number: 9d17fb96241507c955dca25e143ae67a
environments/analysis/charts/chart_type_line: f42dd53238ed4d44def306a61d47d5c4
environments/analysis/charts/chart_type_not_supported: 7ff0afc493b36f3f3c12c7c230df9757
environments/analysis/charts/chart_type_pie: 068a797404233ccf68d07ad63af7b50c
environments/analysis/charts/chart_updated_successfully: a2c210523902c726aa1328bbeda0b357
environments/analysis/charts/configure_description: 2939321f78e4ffbc57b4259ddaddb09d
environments/analysis/charts/configure_title: ab767b11da1d386b98b3f634f79d3abe
environments/analysis/charts/configure_type_label: cd13e4b37fb2021af55903e7690a9856
environments/analysis/charts/contains: 06dd606c0a8f81f9a03b414e9ae89440
environments/analysis/charts/create_chart: ca7fdcc964e01f42ea9709924221edba
environments/analysis/charts/create_chart_description: b9680bd8905dea180fa59a86f61de34e
environments/analysis/charts/custom_range: 99f4d72b64621406acc162cceeb1fed7
environments/analysis/charts/dashboard: c9380ea68c8c76ea451bd9613329a07c
environments/analysis/charts/dashboard_select_placeholder: 9b875f2f10050d650ae63be53fe0d4e8
environments/analysis/charts/data_label: b7b46ab6ce9606032c8f81f6f6afbb9b
environments/analysis/charts/date_preset_last_30_days: a738894cfc5e592052f1e16787744568
environments/analysis/charts/date_preset_last_7_days: 3631df3109bfecfe358ba15dcf8bd6f5
environments/analysis/charts/date_preset_last_month: 848086395b28875c050d56e3933dae61
environments/analysis/charts/date_preset_this_month: 50845a38865204a97773c44dcd2ebb90
environments/analysis/charts/date_preset_this_quarter: 9c77d94783dff2269c069389122cd7bd
environments/analysis/charts/date_preset_this_year: 1e69651c2ac722f8ce138f43cf2e02f9
environments/analysis/charts/date_preset_today: 142173f9752e18e92109623a3ee68cad
environments/analysis/charts/date_preset_yesterday: eeb58908e68ff96c1b7e8f90e389afb7
environments/analysis/charts/date_range: 9b3aa5954144de586931f60ef9594e99
environments/analysis/charts/delete_chart_confirmation: f7fd7b0a08e81c9b392b08c9c1ad2147
environments/analysis/charts/dimensions: f09d837ac25f58986a769bd48ea15022
environments/analysis/charts/dimensions_toggle_description: 50d1c6e73d2cb7320c9e29cec11b4c76
environments/analysis/charts/edit_chart_description: 822890e4b6068096e2fe8b7b78b4474f
environments/analysis/charts/edit_chart_title: fd3e7f8c53280bfad8f4034c055f4c71
environments/analysis/charts/enable_time_dimension: cfcf0af2d22bccd197319c07680c2cb8
environments/analysis/charts/end_date: acbea5a9fd7a6fadf5aa1b4f47188203
environments/analysis/charts/enter_a_name_for_your_chart: b6e992a23d0628136121ebf26eec4a50
environments/analysis/charts/enter_value: a4554ed67c02872e302b0042724f859d
environments/analysis/charts/equals: 264ec282f7f5b67da622cc37f2b57b8a
environments/analysis/charts/failed_to_add_chart_to_dashboard: 355a5606399edcbb3e6d0ba0b66f12a6
environments/analysis/charts/failed_to_execute_query: d1153133aa4cd3d1cd02e39942413168
environments/analysis/charts/failed_to_load_chart: abea098fbf8e728f95414d3ae8bb63a4
environments/analysis/charts/failed_to_load_chart_data: ea980a6d12b1b1efed90d991dd0dd0fd
environments/analysis/charts/failed_to_save_chart: e237cf1a56a8f9ee30067fdb0757f7c5
environments/analysis/charts/field: cfd632297d7809a3539e90c9cd4728d9
environments/analysis/charts/field_label_average_score: 5b5aa7322549521d1e813b1c8312d443
environments/analysis/charts/field_label_collected_at: b41902ddb4586ba4a4611d726b5014aa
environments/analysis/charts/field_label_count: 9c5848662eb8024ddf360f7e4001a968
environments/analysis/charts/field_label_detractor_count: eedb15bc383eb0f14d43043e6666c62a
environments/analysis/charts/field_label_emotion: eb3a31ead51b5c8a8d365d5f904e9206
environments/analysis/charts/field_label_field_type: 2581066dc304c853a4a817c20996fa08
environments/analysis/charts/field_label_nps_score: 9c8d0b0b460f9689bd66e81d45e0a2df
environments/analysis/charts/field_label_nps_value: cb7404025044400e3d7d5600f3133e4f
environments/analysis/charts/field_label_passive_count: ceb71da8d1382eb2097089dc3ecf76da
environments/analysis/charts/field_label_promoter_count: c393131a4bd3a25bf6b297beed20e34f
environments/analysis/charts/field_label_response_id: 73375099cc976dc7203b8e27f5f709e0
environments/analysis/charts/field_label_sentiment: 9ba5719c80c0136c2d0644217619aff6
environments/analysis/charts/field_label_source_name: 157675beca12efcd8ec512c5256b1a61
environments/analysis/charts/field_label_source_type: d1ff69af76c687eb189db72030717570
environments/analysis/charts/field_label_topic: 7f542b783cd528f00f4f485e35b48dc1
environments/analysis/charts/field_label_user_identifier: b0174469c95038766744fb7e64005aec
environments/analysis/charts/filters: acf5accc113ff3c1992688058576732c
environments/analysis/charts/filters_toggle_description: ea18bdb212a6a85620125cab89a4b1c1
environments/analysis/charts/generate_chart: 8b0ca95be31a8401b13eafa26cf01d31
environments/analysis/charts/granularity: 9eb09aef092e7803ce4acb7965cbbaa9
environments/analysis/charts/granularity_day: 47648cd60fc313bc3f05b70357a1d675
environments/analysis/charts/granularity_hour: ec3113f22fc51d01f0c615c5496f8f87
environments/analysis/charts/granularity_month: ae7bef950efc406ff0980affabc1a64c
environments/analysis/charts/granularity_quarter: 7a68ec90d7c90b92b7bb873834a00381
environments/analysis/charts/granularity_week: 436fdd694160827dd6ea4644cdd0a8f8
environments/analysis/charts/granularity_year: ed86f5f60583f9d8ffdbeed306aa0ec7
environments/analysis/charts/greater_than: a4c18b3b45fcaf7c83bf489cf2b506d4
environments/analysis/charts/greater_than_or_equal: d453e26d136847560148168797fece51
environments/analysis/charts/group_by: 3f1cedea7783018ce83f2fab0051a738
environments/analysis/charts/group_by_description: c54368c05d71c1bdbd2a5c0629c1dc03
environments/analysis/charts/guide_button: 3c5e2e28f6d9f1a644759c9c19878539
environments/analysis/charts/guide_chart_type: 1fd60a98a0b5a7f54521e7671772e4a3
environments/analysis/charts/guide_chart_type_desc: 4630292b6955c930a9c6d4169bf656a2
environments/analysis/charts/guide_dimensions: 746caf6f43a222f3ffdaae578323d36a
environments/analysis/charts/guide_dimensions_desc: 909a149ef47c2f811d65f437b34ea719
environments/analysis/charts/guide_filters: acf5accc113ff3c1992688058576732c
environments/analysis/charts/guide_filters_desc: 0c18f563b477cd9a0f2309c31174cd93
environments/analysis/charts/guide_measures: 2e4d2701ebb196e5a9122f03727e93d7
environments/analysis/charts/guide_measures_predefined: cb8b80a960a466aca9ad75d3e870f74b
environments/analysis/charts/guide_quick_ref: 6538588cf9323d85bf11b794448d846d
environments/analysis/charts/guide_term_dimension: 64bd5923ae7aa2cdbf967aca977e4945
environments/analysis/charts/guide_term_filter: c8dc27ccd08e7ec1e268dfd286660e79
environments/analysis/charts/guide_term_measure: ca94a6e1afcb8a7ddb0d79039ecd3bfb
environments/analysis/charts/guide_term_time: ddd5b6a7a0f8525b0fe2b7c3431319f2
environments/analysis/charts/guide_time_dimension: c6fed7f718296b2f23230a918bfe6196
environments/analysis/charts/guide_time_dimension_desc: 4565fd19f4346e0f0d52f79640d7d749
environments/analysis/charts/guide_title: e887f73e68a76c88fdef859bafc866a1
environments/analysis/charts/is_not_set: 906801489132487ef457652af4835142
environments/analysis/charts/is_set: 9850468156356f95884bbaf56b6687aa
environments/analysis/charts/less_than: fb41255dd44bb6de78617b078610c91b
environments/analysis/charts/less_than_or_equal: da4a2816aadf788d33efcdcc3c61802e
environments/analysis/charts/measures: b1e6cf0f356dda0052c4fef4ad4957a2
environments/analysis/charts/no_charts_found: d4a27d5b56e49ebdd38bf28791dbcc42
environments/analysis/charts/no_dashboards_available: f88389b6c5278cfc4d5b360031205dfe
environments/analysis/charts/no_dashboards_create_first: 28ded0d72247191eb23f6f77925df539
environments/analysis/charts/no_data_available: fe1d34a45e22b5611d255b84b2d67232
environments/analysis/charts/no_data_returned: 683acf7b4f3b32aa85fa26f1bb948d4f
environments/analysis/charts/no_data_returned_for_chart: b9ff6c85697c683f40b3d0c05eeb2046
environments/analysis/charts/no_grouping: e3a6943e61407600cae057e0833a482d
environments/analysis/charts/no_valid_data_to_display: d1ba2b0686520c0a2c62ee73daa1c9c9
environments/analysis/charts/not_contains: 5894f5474271b8902d7892e43500d227
environments/analysis/charts/not_equals: 427715f1ea349965c36f5c628784eb08
environments/analysis/charts/open_chart: bc3bed1517ad63c1bcccfbbc430ab333
environments/analysis/charts/open_options: 2c6a35fec9b9d008e41728594bcd07d7
environments/analysis/charts/or_filter_logic: 0208d355f231c386b19390f0bea41b95
environments/analysis/charts/original: 7e55782bdf7cb49f5616b326c003c278
environments/analysis/charts/please_enter_chart_name: 9258b71b2cb09d22ffe33de1755e7309
environments/analysis/charts/please_select_at_least_one_measure: d4163ede267f71ee65945f453e14ff7b
environments/analysis/charts/please_select_dashboard: 8f062db96f815ed8268584dd8d292fa6
environments/analysis/charts/predefined_measures: 7651141f62c991954edcff70899b2a8b
environments/analysis/charts/preset: a17bb0bf56f3326c9567be3ea896ee19
environments/analysis/charts/query_executed_successfully: 9d6f9dad526fcfe0161757c2d2fe2c69
environments/analysis/charts/reset_to_ai_suggestion: 51ced8dd7c0eea8b7fc4e08b35cfbf30
environments/analysis/charts/save_chart: 2e4505f7bf3d1c35b0b37b1e9d3dc566
environments/analysis/charts/save_chart_dialog_title: 2e4505f7bf3d1c35b0b37b1e9d3dc566
environments/analysis/charts/select_dimensions: 6d0d038d027ef9e641bf9b7700edac9f
environments/analysis/charts/select_field: 45665a44f7d5707506364f17f28db3bf
environments/analysis/charts/select_measures: c9f101aeb53bf0d4abdd652aaf60a1bf
environments/analysis/charts/select_preset: e68bad9a209a6ca35c62184f1f1d829c
environments/analysis/charts/showing_first_n_of: 4dec3215fd3150a16ad5c72f17ae02bc
environments/analysis/charts/start_date: 881de78c79b56f5ceb9b7103bf23cb2c
environments/analysis/charts/time_dimension: 5c967f2a6a875b00825068df5cb2ef84
environments/analysis/charts/time_dimension_toggle_description: 28da119989e3c73b098c650fe279ee4a
environments/analysis/dashboards/create_dashboard: 9396aec1ea4a9b05ada94483655d1373
environments/analysis/dashboards/create_dashboard_description: d29f60615f6d8c96cc4265541e75ec26
environments/analysis/dashboards/create_failed: 7b58f15568047a35220b3a47cc3b0f71
environments/analysis/dashboards/create_success: 1fa4dea7702ba03a8a3533295276ff1b
environments/analysis/dashboards/dashboard_name: a2d344bc03f27706b42d7d6a8d0fc752
environments/analysis/dashboards/dashboard_name_placeholder: 02954eeb5671f1c00e3f69b47319916e
environments/analysis/dashboards/delete_confirmation: 468a0fb0e24a985cc47a778b50b334ba
environments/analysis/dashboards/delete_failed: b108acc28b1f9abcb544a358a958b54b
environments/analysis/dashboards/delete_success: 9d161634daab9ea9d17fbfb413eeeffa
environments/analysis/dashboards/description_optional: d5519551a79f18fc414dc127b773485f
environments/analysis/dashboards/description_placeholder: 90a599e6b1695e2b026fb1300d1d5903
environments/analysis/dashboards/duplicate_failed: 6ebaf8ad373b156f88f1ed79a5efd441
environments/analysis/dashboards/duplicate_success: 37cbb14143776d4c215432673e32ebd9
environments/analysis/dashboards/no_dashboards_found: e049ec0356009c3a0aa2c729d916efc6
environments/analysis/dashboards/please_enter_name: b9211ed8a0882c0e0109beba48685d68
environments/connect/congrats: c2f5b597aabdf298cf9f0452863e2dc6
environments/connect/connection_successful_message: fa1f29883e15e8697c6c477bdf5cb645
environments/connect/do_it_later: ab4accfbe53d924ab3ffaf9ea78a75f3
@@ -609,7 +789,6 @@ checksums:
environments/contacts/contacts_table_refresh: 6a959475991dd4ab28ad881bae569a09
environments/contacts/contacts_table_refresh_success: 40951396e88e5c8fdafa0b3bb4fadca8
environments/contacts/create_attribute: 87320615901f95b4f35ee83c290a3a6c
environments/contacts/create_key: 0d385c354af8963acbe35cd646710f86
environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f
environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26
environments/contacts/custom_attributes: fffc7722742d1291b102dc737cf2fc9e
@@ -620,6 +799,7 @@ checksums:
environments/contacts/delete_attribute_confirmation: 01d99b89eb3d27ff468d0db1b4aeb394
environments/contacts/delete_contact_confirmation: 2d45579e0bb4bc40fb1ee75b43c0e7a4
environments/contacts/delete_contact_confirmation_with_quotas: d3d17f13ae46ce04c126c82bf01299ac
environments/contacts/displays: fcc4527002bd045021882be463b8ac72
environments/contacts/edit_attribute: 92a83c96a5d850e7d39002e8fd5898f4
environments/contacts/edit_attribute_description: 073a3084bb2f3b34ed1320ed1cd6db3c
environments/contacts/edit_attribute_values: 44e4e7a661cc1b59200bb07c710072a7
@@ -631,6 +811,7 @@ checksums:
environments/contacts/invalid_csv_column_names: dcb8534e7d4c00b9ea7bdaf389f72328
environments/contacts/invalid_date_format: 5bad9730ac5a5bacd0792098f712b1c4
environments/contacts/invalid_number_format: bd0422507385f671c3046730a6febc64
environments/contacts/no_activity_yet: f88897ac05afd6bf8af0d4834ad24ffc
environments/contacts/no_published_link_surveys_available: 9c1abc5b21aba827443cdf87dd6c8bfe
environments/contacts/no_published_surveys: bd945b0e2e2328c17615c94143bdd62b
environments/contacts/no_responses_found: f10190cffdda4ca1bed479acbb89b13f
@@ -645,6 +826,8 @@ checksums:
environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514
environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79
environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38
environments/contacts/survey_viewed: 646d413218626787b0373ffd71cb7451
environments/contacts/survey_viewed_at: 2ab535237af5c3c3f33acc792a7e70a4
environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6
environments/contacts/unlock_contacts_description: c5572047f02b4c39e5109f9de715499d
environments/contacts/unlock_contacts_title: a8b3d7db03eb404d9267fd5cdd6d5ddb
@@ -711,7 +894,12 @@ checksums:
environments/integrations/google_sheets/link_google_sheet: fa78146ae26ce5b1d2aaf2678f628943
environments/integrations/google_sheets/link_new_sheet: 8ad2ea8708f50ed184c00b84577b325e
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/token_expired_error: 555d34c18c554ec8ac66614f21bd44fc
environments/integrations/include_created_at: 8011355b13e28e638d74e6f3d68a2bbf
environments/integrations/include_hidden_fields: 25f0ea5ca1c6ead2cd121f8754cb8d72
environments/integrations/include_metadata: 750091d965d7cc8d02468b5239816dc5
@@ -988,6 +1176,13 @@ checksums:
environments/settings/enterprise/sso: 95e98e279bb89233d63549b202bd9112
environments/settings/enterprise/teams: 21ab78abcba0f16c3029741563f789ea
environments/settings/enterprise/unlock_the_full_power_of_formbricks_free_for_30_days: 104d07b63a42911c9673ceb08a4dbd43
environments/settings/general/ai_data_analysis_enabled: 45fabb594da6851f73fef50ca40fe525
environments/settings/general/ai_data_analysis_enabled_description: 46d4f0bdf4ebf89e78f79cc961a2de83
environments/settings/general/ai_enabled: 3cb1fce89c525e754448d5bd143eb6b5
environments/settings/general/ai_enabled_description: e8c3e9f362588898a6cea85e18c013a1
environments/settings/general/ai_settings_updated_successfully: 2a6f534dc3a246ced46becd8a4a9543d
environments/settings/general/ai_smart_tools_enabled: 1dda984f5262c5f9120ee9a409236758
environments/settings/general/ai_smart_tools_enabled_description: 1ceca6707746d3ab4a530712a06d91da
environments/settings/general/bulk_invite_warning_description: e8737a2fbd5ff353db5580d17b4b5a37
environments/settings/general/cannot_delete_only_organization: 833cc6848b28f2694a4552b4de91a6ba
environments/settings/general/cannot_leave_only_organization: dd8463262e4299fef7ad73512225c55b
@@ -1161,6 +1356,7 @@ checksums:
environments/surveys/edit/add_fallback_placeholder: 0e77ea487ddd7bc7fc2f1574b018dc08
environments/surveys/edit/add_hidden_field_id: a8f55b51b790cf5f4d898af7770ad1ed
environments/surveys/edit/add_highlight_border: 66f52b21fbb9aa6561c98a090abaaf8f
environments/surveys/edit/add_highlight_border_description: fe548fe03ea10ef5cd9e553d6812b3c2
environments/surveys/edit/add_logic: f234c9f1393a9ed4792dfbd15838c951
environments/surveys/edit/add_none_of_the_above: dbe1ada4512d6c3f80c54c8fac107ec6
environments/surveys/edit/add_option: 143c54f0b201067fe5159284d6daeca2
@@ -1359,7 +1555,6 @@ checksums:
environments/surveys/edit/follow_ups_modal_updated_successfull_toast: 61204fada3231f4f1fe3866e87e1130a
environments/surveys/edit/follow_ups_new: 224c779d252b3e75086e4ed456ba2548
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/four_points: b289628a6b8a6cd0f7d17a14ca6cd7bf
environments/surveys/edit/heading: 79e9dfa461f38a239d34b9833ca103f1
@@ -1530,7 +1725,7 @@ checksums:
environments/surveys/edit/response_limits_redirections_and_more: e4f1cf94e56ad0e1b08701158d688802
environments/surveys/edit/response_options: 2988136d5248d7726583108992dcbaee
environments/surveys/edit/roundness: 5a161c8f5f258defb57ed1d551737cc4
environments/surveys/edit/roundness_description: bde131aa5674836416dcdf2ff517d899
environments/surveys/edit/roundness_description: 03940a6871ae43efa4810cba7cadb74b
environments/surveys/edit/row_used_in_logic_error: f89453ff1b6db77ad84af840fedd9813
environments/surveys/edit/rows: 8f41f34e6ca28221cf1ebd948af4c151
environments/surveys/edit/save_and_close: 6ede705b3f82f30269ff3054a5049e34
@@ -1576,6 +1771,7 @@ checksums:
environments/surveys/edit/survey_completed_subheading: db537c356c3ab6564d24de0d11a0fee2
environments/surveys/edit/survey_display_settings: 8ed19e6a8e1376f7a1ba037d82c4ae11
environments/surveys/edit/survey_placement: 083c10f257337f9648bf9d435b18ec2c
environments/surveys/edit/survey_styling: 7f96d6563e934e65687b74374a33b1dc
environments/surveys/edit/survey_trigger: f0c7014a684ca566698b87074fad5579
environments/surveys/edit/switch_multi_language_on_to_get_started: cca0ef91ee49095da30cd1e3f26c406f
environments/surveys/edit/target_block_not_found: 0a0c401017ab32364fec2fcbf815d832
@@ -1846,6 +2042,7 @@ checksums:
environments/surveys/summary/filtered_responses_excel: 06e57bae9e41979fd7fc4b8bfe3466f9
environments/surveys/summary/generating_qr_code: 5026d4a76f995db458195e5215d9bbd9
environments/surveys/summary/impressions: 7fe38d42d68a64d3fd8436a063751584
environments/surveys/summary/impressions_identified_only: 10f8c491463c73b8e6534314ee00d165
environments/surveys/summary/impressions_tooltip: 4d0823cbf360304770c7c5913e33fdc8
environments/surveys/summary/in_app/connection_description: 9710bbf8048a8a5c3b2b56db9d946b73
environments/surveys/summary/in_app/connection_title: 29e8a40ad6a7fdb5af5ee9451a70a9aa
@@ -1886,6 +2083,7 @@ checksums:
environments/surveys/summary/last_quarter: 2e565a81de9b3d7b1ee709ebb6f6eda1
environments/surveys/summary/last_year: fe7c268a48bf85bc40da000e6e437637
environments/surveys/summary/limit: 347051f1a068e01e8c4e4f6744d8e727
environments/surveys/summary/no_identified_impressions: c3bc42e6feb9010ced905ded51c5afc4
environments/surveys/summary/no_responses_found: f10190cffdda4ca1bed479acbb89b13f
environments/surveys/summary/other_values_found: 48a74ee68c05f7fb162072b50c683b6a
environments/surveys/summary/overall: 6c6d6533013d4739766af84b2871bca6
@@ -2027,7 +2225,7 @@ checksums:
environments/workspace/look/advanced_styling_field_description_size: a0d51c3ab7dc56320ecedc2b27917842
environments/workspace/look/advanced_styling_field_description_size_description: ff880ea1beddd1b1ec7416d0b8a69cf3
environments/workspace/look/advanced_styling_field_description_weight: 514680cc7202ad29835c1cbcde3def1c
environments/workspace/look/advanced_styling_field_description_weight_description: 441ac8db1a32557813eb68fbfd759061
environments/workspace/look/advanced_styling_field_description_weight_description: aa95bc81b5336a548e256bce49350683
environments/workspace/look/advanced_styling_field_font_size: ca44d14429b2175a1b194793b4ab8f6b
environments/workspace/look/advanced_styling_field_font_weight: bfef83778146cf40550df9650d8a07da
environments/workspace/look/advanced_styling_field_headline_color: 4ccf3935ad90c88ad4add24f498673ce
@@ -2036,12 +2234,12 @@ checksums:
environments/workspace/look/advanced_styling_field_headline_size_description: 13debc3855e4edae992c7a1ebff599c3
environments/workspace/look/advanced_styling_field_headline_weight: 0c8b8262945c61f8e2978502362e0a42
environments/workspace/look/advanced_styling_field_headline_weight_description: 1a9c40bd76ff5098b1e48b1d3893171b
environments/workspace/look/advanced_styling_field_height: f4da6d7ecd26e3fa75cfea03abb60c00
environments/workspace/look/advanced_styling_field_height: 40ca2224bb2936ad1329091b35a9ffe2
environments/workspace/look/advanced_styling_field_indicator_bg: 00febda2901af0f1b0c17e44f9917c38
environments/workspace/look/advanced_styling_field_indicator_bg_description: 7eb3b54a8b331354ec95c0dc1545c620
environments/workspace/look/advanced_styling_field_input_border_radius_description: 0007f1bb572b35d9a3720daeb7a55617
environments/workspace/look/advanced_styling_field_input_font_size_description: 5311f95dcbd083623e35c98ea5374c3b
environments/workspace/look/advanced_styling_field_input_height_description: b704fc67e805223992c811d6f86a9c00
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_y_description: 98b4aeff2940516d05ea61bdc1211d0d
environments/workspace/look/advanced_styling_field_input_placeholder_opacity_description: f55a6700884d24014404e58876121ddf
@@ -2050,6 +2248,8 @@ checksums:
environments/workspace/look/advanced_styling_field_input_text_description: 460450df24ea0cc902710118a5000feb
environments/workspace/look/advanced_styling_field_option_bg: 0ceaed10d99ed4ad83cb0934ab970174
environments/workspace/look/advanced_styling_field_option_bg_description: 6cd6ccecbbb9f2f19439d7c682eb67c1
environments/workspace/look/advanced_styling_field_option_border: 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_font_size_description: 5430fd9b08819972f0a613bf3fa659da
environments/workspace/look/advanced_styling_field_option_label: 2767a5db32742073a01aac16488e93dc
@@ -2104,6 +2304,7 @@ checksums:
environments/workspace/look/show_powered_by_formbricks: a0e96edadec8ef326423feccc9d06be7
environments/workspace/look/styling_updated_successfully: b8b74b50dde95abcd498633e9d0c891f
environments/workspace/look/suggest_colors: ddc4543b416ab774007b10a3434343cd
environments/workspace/look/suggested_colors_applied_please_save: 226fa70af5efc8ffa0a3755909c8163e
environments/workspace/look/theme: 21fe00b7a518089576fb83c08631107a
environments/workspace/look/theme_settings_description: 9fc45322818c3774ab4a44ea14d7836e
environments/workspace/tags/add: 87c4a663507f2bcbbf79934af8164e13
@@ -2830,6 +3031,9 @@ checksums:
templates/preview_survey_question_2_choice_2_label: 1af148222f327f28cf0db6513de5989e
templates/preview_survey_question_2_headline: 5cfb173d156555227fbc2c97ad921e72
templates/preview_survey_question_2_subheader: 2e652d8acd68d072e5a0ae686c4011c0
templates/preview_survey_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/prioritize_features_description: 1eae41fad0e3947f803d8539081e59ec
templates/prioritize_features_name: 4ca59ff1f9c319aaa68c3106d820fd6a

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_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_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET;

View File

@@ -1,9 +1,10 @@
import "server-only";
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { z } from "zod";
import { prisma } from "@formbricks/database";
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 { validateInputs } from "../utils/validate";
@@ -23,13 +24,12 @@ export const getDisplayCountBySurveyId = reactCache(
const displayCount = await prisma.display.count({
where: {
surveyId: surveyId,
...(filters &&
filters.createdAt && {
createdAt: {
gte: filters.createdAt.min,
lte: filters.createdAt.max,
},
}),
...(filters?.createdAt && {
createdAt: {
gte: filters.createdAt.min,
lte: filters.createdAt.max,
},
}),
},
});
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> => {
validateInputs([displayId, ZId]);
try {

View File

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

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

View File

@@ -2,7 +2,12 @@ import "server-only";
import { Prisma } from "@prisma/client";
import { z } from "zod";
import { ZString } from "@formbricks/types/common";
import { DatabaseError, UnknownError } from "@formbricks/types/errors";
import {
AuthenticationError,
DatabaseError,
OperationNotAllowedError,
UnknownError,
} from "@formbricks/types/errors";
import {
TIntegrationGoogleSheets,
ZIntegrationGoogleSheets,
@@ -11,8 +16,12 @@ import {
GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET,
GOOGLE_SHEETS_REDIRECT_URL,
GOOGLE_SHEET_MESSAGE_LIMIT,
} 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 { truncateText } from "../utils/strings";
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 (
googleSheetIntegrationData: TIntegrationGoogleSheets,
spreadsheetId: string
@@ -94,7 +114,17 @@ export const getSpreadsheetNameById = async (
return new Promise((resolve, reject) => {
sheets.spreadsheets.get({ spreadsheetId }, (err, response) => {
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;
}
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 client_id = GOOGLE_SHEETS_CLIENT_ID;
const client_secret = GOOGLE_SHEETS_CLIENT_SECRET;
const redirect_uri = GOOGLE_SHEETS_REDIRECT_URL;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
const refresh_token = googleSheetIntegrationData.config.key.refresh_token;
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,
},
});
const key = googleSheetIntegrationData.config.key;
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;
}
};

View File

@@ -40,7 +40,8 @@ describe("auth", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
];
vi.mocked(getOrganizationsByUserId).mockResolvedValue(mockOrganizations);

View File

@@ -55,7 +55,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
};
@@ -110,7 +111,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
},
];
@@ -163,7 +165,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
};
@@ -224,7 +227,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [{ userId: "user1" }, { userId: "user2" }],
projects: [
@@ -256,7 +260,8 @@ describe("Organization Service", () => {
periodStart: expect.any(Date),
period: "monthly",
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
});
expect(prisma.organization.update).toHaveBeenCalledWith({

View File

@@ -25,7 +25,8 @@ export const select: Prisma.OrganizationSelect = {
updatedAt: true,
name: true,
billing: true,
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
whitelabel: true,
};

View File

@@ -22,6 +22,7 @@ import { getElementsFromBlocks } from "@/lib/survey/utils";
import { getIsQuotasEnabled } from "@/modules/ee/license-check/lib/utils";
import { reduceQuotaLimits } from "@/modules/ee/quotas/lib/quotas";
import { deleteFile } from "@/modules/storage/service";
import { resolveStorageUrlsInObject } from "@/modules/storage/utils";
import { getOrganizationIdFromEnvironmentId } from "@/modules/survey/lib/organization";
import { getOrganizationBilling } from "@/modules/survey/lib/survey";
import { ITEMS_PER_PAGE } from "../constants";
@@ -408,9 +409,10 @@ export const getResponseDownloadFile = async (
if (survey.isVerifyEmailEnabled) {
headers.push("Verified Email");
}
const resolvedResponses = responses.map((r) => ({ ...r, data: resolveStorageUrlsInObject(r.data) }));
const jsonData = getResponsesJson(
survey,
responses,
resolvedResponses,
elements,
userAttributes,
hiddenFields,

View File

@@ -60,6 +60,7 @@ export const getSuggestedColors = (brandColor: string = DEFAULT_BRAND_COLOR) =>
// Options (Radio / Checkbox)
"optionBgColor.light": inputBg,
"optionLabelColor.light": questionColor,
"optionBorderColor.light": inputBorder,
// Card
"cardBackgroundColor.light": cardBg,
@@ -118,10 +119,10 @@ export const STYLE_DEFAULTS: TProjectStyling = {
// Inputs
inputTextColor: { light: _colors["inputTextColor.light"] },
inputBorderRadius: 8,
inputHeight: 40,
inputHeight: 20,
inputFontSize: 14,
inputPaddingX: 16,
inputPaddingY: 16,
inputPaddingX: 8,
inputPaddingY: 8,
inputPlaceholderOpacity: 0.5,
inputShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
@@ -138,6 +139,7 @@ export const STYLE_DEFAULTS: TProjectStyling = {
// Options
optionBgColor: { light: _colors["optionBgColor.light"] },
optionLabelColor: { light: _colors["optionLabelColor.light"] },
optionBorderColor: { light: _colors["optionBorderColor.light"] },
optionBorderRadius: 8,
optionPaddingX: 16,
optionPaddingY: 16,
@@ -149,6 +151,43 @@ export const STYLE_DEFAULTS: TProjectStyling = {
progressIndicatorBgColor: { light: _colors["progressIndicatorBgColor.light"] },
};
/**
* Fills in new v4.7 color fields from legacy v4.6 fields when they are missing.
*
* v4.6 stored: brandColor, questionColor, inputColor, inputBorderColor.
* v4.7 adds: elementHeadlineColor, buttonBgColor, optionBgColor, etc.
*
* When loading v4.6 data the new fields are absent. Without this helper the
* form would fall back to STYLE_DEFAULTS (derived from the *default* brand
* colour), causing a visible mismatch. This function derives the new fields
* from the actually-saved legacy fields so the preview and form stay coherent.
*
* Only sets a field when the legacy source exists AND the new field is absent.
*/
export const deriveNewFieldsFromLegacy = (saved: Record<string, unknown>): Record<string, unknown> => {
const light = (key: string): string | undefined =>
(saved[key] as { light?: string } | null | undefined)?.light;
const q = light("questionColor");
const b = light("brandColor");
const i = light("inputColor");
const inputBorder = light("inputBorderColor");
return {
...(q && !saved.elementHeadlineColor && { elementHeadlineColor: { light: q } }),
...(q && !saved.elementDescriptionColor && { elementDescriptionColor: { light: q } }),
...(q && !saved.elementUpperLabelColor && { elementUpperLabelColor: { light: q } }),
...(q && !saved.inputTextColor && { inputTextColor: { light: q } }),
...(q && !saved.optionLabelColor && { optionLabelColor: { light: q } }),
...(b && !saved.buttonBgColor && { buttonBgColor: { light: b } }),
...(b && !saved.buttonTextColor && { buttonTextColor: { light: isLight(b) ? "#0f172a" : "#ffffff" } }),
...(i && !saved.optionBgColor && { optionBgColor: { light: i } }),
...(inputBorder && !saved.optionBorderColor && { optionBorderColor: { light: inputBorder } }),
...(b && !saved.progressIndicatorBgColor && { progressIndicatorBgColor: { light: b } }),
...(b && !saved.progressTrackBgColor && { progressTrackBgColor: { light: mixColor(b, "#ffffff", 0.8) } }),
};
};
/**
* Builds a complete TProjectStyling object from a single brand color.
*
@@ -175,6 +214,7 @@ export const buildStylingFromBrandColor = (brandColor: string = DEFAULT_BRAND_CO
inputTextColor: { light: colors["inputTextColor.light"] },
optionBgColor: { light: colors["optionBgColor.light"] },
optionLabelColor: { light: colors["optionLabelColor.light"] },
optionBorderColor: { light: colors["optionBorderColor.light"] },
cardBackgroundColor: { light: colors["cardBackgroundColor.light"] },
cardBorderColor: { light: colors["cardBorderColor.light"] },
highlightBorderColor: { light: colors["highlightBorderColor.light"] },

View File

@@ -232,7 +232,8 @@ export const mockOrganizationOutput: TOrganization = {
name: "mock Organization",
createdAt: currentDate,
updatedAt: currentDate,
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
billing: {
stripeCustomerId: null,
plan: "free",

View File

@@ -73,7 +73,8 @@ describe("User Service", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
{
id: "org2",
@@ -93,7 +94,8 @@ describe("User Service", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
];

View File

@@ -22,6 +22,9 @@ export type AuditLoggingCtx = {
quotaId?: string;
teamId?: string;
integrationId?: string;
chartId?: string;
dashboardId?: string;
dashboardWidgetId?: string;
};
export type ActionClientCtx = {

View File

@@ -12,11 +12,18 @@ export function validateInputs<T extends ValidationPair<any>[]>(
for (const [value, schema] of pairs) {
const inputValidation = schema.safeParse(value);
if (!inputValidation.success) {
const zodDetails = inputValidation.error.issues
.map((issue) => {
const path = issue?.path?.join(".") ?? "";
return `${path}${issue.message}`;
})
.join("; ");
logger.error(
inputValidation.error,
`Validation failed for ${JSON.stringify(value).substring(0, 100)} and ${JSON.stringify(schema)}`
);
throw new ValidationError("Validation failed");
throw new ValidationError(`Validation failed: ${zodDetails}`);
}
parsedData.push(inputValidation.data);
}

View File

@@ -133,6 +133,7 @@
"allow": "erlauben",
"allow_users_to_exit_by_clicking_outside_the_survey": "Erlaube Nutzern, die Umfrage zu verlassen, indem sie außerhalb klicken",
"an_unknown_error_occurred_while_deleting_table_items": "Beim Löschen von {type}s ist ein unbekannter Fehler aufgetreten",
"analysis": "Analyse",
"and": "und",
"and_response_limit_of": "und Antwortlimit von",
"anonymous": "Anonym",
@@ -149,6 +150,8 @@
"bottom_right": "Unten rechts",
"cancel": "Abbrechen",
"centered_modal": "Zentriertes Modalfenster",
"chart": "Diagramm",
"charts": "Diagramme",
"choices": "Entscheidungen",
"choose_environment": "Umgebung auswählen",
"choose_organization": "Organisation auswählen",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} Attribut} other {{value} Attribute}}",
"count_contacts": "{value, plural, one {{value} Kontakt} other {{value} Kontakte}}",
"count_responses": "{value, plural, one {{value} Antwort} other {{value} Antworten}}",
"create": "Erstellen",
"create_new_organization": "Neue Organisation erstellen",
"create_segment": "Segment erstellen",
"create_survey": "Umfrage erstellen",
@@ -187,6 +191,8 @@
"created_by": "Erstellt von",
"customer_success": "Kundenerfolg",
"dark_overlay": "Dunkle Überlagerung",
"dashboard": "Dashboard",
"dashboards": "Dashboards",
"date": "Datum",
"days": "Tage",
"default": "Standard",
@@ -218,13 +224,16 @@
"error": "Fehler",
"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_loading_data": "Fehler beim Laden der Daten",
"error_rate_limit_description": "Maximale Anzahl an Anfragen erreicht. Bitte später erneut versuchen.",
"error_rate_limit_title": "Rate Limit Überschritten",
"expand_rows": "Zeilen erweitern",
"failed_to_copy_to_clipboard": "Fehler beim Kopieren in die Zwischenablage",
"failed_to_load_organizations": "Fehler beim Laden der Organisationen",
"failed_to_load_workspaces": "Projekte konnten nicht geladen werden",
"filter": "Filter",
"finish": "Fertigstellen",
"first_name": "Vorname",
"follow_these": "Folge diesen",
"formbricks_version": "Formbricks Version",
"full_name": "Name",
@@ -236,7 +245,9 @@
"hidden": "Versteckt",
"hidden_field": "Verstecktes Feld",
"hidden_fields": "Versteckte Felder",
"hide": "Ausblenden",
"hide_column": "Spalte ausblenden",
"id": "ID",
"image": "Bild",
"images": "Bilder",
"import": "Importieren",
@@ -254,6 +265,7 @@
"key": "Schlüssel",
"label": "Bezeichnung",
"language": "Sprache",
"last_name": "Nachname",
"learn_more": "Mehr erfahren",
"license_expired": "License Expired",
"light_overlay": "Helle Überlagerung",
@@ -280,6 +292,7 @@
"move_down": "Nach unten bewegen",
"move_up": "Nach oben bewegen",
"multiple_languages": "Mehrsprachigkeit",
"my_product": "mein Produkt",
"name": "Name",
"new": "Neu",
"new_version_available": "Formbricks {version} ist da. Jetzt aktualisieren!",
@@ -303,6 +316,7 @@
"on": "An",
"only_one_file_allowed": "Es ist nur eine Datei erlaubt",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Nur Eigentümer, Manager und Mitglieder mit Zugriff auf das Management können diese Aktion ausführen.",
"open_options": "Optionen öffnen",
"option_id": "Option-ID",
"option_ids": "Option-IDs",
"optional": "Optional",
@@ -428,6 +442,7 @@
"top_right": "Oben rechts",
"try_again": "Versuch's nochmal",
"type": "Typ",
"unknown_survey": "Unbekannte Umfrage",
"unlock_more_workspaces_with_a_higher_plan": "Schalten Sie mehr Projekte mit einem höheren Tarif frei.",
"update": "Aktualisierung",
"updated": "Aktualisiert",
@@ -444,6 +459,7 @@
"variables": "Variablen",
"verified_email": "Verifizierte E-Mail",
"video": "Video",
"view": "Ansehen",
"warning": "Warnung",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Wir konnten Ihre Lizenz nicht überprüfen, da der Lizenzserver nicht erreichbar ist.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Ihre Umfrage wäre unter dieser URL angezeigt.",
"your_survey_would_not_be_shown": "Ihre Umfrage wäre nicht angezeigt."
},
"analysis": {
"charts": {
"OR": "ODER",
"add_chart_to_dashboard": "Diagramm zum Dashboard hinzufügen",
"add_chart_to_dashboard_description": "Wähle ein Dashboard aus, um dieses Diagramm hinzuzufügen. Das Diagramm wird automatisch gespeichert.",
"add_filter": "Filter hinzufügen",
"add_to_dashboard": "Zum Dashboard hinzufügen",
"advanced_chart_builder_config_prompt": "Konfiguriere dein Diagramm und klicke auf \"Abfrage ausführen\", um eine Vorschau zu sehen",
"ai_query_placeholder": "z.B. Wie viele Nutzer haben sich letzte Woche angemeldet?",
"ai_query_section_description": "Beschreibe, was du sehen möchtest, und lass die KI das Diagramm erstellen.",
"ai_query_section_title": "Frag deine Daten",
"apply_changes": "Änderungen übernehmen",
"chart": "Diagramm",
"chart_added_to_dashboard": "Diagramm zum Dashboard hinzugefügt!",
"chart_builder_choose_chart_type": "Diagrammtyp auswählen",
"chart_data": "Diagrammdaten",
"chart_data_tab": "Daten",
"chart_deleted_successfully": "Diagramm erfolgreich gelöscht",
"chart_deletion_error": "Diagramm konnte nicht gelöscht werden",
"chart_duplicated_successfully": "Diagramm erfolgreich dupliziert",
"chart_duplication_error": "Diagramm konnte nicht dupliziert werden",
"chart_name": "Diagrammname",
"chart_name_placeholder": "Diagrammname",
"chart_preview": "Diagrammvorschau",
"chart_render_error": "Beim Rendern dieses Diagramms ist etwas schiefgelaufen.",
"chart_saved_successfully": "Diagramm erfolgreich gespeichert!",
"chart_type_area": "Flächendiagramm",
"chart_type_bar": "Balkendiagramm",
"chart_type_big_number": "Große Zahl",
"chart_type_line": "Liniendiagramm",
"chart_type_not_supported": "Diagrammtyp \"{{chartType}}\" wird noch nicht unterstützt",
"chart_type_pie": "Kreisdiagramm",
"chart_updated_successfully": "Diagramm erfolgreich aktualisiert!",
"configure_description": "Ändere den Diagrammtyp und andere Einstellungen für diese Visualisierung.",
"configure_title": "Diagramm konfigurieren",
"configure_type_label": "Diagrammtyp",
"contains": "enthält",
"create_chart": "Diagramm erstellen",
"create_chart_description": "Nutze KI, um ein Diagramm zu generieren, oder erstelle es manuell.",
"custom_range": "Benutzerdefinierter Bereich",
"dashboard": "Dashboard",
"dashboard_select_placeholder": "Wähle ein Dashboard aus",
"data_label": "Daten",
"date_preset_last_30_days": "Letzte 30 Tage",
"date_preset_last_7_days": "Letzte 7 Tage",
"date_preset_last_month": "Letzter Monat",
"date_preset_this_month": "Dieser Monat",
"date_preset_this_quarter": "Dieses Quartal",
"date_preset_this_year": "Dieses Jahr",
"date_preset_today": "Heute",
"date_preset_yesterday": "Gestern",
"date_range": "Datumsbereich",
"delete_chart_confirmation": "Bist du sicher, dass du dieses Diagramm löschen möchtest?",
"dimensions": "Dimensionen",
"dimensions_toggle_description": "Gruppiere Daten nach Kategorien. Die Reihenfolge ist bei mehrdimensionalen Diagrammen wichtig.",
"edit_chart_description": "Sieh dir deine Diagrammkonfiguration an und bearbeite sie.",
"edit_chart_title": "Diagramm bearbeiten",
"enable_time_dimension": "Zeitdimension aktivieren",
"end_date": "Enddatum",
"enter_a_name_for_your_chart": "Gib einen Namen für dein Diagramm ein, um es zu speichern.",
"enter_value": "Wert eingeben",
"equals": "gleich",
"failed_to_add_chart_to_dashboard": "Diagramm konnte nicht zum Dashboard hinzugefügt werden",
"failed_to_execute_query": "Abfrage konnte nicht ausgeführt werden",
"failed_to_load_chart": "Diagramm konnte nicht geladen werden",
"failed_to_load_chart_data": "Diagrammdaten konnten nicht geladen werden",
"failed_to_save_chart": "Diagramm konnte nicht gespeichert werden",
"field": "Feld",
"field_label_average_score": "Durchschnittliche Bewertung",
"field_label_collected_at": "Erfasst am",
"field_label_count": "Anzahl",
"field_label_detractor_count": "Anzahl Kritiker",
"field_label_emotion": "Emotion",
"field_label_field_type": "Feldtyp",
"field_label_nps_score": "NPS-Score",
"field_label_nps_value": "NPS-Wert",
"field_label_passive_count": "Anzahl Passive",
"field_label_promoter_count": "Anzahl Promoter",
"field_label_response_id": "Antwort-ID",
"field_label_sentiment": "Stimmung",
"field_label_source_name": "Quellenname",
"field_label_source_type": "Quellentyp",
"field_label_topic": "Thema",
"field_label_user_identifier": "Benutzerkennung",
"filters": "Filter",
"filters_toggle_description": "Nur Daten einbeziehen, die die folgenden Bedingungen erfüllen.",
"generate_chart": "Diagramm generieren",
"granularity": "Granularität",
"granularity_day": "Tag",
"granularity_hour": "Stunde",
"granularity_month": "Monat",
"granularity_quarter": "Quartal",
"granularity_week": "Woche",
"granularity_year": "Jahr",
"greater_than": "größer als",
"greater_than_or_equal": "größer als oder gleich",
"group_by": "Gruppieren nach",
"group_by_description": "Wähle Dimensionen aus, um deine Daten aufzuschlüsseln. Die Reihenfolge ist bei mehrdimensionalen Diagrammen wichtig.",
"guide_button": "Feldleitfaden anzeigen",
"guide_chart_type": "Diagrammtyp",
"guide_chart_type_desc": "Wie die Daten visualisiert werden: Fläche, Balken, Linie, Kreis oder große Zahl. Wähle basierend darauf, was du zeigen möchtest (Trends, Vergleiche, Teile eines Ganzen usw.).",
"guide_dimensions": "Dimensionen (Gruppieren nach)",
"guide_dimensions_desc": "Wie du die Daten aufteilst oder gruppierst. Jede Dimension wird zu einer Kategorie im Diagramm (z. B. Stimmung, Quellentyp, Umfragename, Kanal, Thema). Die Reihenfolge ist bei mehrdimensionalen Diagrammen wichtig.",
"guide_filters": "Filter",
"guide_filters_desc": "Bedingungen, die einschränken, welche Daten einbezogen werden. Jeder Filter hat ein Feld, einen Operator (gleich, enthält, größer als usw.) und Werte. Und = alle müssen übereinstimmen; Oder = beliebige können übereinstimmen.",
"guide_measures": "Kennzahlen (was du zählst oder aggregierst)",
"guide_measures_predefined": "Vordefinierte Kennzahlen sind vorgefertigte Metriken aus deinen Feedback-Daten: Count (Gesamtantworten), Promoter/Detractor/Passive Count (NPS-Segmente), NPS Score, Average Score, Completion Rate.",
"guide_quick_ref": "Schnellreferenz",
"guide_term_dimension": "Kategorisches Feld, das zum Gruppieren oder Aufteilen von Daten verwendet wird",
"guide_term_filter": "Bedingung, die einschränkt, welche Zeilen einbezogen werden",
"guide_term_measure": "Numerischer Wert, den du aggregierst (count, sum, avg usw.)",
"guide_term_time": "Zeitbasierte Gruppierung mit Granularität und Datumsbereich",
"guide_time_dimension": "Zeitdimension",
"guide_time_dimension_desc": "Zeitbasierte Gruppierung: Wähle ein Zeitfeld (normalerweise Erfasst am), Granularität (Stunde, Tag, Woche, Monat usw.) und Datumsbereich (Voreinstellung oder benutzerdefiniert). Verwende dies für Trends im Zeitverlauf.",
"guide_title": "Diagramm-Builder Feldleitfaden",
"is_not_set": "ist nicht festgelegt",
"is_set": "ist festgelegt",
"less_than": "kleiner als",
"less_than_or_equal": "kleiner oder gleich",
"measures": "Kennzahlen",
"no_charts_found": "Keine Diagramme gefunden.",
"no_dashboards_available": "Keine Dashboards verfügbar",
"no_dashboards_create_first": "Erstelle zuerst ein Dashboard, um Diagramme hinzuzufügen.",
"no_data_available": "Keine Daten verfügbar",
"no_data_returned": "Keine Daten von der Abfrage zurückgegeben",
"no_data_returned_for_chart": "Keine Daten für Diagramm zurückgegeben",
"no_grouping": "Keine (nur Filter)",
"no_valid_data_to_display": "Keine gültigen Daten zur Anzeige",
"not_contains": "enthält nicht",
"not_equals": "ist nicht gleich",
"open_chart": "Diagramm {{name}} öffnen",
"open_options": "Diagrammoptionen öffnen",
"or_filter_logic": "ODER",
"original": "Original",
"please_enter_chart_name": "Bitte gib einen Diagrammnamen ein",
"please_select_at_least_one_measure": "Bitte wähle mindestens eine Kennzahl aus",
"please_select_dashboard": "Bitte wähle ein Dashboard aus",
"predefined_measures": "Vordefinierte Kennzahlen",
"preset": "Vorlage",
"query_executed_successfully": "Abfrage erfolgreich ausgeführt",
"reset_to_ai_suggestion": "Auf KI-Vorschlag zurücksetzen",
"save_chart": "Diagramm speichern",
"save_chart_dialog_title": "Diagramm speichern",
"select_dimensions": "Dimensionen auswählen...",
"select_field": "Feld auswählen",
"select_measures": "Metriken auswählen...",
"select_preset": "Vorlage auswählen",
"showing_first_n_of": "Zeige die ersten {{n}} von {{count}} Zeilen",
"start_date": "Startdatum",
"time_dimension": "Zeitdimension",
"time_dimension_toggle_description": "Füge zeitbasierte Gruppierung hinzu, um Trends im Zeitverlauf zu sehen."
},
"dashboards": {
"create_dashboard": "Dashboard erstellen",
"create_dashboard_description": "Gib einen Namen für dein neues Dashboard ein.",
"create_failed": "Dashboard konnte nicht erstellt werden",
"create_success": "Dashboard erfolgreich erstellt!",
"dashboard_name": "Dashboard-Name",
"dashboard_name_placeholder": "Mein Dashboard",
"delete_confirmation": "Bist du sicher, dass du dieses Dashboard löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"delete_failed": "Dashboard konnte nicht gelöscht werden",
"delete_success": "Dashboard erfolgreich gelöscht",
"description_optional": "Beschreibung (optional)",
"description_placeholder": "Dashboard-Beschreibung",
"duplicate_failed": "Dashboard konnte nicht dupliziert werden",
"duplicate_success": "Dashboard erfolgreich dupliziert!",
"no_dashboards_found": "Keine Dashboards gefunden.",
"please_enter_name": "Bitte gib einen Dashboard-Namen ein"
}
},
"connect": {
"congrats": "Glückwunsch!",
"connection_successful_message": "Gut gemacht! Wir sind verbunden.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Kontakte aktualisieren",
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
"create_attribute": "Attribut erstellen",
"create_key": "Schlüssel erstellen",
"create_new_attribute": "Neues Attribut erstellen",
"create_new_attribute_description": "Erstellen Sie ein neues Attribut für Segmentierungszwecke.",
"custom_attributes": "Benutzerdefinierte Attribute",
@@ -656,6 +841,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_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.}}",
"displays": "Anzeigen",
"edit_attribute": "Attribut bearbeiten",
"edit_attribute_description": "Aktualisieren Sie die Bezeichnung und Beschreibung für dieses Attribut.",
"edit_attribute_values": "Attribute bearbeiten",
@@ -667,6 +853,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_date_format": "Ungültiges Datumsformat. Bitte verwende ein gültiges Datum.",
"invalid_number_format": "Ungültiges Zahlenformat. Bitte gib eine gültige Zahl ein.",
"no_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_surveys": "Keine veröffentlichten Umfragen",
"no_responses_found": "Keine Antworten gefunden",
@@ -681,6 +868,8 @@
"select_a_survey": "Wähle eine Umfrage aus",
"select_attribute": "Attribut auswählen",
"select_attribute_key": "Attributschlüssel auswählen",
"survey_viewed": "Umfrage angesehen",
"survey_viewed_at": "Angesehen am",
"system_attributes": "Systemattribute",
"unlock_contacts_description": "Verwalte Kontakte und sende gezielte Umfragen",
"unlock_contacts_title": "Kontakte mit einem höheren Plan freischalten",
@@ -752,7 +941,12 @@
"link_google_sheet": "Tabelle verlinken",
"link_new_sheet": "Neues Blatt verknüpfen",
"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_hidden_fields": "Versteckte Felder (hidden fields) einbeziehen",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos."
},
"general": {
"ai_data_analysis_enabled": "Datenanreicherung & Analyse (KI)",
"ai_data_analysis_enabled_description": "KI, um mehr aus deinen Daten herauszuholen, Dashboards, Diagramme, Berichte und mehr einzurichten. Greift auf deine Erfahrungsdaten zu.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Verwalte KI-gestützte Funktionen für diese Organisation.",
"ai_settings_updated_successfully": "KI-Einstellungen erfolgreich aktualisiert",
"ai_smart_tools_enabled": "Intelligente Funktionen (KI)",
"ai_smart_tools_enabled_description": "KI, um dir zu helfen, in kürzerer Zeit mehr zu erreichen. Greift niemals auf mit Formbricks gesammelte Daten zu. Wird nur verwendet, um z. B. Umfragen in andere Sprachen zu übersetzen.",
"bulk_invite_warning_description": "Bitte beachte, dass im Free-Plan alle Organisationsmitglieder automatisch die Rolle \"Owner\" zugewiesen bekommen, unabhängig von der im CSV-File angegebenen Rolle.",
"cannot_delete_only_organization": "Das ist deine einzige Organisation, sie kann nicht gelöscht werden. Erstelle zuerst eine neue Organisation.",
"cannot_leave_only_organization": "Du kannst diese Organisation nicht verlassen, da es deine einzige Organisation ist. Erstelle zuerst eine neue Organisation.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Platzhalter hinzufügen, falls kein Wert zur Verfügung steht.",
"add_hidden_field_id": "Verstecktes Feld ID hinzufügen",
"add_highlight_border": "Rahmen hinzufügen",
"add_highlight_border_description": "Gilt nur für In-Product-Umfragen.",
"add_logic": "Logik hinzufügen",
"add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu",
"add_option": "Option hinzufügen",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "Nachverfolgung aktualisiert und wird gespeichert, sobald du die Umfrage speicherst.",
"follow_ups_new": "Neues Follow-up",
"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",
"four_points": "4 Punkte",
"heading": "Überschrift",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Antwort Limits, Weiterleitungen und mehr.",
"response_options": "Antwortoptionen",
"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.",
"rows": "Zeilen",
"save_and_close": "Speichern & Schließen",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Diese kostenlose und quelloffene Umfrage wurde geschlossen",
"survey_display_settings": "Einstellungen zur Anzeige der Umfrage",
"survey_placement": "Platzierung der Umfrage",
"survey_styling": "Umfrage Styling",
"survey_trigger": "Auslöser der Umfrage",
"switch_multi_language_on_to_get_started": "Aktiviere Mehrsprachigkeit, um loszulegen 👉",
"target_block_not_found": "Zielblock nicht gefunden",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Gefilterte Antworten (Excel)",
"generating_qr_code": "QR-Code wird generiert",
"impressions": "Eindrücke",
"impressions_identified_only": "Zeigt nur Impressionen von identifizierten Kontakten",
"impressions_tooltip": "Anzahl der Aufrufe der Umfrage.",
"in_app": {
"connection_description": "Die Umfrage wird den Nutzern Ihrer Website angezeigt, die den unten aufgeführten Kriterien entsprechen",
@@ -1989,6 +2192,7 @@
"last_quarter": "Letztes Quartal",
"last_year": "Letztes Jahr",
"limit": "Limit",
"no_identified_impressions": "Keine Impressionen von identifizierten Kontakten",
"no_responses_found": "Keine Antworten gefunden",
"other_values_found": "Andere Werte gefunden",
"overall": "Insgesamt",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Skaliert den Überschriftentext.",
"advanced_styling_field_headline_weight": "Schriftstärke der Überschrift",
"advanced_styling_field_headline_weight_description": "Macht den Überschriftentext heller oder fetter.",
"advanced_styling_field_height": "Höhe",
"advanced_styling_field_height": "Mindesthöhe",
"advanced_styling_field_indicator_bg": "Indikator-Hintergrund",
"advanced_styling_field_indicator_bg_description": "Färbt den gefüllten Teil des Balkens.",
"advanced_styling_field_input_border_radius_description": "Rundet die Eingabeecken ab.",
"advanced_styling_field_input_font_size_description": "Skaliert den eingegebenen Text in Eingabefeldern.",
"advanced_styling_field_input_height_description": "Steuert die Höhe des Eingabefelds.",
"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_y_description": "Fügt oben und unten Abstand hinzu.",
"advanced_styling_field_input_placeholder_opacity_description": "Blendet den Platzhaltertext aus.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Färbt den eingegebenen Text in Eingabefeldern.",
"advanced_styling_field_option_bg": "Hintergrund",
"advanced_styling_field_option_bg_description": "Füllt die Optionselemente.",
"advanced_styling_field_option_border": "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_font_size_description": "Skaliert den Text der Optionsbeschriftung.",
"advanced_styling_field_option_label": "Label-Farbe",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "\"Powered by Formbricks\"-Signatur anzeigen",
"styling_updated_successfully": "Styling erfolgreich aktualisiert",
"suggest_colors": "Farben vorschlagen",
"suggested_colors_applied_please_save": "Vorgeschlagene Farben erfolgreich generiert. Drücke \"Speichern\", um die Änderungen zu übernehmen.",
"theme": "Theme",
"theme_settings_description": "Erstelle ein Style-Theme für alle Umfragen. Du kannst für jede Umfrage individuelles Styling aktivieren."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Nein, danke!",
"preview_survey_question_2_headline": "Möchtest Du auf dem Laufenden bleiben?",
"preview_survey_question_2_subheader": "Dies ist eine Beispielbeschreibung.",
"preview_survey_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!",
"prioritize_features_description": "Identifiziere die Funktionen, die deine Nutzer am meisten und am wenigsten brauchen.",
"prioritize_features_name": "Funktionen priorisieren",

View File

@@ -133,6 +133,7 @@
"allow": "Allow",
"allow_users_to_exit_by_clicking_outside_the_survey": "Allow users to exit by clicking outside the survey",
"an_unknown_error_occurred_while_deleting_table_items": "An unknown error occurred while deleting {type}s",
"analysis": "Analysis",
"and": "And",
"and_response_limit_of": "and response limit of",
"anonymous": "Anonymous",
@@ -149,6 +150,8 @@
"bottom_right": "Bottom Right",
"cancel": "Cancel",
"centered_modal": "Centered Modal",
"chart": "Chart",
"charts": "Charts",
"choices": "Choices",
"choose_environment": "Choose environment",
"choose_organization": "Choose organization",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} attribute} other {{value} attributes}}",
"count_contacts": "{value, plural, one {{value} contact} other {{value} contacts}}",
"count_responses": "{value, plural, one {{value} response} other {{value} responses}}",
"create": "Create",
"create_new_organization": "Create new organization",
"create_segment": "Create segment",
"create_survey": "Create survey",
@@ -187,6 +191,8 @@
"created_by": "Created by",
"customer_success": "Customer Success",
"dark_overlay": "Dark overlay",
"dashboard": "Dashboard",
"dashboards": "Dashboards",
"date": "Date",
"days": "days",
"default": "Default",
@@ -218,13 +224,16 @@
"error": "Error",
"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_loading_data": "Error loading data",
"error_rate_limit_description": "Maximum number of requests reached. Please try again later.",
"error_rate_limit_title": "Rate Limit Exceeded",
"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": "Filter",
"finish": "Finish",
"first_name": "First Name",
"follow_these": "Follow these",
"formbricks_version": "Formbricks Version",
"full_name": "Full name",
@@ -236,7 +245,9 @@
"hidden": "Hidden",
"hidden_field": "Hidden field",
"hidden_fields": "Hidden fields",
"hide": "Hide",
"hide_column": "Hide column",
"id": "ID",
"image": "Image",
"images": "Images",
"import": "Import",
@@ -254,6 +265,7 @@
"key": "Key",
"label": "Label",
"language": "Language",
"last_name": "Last Name",
"learn_more": "Learn more",
"license_expired": "License Expired",
"light_overlay": "Light overlay",
@@ -280,6 +292,7 @@
"move_down": "Move down",
"move_up": "Move up",
"multiple_languages": "Multiple languages",
"my_product": "my Product",
"name": "Name",
"new": "New",
"new_version_available": "Formbricks {version} is here. Upgrade now!",
@@ -303,6 +316,7 @@
"on": "On",
"only_one_file_allowed": "Only one file is allowed",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Only owners and managers can perform this action.",
"open_options": "Open options",
"option_id": "Option ID",
"option_ids": "Option IDs",
"optional": "Optional",
@@ -428,6 +442,7 @@
"top_right": "Top Right",
"try_again": "Try again",
"type": "Type",
"unknown_survey": "Unknown survey",
"unlock_more_workspaces_with_a_higher_plan": "Unlock more workspaces with a higher plan.",
"update": "Update",
"updated": "Updated",
@@ -444,6 +459,7 @@
"variables": "Variables",
"verified_email": "Verified Email",
"video": "Video",
"view": "View",
"warning": "Warning",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "We were unable to verify your license because the license server is unreachable.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Your survey would be shown on this URL.",
"your_survey_would_not_be_shown": "Your survey would not be shown."
},
"analysis": {
"charts": {
"OR": "OR",
"add_chart_to_dashboard": "Add Chart to Dashboard",
"add_chart_to_dashboard_description": "Select a dashboard to add this chart to. The chart will be saved automatically.",
"add_filter": "Add filter",
"add_to_dashboard": "Add to Dashboard",
"advanced_chart_builder_config_prompt": "Configure your chart and click \"Run Query\" to preview",
"ai_query_placeholder": "e.g. How many users signed up last week?",
"ai_query_section_description": "Describe what you want to see and let AI build the chart.",
"ai_query_section_title": "Ask your data",
"apply_changes": "Apply Changes",
"chart": "Chart",
"chart_added_to_dashboard": "Chart added to dashboard!",
"chart_builder_choose_chart_type": "Choose chart type",
"chart_data": "Chart Data",
"chart_data_tab": "Data",
"chart_deleted_successfully": "Chart deleted successfully",
"chart_deletion_error": "Failed to delete chart",
"chart_duplicated_successfully": "Chart duplicated successfully",
"chart_duplication_error": "Failed to duplicate chart",
"chart_name": "Chart Name",
"chart_name_placeholder": "Chart name",
"chart_preview": "Chart Preview",
"chart_render_error": "Something went wrong while rendering this chart.",
"chart_saved_successfully": "Chart saved successfully!",
"chart_type_area": "Area Chart",
"chart_type_bar": "Bar Chart",
"chart_type_big_number": "Big Number",
"chart_type_line": "Line Chart",
"chart_type_not_supported": "Chart type \"{{chartType}}\" not yet supported",
"chart_type_pie": "Pie Chart",
"chart_updated_successfully": "Chart updated successfully!",
"configure_description": "Modify the chart type and other settings for this visualization.",
"configure_title": "Configure Chart",
"configure_type_label": "Chart Type",
"contains": "contains",
"create_chart": "Create Chart",
"create_chart_description": "Use AI to generate a chart or build one manually.",
"custom_range": "Custom Range",
"dashboard": "Dashboard",
"dashboard_select_placeholder": "Select a dashboard",
"data_label": "Data",
"date_preset_last_30_days": "Last 30 days",
"date_preset_last_7_days": "Last 7 days",
"date_preset_last_month": "Last month",
"date_preset_this_month": "This month",
"date_preset_this_quarter": "This quarter",
"date_preset_this_year": "This year",
"date_preset_today": "Today",
"date_preset_yesterday": "Yesterday",
"date_range": "Date Range",
"delete_chart_confirmation": "Are you sure you want to delete this chart?",
"dimensions": "Dimensions",
"dimensions_toggle_description": "Group data by categories. Order matters for multi-dimensional charts.",
"edit_chart_description": "View and edit your chart configuration.",
"edit_chart_title": "Edit Chart",
"enable_time_dimension": "Enable Time Dimension",
"end_date": "End date",
"enter_a_name_for_your_chart": "Enter a name for your chart to save it.",
"enter_value": "Enter value",
"equals": "equals",
"failed_to_add_chart_to_dashboard": "Failed to add chart to dashboard",
"failed_to_execute_query": "Failed to execute query",
"failed_to_load_chart": "Failed to load chart",
"failed_to_load_chart_data": "Failed to load chart data",
"failed_to_save_chart": "Failed to save chart",
"field": "Field",
"field_label_average_score": "Average Score",
"field_label_collected_at": "Collected At",
"field_label_count": "Count",
"field_label_detractor_count": "Detractor Count",
"field_label_emotion": "Emotion",
"field_label_field_type": "Field Type",
"field_label_nps_score": "NPS Score",
"field_label_nps_value": "NPS Value",
"field_label_passive_count": "Passive Count",
"field_label_promoter_count": "Promoter Count",
"field_label_response_id": "Response ID",
"field_label_sentiment": "Sentiment",
"field_label_source_name": "Source Name",
"field_label_source_type": "Source Type",
"field_label_topic": "Topic",
"field_label_user_identifier": "User Identifier",
"filters": "Filters",
"filters_toggle_description": "Only include data that meets the following conditions.",
"generate_chart": "Generate Chart",
"granularity": "Granularity",
"granularity_day": "Day",
"granularity_hour": "Hour",
"granularity_month": "Month",
"granularity_quarter": "Quarter",
"granularity_week": "Week",
"granularity_year": "Year",
"greater_than": "greater than",
"greater_than_or_equal": "greater than or equal",
"group_by": "Group By",
"group_by_description": "Select dimensions to break down your data. The order matters for multi-dimensional charts.",
"guide_button": "View field guide",
"guide_chart_type": "Chart type",
"guide_chart_type_desc": "How the data is visualized: Area, Bar, Line, Pie, or Big Number. Choose based on what you want to show (trends, comparisons, parts of a whole, etc.).",
"guide_dimensions": "Dimensions (Group By)",
"guide_dimensions_desc": "How you split or group the data. Each dimension becomes a category on the chart (e.g. Sentiment, Source Type, Survey Name, Channel, Topic). Order matters for multi-dimensional charts.",
"guide_filters": "Filters",
"guide_filters_desc": "Conditions that limit which data is included. Each filter has a field, operator (equals, contains, greater than, etc.), and values. And = all must match; Or = any can match.",
"guide_measures": "Measures (what you count or aggregate)",
"guide_measures_predefined": "Predefined measures are pre-built metrics from your feedback data: Count (total responses), Promoter/Detractor/Passive Count (NPS segments), NPS Score, Average Score, Completion Rate.",
"guide_quick_ref": "Quick reference",
"guide_term_dimension": "Categorical field used to group or split data",
"guide_term_filter": "Condition that limits which rows are included",
"guide_term_measure": "Numeric value you aggregate (count, sum, avg, etc.)",
"guide_term_time": "Time-based grouping with granularity and date range",
"guide_time_dimension": "Time dimension",
"guide_time_dimension_desc": "Time-based grouping: pick a time field (usually Collected At), granularity (Hour, Day, Week, Month, etc.), and date range (preset or custom). Use for trends over time.",
"guide_title": "Chart Builder Field Guide",
"is_not_set": "is not set",
"is_set": "is set",
"less_than": "less than",
"less_than_or_equal": "less than or equal",
"measures": "Measures",
"no_charts_found": "No charts found.",
"no_dashboards_available": "No dashboards available",
"no_dashboards_create_first": "Create a dashboard first to add charts to it.",
"no_data_available": "No data available",
"no_data_returned": "No data returned from query",
"no_data_returned_for_chart": "No data returned for chart",
"no_grouping": "None (filter only)",
"no_valid_data_to_display": "No valid data to display",
"not_contains": "not contains",
"not_equals": "not equals",
"open_chart": "Open chart {{name}}",
"open_options": "Open chart options",
"or_filter_logic": "OR",
"original": "Original",
"please_enter_chart_name": "Please enter a chart name",
"please_select_at_least_one_measure": "Please select at least one measure",
"please_select_dashboard": "Please select a dashboard",
"predefined_measures": "Predefined Measures",
"preset": "Preset",
"query_executed_successfully": "Query executed successfully",
"reset_to_ai_suggestion": "Reset to AI suggestion",
"save_chart": "Save Chart",
"save_chart_dialog_title": "Save Chart",
"select_dimensions": "Select dimensions...",
"select_field": "Select field",
"select_measures": "Select measures...",
"select_preset": "Select preset",
"showing_first_n_of": "Showing first {{n}} of {{count}} rows",
"start_date": "Start date",
"time_dimension": "Time Dimension",
"time_dimension_toggle_description": "Add time-based grouping for trends over time."
},
"dashboards": {
"create_dashboard": "Create Dashboard",
"create_dashboard_description": "Enter a name for your new dashboard.",
"create_failed": "Failed to create dashboard",
"create_success": "Dashboard created successfully!",
"dashboard_name": "Dashboard Name",
"dashboard_name_placeholder": "My dashboard",
"delete_confirmation": "Are you sure you want to delete this dashboard? This action cannot be undone.",
"delete_failed": "Failed to delete dashboard",
"delete_success": "Dashboard deleted successfully",
"description_optional": "Description (Optional)",
"description_placeholder": "Dashboard description",
"duplicate_failed": "Failed to duplicate dashboard",
"duplicate_success": "Dashboard duplicated successfully!",
"no_dashboards_found": "No dashboards found.",
"please_enter_name": "Please enter a dashboard name"
}
},
"connect": {
"congrats": "Congrats!",
"connection_successful_message": "Well done! We are connected.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Refresh contacts",
"contacts_table_refresh_success": "Contacts refreshed successfully",
"create_attribute": "Create attribute",
"create_key": "Create Key",
"create_new_attribute": "Create new attribute",
"create_new_attribute_description": "Create a new attribute for segmentation purposes.",
"custom_attributes": "Custom Attributes",
@@ -656,6 +841,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_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.}}",
"displays": "Displays",
"edit_attribute": "Edit attribute",
"edit_attribute_description": "Update the label and description for this attribute.",
"edit_attribute_values": "Edit attributes",
@@ -667,6 +853,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_date_format": "Invalid date format. Please use a valid date.",
"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_surveys": "No published surveys",
"no_responses_found": "No responses found",
@@ -681,6 +868,8 @@
"select_a_survey": "Select a survey",
"select_attribute": "Select Attribute",
"select_attribute_key": "Select attribute key",
"survey_viewed": "Survey viewed",
"survey_viewed_at": "Viewed At",
"system_attributes": "System Attributes",
"unlock_contacts_description": "Manage contacts and send out targeted surveys",
"unlock_contacts_title": "Unlock contacts with a higher plan",
@@ -752,7 +941,12 @@
"link_google_sheet": "Link Google Sheet",
"link_new_sheet": "Link new Sheet",
"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_hidden_fields": "Include Hidden Fields",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Unlock the full power of Formbricks. Free for 30 days."
},
"general": {
"ai_data_analysis_enabled": "Data enrichment & analysis (AI)",
"ai_data_analysis_enabled_description": "AI to get more out of your data, setup dashboards, charts, reports and more. Touches your experience data.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Manage AI-powered features for this organization.",
"ai_settings_updated_successfully": "AI settings updated successfully",
"ai_smart_tools_enabled": "Smart functionality (AI)",
"ai_smart_tools_enabled_description": "AI to help you achieve more in less time. Never touches data collected with Formbricks. Only used to e.g. translate surveys to other languages.",
"bulk_invite_warning_description": "On the free plan, all organization members are always assigned the “Owner” role.",
"cannot_delete_only_organization": "This is your only organization, it cannot be deleted. Create a new organization first.",
"cannot_leave_only_organization": "You cannot leave this organization as it is your only organization. Create a new organization first.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Add a placeholder to show if there is no value to recall.",
"add_hidden_field_id": "Add hidden field ID",
"add_highlight_border": "Add highlight border",
"add_highlight_border_description": "Only applies to in-product surveys.",
"add_logic": "Add logic",
"add_none_of_the_above": "Add “None of the Above”",
"add_option": "Add option",
@@ -1430,7 +1632,6 @@
"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_upgrade_button_text": "Upgrade to enable follow-ups",
"form_styling": "Form styling",
"formbricks_sdk_is_not_connected": "Formbricks SDK is not connected",
"four_points": "4 points",
"heading": "Heading",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Response limits, redirections and more.",
"response_options": "Response Options",
"roundness": "Roundness",
"roundness_description": "Controls how rounded the card corners are.",
"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.",
"rows": "Rows",
"save_and_close": "Save & Close",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "This free & open-source survey has been closed",
"survey_display_settings": "Survey Display Settings",
"survey_placement": "Survey Placement",
"survey_styling": "Survey styling",
"survey_trigger": "Survey Trigger",
"switch_multi_language_on_to_get_started": "Switch multi-language on to get started 👉",
"target_block_not_found": "Target block not found",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Filtered responses (Excel)",
"generating_qr_code": "Generating QR code",
"impressions": "Impressions",
"impressions_identified_only": "Only showing impressions from identified contacts",
"impressions_tooltip": "Number of times the survey has been viewed.",
"in_app": {
"connection_description": "The survey will be shown to users of your website, that match the criteria listed below",
@@ -1989,6 +2192,7 @@
"last_quarter": "Last quarter",
"last_year": "Last year",
"limit": "Limit",
"no_identified_impressions": "No impressions from identified contacts",
"no_responses_found": "No responses found",
"other_values_found": "Other values found",
"overall": "Overall",
@@ -2144,7 +2348,7 @@
"advanced_styling_field_description_size": "Description Font Size",
"advanced_styling_field_description_size_description": "Scales the description text.",
"advanced_styling_field_description_weight": "Description Font Weight",
"advanced_styling_field_description_weight_description": "Makes description text lighter or bolder.",
"advanced_styling_field_description_weight_description": "Makes descr. text lighter or bolder.",
"advanced_styling_field_font_size": "Font Size",
"advanced_styling_field_font_weight": "Font Weight",
"advanced_styling_field_headline_color": "Headline Color",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Scales the headline text.",
"advanced_styling_field_headline_weight": "Headline Font Weight",
"advanced_styling_field_headline_weight_description": "Makes headline text lighter or bolder.",
"advanced_styling_field_height": "Height",
"advanced_styling_field_height": "Minimum Height",
"advanced_styling_field_indicator_bg": "Indicator Background",
"advanced_styling_field_indicator_bg_description": "Colors the filled portion of the bar.",
"advanced_styling_field_input_border_radius_description": "Rounds the input corners.",
"advanced_styling_field_input_font_size_description": "Scales the typed text in inputs.",
"advanced_styling_field_input_height_description": "Controls the input field height.",
"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_y_description": "Adds space on the top and bottom.",
"advanced_styling_field_input_placeholder_opacity_description": "Fades the placeholder hint text.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colors the typed text in inputs.",
"advanced_styling_field_option_bg": "Background",
"advanced_styling_field_option_bg_description": "Fills the option items.",
"advanced_styling_field_option_border": "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_font_size_description": "Scales the option label text.",
"advanced_styling_field_option_label": "Label Color",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Show “Powered by Formbricks” Signature",
"styling_updated_successfully": "Styling updated successfully",
"suggest_colors": "Suggest colors",
"suggested_colors_applied_please_save": "Suggested colors generated successfully. Press \"Save\" to persist the changes.",
"theme": "Theme",
"theme_settings_description": "Create a style theme for all surveys. You can enable custom styling for each survey."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "No, thank you!",
"preview_survey_question_2_headline": "Want to stay in the loop?",
"preview_survey_question_2_subheader": "This is an example description.",
"preview_survey_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!",
"prioritize_features_description": "Identify features your users need most and least.",
"prioritize_features_name": "Prioritize Features",

View File

@@ -133,6 +133,7 @@
"allow": "Permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir a los usuarios salir haciendo clic fuera de la encuesta",
"an_unknown_error_occurred_while_deleting_table_items": "Se ha producido un error desconocido al eliminar {type}s",
"analysis": "Análisis",
"and": "Y",
"and_response_limit_of": "y límite de respuesta de",
"anonymous": "Anónimo",
@@ -149,6 +150,8 @@
"bottom_right": "Inferior derecha",
"cancel": "Cancelar",
"centered_modal": "Modal centrado",
"chart": "Gráfico",
"charts": "Gráficos",
"choices": "Opciones",
"choose_environment": "Elegir entorno",
"choose_organization": "Elegir organización",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}",
"count_contacts": "{value, plural, one {{value} contacto} other {{value} contactos}}",
"count_responses": "{value, plural, one {{value} respuesta} other {{value} respuestas}}",
"create": "Crear",
"create_new_organization": "Crear organización nueva",
"create_segment": "Crear segmento",
"create_survey": "Crear encuesta",
@@ -187,6 +191,8 @@
"created_by": "Creado por",
"customer_success": "Éxito del cliente",
"dark_overlay": "Superposición oscura",
"dashboard": "Panel de control",
"dashboards": "Paneles",
"date": "Fecha",
"days": "días",
"default": "Predeterminado",
@@ -218,13 +224,16 @@
"error": "Error",
"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_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_title": "Límite de frecuencia excedido",
"expand_rows": "Expandir filas",
"failed_to_copy_to_clipboard": "Error al copiar al portapapeles",
"failed_to_load_organizations": "Error al cargar organizaciones",
"failed_to_load_workspaces": "Error al cargar los proyectos",
"filter": "Filtro",
"finish": "Finalizar",
"first_name": "Nombre",
"follow_these": "Sigue estos",
"formbricks_version": "Versión de Formbricks",
"full_name": "Nombre completo",
@@ -236,7 +245,9 @@
"hidden": "Oculto",
"hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos",
"hide": "Ocultar",
"hide_column": "Ocultar columna",
"id": "ID",
"image": "Imagen",
"images": "Imágenes",
"import": "Importar",
@@ -254,6 +265,7 @@
"key": "Clave",
"label": "Etiqueta",
"language": "Idioma",
"last_name": "Apellido",
"learn_more": "Saber más",
"license_expired": "License Expired",
"light_overlay": "Superposición clara",
@@ -280,6 +292,7 @@
"move_down": "Mover hacia abajo",
"move_up": "Mover hacia arriba",
"multiple_languages": "Múltiples idiomas",
"my_product": "mi producto",
"name": "Nombre",
"new": "Nuevo",
"new_version_available": "Formbricks {version} está aquí. ¡Actualiza ahora!",
@@ -303,6 +316,7 @@
"on": "Activado",
"only_one_file_allowed": "Solo se permite un archivo",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Solo los propietarios y gestores pueden realizar esta acción.",
"open_options": "Abrir opciones",
"option_id": "ID de opción",
"option_ids": "IDs de opciones",
"optional": "Opcional",
@@ -428,6 +442,7 @@
"top_right": "Superior derecha",
"try_again": "Intentar de nuevo",
"type": "Tipo",
"unknown_survey": "Encuesta desconocida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloquea más proyectos con un plan superior.",
"update": "Actualizar",
"updated": "Actualizado",
@@ -444,6 +459,7 @@
"variables": "Variables",
"verified_email": "Correo electrónico verificado",
"video": "Vídeo",
"view": "Ver",
"warning": "Advertencia",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "No pudimos verificar tu licencia porque el servidor de licencias no está accesible.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Tu encuesta se mostraría en esta URL.",
"your_survey_would_not_be_shown": "Tu encuesta no se mostraría."
},
"analysis": {
"charts": {
"OR": "O",
"add_chart_to_dashboard": "Añadir gráfico al panel de control",
"add_chart_to_dashboard_description": "Selecciona un panel de control para añadir este gráfico. El gráfico se guardará automáticamente.",
"add_filter": "Añadir filtro",
"add_to_dashboard": "Añadir al panel de control",
"advanced_chart_builder_config_prompt": "Configura tu gráfico y haz clic en \"Ejecutar consulta\" para previsualizar",
"ai_query_placeholder": "p. ej. ¿Cuántos usuarios se registraron la semana pasada?",
"ai_query_section_description": "Describe lo que quieres ver y deja que la IA construya el gráfico.",
"ai_query_section_title": "Pregunta a tus datos",
"apply_changes": "Aplicar cambios",
"chart": "Gráfico",
"chart_added_to_dashboard": "¡Gráfico añadido al panel de control!",
"chart_builder_choose_chart_type": "Elige el tipo de gráfico",
"chart_data": "Datos del gráfico",
"chart_data_tab": "Datos",
"chart_deleted_successfully": "Gráfico eliminado correctamente",
"chart_deletion_error": "Error al eliminar el gráfico",
"chart_duplicated_successfully": "Gráfico duplicado correctamente",
"chart_duplication_error": "Error al duplicar el gráfico",
"chart_name": "Nombre del gráfico",
"chart_name_placeholder": "Nombre del gráfico",
"chart_preview": "Vista previa del gráfico",
"chart_render_error": "Algo salió mal al renderizar este gráfico.",
"chart_saved_successfully": "¡Gráfico guardado correctamente!",
"chart_type_area": "Gráfico de área",
"chart_type_bar": "Gráfico de barras",
"chart_type_big_number": "Número grande",
"chart_type_line": "Gráfico de líneas",
"chart_type_not_supported": "El tipo de gráfico \"{{chartType}}\" aún no está soportado",
"chart_type_pie": "Gráfico circular",
"chart_updated_successfully": "¡Gráfico actualizado correctamente!",
"configure_description": "Modifica el tipo de gráfico y otros ajustes para esta visualización.",
"configure_title": "Configurar gráfico",
"configure_type_label": "Tipo de gráfico",
"contains": "contiene",
"create_chart": "Crear gráfico",
"create_chart_description": "Usa IA para generar un gráfico o créalo manualmente.",
"custom_range": "Rango personalizado",
"dashboard": "Panel de control",
"dashboard_select_placeholder": "Selecciona un panel de control",
"data_label": "Datos",
"date_preset_last_30_days": "Últimos 30 días",
"date_preset_last_7_days": "Últimos 7 días",
"date_preset_last_month": "Último mes",
"date_preset_this_month": "Este mes",
"date_preset_this_quarter": "Este trimestre",
"date_preset_this_year": "Este año",
"date_preset_today": "Hoy",
"date_preset_yesterday": "Ayer",
"date_range": "Rango de fechas",
"delete_chart_confirmation": "¿Estás seguro de que quieres eliminar este gráfico?",
"dimensions": "Dimensiones",
"dimensions_toggle_description": "Agrupa datos por categorías. El orden importa para gráficos multidimensionales.",
"edit_chart_description": "Visualiza y edita la configuración de tu gráfico.",
"edit_chart_title": "Editar gráfico",
"enable_time_dimension": "Activar dimensión temporal",
"end_date": "Fecha de finalización",
"enter_a_name_for_your_chart": "Introduce un nombre para tu gráfico para guardarlo.",
"enter_value": "Introduce un valor",
"equals": "es igual a",
"failed_to_add_chart_to_dashboard": "Error al añadir el gráfico al panel de control",
"failed_to_execute_query": "Error al ejecutar la consulta",
"failed_to_load_chart": "Error al cargar el gráfico",
"failed_to_load_chart_data": "Error al cargar los datos del gráfico",
"failed_to_save_chart": "Error al guardar el gráfico",
"field": "Campo",
"field_label_average_score": "Puntuación media",
"field_label_collected_at": "Recopilado el",
"field_label_count": "Recuento",
"field_label_detractor_count": "Recuento de detractores",
"field_label_emotion": "Emoción",
"field_label_field_type": "Tipo de campo",
"field_label_nps_score": "Puntuación NPS",
"field_label_nps_value": "Valor NPS",
"field_label_passive_count": "Recuento de pasivos",
"field_label_promoter_count": "Recuento de promotores",
"field_label_response_id": "ID de respuesta",
"field_label_sentiment": "Sentimiento",
"field_label_source_name": "Nombre de origen",
"field_label_source_type": "Tipo de origen",
"field_label_topic": "Tema",
"field_label_user_identifier": "Identificador de usuario",
"filters": "Filtros",
"filters_toggle_description": "Incluye solo los datos que cumplan las siguientes condiciones.",
"generate_chart": "Generar gráfico",
"granularity": "Granularidad",
"granularity_day": "Día",
"granularity_hour": "Hora",
"granularity_month": "Mes",
"granularity_quarter": "Trimestre",
"granularity_week": "Semana",
"granularity_year": "Año",
"greater_than": "mayor que",
"greater_than_or_equal": "mayor o igual que",
"group_by": "Agrupar por",
"group_by_description": "Selecciona dimensiones para desglosar tus datos. El orden importa para gráficos multidimensionales.",
"guide_button": "Ver guía de campos",
"guide_chart_type": "Tipo de gráfico",
"guide_chart_type_desc": "Cómo se visualizan los datos: área, barras, líneas, circular o número grande. Elige según lo que quieras mostrar (tendencias, comparaciones, partes de un todo, etc.).",
"guide_dimensions": "Dimensiones (agrupar por)",
"guide_dimensions_desc": "Cómo divides o agrupas los datos. Cada dimensión se convierte en una categoría en el gráfico (p. ej., sentimiento, tipo de fuente, nombre de encuesta, canal, tema). El orden importa para gráficos multidimensionales.",
"guide_filters": "Filtros",
"guide_filters_desc": "Condiciones que limitan qué datos se incluyen. Cada filtro tiene un campo, un operador (igual, contiene, mayor que, etc.) y valores. Y = todos deben coincidir; O = cualquiera puede coincidir.",
"guide_measures": "Medidas (qué cuentas o agregas)",
"guide_measures_predefined": "Las medidas predefinidas son métricas preconstruidas de tus datos de feedback: recuento (respuestas totales), recuento de promotores/detractores/pasivos (segmentos NPS), puntuación NPS, puntuación promedio, tasa de finalización.",
"guide_quick_ref": "Referencia rápida",
"guide_term_dimension": "Campo categórico usado para agrupar o dividir datos",
"guide_term_filter": "Condición que limita qué filas se incluyen",
"guide_term_measure": "Valor numérico que agregas (contar, sumar, promediar, etc.)",
"guide_term_time": "Agrupación basada en tiempo con granularidad y rango de fechas",
"guide_time_dimension": "Dimensión temporal",
"guide_time_dimension_desc": "Agrupación basada en tiempo: elige un campo temporal (normalmente Recopilado en), granularidad (Hora, Día, Semana, Mes, etc.) y rango de fechas (predefinido o personalizado). Úsalo para tendencias a lo largo del tiempo.",
"guide_title": "Guía de campos del constructor de gráficos",
"is_not_set": "no está establecido",
"is_set": "está establecido",
"less_than": "menor que",
"less_than_or_equal": "menor o igual que",
"measures": "Medidas",
"no_charts_found": "No se encontraron gráficos.",
"no_dashboards_available": "No hay paneles de control disponibles",
"no_dashboards_create_first": "Crea primero un panel de control para añadirle gráficos.",
"no_data_available": "No hay datos disponibles",
"no_data_returned": "La consulta no ha devuelto datos",
"no_data_returned_for_chart": "No se han devuelto datos para el gráfico",
"no_grouping": "Ninguno (solo filtro)",
"no_valid_data_to_display": "No hay datos válidos para mostrar",
"not_contains": "no contiene",
"not_equals": "no es igual a",
"open_chart": "Abrir gráfico {{name}}",
"open_options": "Abrir opciones del gráfico",
"or_filter_logic": "O",
"original": "Original",
"please_enter_chart_name": "Introduce un nombre para el gráfico",
"please_select_at_least_one_measure": "Selecciona al menos una medida",
"please_select_dashboard": "Selecciona un panel de control",
"predefined_measures": "Medidas predefinidas",
"preset": "Preajuste",
"query_executed_successfully": "Consulta ejecutada correctamente",
"reset_to_ai_suggestion": "Restablecer a sugerencia de IA",
"save_chart": "Guardar gráfico",
"save_chart_dialog_title": "Guardar gráfico",
"select_dimensions": "Seleccionar dimensiones...",
"select_field": "Seleccionar campo",
"select_measures": "Seleccionar medidas...",
"select_preset": "Seleccionar preajuste",
"showing_first_n_of": "Mostrando las primeras {{n}} de {{count}} filas",
"start_date": "Fecha de inicio",
"time_dimension": "Dimensión temporal",
"time_dimension_toggle_description": "Añade agrupación basada en tiempo para tendencias a lo largo del tiempo."
},
"dashboards": {
"create_dashboard": "Crear panel de control",
"create_dashboard_description": "Introduce un nombre para tu panel de control nuevo.",
"create_failed": "Error al crear el panel de control",
"create_success": "Panel de control creado correctamente",
"dashboard_name": "Nombre del panel de control",
"dashboard_name_placeholder": "Mi panel de control",
"delete_confirmation": "¿Estás seguro de que quieres eliminar este panel de control? Esta acción no se puede deshacer.",
"delete_failed": "Error al eliminar el panel de control",
"delete_success": "Panel de control eliminado correctamente",
"description_optional": "Descripción (opcional)",
"description_placeholder": "Descripción del panel de control",
"duplicate_failed": "Error al duplicar el panel de control",
"duplicate_success": "Panel de control duplicado correctamente",
"no_dashboards_found": "No se han encontrado paneles de control.",
"please_enter_name": "Por favor, introduce un nombre para el panel de control"
}
},
"connect": {
"congrats": "¡Enhorabuena!",
"connection_successful_message": "¡Bien hecho! Estamos conectados.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Actualizar contactos",
"contacts_table_refresh_success": "Contactos actualizados correctamente",
"create_attribute": "Crear atributo",
"create_key": "Crear clave",
"create_new_attribute": "Crear atributo nuevo",
"create_new_attribute_description": "Crea un atributo nuevo para fines de segmentación.",
"custom_attributes": "Atributos personalizados",
@@ -656,6 +841,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_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.}}",
"displays": "Visualizaciones",
"edit_attribute": "Editar atributo",
"edit_attribute_description": "Actualiza la etiqueta y la descripción de este atributo.",
"edit_attribute_values": "Editar atributos",
@@ -667,6 +853,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_date_format": "Formato de fecha no válido. Por favor, usa una fecha válida.",
"invalid_number_format": "Formato de número no válido. Por favor, introduce un número válido.",
"no_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_surveys": "No hay encuestas publicadas",
"no_responses_found": "No se encontraron respuestas",
@@ -681,6 +868,8 @@
"select_a_survey": "Selecciona una encuesta",
"select_attribute": "Seleccionar atributo",
"select_attribute_key": "Seleccionar clave de atributo",
"survey_viewed": "Encuesta vista",
"survey_viewed_at": "Vista el",
"system_attributes": "Atributos del sistema",
"unlock_contacts_description": "Gestiona contactos y envía encuestas dirigidas",
"unlock_contacts_title": "Desbloquea contactos con un plan superior",
@@ -752,7 +941,12 @@
"link_google_sheet": "Vincular Google Sheet",
"link_new_sheet": "Vincular nueva hoja",
"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_hidden_fields": "Incluir campos ocultos",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimiento y análisis de datos (IA)",
"ai_data_analysis_enabled_description": "IA para sacar más partido a tus datos, configurar paneles, gráficos, informes y más. Accede a los datos de experiencia.",
"ai_enabled": "IA de Formbricks",
"ai_enabled_description": "Gestiona las funciones impulsadas por IA para esta organización.",
"ai_settings_updated_successfully": "Configuración de IA actualizada correctamente",
"ai_smart_tools_enabled": "Funcionalidad inteligente (IA)",
"ai_smart_tools_enabled_description": "IA para ayudarte a conseguir más en menos tiempo. Nunca accede a los datos recopilados con Formbricks. Solo se usa para, por ejemplo, traducir encuestas a otros idiomas.",
"bulk_invite_warning_description": "En el plan gratuito, a todos los miembros de la organización se les asigna siempre el rol de \"Propietario\".",
"cannot_delete_only_organization": "Esta es tu única organización, no se puede eliminar. Crea una nueva organización primero.",
"cannot_leave_only_organization": "No puedes abandonar esta organización ya que es tu única organización. Crea una nueva organización primero.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Añadir un marcador de posición para mostrar si no hay valor que recuperar.",
"add_hidden_field_id": "Añadir ID de campo oculto",
"add_highlight_border": "Añadir borde destacado",
"add_highlight_border_description": "Solo se aplica a encuestas dentro del producto.",
"add_logic": "Añadir lógica",
"add_none_of_the_above": "Añadir \"Ninguna de las anteriores\"",
"add_option": "Añadir opción",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "Seguimiento actualizado y se guardará cuando guardes la encuesta.",
"follow_ups_new": "Nuevo seguimiento",
"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",
"four_points": "4 puntos",
"heading": "Encabezado",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Límites de respuestas, redirecciones y más.",
"response_options": "Opciones de respuesta",
"roundness": "Redondez",
"roundness_description": "Controla qué tan redondeadas están las esquinas de la tarjeta.",
"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.",
"rows": "Filas",
"save_and_close": "Guardar y cerrar",
@@ -1649,6 +1850,7 @@
"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_placement": "Ubicación de la encuesta",
"survey_styling": "Estilo del formulario",
"survey_trigger": "Activador de la encuesta",
"switch_multi_language_on_to_get_started": "Activa el modo multiidioma para comenzar 👉",
"target_block_not_found": "Bloque objetivo no encontrado",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Respuestas filtradas (Excel)",
"generating_qr_code": "Generando código QR",
"impressions": "Impresiones",
"impressions_identified_only": "Solo se muestran impresiones de contactos identificados",
"impressions_tooltip": "Número de veces que se ha visto la encuesta.",
"in_app": {
"connection_description": "La encuesta se mostrará a los usuarios de tu sitio web que cumplan con los criterios enumerados a continuación",
@@ -1989,6 +2192,7 @@
"last_quarter": "Último trimestre",
"last_year": "Último año",
"limit": "Límite",
"no_identified_impressions": "No hay impresiones de contactos identificados",
"no_responses_found": "No se han encontrado respuestas",
"other_values_found": "Otros valores encontrados",
"overall": "General",
@@ -2144,7 +2348,7 @@
"advanced_styling_field_description_size": "Tamaño de fuente de la descripción",
"advanced_styling_field_description_size_description": "Escala el texto de la descripción.",
"advanced_styling_field_description_weight": "Grosor de fuente de la descripción",
"advanced_styling_field_description_weight_description": "Hace el texto de la descripción más ligero o más grueso.",
"advanced_styling_field_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_weight": "Grosor de fuente",
"advanced_styling_field_headline_color": "Color del titular",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Escala el texto del titular.",
"advanced_styling_field_headline_weight": "Grosor de fuente del titular",
"advanced_styling_field_headline_weight_description": "Hace el texto del titular más ligero o más grueso.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_height": "Altura mínima",
"advanced_styling_field_indicator_bg": "Fondo del indicador",
"advanced_styling_field_indicator_bg_description": "Colorea la porción rellena de la barra.",
"advanced_styling_field_input_border_radius_description": "Redondea las esquinas del campo.",
"advanced_styling_field_input_font_size_description": "Escala el texto escrito en los campos.",
"advanced_styling_field_input_height_description": "Controla la altura 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_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.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colorea el texto escrito en los campos de entrada.",
"advanced_styling_field_option_bg": "Fondo",
"advanced_styling_field_option_bg_description": "Rellena los elementos de opción.",
"advanced_styling_field_option_border": "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_font_size_description": "Escala el texto de la etiqueta de opción.",
"advanced_styling_field_option_label": "Color de la etiqueta",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Mostrar firma 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo actualizado correctamente",
"suggest_colors": "Sugerir colores",
"suggested_colors_applied_please_save": "Colores sugeridos generados correctamente. Pulsa \"Guardar\" para conservar los cambios.",
"theme": "Tema",
"theme_settings_description": "Crea un tema de estilo para todas las encuestas. Puedes activar el estilo personalizado para cada encuesta."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "¡No, gracias!",
"preview_survey_question_2_headline": "¿Quieres estar al tanto?",
"preview_survey_question_2_subheader": "Esta es una descripción de ejemplo.",
"preview_survey_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!",
"prioritize_features_description": "Identifica las funciones que tus usuarios necesitan más y menos.",
"prioritize_features_name": "Priorizar funciones",

View File

@@ -133,6 +133,7 @@
"allow": "Autoriser",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permettre aux utilisateurs de quitter en cliquant hors de l'enquête",
"an_unknown_error_occurred_while_deleting_table_items": "Une erreur inconnue est survenue lors de la suppression des {type}s",
"analysis": "Analyse",
"and": "Et",
"and_response_limit_of": "et limite de réponse de",
"anonymous": "Anonyme",
@@ -149,6 +150,8 @@
"bottom_right": "En bas à droite",
"cancel": "Annuler",
"centered_modal": "Au centre",
"chart": "Graphique",
"charts": "Graphiques",
"choices": "Choix",
"choose_environment": "Choisir l'environnement",
"choose_organization": "Choisir l'organisation",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} attribut} other {{value} attributs}}",
"count_contacts": "{value, plural, one {# contact} other {# contacts} }",
"count_responses": "{value, plural, other {# réponses}}",
"create": "Créer",
"create_new_organization": "Créer une nouvelle organisation",
"create_segment": "Créer un segment",
"create_survey": "Créer un sondage",
@@ -187,6 +191,8 @@
"created_by": "Créé par",
"customer_success": "Succès Client",
"dark_overlay": "Foncée",
"dashboard": "Tableau de bord",
"dashboards": "Tableaux de bord",
"date": "Date",
"days": "jours",
"default": "Par défaut",
@@ -218,13 +224,16 @@
"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_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_title": "Limite de Taux Dépassée",
"expand_rows": "Développer les lignes",
"failed_to_copy_to_clipboard": "Échec de la copie dans le presse-papiers",
"failed_to_load_organizations": "Échec du chargement des organisations",
"failed_to_load_workspaces": "Échec du chargement des projets",
"filter": "Filtre",
"finish": "Terminer",
"first_name": "Prénom",
"follow_these": "Suivez ceci",
"formbricks_version": "Version de Formbricks",
"full_name": "Nom complet",
@@ -236,7 +245,9 @@
"hidden": "Caché",
"hidden_field": "Champ caché",
"hidden_fields": "Champs cachés",
"hide": "Masquer",
"hide_column": "Cacher la colonne",
"id": "ID",
"image": "Image",
"images": "Images",
"import": "Importer",
@@ -254,6 +265,7 @@
"key": "Clé",
"label": "Étiquette",
"language": "Langue",
"last_name": "Nom de famille",
"learn_more": "En savoir plus",
"license_expired": "License Expired",
"light_overlay": "Claire",
@@ -280,6 +292,7 @@
"move_down": "Déplacer vers le bas",
"move_up": "Déplacer vers le haut",
"multiple_languages": "Plusieurs langues",
"my_product": "mon produit",
"name": "Nom",
"new": "Nouveau",
"new_version_available": "Formbricks {version} est là. Mettez à jour maintenant !",
@@ -303,6 +316,7 @@
"on": "Sur",
"only_one_file_allowed": "Un seul fichier est autorisé",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Seules les propriétaires, les gestionnaires et les membres ayant accès à la gestion peuvent effectuer cette action.",
"open_options": "Ouvrir les options",
"option_id": "Identifiant de l'option",
"option_ids": "Identifiants des options",
"optional": "Facultatif",
@@ -428,6 +442,7 @@
"top_right": "En haut à droite",
"try_again": "Réessayer",
"type": "Type",
"unknown_survey": "Enquête inconnue",
"unlock_more_workspaces_with_a_higher_plan": "Débloquez plus de projets avec un forfait supérieur.",
"update": "Mise à jour",
"updated": "Mise à jour",
@@ -444,6 +459,7 @@
"variables": "Variables",
"verified_email": "Email vérifié",
"video": "Vidéo",
"view": "Afficher",
"warning": "Avertissement",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Nous n'avons pas pu vérifier votre licence car le serveur de licence est inaccessible.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Votre enquête serait affichée sur cette URL.",
"your_survey_would_not_be_shown": "Votre enquête ne serait pas affichée."
},
"analysis": {
"charts": {
"OR": "OU",
"add_chart_to_dashboard": "Ajouter le graphique au tableau de bord",
"add_chart_to_dashboard_description": "Sélectionnez un tableau de bord pour y ajouter ce graphique. Le graphique sera enregistré automatiquement.",
"add_filter": "Ajouter un filtre",
"add_to_dashboard": "Ajouter au tableau de bord",
"advanced_chart_builder_config_prompt": "Configurez votre graphique et cliquez sur «Exécuter la requête» pour prévisualiser",
"ai_query_placeholder": "ex. Combien d'utilisateurs se sont inscrits la semaine dernière?",
"ai_query_section_description": "Décrivez ce que vous souhaitez voir et laissez l'IA créer le graphique.",
"ai_query_section_title": "Interrogez vos données",
"apply_changes": "Appliquer les modifications",
"chart": "Graphique",
"chart_added_to_dashboard": "Graphique ajouté au tableau de bord!",
"chart_builder_choose_chart_type": "Choisir le type de graphique",
"chart_data": "Données du graphique",
"chart_data_tab": "Données",
"chart_deleted_successfully": "Graphique supprimé avec succès",
"chart_deletion_error": "Échec de la suppression du graphique",
"chart_duplicated_successfully": "Graphique dupliqué avec succès",
"chart_duplication_error": "Échec de la duplication du graphique",
"chart_name": "Nom du graphique",
"chart_name_placeholder": "Nom du graphique",
"chart_preview": "Aperçu du graphique",
"chart_render_error": "Une erreur s'est produite lors du rendu de ce graphique.",
"chart_saved_successfully": "Graphique enregistré avec succès!",
"chart_type_area": "Graphique en aires",
"chart_type_bar": "Graphique à barres",
"chart_type_big_number": "Grand nombre",
"chart_type_line": "Graphique linéaire",
"chart_type_not_supported": "Le type de graphique « {{chartType}} » n'est pas encore pris en charge",
"chart_type_pie": "Graphique circulaire",
"chart_updated_successfully": "Graphique mis à jour avec succès!",
"configure_description": "Modifiez le type de graphique et d'autres paramètres pour cette visualisation.",
"configure_title": "Configurer le graphique",
"configure_type_label": "Type de graphique",
"contains": "contient",
"create_chart": "Créer un graphique",
"create_chart_description": "Utilisez l'IA pour générer un graphique ou créez-en un manuellement.",
"custom_range": "Plage personnalisée",
"dashboard": "Tableau de bord",
"dashboard_select_placeholder": "Sélectionnez un tableau de bord",
"data_label": "Données",
"date_preset_last_30_days": "30 derniers jours",
"date_preset_last_7_days": "7 derniers jours",
"date_preset_last_month": "Le mois dernier",
"date_preset_this_month": "Ce mois-ci",
"date_preset_this_quarter": "Ce trimestre",
"date_preset_this_year": "Cette année",
"date_preset_today": "Aujourd'hui",
"date_preset_yesterday": "Hier",
"date_range": "Plage de dates",
"delete_chart_confirmation": "Êtes-vous sûr de vouloir supprimer ce graphique?",
"dimensions": "Dimensions",
"dimensions_toggle_description": "Regroupez les données par catégories. L'ordre est important pour les graphiques multidimensionnels.",
"edit_chart_description": "Consultez et modifiez la configuration de votre graphique.",
"edit_chart_title": "Modifier le graphique",
"enable_time_dimension": "Activer la dimension temporelle",
"end_date": "Date de fin",
"enter_a_name_for_your_chart": "Saisissez un nom pour votre graphique afin de l'enregistrer.",
"enter_value": "Saisissez une valeur",
"equals": "égal",
"failed_to_add_chart_to_dashboard": "Échec de l'ajout du graphique au tableau de bord",
"failed_to_execute_query": "Échec de l'exécution de la requête",
"failed_to_load_chart": "Échec du chargement du graphique",
"failed_to_load_chart_data": "Échec du chargement des données du graphique",
"failed_to_save_chart": "Échec de l'enregistrement du graphique",
"field": "Champ",
"field_label_average_score": "Score moyen",
"field_label_collected_at": "Collecté le",
"field_label_count": "Nombre",
"field_label_detractor_count": "Nombre de détracteurs",
"field_label_emotion": "Émotion",
"field_label_field_type": "Type de champ",
"field_label_nps_score": "Score NPS",
"field_label_nps_value": "Valeur NPS",
"field_label_passive_count": "Nombre de passifs",
"field_label_promoter_count": "Nombre de promoteurs",
"field_label_response_id": "ID de réponse",
"field_label_sentiment": "Sentiment",
"field_label_source_name": "Nom de la source",
"field_label_source_type": "Type de source",
"field_label_topic": "Sujet",
"field_label_user_identifier": "Identifiant utilisateur",
"filters": "Filtres",
"filters_toggle_description": "Inclure uniquement les données qui répondent aux conditions suivantes.",
"generate_chart": "Générer le graphique",
"granularity": "Granularité",
"granularity_day": "Jour",
"granularity_hour": "Heure",
"granularity_month": "Mois",
"granularity_quarter": "Trimestre",
"granularity_week": "Semaine",
"granularity_year": "Année",
"greater_than": "supérieur à",
"greater_than_or_equal": "supérieur ou égal à",
"group_by": "Regrouper par",
"group_by_description": "Sélectionnez les dimensions pour décomposer vos données. L'ordre est important pour les graphiques multidimensionnels.",
"guide_button": "Voir le guide des champs",
"guide_chart_type": "Type de graphique",
"guide_chart_type_desc": "Comment les données sont visualisées: aire, barres, lignes, secteurs ou grand nombre. Choisissez en fonction de ce que vous souhaitez montrer (tendances, comparaisons, parties d'un tout, etc.).",
"guide_dimensions": "Dimensions (regrouper par)",
"guide_dimensions_desc": "Comment vous divisez ou regroupez les données. Chaque dimension devient une catégorie sur le graphique (par ex. sentiment, type de source, nom du sondage, canal, sujet). L'ordre est important pour les graphiques multidimensionnels.",
"guide_filters": "Filtres",
"guide_filters_desc": "Conditions qui limitent les données incluses. Chaque filtre a un champ, un opérateur (égal à, contient, supérieur à, etc.) et des valeurs. Et = tous doivent correspondre; Ou = n'importe lequel peut correspondre.",
"guide_measures": "Mesures (ce que vous comptez ou agrégez)",
"guide_measures_predefined": "Les mesures prédéfinies sont des métriques préconstruites à partir de vos données de feedback: count (total des réponses), promoter/detractor/passive count (segments NPS), NPS score, average score, completion rate.",
"guide_quick_ref": "Référence rapide",
"guide_term_dimension": "Champ catégoriel utilisé pour regrouper ou diviser les données",
"guide_term_filter": "Condition qui limite les lignes incluses",
"guide_term_measure": "Valeur numérique que vous agrégez (count, sum, avg, etc.)",
"guide_term_time": "Regroupement temporel avec granularité et plage de dates",
"guide_time_dimension": "Dimension temporelle",
"guide_time_dimension_desc": "Regroupement temporel: choisissez un champ temporel (généralement Collecté le), une granularité (Heure, Jour, Semaine, Mois, etc.) et une plage de dates (prédéfinie ou personnalisée). Utilisez pour les tendances dans le temps.",
"guide_title": "Guide des champs du créateur de graphiques",
"is_not_set": "n'est pas défini",
"is_set": "est défini",
"less_than": "inférieur à",
"less_than_or_equal": "inférieur ou égal à",
"measures": "Mesures",
"no_charts_found": "Aucun graphique trouvé.",
"no_dashboards_available": "Aucun tableau de bord disponible",
"no_dashboards_create_first": "Créez d'abord un tableau de bord pour y ajouter des graphiques.",
"no_data_available": "Aucune donnée disponible",
"no_data_returned": "Aucune donnée retournée par la requête",
"no_data_returned_for_chart": "Aucune donnée retournée pour le graphique",
"no_grouping": "Aucun (filtre uniquement)",
"no_valid_data_to_display": "Aucune donnée valide à afficher",
"not_contains": "ne contient pas",
"not_equals": "n'est pas égal à",
"open_chart": "Ouvrir le graphique {{name}}",
"open_options": "Ouvrir les options du graphique",
"or_filter_logic": "OU",
"original": "Original",
"please_enter_chart_name": "Veuillez saisir un nom de graphique",
"please_select_at_least_one_measure": "Veuillez sélectionner au moins une mesure",
"please_select_dashboard": "Veuillez sélectionner un tableau de bord",
"predefined_measures": "Mesures prédéfinies",
"preset": "Préréglage",
"query_executed_successfully": "Requête exécutée avec succès",
"reset_to_ai_suggestion": "Réinitialiser à la suggestion IA",
"save_chart": "Enregistrer le graphique",
"save_chart_dialog_title": "Enregistrer le graphique",
"select_dimensions": "Sélectionner les dimensions...",
"select_field": "Sélectionner un champ",
"select_measures": "Sélectionner les mesures...",
"select_preset": "Sélectionner un préréglage",
"showing_first_n_of": "Affichage des {{n}} premières lignes sur {{count}}",
"start_date": "Date de début",
"time_dimension": "Dimension temporelle",
"time_dimension_toggle_description": "Ajouter un regroupement temporel pour les tendances dans le temps."
},
"dashboards": {
"create_dashboard": "Créer un tableau de bord",
"create_dashboard_description": "Saisissez un nom pour votre nouveau tableau de bord.",
"create_failed": "Échec de la création du tableau de bord",
"create_success": "Tableau de bord créé avec succès!",
"dashboard_name": "Nom du tableau de bord",
"dashboard_name_placeholder": "Mon tableau de bord",
"delete_confirmation": "Êtes-vous sûr de vouloir supprimer ce tableau de bord? Cette action est irréversible.",
"delete_failed": "Échec de la suppression du tableau de bord",
"delete_success": "Tableau de bord supprimé avec succès",
"description_optional": "Description (facultatif)",
"description_placeholder": "Description du tableau de bord",
"duplicate_failed": "Échec de la duplication du tableau de bord",
"duplicate_success": "Tableau de bord dupliqué avec succès!",
"no_dashboards_found": "Aucun tableau de bord trouvé.",
"please_enter_name": "Veuillez saisir un nom de tableau de bord"
}
},
"connect": {
"congrats": "Félicitations !",
"connection_successful_message": "Bien joué ! Nous sommes connectés.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Actualiser les contacts",
"contacts_table_refresh_success": "Contacts rafraîchis avec succès",
"create_attribute": "Créer un attribut",
"create_key": "Créer une clé",
"create_new_attribute": "Créer un nouvel attribut",
"create_new_attribute_description": "Créez un nouvel attribut à des fins de segmentation.",
"custom_attributes": "Attributs personnalisés",
@@ -656,6 +841,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_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.}}",
"displays": "Affichages",
"edit_attribute": "Modifier l'attribut",
"edit_attribute_description": "Mettez à jour l'étiquette et la description de cet attribut.",
"edit_attribute_values": "Modifier les attributs",
@@ -667,6 +853,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_date_format": "Format de date invalide. Merci d'utiliser une date valide.",
"invalid_number_format": "Format de nombre invalide. Veuillez saisir un nombre valide.",
"no_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_surveys": "Aucune enquête publiée",
"no_responses_found": "Aucune réponse trouvée",
@@ -681,6 +868,8 @@
"select_a_survey": "Sélectionner une enquête",
"select_attribute": "Sélectionner un 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",
"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.",
@@ -752,7 +941,12 @@
"link_google_sheet": "Lien Google Sheet",
"link_new_sheet": "Lier une nouvelle feuille",
"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_hidden_fields": "Inclure les champs cachés",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours."
},
"general": {
"ai_data_analysis_enabled": "Enrichissement et analyse des données (IA)",
"ai_data_analysis_enabled_description": "L'IA pour tirer le meilleur parti de vos données, configurer des tableaux de bord, des graphiques, des rapports et plus encore. Accède à vos données d'expérience.",
"ai_enabled": "IA Formbricks",
"ai_enabled_description": "Gérer les fonctionnalités alimentées par l'IA pour cette organisation.",
"ai_settings_updated_successfully": "Paramètres IA mis à jour avec succès",
"ai_smart_tools_enabled": "Fonctionnalités intelligentes (IA)",
"ai_smart_tools_enabled_description": "L'IA pour vous aider à accomplir plus en moins de temps. N'accède jamais aux données collectées avec Formbricks. Utilisée uniquement pour, par exemple, traduire les sondages dans d'autres langues.",
"bulk_invite_warning_description": "Dans le plan gratuit, tous les membres de l'organisation se voient toujours attribuer le rôle \"Owner\".",
"cannot_delete_only_organization": "C'est votre seule organisation, elle ne peut pas être supprimée. Créez d'abord une nouvelle organisation.",
"cannot_leave_only_organization": "Vous ne pouvez pas quitter cette organisation car c'est votre seule organisation. Créez d'abord une nouvelle organisation.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Ajouter un espace réservé à afficher s'il n'y a pas de valeur à rappeler.",
"add_hidden_field_id": "Ajouter un champ caché ID",
"add_highlight_border": "Ajouter une bordure de surlignage",
"add_highlight_border_description": "S'applique uniquement aux sondages intégrés au produit.",
"add_logic": "Ajouter de la logique",
"add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"",
"add_option": "Ajouter une option",
@@ -1430,7 +1632,6 @@
"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_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é",
"four_points": "4 points",
"heading": "En-tête",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Limites de réponse, redirections et plus.",
"response_options": "Options de réponse",
"roundness": "Rondeur",
"roundness_description": "Contrôle l'arrondi des coins de la carte.",
"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.",
"rows": "Lignes",
"save_and_close": "Enregistrer et fermer",
@@ -1649,6 +1850,7 @@
"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_placement": "Placement de l'enquête",
"survey_styling": "Style de formulaire",
"survey_trigger": "Déclencheur d'enquête",
"switch_multi_language_on_to_get_started": "Activez le mode multilingue pour commencer 👉",
"target_block_not_found": "Bloc cible non trouvé",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Réponses filtrées (Excel)",
"generating_qr_code": "Génération du code QR",
"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.",
"in_app": {
"connection_description": "Le sondage sera affiché aux utilisateurs de votre site web, qui correspondent aux critères listés ci-dessous",
@@ -1989,6 +2192,7 @@
"last_quarter": "dernier trimestre",
"last_year": "l'année dernière",
"limit": "Limite",
"no_identified_impressions": "Aucune impression des contacts identifiés",
"no_responses_found": "Aucune réponse trouvée",
"other_values_found": "D'autres valeurs trouvées",
"overall": "Globalement",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Ajuste la taille du texte du titre.",
"advanced_styling_field_headline_weight": "Graisse de police du titre",
"advanced_styling_field_headline_weight_description": "Rend le texte du titre plus léger ou plus gras.",
"advanced_styling_field_height": "Hauteur",
"advanced_styling_field_height": "Hauteur minimale",
"advanced_styling_field_indicator_bg": "Arrière-plan de l'indicateur",
"advanced_styling_field_indicator_bg_description": "Colore la partie remplie de la barre.",
"advanced_styling_field_input_border_radius_description": "Arrondit les coins du champ de saisie.",
"advanced_styling_field_input_font_size_description": "Ajuste la taille du texte saisi dans les champs.",
"advanced_styling_field_input_height_description": "Contrôle la hauteur 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_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.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colore le texte saisi dans les champs.",
"advanced_styling_field_option_bg": "Arrière-plan",
"advanced_styling_field_option_bg_description": "Remplit les éléments d'option.",
"advanced_styling_field_option_border": "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_font_size_description": "Ajuste la taille du texte des libellés d'option.",
"advanced_styling_field_option_label": "Couleur de l'étiquette",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Afficher la signature «Propulsé par Formbricks»",
"styling_updated_successfully": "Style mis à jour avec succès",
"suggest_colors": "Suggérer des couleurs",
"suggested_colors_applied_please_save": "Couleurs suggérées générées avec succès. Appuyez sur «Enregistrer» pour conserver les modifications.",
"theme": "Thème",
"theme_settings_description": "Créez un thème de style pour toutes les enquêtes. Vous pouvez activer un style personnalisé pour chaque enquête."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Non, merci !",
"preview_survey_question_2_headline": "Souhaitez-vous être informé ?",
"preview_survey_question_2_subheader": "Ceci est un exemple de description.",
"preview_survey_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 !",
"prioritize_features_description": "Identifiez les fonctionnalités dont vos utilisateurs ont le plus et le moins besoin.",
"prioritize_features_name": "Prioriser les fonctionnalités",

View File

@@ -133,6 +133,7 @@
"allow": "Engedélyezés",
"allow_users_to_exit_by_clicking_outside_the_survey": "Lehetővé tétel a felhasználók számára, hogy a kérdőíven kívülre kattintva kilépjenek",
"an_unknown_error_occurred_while_deleting_table_items": "{type} típusok törlésekor ismeretlen hiba történt",
"analysis": "Elemzés",
"and": "És",
"and_response_limit_of": "és kérdéskorlátja ennek:",
"anonymous": "Névtelen",
@@ -149,6 +150,8 @@
"bottom_right": "Jobbra lent",
"cancel": "Mégse",
"centered_modal": "Középre helyezett kizárólagos",
"chart": "Diagram",
"charts": "Diagramok",
"choices": "Választási lehetőségek",
"choose_environment": "Környezet kiválasztása",
"choose_organization": "Szervezet kiválasztása",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} attribútum} other {{value} attribútum}}",
"count_contacts": "{value, plural, one {{value} partner} other {{value} partner}}",
"count_responses": "{value, plural, one {{value} válasz} other {{value} válasz}}",
"create": "Létrehozás",
"create_new_organization": "Új szervezet létrehozása",
"create_segment": "Szakasz létrehozása",
"create_survey": "Kérdőív létrehozása",
@@ -187,6 +191,8 @@
"created_by": "Létrehozta",
"customer_success": "Ügyfélsiker",
"dark_overlay": "Sötét rávetítés",
"dashboard": "Vezérlőpult",
"dashboards": "Irányítópultok",
"date": "Dátum",
"days": "napok",
"default": "Alapértelmezett",
@@ -218,13 +224,16 @@
"error": "Hiba",
"error_component_description": "Ez az erőforrás nem létezik, vagy nem rendelkezik a hozzáféréshez szükséges jogosultságokkal.",
"error_component_title": "Hiba az erőforrások betöltésekor",
"error_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_title": "A sebességkorlát elérve",
"expand_rows": "Sorok kinyitása",
"failed_to_copy_to_clipboard": "Nem sikerült másolni a vágólapra",
"failed_to_load_organizations": "Nem sikerült betölteni a szervezeteket",
"failed_to_load_workspaces": "Nem sikerült a munkaterületek betöltése",
"filter": "Szűrő",
"finish": "Befejezés",
"first_name": "Keresztnév",
"follow_these": "Ezek követése",
"formbricks_version": "Formbricks verziója",
"full_name": "Teljes név",
@@ -236,7 +245,9 @@
"hidden": "Rejtett",
"hidden_field": "Rejtett mező",
"hidden_fields": "Rejtett mezők",
"hide": "Elrejtés",
"hide_column": "Oszlop elrejtése",
"id": "ID",
"image": "Kép",
"images": "Képek",
"import": "Importálás",
@@ -254,6 +265,7 @@
"key": "Kulcs",
"label": "Címke",
"language": "Nyelv",
"last_name": "Vezetéknév",
"learn_more": "Tudjon meg többet",
"license_expired": "A licenc lejárt",
"light_overlay": "Világos rávetítés",
@@ -280,6 +292,7 @@
"move_down": "Mozgatás le",
"move_up": "Mozgatás fel",
"multiple_languages": "Több nyelv",
"my_product": "saját termék",
"name": "Név",
"new": "Új",
"new_version_available": "A Formbricks {version} megérkezett. Frissítsen most!",
@@ -303,6 +316,7 @@
"on": "Be",
"only_one_file_allowed": "Csak egy fájl engedélyezett",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Csak tulajdonosok és kezelők hajthatják végre ezt a műveletet.",
"open_options": "Beállítások megnyitása",
"option_id": "Választásazonosító",
"option_ids": "Választásazonosítók",
"optional": "Elhagyható",
@@ -428,6 +442,7 @@
"top_right": "Jobbra fent",
"try_again": "Próbálja újra",
"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.",
"update": "Frissítés",
"updated": "Frissítve",
@@ -444,6 +459,7 @@
"variables": "Változók",
"verified_email": "Ellenőrzött e-mail-cím",
"video": "Videó",
"view": "Megtekintés",
"warning": "Figyelmeztetés",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Nem tudtuk ellenőrizni a licencét, mert a licenckiszolgáló nem érhető el.",
"webhook": "Webhorog",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "A kérdőív ezen az URL-en jelenne meg.",
"your_survey_would_not_be_shown": "A kérdőív nem jelenne meg."
},
"analysis": {
"charts": {
"OR": "VAGY",
"add_chart_to_dashboard": "Diagram hozzáadása a vezérlőpulthoz",
"add_chart_to_dashboard_description": "Válassz egy vezérlőpultot, amelyhez hozzáadod ezt a diagramot. A diagram automatikusan mentésre kerül.",
"add_filter": "Szűrő hozzáadása",
"add_to_dashboard": "Hozzáadás a vezérlőpulthoz",
"advanced_chart_builder_config_prompt": "Állítsd be a diagramot, és kattints a \"Lekérdezés futtatása\" gombra az előnézethez",
"ai_query_placeholder": "pl. Hány felhasználó regisztrált a múlt héten?",
"ai_query_section_description": "Írd le, mit szeretnél látni, és hagyd, hogy az AI elkészítse a diagramot.",
"ai_query_section_title": "Kérdezd meg az adataidat",
"apply_changes": "Módosítások alkalmazása",
"chart": "Diagram",
"chart_added_to_dashboard": "A diagram hozzáadva a vezérlőpulthoz!",
"chart_builder_choose_chart_type": "Válassz diagramtípust",
"chart_data": "Diagram adatai",
"chart_data_tab": "Adatok",
"chart_deleted_successfully": "A diagram sikeresen törölve",
"chart_deletion_error": "A diagram törlése sikertelen",
"chart_duplicated_successfully": "A diagram sikeresen duplikálva",
"chart_duplication_error": "A diagram duplikálása sikertelen",
"chart_name": "Diagram neve",
"chart_name_placeholder": "Diagram neve",
"chart_preview": "Diagram előnézete",
"chart_render_error": "Hiba történt a diagram megjelenítése során.",
"chart_saved_successfully": "A diagram sikeresen mentve!",
"chart_type_area": "Területdiagram",
"chart_type_bar": "Oszlopdiagram",
"chart_type_big_number": "Nagy szám",
"chart_type_line": "Vonaldiagram",
"chart_type_not_supported": "A(z) \"{{chartType}}\" diagramtípus még nem támogatott",
"chart_type_pie": "Kördiagram",
"chart_updated_successfully": "A diagram sikeresen frissítve!",
"configure_description": "Módosítsd a diagram típusát és egyéb beállításait ehhez a vizualizációhoz.",
"configure_title": "Diagram konfigurálása",
"configure_type_label": "Diagram típusa",
"contains": "tartalmazza",
"create_chart": "Diagram létrehozása",
"create_chart_description": "Használj mesterséges intelligenciát diagram generálásához, vagy készíts egyet manuálisan.",
"custom_range": "Egyéni tartomány",
"dashboard": "Vezérlőpult",
"dashboard_select_placeholder": "Válassz egy vezérlőpultot",
"data_label": "Adat",
"date_preset_last_30_days": "Elmúlt 30 nap",
"date_preset_last_7_days": "Elmúlt 7 nap",
"date_preset_last_month": "Elmúlt hónap",
"date_preset_this_month": "Ez a hónap",
"date_preset_this_quarter": "Ez a negyedév",
"date_preset_this_year": "Ez az év",
"date_preset_today": "Ma",
"date_preset_yesterday": "Tegnap",
"date_range": "Dátumtartomány",
"delete_chart_confirmation": "Biztosan törölni szeretnéd ezt a diagramot?",
"dimensions": "Dimenziók",
"dimensions_toggle_description": "Csoportosítsd az adatokat kategóriák szerint. A sorrend számít többdimenziós diagramoknál.",
"edit_chart_description": "Tekintsd meg és szerkeszd a diagram konfigurációját.",
"edit_chart_title": "Diagram szerkesztése",
"enable_time_dimension": "Időbeli dimenzió engedélyezése",
"end_date": "Befejezési dátum",
"enter_a_name_for_your_chart": "Add meg a diagram nevét a mentéshez.",
"enter_value": "Adj meg értéket",
"equals": "egyenlő",
"failed_to_add_chart_to_dashboard": "A diagram hozzáadása a vezérlőpulthoz sikertelen",
"failed_to_execute_query": "A lekérdezés végrehajtása sikertelen",
"failed_to_load_chart": "A diagram betöltése sikertelen",
"failed_to_load_chart_data": "A diagram adatainak betöltése sikertelen",
"failed_to_save_chart": "A diagram mentése sikertelen",
"field": "Mező",
"field_label_average_score": "Átlagos pontszám",
"field_label_collected_at": "Gyűjtve",
"field_label_count": "Darabszám",
"field_label_detractor_count": "Kritikusok száma",
"field_label_emotion": "Érzelem",
"field_label_field_type": "Mező típusa",
"field_label_nps_score": "NPS pontszám",
"field_label_nps_value": "NPS érték",
"field_label_passive_count": "Passzívak száma",
"field_label_promoter_count": "Támogatók száma",
"field_label_response_id": "Válaszazonosító",
"field_label_sentiment": "Hangulat",
"field_label_source_name": "Forrás neve",
"field_label_source_type": "Forrás típusa",
"field_label_topic": "Téma",
"field_label_user_identifier": "Felhasználóazonosító",
"filters": "Szűrők",
"filters_toggle_description": "Csak azokat az adatokat tartalmazza, amelyek megfelelnek a következő feltételeknek.",
"generate_chart": "Diagram generálása",
"granularity": "Részletesség",
"granularity_day": "Nap",
"granularity_hour": "óra",
"granularity_month": "hónap",
"granularity_quarter": "negyedév",
"granularity_week": "hét",
"granularity_year": "év",
"greater_than": "nagyobb mint",
"greater_than_or_equal": "nagyobb vagy egyenlő",
"group_by": "Csoportosítás",
"group_by_description": "Válassz dimenziókat az adatok lebontásához. A sorrend számít többdimenziós diagramok esetén.",
"guide_button": "Mezőkalauz megtekintése",
"guide_chart_type": "Diagram típusa",
"guide_chart_type_desc": "Az adatok megjelenítésének módja: terület, oszlop, vonal, kör vagy nagy szám. Válaszd azt, ami alapján meg szeretnéd jeleníteni (trendek, összehasonlítások, részek az egészből stb.).",
"guide_dimensions": "Dimenziók (csoportosítás)",
"guide_dimensions_desc": "Az adatok felosztásának vagy csoportosításának módja. Minden dimenzió egy kategóriává válik a diagramon (pl. hangulat, forrás típusa, felmérés neve, csatorna, téma). A sorrend számít többdimenziós diagramok esetén.",
"guide_filters": "Szűrők",
"guide_filters_desc": "Feltételek, amelyek korlátozzák, hogy mely adatok kerüljenek be. Minden szűrő tartalmaz egy mezőt, operátort (egyenlő, tartalmaz, nagyobb mint stb.) és értékeket. És = mindegyiknek egyeznie kell; Vagy = bármelyik egyezhet.",
"guide_measures": "Mértékek (amit számolsz vagy aggregálsz)",
"guide_measures_predefined": "Az előre definiált mértékek előre elkészített metrikák a visszajelzési adataidból: darabszám (összes válasz), támogató/ellenzői/passzív darabszám (NPS szegmensek), NPS pontszám, átlagos pontszám, befejezési arány.",
"guide_quick_ref": "Gyors referencia",
"guide_term_dimension": "Kategorikus mező, amelyet az adatok csoportosítására vagy felosztására használnak",
"guide_term_filter": "Feltétel, amely korlátozza, hogy mely sorok kerüljenek be",
"guide_term_measure": "Numerikus érték, amelyet aggregálsz (darabszám, összeg, átlag stb.)",
"guide_term_time": "Időalapú csoportosítás részletességgel és dátumtartománnyal",
"guide_time_dimension": "Idődimenziós",
"guide_time_dimension_desc": "Időalapú csoportosítás: válassz egy időmezőt (általában Gyűjtés időpontja), részletességet (Óra, Nap, Hét, Hónap stb.) és dátumtartományt (előre beállított vagy egyéni). Használd időbeli trendekhez.",
"guide_title": "Diagramkészítő útmutató",
"is_not_set": "nincs beállítva",
"is_set": "beállítva",
"less_than": "kisebb mint",
"less_than_or_equal": "kisebb vagy egyenlő",
"measures": "Mérőszámok",
"no_charts_found": "Nem található diagram.",
"no_dashboards_available": "Nincsenek elérhető vezérlőpultok",
"no_dashboards_create_first": "Először hozz létre egy vezérlőpultot, hogy diagramokat adhass hozzá.",
"no_data_available": "Nincsenek elérhető adatok",
"no_data_returned": "A lekérdezés nem adott vissza adatokat",
"no_data_returned_for_chart": "A diagram nem adott vissza adatokat",
"no_grouping": "Nincs (csak szűrés)",
"no_valid_data_to_display": "Nincsenek megjeleníthető érvényes adatok",
"not_contains": "nem tartalmazza",
"not_equals": "nem egyenlő",
"open_chart": "{{name}} diagram megnyitása",
"open_options": "Diagram beállításainak megnyitása",
"or_filter_logic": "VAGY",
"original": "Eredeti",
"please_enter_chart_name": "Kérjük, add meg a diagram nevét",
"please_select_at_least_one_measure": "Kérjük, válassz ki legalább egy mérőszámot",
"please_select_dashboard": "Kérjük, válassz egy vezérlőpultot",
"predefined_measures": "Előre definiált mérőszámok",
"preset": "Előbeállítás",
"query_executed_successfully": "Lekérdezés sikeresen végrehajtva",
"reset_to_ai_suggestion": "Visszaállítás AI javaslatra",
"save_chart": "Diagram mentése",
"save_chart_dialog_title": "Diagram mentése",
"select_dimensions": "Dimenziók kiválasztása...",
"select_field": "Mező kiválasztása",
"select_measures": "Mérőszámok kiválasztása...",
"select_preset": "Előbeállítás kiválasztása",
"showing_first_n_of": "Az első {{n}} sor megjelenítése {{count}} sorból",
"start_date": "Kezdési dátum",
"time_dimension": "Időbeli dimenzió",
"time_dimension_toggle_description": "Időalapú csoportosítás hozzáadása az időbeli trendek megjelenítéséhez."
},
"dashboards": {
"create_dashboard": "Vezérlőpult létrehozása",
"create_dashboard_description": "Adjon nevet az új vezérlőpultnak.",
"create_failed": "A vezérlőpult létrehozása sikertelen",
"create_success": "A vezérlőpult sikeresen létrehozva!",
"dashboard_name": "Vezérlőpult neve",
"dashboard_name_placeholder": "Saját vezérlőpult",
"delete_confirmation": "Biztosan törölni szeretné ezt a vezérlőpultot? Ez a művelet nem vonható vissza.",
"delete_failed": "A vezérlőpult törlése sikertelen",
"delete_success": "A vezérlőpult sikeresen törölve",
"description_optional": "Leírás (opcionális)",
"description_placeholder": "Vezérlőpult leírása",
"duplicate_failed": "A vezérlőpult másolása sikertelen",
"duplicate_success": "A vezérlőpult sikeresen lemásolva!",
"no_dashboards_found": "Nem található vezérlőpult.",
"please_enter_name": "Kérjük, adjon nevet a vezérlőpultnak"
}
},
"connect": {
"congrats": "Gratulálunk!",
"connection_successful_message": "Szép munka! Kapcsolódtunk.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Partnerek frissítése",
"contacts_table_refresh_success": "A partnerek sikeresen frissítve",
"create_attribute": "Attribútum létrehozása",
"create_key": "Kulcs létrehozása",
"create_new_attribute": "Új attribútum létrehozása",
"create_new_attribute_description": "Új attribútum létrehozása szakaszolási célokhoz.",
"custom_attributes": "Egyéni attribútumok",
@@ -656,6 +841,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_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.}}",
"displays": "Megjelenítések",
"edit_attribute": "Attribútum szerkesztése",
"edit_attribute_description": "Az attribútum címkéjének és leírásának frissítése.",
"edit_attribute_values": "Attribútumok szerkesztése",
@@ -667,6 +853,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_date_format": "Érvénytelen dátumformátum. Kérlek, adj meg egy érvényes dátumot.",
"invalid_number_format": "Érvénytelen számformátum. Kérlek, adj meg egy érvényes számot.",
"no_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_surveys": "Nincsenek közzétett kérdőívek",
"no_responses_found": "Nem találhatók válaszok",
@@ -681,6 +868,8 @@
"select_a_survey": "Kérdőív kiválasztása",
"select_attribute": "Attribútum kiválasztása",
"select_attribute_key": "Attribútum kulcs kiválasztása",
"survey_viewed": "Kérdőív megtekintve",
"survey_viewed_at": "Megtekintve",
"system_attributes": "Rendszer attribútumok",
"unlock_contacts_description": "Partnerek kezelése és célzott kérdőívek kiküldése",
"unlock_contacts_title": "Partnerek feloldása egy magasabb csomaggal",
@@ -752,7 +941,12 @@
"link_google_sheet": "Google Táblázatok összekapcsolása",
"link_new_sheet": "Új táblázat összekapcsolása",
"no_integrations_yet": "A Google Táblázatok integrációi itt fognak megjelenni, amint hozzáadja azokat. ⏲️",
"spreadsheet_url": "Táblázat URL-e"
"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_hidden_fields": "Rejtett mezők felvétele",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "A Formbricks teljes erejének feloldása. 30 napig ingyen."
},
"general": {
"ai_data_analysis_enabled": "Adatgazdagítás és elemzés (AI)",
"ai_data_analysis_enabled_description": "AI segítségével többet hozhat ki az adataiból, irányítópultokat, diagramokat, jelentéseket és egyebeket állíthat be. Hozzáfér az élményekhez kapcsolódó adatokhoz.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "AI-alapú funkciók kezelése ehhez a szervezethez.",
"ai_settings_updated_successfully": "AI beállítások sikeresen frissítve",
"ai_smart_tools_enabled": "Intelligens funkciók (AI)",
"ai_smart_tools_enabled_description": "AI segítségével kevesebb idő alatt többet érhet el. Soha nem fér hozzá a Formbricks által gyűjtött adatokhoz. Csak például felmérések más nyelvekre történő fordításához használatos.",
"bulk_invite_warning_description": "Az ingyenes csomagban az összes szervezeti tag mindig a „Tulajdonos” szerephez van hozzárendelve.",
"cannot_delete_only_organization": "Ez az egyetlen szervezete, nem lehet törölni. Először hozzon létre egy új szervezetet.",
"cannot_leave_only_organization": "Nem hagyhatja el ezt a szervezetet, mivel ez az egyetlen szervezete. Először hozzon létre egy új szervezetet.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Helykitöltő hozzáadása annak megjelenítéshez, hogy nincs visszahívandó érték.",
"add_hidden_field_id": "Rejtett mezőazonosító hozzáadása",
"add_highlight_border": "Kiemelési szegély hozzáadása",
"add_highlight_border_description": "Csak a terméken belüli felmérésekre vonatkozik.",
"add_logic": "Logika hozzáadása",
"add_none_of_the_above": "„A fentiek közül egyik sem” hozzáadása",
"add_option": "Lehetőség hozzáadása",
@@ -1430,7 +1632,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_new": "Új követés",
"follow_ups_upgrade_button_text": "Magasabb csomagra váltás a követések engedélyezéséhez",
"form_styling": "Űrlap stílusának beállítása",
"formbricks_sdk_is_not_connected": "A Formbricks SDK nincs csatlakoztatva",
"four_points": "4 pont",
"heading": "Címsor",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Válaszkorlátok, átirányítások és egyebek.",
"response_options": "Válasz beállításai",
"roundness": "Kerekesség",
"roundness_description": "Annak vezérlése, hogy a kártya sarkai mennyire legyenek lekerekítve.",
"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.",
"rows": "Sorok",
"save_and_close": "Mentés és bezárás",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Ez a szabad és nyílt forráskódú kérdőív le lett zárva",
"survey_display_settings": "Kérdőív megjelenítésének beállításai",
"survey_placement": "Kérdőív elhelyezése",
"survey_styling": "Űrlap stílusának beállítása",
"survey_trigger": "Kérdőív aktiválója",
"switch_multi_language_on_to_get_started": "Kapcsolja be a többnyelvűséget a kezdéshez 👉",
"target_block_not_found": "A célblokk nem található",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Szűrt válaszok (Excel)",
"generating_qr_code": "QR-kód előállítása",
"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.",
"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",
@@ -1989,6 +2192,7 @@
"last_quarter": "Elmúlt negyedév",
"last_year": "Elmúlt év",
"limit": "Korlát",
"no_identified_impressions": "Nincsenek megjelenítések azonosított kapcsolatoktól",
"no_responses_found": "Nem találhatók válaszok",
"other_values_found": "Más értékek találhatók",
"overall": "Összesen",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Átméretezi a címsor szövegét.",
"advanced_styling_field_headline_weight": "Címsor betűvastagsága",
"advanced_styling_field_headline_weight_description": "Vékonyabbá vagy vastagabbá teszi a címsor szövegét.",
"advanced_styling_field_height": "Magasság",
"advanced_styling_field_height": "Minimális magasság",
"advanced_styling_field_indicator_bg": "Jelző háttere",
"advanced_styling_field_indicator_bg_description": "Kiszínezi a sáv kitöltött részét.",
"advanced_styling_field_input_border_radius_description": "Lekerekíti a beviteli mező sarkait.",
"advanced_styling_field_input_font_size_description": "Átméretezi a beviteli mezőkbe beírt szöveget.",
"advanced_styling_field_input_height_description": "A beviteli mező magasságát vezérli.",
"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_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.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Kiszínezi a beviteli mezőkbe beírt szöveget.",
"advanced_styling_field_option_bg": "Háttér",
"advanced_styling_field_option_bg_description": "Kitölti a választási lehetőség elemeit.",
"advanced_styling_field_option_border": "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_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",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Az „A gépházban: Formbricks” aláírás megjelenítése",
"styling_updated_successfully": "A stílus sikeresen frissítve",
"suggest_colors": "Színek ajánlása",
"suggested_colors_applied_please_save": "A javasolt színek sikeresen generálva. Nyomd meg a \"Mentés\" gombot a változtatások véglegesítéséhez.",
"theme": "Téma",
"theme_settings_description": "Stílustéma létrehozása az összes kérdőívhez. Egyéni stílust engedélyezhet minden egyes kérdőívhez."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Nem, köszönöm!",
"preview_survey_question_2_headline": "Szeretne naprakész maradni?",
"preview_survey_question_2_subheader": "Ez egy példa a leírásra.",
"preview_survey_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!",
"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",

View File

@@ -133,6 +133,7 @@
"allow": "許可",
"allow_users_to_exit_by_clicking_outside_the_survey": "フォームの外側をクリックしてユーザーが終了できるようにする",
"an_unknown_error_occurred_while_deleting_table_items": "{type}の削除中に不明なエラーが発生しました",
"analysis": "分析",
"and": "および",
"and_response_limit_of": "と回答数の上限",
"anonymous": "匿名",
@@ -149,6 +150,8 @@
"bottom_right": "右下",
"cancel": "キャンセル",
"centered_modal": "中央モーダル",
"chart": "チャート",
"charts": "チャート",
"choices": "選択肢",
"choose_environment": "環境を選択",
"choose_organization": "組織を選択",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, other {{value}個の属性}}",
"count_contacts": "{count, plural, other {# 件の連絡先}}",
"count_responses": "{count, plural, other {# 件の回答}}",
"create": "作成",
"create_new_organization": "新しい組織を作成",
"create_segment": "セグメントを作成",
"create_survey": "フォームを作成",
@@ -187,6 +191,8 @@
"created_by": "作成者",
"customer_success": "カスタマーサクセス",
"dark_overlay": "暗いオーバーレイ",
"dashboard": "ダッシュボード",
"dashboards": "ダッシュボード",
"date": "日付",
"days": "日",
"default": "デフォルト",
@@ -218,13 +224,16 @@
"error": "エラー",
"error_component_description": "この リソース は 存在 しない か、アクセス する ための 必要な 権限 が ありません。",
"error_component_title": "リソース の 読み込み エラー",
"error_loading_data": "データの読み込みエラー",
"error_rate_limit_description": "リクエストの最大数に達しました。後でもう一度試してください。",
"error_rate_limit_title": "レート制限を超えました",
"expand_rows": "行を展開",
"failed_to_copy_to_clipboard": "クリップボードへのコピーに失敗しました",
"failed_to_load_organizations": "組織の読み込みに失敗しました",
"failed_to_load_workspaces": "ワークスペースの読み込みに失敗しました",
"filter": "フィルター",
"finish": "完了",
"first_name": "名",
"follow_these": "こちらの手順に従って",
"formbricks_version": "Formbricksバージョン",
"full_name": "氏名",
@@ -236,7 +245,9 @@
"hidden": "非表示",
"hidden_field": "非表示フィールド",
"hidden_fields": "非表示フィールド",
"hide": "非表示",
"hide_column": "列を非表示",
"id": "ID",
"image": "画像",
"images": "画像",
"import": "インポート",
@@ -254,6 +265,7 @@
"key": "キー",
"label": "ラベル",
"language": "言語",
"last_name": "姓",
"learn_more": "詳細を見る",
"license_expired": "License Expired",
"light_overlay": "明るいオーバーレイ",
@@ -280,6 +292,7 @@
"move_down": "下に移動",
"move_up": "上に移動",
"multiple_languages": "多言語",
"my_product": "マイプロダクト",
"name": "名前",
"new": "新規",
"new_version_available": "Formbricks {version} が利用可能です。今すぐアップグレード!",
@@ -303,6 +316,7 @@
"on": "オン",
"only_one_file_allowed": "ファイルは1つのみ許可されています",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "このアクションを実行できるのは、オーナーと管理者のみです。",
"open_options": "オプションを開く",
"option_id": "オプションID",
"option_ids": "オプションID",
"optional": "任意",
@@ -428,6 +442,7 @@
"top_right": "右上",
"try_again": "もう一度お試しください",
"type": "種類",
"unknown_survey": "不明なフォーム",
"unlock_more_workspaces_with_a_higher_plan": "上位プランでより多くのワークスペースを利用できます。",
"update": "更新",
"updated": "更新済み",
@@ -444,6 +459,7 @@
"variables": "変数",
"verified_email": "認証済みメールアドレス",
"video": "動画",
"view": "表示",
"warning": "警告",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "ライセンスサーバーにアクセスできないため、ライセンスを認証できませんでした。",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "あなたのフォームはこのURLに表示されます。",
"your_survey_would_not_be_shown": "あなたのフォームは表示されません。"
},
"analysis": {
"charts": {
"OR": "OR",
"add_chart_to_dashboard": "ダッシュボードにチャートを追加",
"add_chart_to_dashboard_description": "このチャートを追加するダッシュボードを選択してください。チャートは自動的に保存されます。",
"add_filter": "フィルターを追加",
"add_to_dashboard": "ダッシュボードに追加",
"advanced_chart_builder_config_prompt": "チャートを設定して「クエリを実行」をクリックしてプレビューを表示",
"ai_query_placeholder": "例: 先週何人のユーザーが登録しましたか?",
"ai_query_section_description": "表示したい内容を説明すると、AIがチャートを作成します。",
"ai_query_section_title": "データに質問する",
"apply_changes": "変更を適用",
"chart": "チャート",
"chart_added_to_dashboard": "チャートをダッシュボードに追加しました!",
"chart_builder_choose_chart_type": "チャートタイプを選択",
"chart_data": "チャートデータ",
"chart_data_tab": "データ",
"chart_deleted_successfully": "チャートを削除しました",
"chart_deletion_error": "チャートの削除に失敗しました",
"chart_duplicated_successfully": "チャートを複製しました",
"chart_duplication_error": "チャートの複製に失敗しました",
"chart_name": "チャート名",
"chart_name_placeholder": "チャート名",
"chart_preview": "チャートプレビュー",
"chart_render_error": "このチャートの表示中に問題が発生しました。",
"chart_saved_successfully": "チャートを保存しました!",
"chart_type_area": "エリアチャート",
"chart_type_bar": "棒グラフ",
"chart_type_big_number": "大きな数値",
"chart_type_line": "折れ線グラフ",
"chart_type_not_supported": "チャートタイプ「{{chartType}}」はまだサポートされていません",
"chart_type_pie": "円グラフ",
"chart_updated_successfully": "チャートを更新しました!",
"configure_description": "このビジュアライゼーションのチャートタイプやその他の設定を変更します。",
"configure_title": "チャートを設定",
"configure_type_label": "チャートタイプ",
"contains": "を含む",
"create_chart": "チャートを作成",
"create_chart_description": "AIを使用してチャートを生成するか、手動で作成します。",
"custom_range": "カスタム範囲",
"dashboard": "ダッシュボード",
"dashboard_select_placeholder": "ダッシュボードを選択",
"data_label": "データ",
"date_preset_last_30_days": "過去30日間",
"date_preset_last_7_days": "過去7日間",
"date_preset_last_month": "先月",
"date_preset_this_month": "今月",
"date_preset_this_quarter": "今四半期",
"date_preset_this_year": "今年",
"date_preset_today": "今日",
"date_preset_yesterday": "昨日",
"date_range": "日付範囲",
"delete_chart_confirmation": "このチャートを削除してもよろしいですか?",
"dimensions": "ディメンション",
"dimensions_toggle_description": "カテゴリ別にデータをグループ化します。多次元チャートでは順序が重要です。",
"edit_chart_description": "チャート設定を表示および編集します。",
"edit_chart_title": "チャートを編集",
"enable_time_dimension": "時間ディメンションを有効化",
"end_date": "終了日",
"enter_a_name_for_your_chart": "チャートを保存するには名前を入力してください。",
"enter_value": "値を入力",
"equals": "と等しい",
"failed_to_add_chart_to_dashboard": "ダッシュボードへのチャート追加に失敗しました",
"failed_to_execute_query": "クエリの実行に失敗しました",
"failed_to_load_chart": "チャートの読み込みに失敗しました",
"failed_to_load_chart_data": "チャートデータの読み込みに失敗しました",
"failed_to_save_chart": "チャートの保存に失敗しました",
"field": "フィールド",
"field_label_average_score": "平均スコア",
"field_label_collected_at": "収集日時",
"field_label_count": "カウント",
"field_label_detractor_count": "批判者数",
"field_label_emotion": "感情",
"field_label_field_type": "フィールドタイプ",
"field_label_nps_score": "NPSスコア",
"field_label_nps_value": "NPS値",
"field_label_passive_count": "中立者数",
"field_label_promoter_count": "推奨者数",
"field_label_response_id": "回答ID",
"field_label_sentiment": "感情分析",
"field_label_source_name": "ソース名",
"field_label_source_type": "ソースタイプ",
"field_label_topic": "トピック",
"field_label_user_identifier": "ユーザー識別子",
"filters": "フィルター",
"filters_toggle_description": "以下の条件を満たすデータのみを含めます。",
"generate_chart": "チャートを生成",
"granularity": "粒度",
"granularity_day": "日",
"granularity_hour": "時間",
"granularity_month": "月",
"granularity_quarter": "四半期",
"granularity_week": "週",
"granularity_year": "年",
"greater_than": "より大きい",
"greater_than_or_equal": "以上",
"group_by": "グループ化",
"group_by_description": "データを分類するディメンションを選択します。多次元チャートでは順序が重要です。",
"guide_button": "フィールドガイドを表示",
"guide_chart_type": "チャートタイプ",
"guide_chart_type_desc": "データの視覚化方法:エリア、棒グラフ、折れ線グラフ、円グラフ、またはビッグナンバー。表示したい内容(トレンド、比較、全体の一部など)に基づいて選択します。",
"guide_dimensions": "ディメンション(グループ化)",
"guide_dimensions_desc": "データを分割またはグループ化する方法。各ディメンションはチャート上のカテゴリになります(例:センチメント、ソースタイプ、サーベイ名、チャネル、トピック)。多次元チャートでは順序が重要です。",
"guide_filters": "フィルター",
"guide_filters_desc": "含めるデータを制限する条件。各フィルターにはフィールド、演算子(等しい、含む、より大きいなど)、および値があります。And = すべて一致する必要があります。Or = いずれか一致すればよい。",
"guide_measures": "メジャー(カウントまたは集計する内容)",
"guide_measures_predefined": "事前定義メジャーは、フィードバックデータから構築済みのメトリクスです:カウント(総回答数)、プロモーター/デトラクター/パッシブカウント(NPSセグメント)、NPSスコア、平均スコア、完了率。",
"guide_quick_ref": "クイックリファレンス",
"guide_term_dimension": "データをグループ化または分割するために使用されるカテゴリフィールド",
"guide_term_filter": "含める行を制限する条件",
"guide_term_measure": "集計する数値(count、sum、avgなど)",
"guide_term_time": "粒度と日付範囲を持つ時間ベースのグループ化",
"guide_time_dimension": "時間ディメンション",
"guide_time_dimension_desc": "時間ベースのグループ化:時間フィールド(通常は収集日時)、粒度(時間、日、週、月など)、日付範囲(プリセットまたはカスタム)を選択します。時系列のトレンド分析に使用します。",
"guide_title": "チャートビルダーフィールドガイド",
"is_not_set": "設定されていない",
"is_set": "設定されている",
"less_than": "より小さい",
"less_than_or_equal": "以下",
"measures": "メジャー",
"no_charts_found": "チャートが見つかりません。",
"no_dashboards_available": "利用可能なダッシュボードがありません",
"no_dashboards_create_first": "チャートを追加するには、まずダッシュボードを作成してください。",
"no_data_available": "利用可能なデータがありません",
"no_data_returned": "クエリからデータが返されませんでした",
"no_data_returned_for_chart": "チャートのデータが返されませんでした",
"no_grouping": "なし(フィルターのみ)",
"no_valid_data_to_display": "表示する有効なデータがありません",
"not_contains": "を含まない",
"not_equals": "と等しくない",
"open_chart": "チャート{{name}}を開く",
"open_options": "チャートオプションを開く",
"or_filter_logic": "OR",
"original": "オリジナル",
"please_enter_chart_name": "チャート名を入力してください",
"please_select_at_least_one_measure": "少なくとも1つのメジャーを選択してください",
"please_select_dashboard": "ダッシュボードを選択してください",
"predefined_measures": "事前定義されたメジャー",
"preset": "プリセット",
"query_executed_successfully": "クエリが正常に実行されました",
"reset_to_ai_suggestion": "AIの提案にリセット",
"save_chart": "チャートを保存",
"save_chart_dialog_title": "チャートを保存",
"select_dimensions": "ディメンションを選択...",
"select_field": "フィールドを選択",
"select_measures": "メジャーを選択...",
"select_preset": "プリセットを選択",
"showing_first_n_of": "{{count}}行中、最初の{{n}}行を表示",
"start_date": "開始日",
"time_dimension": "時間ディメンション",
"time_dimension_toggle_description": "時系列のトレンド分析のために時間ベースのグループ化を追加します。"
},
"dashboards": {
"create_dashboard": "ダッシュボードを作成",
"create_dashboard_description": "新しいダッシュボードの名前を入力してください。",
"create_failed": "ダッシュボードの作成に失敗しました",
"create_success": "ダッシュボードを正常に作成しました!",
"dashboard_name": "ダッシュボード名",
"dashboard_name_placeholder": "マイダッシュボード",
"delete_confirmation": "このダッシュボードを削除してもよろしいですか?この操作は元に戻せません。",
"delete_failed": "ダッシュボードの削除に失敗しました",
"delete_success": "ダッシュボードを正常に削除しました",
"description_optional": "説明(任意)",
"description_placeholder": "ダッシュボードの説明",
"duplicate_failed": "ダッシュボードの複製に失敗しました",
"duplicate_success": "ダッシュボードを正常に複製しました!",
"no_dashboards_found": "ダッシュボードが見つかりません。",
"please_enter_name": "ダッシュボード名を入力してください"
}
},
"connect": {
"congrats": "おめでとうございます!",
"connection_successful_message": "うまくいきました!接続されました。",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "連絡先を更新",
"contacts_table_refresh_success": "連絡先を正常に更新しました",
"create_attribute": "属性を作成",
"create_key": "キーを作成",
"create_new_attribute": "新しい属性を作成",
"create_new_attribute_description": "セグメンテーション用の新しい属性を作成します。",
"custom_attributes": "カスタム属性",
@@ -656,6 +841,7 @@
"delete_attribute_confirmation": "{value, plural, one {選択した属性を削除します。この属性に関連付けられたすべてのコンタクトデータは失われます。} other {選択した属性を削除します。これらの属性に関連付けられたすべてのコンタクトデータは失われます。}}",
"delete_contact_confirmation": "これにより、この連絡先に関連付けられているすべてのフォーム回答と連絡先属性が削除されます。この連絡先のデータに基づいたターゲティングとパーソナライゼーションはすべて失われます。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {これにより この連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。この連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。この連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。} other {これにより これらの連絡先に関連するすべてのアンケート応答と連絡先属性が削除されます。これらの連絡先のデータに基づくターゲティングとパーソナライゼーションが失われます。これらの連絡先がアンケートの割当量を考慮した回答を持っている場合、割当量カウントは減少しますが、割当量の制限は変更されません。}}",
"displays": "表示回数",
"edit_attribute": "属性を編集",
"edit_attribute_description": "この属性のラベルと説明を更新します。",
"edit_attribute_values": "属性を編集",
@@ -667,6 +853,7 @@
"invalid_csv_column_names": "無効なCSV列名: {columns}。新しい属性となる列名は、小文字、数字、アンダースコアのみを含み、文字で始まる必要があります。",
"invalid_date_format": "無効な日付形式です。有効な日付を使用してください。",
"invalid_number_format": "無効な数値形式です。有効な数値を入力してください。",
"no_activity_yet": "まだアクティビティがありません",
"no_published_link_surveys_available": "公開されたリンクフォームはありません。まずリンクフォームを公開してください。",
"no_published_surveys": "公開されたフォームはありません",
"no_responses_found": "回答が見つかりません",
@@ -681,6 +868,8 @@
"select_a_survey": "フォームを選択",
"select_attribute": "属性を選択",
"select_attribute_key": "属性キーを選択",
"survey_viewed": "フォームを閲覧",
"survey_viewed_at": "閲覧日時",
"system_attributes": "システム属性",
"unlock_contacts_description": "連絡先を管理し、特定のフォームを送信します",
"unlock_contacts_title": "上位プランで連絡先をアンロック",
@@ -752,7 +941,12 @@
"link_google_sheet": "スプレッドシートをリンク",
"link_new_sheet": "新しいシートをリンク",
"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_hidden_fields": "非表示フィールドを含める",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。"
},
"general": {
"ai_data_analysis_enabled": "データエンリッチメントと分析AI",
"ai_data_analysis_enabled_description": "AIを活用してデータから最大限の価値を引き出し、ダッシュボード、チャート、レポートなどを設定できます。エクスペリエンスデータに触れます。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "この組織のAI機能を管理します。",
"ai_settings_updated_successfully": "AI設定が正常に更新されました",
"ai_smart_tools_enabled": "スマート機能AI",
"ai_smart_tools_enabled_description": "AIを活用して、より短時間でより多くのことを達成できます。Formbricksで収集されたデータには一切触れません。アンケートを他の言語に翻訳するなどの用途にのみ使用されます。",
"bulk_invite_warning_description": "無料プランでは、すべての組織メンバーに常に「オーナー」ロールが割り当てられます。",
"cannot_delete_only_organization": "これはあなたの唯一の組織です。削除できません。まず新しい組織を作成してください。",
"cannot_leave_only_organization": "これはあなたの唯一の組織であるため、離れることはできません。まず新しい組織を作成してください。",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "質問がスキップされた場合に表示するプレースホルダーを追加:",
"add_hidden_field_id": "非表示フィールドIDを追加",
"add_highlight_border": "ハイライトボーダーを追加",
"add_highlight_border_description": "プロダクト内サーベイにのみ適用されます。",
"add_logic": "ロジックを追加",
"add_none_of_the_above": "\"いずれも該当しません\" を追加",
"add_option": "オプションを追加",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "フォローアップ が 更新され、 アンケートを 保存すると保存されます。",
"follow_ups_new": "新しいフォローアップ",
"follow_ups_upgrade_button_text": "フォローアップを有効にするためにアップグレード",
"form_styling": "フォームのスタイル",
"formbricks_sdk_is_not_connected": "Formbricks SDKが接続されていません",
"four_points": "4点",
"heading": "見出し",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "回答数の上限、リダイレクトなど。",
"response_options": "回答オプション",
"roundness": "丸み",
"roundness_description": "カードの角の丸みを調整します。",
"roundness_description": "角の丸みを調整します。",
"row_used_in_logic_error": "この行は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
"rows": "行",
"save_and_close": "保存して閉じる",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "この無料のオープンソースフォームは閉鎖されました",
"survey_display_settings": "フォーム表示設定",
"survey_placement": "フォームの配置",
"survey_styling": "フォームのスタイル",
"survey_trigger": "フォームのトリガー",
"switch_multi_language_on_to_get_started": "多言語機能をオンにして開始 👉",
"target_block_not_found": "対象ブロックが見つかりません",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "フィルター済み回答 (Excel)",
"generating_qr_code": "QRコードを生成中",
"impressions": "表示回数",
"impressions_identified_only": "識別済みコンタクトからのインプレッションのみを表示しています",
"impressions_tooltip": "フォームが表示された回数。",
"in_app": {
"connection_description": "このフォームは、以下の条件に一致するあなたのウェブサイトのユーザーに表示されます",
@@ -1989,6 +2192,7 @@
"last_quarter": "前四半期",
"last_year": "昨年",
"limit": "制限",
"no_identified_impressions": "識別済みコンタクトからのインプレッションはありません",
"no_responses_found": "回答が見つかりません",
"other_values_found": "他の値が見つかりました",
"overall": "全体",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "見出しテキストのサイズを調整します。",
"advanced_styling_field_headline_weight": "見出しのフォントの太さ",
"advanced_styling_field_headline_weight_description": "見出しテキストを細くまたは太くします。",
"advanced_styling_field_height": "高さ",
"advanced_styling_field_height": "最小の高さ",
"advanced_styling_field_indicator_bg": "インジケーターの背景",
"advanced_styling_field_indicator_bg_description": "バーの塗りつぶし部分に色を付けます。",
"advanced_styling_field_input_border_radius_description": "入力フィールドの角を丸めます。",
"advanced_styling_field_input_font_size_description": "入力フィールド内の入力テキストのサイズを調整します。",
"advanced_styling_field_input_height_description": "入力フィールドの高さを調整します。",
"advanced_styling_field_input_height_description": "入力欄の最小の高さを調整します。",
"advanced_styling_field_input_padding_x_description": "左右にスペースを追加します。",
"advanced_styling_field_input_padding_y_description": "上下にスペースを追加します。",
"advanced_styling_field_input_placeholder_opacity_description": "プレースホルダーのヒントテキストを薄くします。",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "入力フィールドに入力されたテキストの色を設定します。",
"advanced_styling_field_option_bg": "背景",
"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_font_size_description": "オプションラベルのテキストサイズを調整します。",
"advanced_styling_field_option_label": "ラベルの色",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "「Powered by Formbricks」署名を表示",
"styling_updated_successfully": "スタイルを正常に更新しました",
"suggest_colors": "カラーを提案",
"suggested_colors_applied_please_save": "推奨カラーが正常に生成されました。変更を保存するには「保存」を押してください。",
"theme": "テーマ",
"theme_settings_description": "すべてのアンケート用のスタイルテーマを作成します。各アンケートでカスタムスタイルを有効にできます。"
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "いいえ、結構です!",
"preview_survey_question_2_headline": "最新情報を知りたいですか?",
"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": "ようこそ!",
"prioritize_features_description": "ユーザーが最も必要とする機能と最も必要としない機能を特定する。",
"prioritize_features_name": "機能の優先順位付け",

View File

@@ -133,6 +133,7 @@
"allow": "Toestaan",
"allow_users_to_exit_by_clicking_outside_the_survey": "Laat gebruikers afsluiten door buiten de enquête te klikken",
"an_unknown_error_occurred_while_deleting_table_items": "Er is een onbekende fout opgetreden bij het verwijderen van {type}s",
"analysis": "Analyse",
"and": "En",
"and_response_limit_of": "en responslimiet van",
"anonymous": "Anoniem",
@@ -149,6 +150,8 @@
"bottom_right": "Rechtsonder",
"cancel": "Annuleren",
"centered_modal": "Gecentreerd modaal",
"chart": "Grafiek",
"charts": "Grafieken",
"choices": "Keuzes",
"choose_environment": "Kies omgeving",
"choose_organization": "Kies organisatie",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} attribuut} other {{value} attributen}}",
"count_contacts": "{value, plural, one {{value} contact} other {{value} contacten}}",
"count_responses": "{value, plural, one {{value} reactie} other {{value} reacties}}",
"create": "Creëren",
"create_new_organization": "Creëer een nieuwe organisatie",
"create_segment": "Segment maken",
"create_survey": "Enquête maken",
@@ -187,6 +191,8 @@
"created_by": "Gemaakt door",
"customer_success": "Klant succes",
"dark_overlay": "Donkere overlay",
"dashboard": "Dashboard",
"dashboards": "Dashboards",
"date": "Datum",
"days": "dagen",
"default": "Standaard",
@@ -218,13 +224,16 @@
"error": "Fout",
"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_loading_data": "Fout bij het laden van gegevens",
"error_rate_limit_description": "Maximaal aantal verzoeken bereikt. Probeer het later opnieuw.",
"error_rate_limit_title": "Tarieflimiet overschreden",
"expand_rows": "Vouw rijen uit",
"failed_to_copy_to_clipboard": "Kopiëren naar klembord mislukt",
"failed_to_load_organizations": "Laden van organisaties mislukt",
"failed_to_load_workspaces": "Laden van werkruimtes mislukt",
"filter": "Filter",
"finish": "Finish",
"first_name": "Voornaam",
"follow_these": "Volg deze",
"formbricks_version": "Formbricks-versie",
"full_name": "Volledige naam",
@@ -236,7 +245,9 @@
"hidden": "Verborgen",
"hidden_field": "Verborgen veld",
"hidden_fields": "Verborgen velden",
"hide": "Verbergen",
"hide_column": "Kolom verbergen",
"id": "ID",
"image": "Afbeelding",
"images": "Afbeeldingen",
"import": "Importeren",
@@ -254,6 +265,7 @@
"key": "Sleutel",
"label": "Label",
"language": "Taal",
"last_name": "Achternaam",
"learn_more": "Meer informatie",
"license_expired": "License Expired",
"light_overlay": "Lichte overlay",
@@ -280,6 +292,7 @@
"move_down": "Ga naar beneden",
"move_up": "Ga omhoog",
"multiple_languages": "Meerdere talen",
"my_product": "mijn product",
"name": "Naam",
"new": "Nieuw",
"new_version_available": "Formbricks {version} is hier. Upgrade nu!",
@@ -303,6 +316,7 @@
"on": "Op",
"only_one_file_allowed": "Er is slechts één bestand toegestaan",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Alleen eigenaren en beheerders kunnen deze actie uitvoeren.",
"open_options": "Opties openen",
"option_id": "Optie-ID",
"option_ids": "Optie-ID's",
"optional": "Optioneel",
@@ -428,6 +442,7 @@
"top_right": "Rechtsboven",
"try_again": "Probeer het opnieuw",
"type": "Type",
"unknown_survey": "Onbekende enquête",
"unlock_more_workspaces_with_a_higher_plan": "Ontgrendel meer werkruimtes met een hoger abonnement.",
"update": "Update",
"updated": "Bijgewerkt",
@@ -444,6 +459,7 @@
"variables": "Variabelen",
"verified_email": "Geverifieerde e-mail",
"video": "Video",
"view": "Bekijken",
"warning": "Waarschuwing",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "We kunnen uw licentie niet verifiëren omdat de licentieserver niet bereikbaar is.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Uw enquête wordt op deze URL weergegeven.",
"your_survey_would_not_be_shown": "Uw enquête wordt niet getoond."
},
"analysis": {
"charts": {
"OR": "OF",
"add_chart_to_dashboard": "Grafiek toevoegen aan dashboard",
"add_chart_to_dashboard_description": "Selecteer een dashboard om deze grafiek aan toe te voegen. De grafiek wordt automatisch opgeslagen.",
"add_filter": "Filter toevoegen",
"add_to_dashboard": "Toevoegen aan dashboard",
"advanced_chart_builder_config_prompt": "Configureer je grafiek en klik op \"Query uitvoeren\" om een voorbeeld te zien",
"ai_query_placeholder": "bijv. Hoeveel gebruikers hebben zich vorige week aangemeld?",
"ai_query_section_description": "Beschrijf wat je wilt zien en laat AI de grafiek bouwen.",
"ai_query_section_title": "Vraag het aan je data",
"apply_changes": "Wijzigingen toepassen",
"chart": "Grafiek",
"chart_added_to_dashboard": "Grafiek toegevoegd aan dashboard!",
"chart_builder_choose_chart_type": "Kies grafiektype",
"chart_data": "Grafiekdata",
"chart_data_tab": "Data",
"chart_deleted_successfully": "Grafiek succesvol verwijderd",
"chart_deletion_error": "Verwijderen van grafiek mislukt",
"chart_duplicated_successfully": "Grafiek succesvol gedupliceerd",
"chart_duplication_error": "Dupliceren van grafiek mislukt",
"chart_name": "Grafieknaam",
"chart_name_placeholder": "Grafieknaam",
"chart_preview": "Grafiekvoorbeeld",
"chart_render_error": "Er is iets misgegaan bij het weergeven van deze grafiek.",
"chart_saved_successfully": "Grafiek succesvol opgeslagen!",
"chart_type_area": "Vlakdiagram",
"chart_type_bar": "Staafdiagram",
"chart_type_big_number": "Groot getal",
"chart_type_line": "Lijndiagram",
"chart_type_not_supported": "Grafiektype \"{{chartType}}\" wordt nog niet ondersteund",
"chart_type_pie": "Cirkeldiagram",
"chart_updated_successfully": "Grafiek succesvol bijgewerkt!",
"configure_description": "Pas het diagramtype en andere instellingen voor deze visualisatie aan.",
"configure_title": "Diagram configureren",
"configure_type_label": "Diagramtype",
"contains": "bevat",
"create_chart": "Diagram maken",
"create_chart_description": "Gebruik AI om een diagram te genereren of bouw er handmatig een.",
"custom_range": "Aangepast bereik",
"dashboard": "Dashboard",
"dashboard_select_placeholder": "Selecteer een dashboard",
"data_label": "Data",
"date_preset_last_30_days": "Laatste 30 dagen",
"date_preset_last_7_days": "Laatste 7 dagen",
"date_preset_last_month": "Vorige maand",
"date_preset_this_month": "Deze maand",
"date_preset_this_quarter": "Dit kwartaal",
"date_preset_this_year": "Dit jaar",
"date_preset_today": "Vandaag",
"date_preset_yesterday": "Gisteren",
"date_range": "Datumbereik",
"delete_chart_confirmation": "Weet je zeker dat je deze grafiek wilt verwijderen?",
"dimensions": "Dimensies",
"dimensions_toggle_description": "Groepeer data op categorieën. Volgorde is belangrijk voor multidimensionale diagrammen.",
"edit_chart_description": "Bekijk en bewerk je diagramconfiguratie.",
"edit_chart_title": "Diagram bewerken",
"enable_time_dimension": "Tijdsdimensie inschakelen",
"end_date": "Einddatum",
"enter_a_name_for_your_chart": "Voer een naam in voor je diagram om het op te slaan.",
"enter_value": "Voer waarde in",
"equals": "is gelijk aan",
"failed_to_add_chart_to_dashboard": "Diagram toevoegen aan dashboard mislukt",
"failed_to_execute_query": "Query uitvoeren mislukt",
"failed_to_load_chart": "Diagram laden mislukt",
"failed_to_load_chart_data": "Diagramdata laden mislukt",
"failed_to_save_chart": "Opslaan van diagram mislukt",
"field": "Veld",
"field_label_average_score": "Gemiddelde score",
"field_label_collected_at": "Verzameld op",
"field_label_count": "Aantal",
"field_label_detractor_count": "Aantal detractors",
"field_label_emotion": "Emotie",
"field_label_field_type": "Veldtype",
"field_label_nps_score": "NPS-score",
"field_label_nps_value": "NPS-waarde",
"field_label_passive_count": "Aantal passieven",
"field_label_promoter_count": "Aantal promoters",
"field_label_response_id": "Antwoord-ID",
"field_label_sentiment": "Sentiment",
"field_label_source_name": "Bronnaam",
"field_label_source_type": "Brontype",
"field_label_topic": "Onderwerp",
"field_label_user_identifier": "Gebruikersidentificatie",
"filters": "Filters",
"filters_toggle_description": "Neem alleen gegevens op die aan de volgende voorwaarden voldoen.",
"generate_chart": "Diagram genereren",
"granularity": "Granulariteit",
"granularity_day": "Dag",
"granularity_hour": "Uur",
"granularity_month": "Maand",
"granularity_quarter": "Kwartaal",
"granularity_week": "Week",
"granularity_year": "Jaar",
"greater_than": "groter dan",
"greater_than_or_equal": "groter dan of gelijk aan",
"group_by": "Groeperen op",
"group_by_description": "Selecteer dimensies om je gegevens op te splitsen. De volgorde is belangrijk voor multidimensionale diagrammen.",
"guide_button": "Veldgids bekijken",
"guide_chart_type": "Diagramtype",
"guide_chart_type_desc": "Hoe de gegevens worden gevisualiseerd: vlak, staaf, lijn, cirkel of groot getal. Kies op basis van wat je wilt tonen (trends, vergelijkingen, delen van een geheel, etc.).",
"guide_dimensions": "Dimensies (groeperen op)",
"guide_dimensions_desc": "Hoe je de gegevens splitst of groepeert. Elke dimensie wordt een categorie in het diagram (bijv. sentiment, brontype, enquêtenaam, kanaal, onderwerp). De volgorde is belangrijk voor multidimensionale diagrammen.",
"guide_filters": "Filters",
"guide_filters_desc": "Voorwaarden die bepalen welke gegevens worden opgenomen. Elk filter heeft een veld, operator (is gelijk aan, bevat, groter dan, etc.) en waarden. En = alle moeten overeenkomen; Of = één mag overeenkomen.",
"guide_measures": "Metingen (wat je telt of aggregeert)",
"guide_measures_predefined": "Voorgedefinieerde metingen zijn vooraf gebouwde statistieken uit je feedbackgegevens: aantal (totaal aantal reacties), promotor-/detractor-/passief aantal (NPS-segmenten), NPS-score, gemiddelde score, voltooiingspercentage.",
"guide_quick_ref": "Snelle referentie",
"guide_term_dimension": "Categorisch veld gebruikt om gegevens te groeperen of splitsen",
"guide_term_filter": "Voorwaarde die bepaalt welke rijen worden opgenomen",
"guide_term_measure": "Numerieke waarde die je aggregeert (count, sum, avg, etc.)",
"guide_term_time": "Tijdgebaseerde groepering met granulariteit en datumbereik",
"guide_time_dimension": "Tijddimensie",
"guide_time_dimension_desc": "Tijdgebaseerde groepering: kies een tijdveld (meestal Verzameld op), granulariteit (Uur, Dag, Week, Maand, etc.) en datumbereik (vooraf ingesteld of aangepast). Gebruik voor trends over tijd.",
"guide_title": "Veldgids voor diagrambouwer",
"is_not_set": "is niet ingesteld",
"is_set": "is ingesteld",
"less_than": "kleiner dan",
"less_than_or_equal": "kleiner dan of gelijk aan",
"measures": "Metingen",
"no_charts_found": "Geen diagrammen gevonden.",
"no_dashboards_available": "Geen dashboards beschikbaar",
"no_dashboards_create_first": "Maak eerst een dashboard aan om er diagrammen aan toe te voegen.",
"no_data_available": "Geen gegevens beschikbaar",
"no_data_returned": "Geen gegevens geretourneerd uit query",
"no_data_returned_for_chart": "Geen gegevens geretourneerd voor diagram",
"no_grouping": "Geen (alleen filteren)",
"no_valid_data_to_display": "Geen geldige gegevens om weer te geven",
"not_contains": "bevat niet",
"not_equals": "is niet gelijk aan",
"open_chart": "Open diagram {{name}}",
"open_options": "Open diagramopties",
"or_filter_logic": "OF",
"original": "Origineel",
"please_enter_chart_name": "Voer een diagramnaam in",
"please_select_at_least_one_measure": "Selecteer ten minste één meting",
"please_select_dashboard": "Selecteer een dashboard",
"predefined_measures": "Vooraf gedefinieerde metingen",
"preset": "Voorinstelling",
"query_executed_successfully": "Query succesvol uitgevoerd",
"reset_to_ai_suggestion": "Herstel naar AI-suggestie",
"save_chart": "Diagram opslaan",
"save_chart_dialog_title": "Diagram opslaan",
"select_dimensions": "Selecteer dimensies...",
"select_field": "Selecteer veld",
"select_measures": "Selecteer metingen...",
"select_preset": "Selecteer voorinstelling",
"showing_first_n_of": "Eerste {{n}} van {{count}} rijen worden getoond",
"start_date": "Startdatum",
"time_dimension": "Tijdsdimensie",
"time_dimension_toggle_description": "Voeg tijdgebaseerde groepering toe voor trends over tijd."
},
"dashboards": {
"create_dashboard": "Dashboard creëren",
"create_dashboard_description": "Voer een naam in voor je nieuwe dashboard.",
"create_failed": "Dashboard creëren mislukt",
"create_success": "Dashboard succesvol aangemaakt!",
"dashboard_name": "Dashboardnaam",
"dashboard_name_placeholder": "Mijn dashboard",
"delete_confirmation": "Weet je zeker dat je dit dashboard wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
"delete_failed": "Dashboard verwijderen mislukt",
"delete_success": "Dashboard succesvol verwijderd",
"description_optional": "Beschrijving (optioneel)",
"description_placeholder": "Dashboardbeschrijving",
"duplicate_failed": "Dashboard dupliceren mislukt",
"duplicate_success": "Dashboard succesvol gedupliceerd!",
"no_dashboards_found": "Geen dashboards gevonden.",
"please_enter_name": "Voer een dashboardnaam in"
}
},
"connect": {
"congrats": "Gefeliciteerd!",
"connection_successful_message": "Goed gedaan! We zijn verbonden.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Vernieuw contacten",
"contacts_table_refresh_success": "Contacten zijn vernieuwd",
"create_attribute": "Attribuut aanmaken",
"create_key": "Sleutel aanmaken",
"create_new_attribute": "Nieuw attribuut aanmaken",
"create_new_attribute_description": "Maak een nieuw attribuut aan voor segmentatiedoeleinden.",
"custom_attributes": "Aangepaste kenmerken",
@@ -656,6 +841,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_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.}}",
"displays": "Weergaven",
"edit_attribute": "Attribuut bewerken",
"edit_attribute_description": "Werk het label en de beschrijving voor dit attribuut bij.",
"edit_attribute_values": "Attributen bewerken",
@@ -667,6 +853,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_date_format": "Ongeldig datumformaat. Gebruik een geldige datum.",
"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_surveys": "Geen gepubliceerde enquêtes",
"no_responses_found": "Geen reacties gevonden",
@@ -681,6 +868,8 @@
"select_a_survey": "Selecteer een enquête",
"select_attribute": "Selecteer Kenmerk",
"select_attribute_key": "Selecteer kenmerksleutel",
"survey_viewed": "Enquête bekeken",
"survey_viewed_at": "Bekeken op",
"system_attributes": "Systeemkenmerken",
"unlock_contacts_description": "Beheer contacten en verstuur gerichte enquêtes",
"unlock_contacts_title": "Ontgrendel contacten met een hoger abonnement",
@@ -752,7 +941,12 @@
"link_google_sheet": "Link Google Spreadsheet",
"link_new_sheet": "Nieuw blad koppelen",
"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_hidden_fields": "Inclusief verborgen velden",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis."
},
"general": {
"ai_data_analysis_enabled": "Dataverrijking & analyse (AI)",
"ai_data_analysis_enabled_description": "AI om meer uit je data te halen, dashboards op te zetten, grafieken, rapporten en meer. Raakt je ervaringsdata aan.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Beheer AI-functies voor deze organisatie.",
"ai_settings_updated_successfully": "AI-instellingen succesvol bijgewerkt",
"ai_smart_tools_enabled": "Slimme functionaliteit (AI)",
"ai_smart_tools_enabled_description": "AI om je te helpen meer te bereiken in minder tijd. Raakt nooit data aan die met Formbricks is verzameld. Wordt alleen gebruikt om bijvoorbeeld enquêtes naar andere talen te vertalen.",
"bulk_invite_warning_description": "Bij het gratis abonnement krijgen alle organisatieleden altijd de rol 'Eigenaar' toegewezen.",
"cannot_delete_only_organization": "Dit is uw enige organisatie. Deze kan niet worden verwijderd. Maak eerst een nieuwe organisatie aan.",
"cannot_leave_only_organization": "U kunt deze organisatie niet verlaten, aangezien dit uw enige organisatie is. Maak eerst een nieuwe organisatie aan.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Voeg een tijdelijke aanduiding toe om aan te geven of er geen waarde is om te onthouden.",
"add_hidden_field_id": "Voeg een verborgen veld-ID toe",
"add_highlight_border": "Markeerrand toevoegen",
"add_highlight_border_description": "Geldt alleen voor in-product enquêtes.",
"add_logic": "Voeg logica toe",
"add_none_of_the_above": "Voeg 'Geen van bovenstaande' toe",
"add_option": "Optie toevoegen",
@@ -1430,7 +1632,6 @@
"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_upgrade_button_text": "Upgrade om follow-ups mogelijk te maken",
"form_styling": "Vorm styling",
"formbricks_sdk_is_not_connected": "Formbricks SDK is niet verbonden",
"four_points": "4 punten",
"heading": "Rubriek",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Reactielimieten, omleidingen en meer.",
"response_options": "Reactieopties",
"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.",
"rows": "Rijen",
"save_and_close": "Opslaan en sluiten",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Deze gratis en open source-enquête is gesloten",
"survey_display_settings": "Enquêteweergave-instellingen",
"survey_placement": "Enquête plaatsing",
"survey_styling": "Vorm styling",
"survey_trigger": "Enquêtetrigger",
"switch_multi_language_on_to_get_started": "Schakel meertaligheid in om te beginnen 👉",
"target_block_not_found": "Doelblok niet gevonden",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Gefilterde reacties (Excel)",
"generating_qr_code": "QR-code genereren",
"impressions": "Indrukken",
"impressions_identified_only": "Alleen weergaven van geïdentificeerde contacten worden getoond",
"impressions_tooltip": "Aantal keren dat de enquête is bekeken.",
"in_app": {
"connection_description": "De enquête wordt getoond aan gebruikers van uw website die voldoen aan de onderstaande criteria",
@@ -1989,6 +2192,7 @@
"last_quarter": "Laatste kwartaal",
"last_year": "Vorig jaar",
"limit": "Beperken",
"no_identified_impressions": "Geen weergaven van geïdentificeerde contacten",
"no_responses_found": "Geen reacties gevonden",
"other_values_found": "Andere waarden gevonden",
"overall": "Algemeen",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Schaalt de koptekst.",
"advanced_styling_field_headline_weight": "Letterdikte kop",
"advanced_styling_field_headline_weight_description": "Maakt koptekst lichter of vetter.",
"advanced_styling_field_height": "Hoogte",
"advanced_styling_field_height": "Minimale hoogte",
"advanced_styling_field_indicator_bg": "Indicatorachtergrond",
"advanced_styling_field_indicator_bg_description": "Kleurt het gevulde deel van de balk.",
"advanced_styling_field_input_border_radius_description": "Rondt de invoerhoeken af.",
"advanced_styling_field_input_font_size_description": "Schaalt de getypte tekst in invoervelden.",
"advanced_styling_field_input_height_description": "Bepaalt de 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_y_description": "Voegt ruimte toe aan de boven- en onderkant.",
"advanced_styling_field_input_placeholder_opacity_description": "Vervaagt de tijdelijke aanwijzingstekst.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Kleurt de getypte tekst in invoervelden.",
"advanced_styling_field_option_bg": "Achtergrond",
"advanced_styling_field_option_bg_description": "Vult de optie-items.",
"advanced_styling_field_option_border": "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_font_size_description": "Schaalt de tekst van optielabels.",
"advanced_styling_field_option_label": "Labelkleur",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Toon 'Powered by Formbricks' handtekening",
"styling_updated_successfully": "Styling succesvol bijgewerkt",
"suggest_colors": "Kleuren voorstellen",
"suggested_colors_applied_please_save": "Voorgestelde kleuren succesvol gegenereerd. Druk op \"Opslaan\" om de wijzigingen te behouden.",
"theme": "Thema",
"theme_settings_description": "Maak een stijlthema voor alle enquêtes. Je kunt aangepaste styling inschakelen voor elke enquête."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Nee, dank je!",
"preview_survey_question_2_headline": "Wil je op de hoogte blijven?",
"preview_survey_question_2_subheader": "Dit is een voorbeeldbeschrijving.",
"preview_survey_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!",
"prioritize_features_description": "Identificeer functies die uw gebruikers het meest en het minst nodig hebben.",
"prioritize_features_name": "Geef prioriteit aan functies",

View File

@@ -133,6 +133,7 @@
"allow": "permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir que os usuários saiam clicando fora da pesquisa",
"an_unknown_error_occurred_while_deleting_table_items": "Ocorreu um erro desconhecido ao deletar {type}s",
"analysis": "Análise",
"and": "E",
"and_response_limit_of": "e limite de resposta de",
"anonymous": "Anônimo",
@@ -149,6 +150,8 @@
"bottom_right": "Canto Inferior Direito",
"cancel": "Cancelar",
"centered_modal": "Modal Centralizado",
"chart": "Gráfico",
"charts": "Gráficos",
"choices": "Escolhas",
"choose_environment": "Escolher ambiente",
"choose_organization": "Escolher organização",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}",
"count_contacts": "{value, plural, one {# contato} other {# contatos} }",
"count_responses": "{value, plural, other {# respostas}}",
"create": "Criar",
"create_new_organization": "Criar nova organização",
"create_segment": "Criar segmento",
"create_survey": "Criar pesquisa",
@@ -187,6 +191,8 @@
"created_by": "Criado por",
"customer_success": "Sucesso do Cliente",
"dark_overlay": "sobreposição escura",
"dashboard": "Painel",
"dashboards": "Painéis",
"date": "Encontro",
"days": "dias",
"default": "Padrão",
@@ -218,13 +224,16 @@
"error": "Erro",
"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_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_title": "Limite de Taxa Excedido",
"expand_rows": "Expandir linhas",
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
"failed_to_load_organizations": "Falha ao carregar organizações",
"failed_to_load_workspaces": "Falha ao carregar projetos",
"filter": "Filtro",
"finish": "Terminar",
"first_name": "Primeiro nome",
"follow_these": "Siga esses",
"formbricks_version": "Versão do Formbricks",
"full_name": "Nome completo",
@@ -236,7 +245,9 @@
"hidden": "Escondido",
"hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos",
"hide": "Ocultar",
"hide_column": "Ocultar coluna",
"id": "ID",
"image": "imagem",
"images": "Imagens",
"import": "importar",
@@ -254,6 +265,7 @@
"key": "Chave",
"label": "Etiqueta",
"language": "Língua",
"last_name": "Sobrenome",
"learn_more": "Saiba mais",
"license_expired": "License Expired",
"light_overlay": "sobreposição leve",
@@ -280,6 +292,7 @@
"move_down": "Descer",
"move_up": "Subir",
"multiple_languages": "Vários idiomas",
"my_product": "meu produto",
"name": "Nome",
"new": "Novo",
"new_version_available": "Formbricks {version} chegou. Atualize agora!",
@@ -303,6 +316,7 @@
"on": "ligado",
"only_one_file_allowed": "É permitido apenas um arquivo",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Apenas proprietários, gerentes e membros com acesso de gerenciamento podem realizar essa ação.",
"open_options": "Abrir opções",
"option_id": "ID da opção",
"option_ids": "IDs da Opção",
"optional": "Opcional",
@@ -428,6 +442,7 @@
"top_right": "Canto Superior Direito",
"try_again": "Tenta de novo",
"type": "Tipo",
"unknown_survey": "Pesquisa desconhecida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "atualizar",
"updated": "atualizado",
@@ -444,6 +459,7 @@
"variables": "Variáveis",
"verified_email": "Email Verificado",
"video": "vídeo",
"view": "Visualizar",
"warning": "Aviso",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Não conseguimos verificar sua licença porque o servidor de licenças está inacessível.",
"webhook": "webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Sua pesquisa seria exibida neste URL.",
"your_survey_would_not_be_shown": "Sua pesquisa não seria exibida."
},
"analysis": {
"charts": {
"OR": "OU",
"add_chart_to_dashboard": "Adicionar gráfico ao painel",
"add_chart_to_dashboard_description": "Selecione um painel para adicionar este gráfico. O gráfico será salvo automaticamente.",
"add_filter": "Adicionar filtro",
"add_to_dashboard": "Adicionar ao painel",
"advanced_chart_builder_config_prompt": "Configure seu gráfico e clique em \"Executar consulta\" para visualizar",
"ai_query_placeholder": "ex: Quantos usuários se cadastraram na semana passada?",
"ai_query_section_description": "Descreva o que você quer ver e deixe a IA construir o gráfico.",
"ai_query_section_title": "Pergunte aos seus dados",
"apply_changes": "Aplicar alterações",
"chart": "Gráfico",
"chart_added_to_dashboard": "Gráfico adicionado ao painel!",
"chart_builder_choose_chart_type": "Escolher tipo de gráfico",
"chart_data": "Dados do gráfico",
"chart_data_tab": "Dados",
"chart_deleted_successfully": "Gráfico excluído com sucesso",
"chart_deletion_error": "Falha ao excluir gráfico",
"chart_duplicated_successfully": "Gráfico duplicado com sucesso",
"chart_duplication_error": "Falha ao duplicar gráfico",
"chart_name": "Nome do gráfico",
"chart_name_placeholder": "Nome do gráfico",
"chart_preview": "Visualização do gráfico",
"chart_render_error": "Algo deu errado ao renderizar este gráfico.",
"chart_saved_successfully": "Gráfico salvo com sucesso!",
"chart_type_area": "Gráfico de área",
"chart_type_bar": "Gráfico de barras",
"chart_type_big_number": "Número grande",
"chart_type_line": "Gráfico de linhas",
"chart_type_not_supported": "Tipo de gráfico \"{{chartType}}\" ainda não suportado",
"chart_type_pie": "Gráfico de pizza",
"chart_updated_successfully": "Gráfico atualizado com sucesso!",
"configure_description": "Modifique o tipo de gráfico e outras configurações para esta visualização.",
"configure_title": "Configurar gráfico",
"configure_type_label": "Tipo de gráfico",
"contains": "contém",
"create_chart": "Criar gráfico",
"create_chart_description": "Use IA para gerar um gráfico ou crie um manualmente.",
"custom_range": "Intervalo personalizado",
"dashboard": "Painel",
"dashboard_select_placeholder": "Selecione um painel",
"data_label": "Dados",
"date_preset_last_30_days": "Últimos 30 dias",
"date_preset_last_7_days": "Últimos 7 dias",
"date_preset_last_month": "Último mês",
"date_preset_this_month": "Este mês",
"date_preset_this_quarter": "Este trimestre",
"date_preset_this_year": "Este ano",
"date_preset_today": "Hoje",
"date_preset_yesterday": "Ontem",
"date_range": "Intervalo de datas",
"delete_chart_confirmation": "Tem certeza de que deseja excluir este gráfico?",
"dimensions": "Dimensões",
"dimensions_toggle_description": "Agrupe dados por categorias. A ordem importa para gráficos multidimensionais.",
"edit_chart_description": "Visualize e edite a configuração do seu gráfico.",
"edit_chart_title": "Editar gráfico",
"enable_time_dimension": "Ativar dimensão de tempo",
"end_date": "Data final",
"enter_a_name_for_your_chart": "Digite um nome para o seu gráfico para salvá-lo.",
"enter_value": "Digite o valor",
"equals": "igual",
"failed_to_add_chart_to_dashboard": "Falha ao adicionar gráfico ao painel",
"failed_to_execute_query": "Falha ao executar consulta",
"failed_to_load_chart": "Falha ao carregar gráfico",
"failed_to_load_chart_data": "Falha ao carregar dados do gráfico",
"failed_to_save_chart": "Falha ao salvar gráfico",
"field": "Campo",
"field_label_average_score": "Pontuação média",
"field_label_collected_at": "Coletado em",
"field_label_count": "Contagem",
"field_label_detractor_count": "Contagem de detratores",
"field_label_emotion": "Emoção",
"field_label_field_type": "Tipo de campo",
"field_label_nps_score": "Pontuação de NPS",
"field_label_nps_value": "Valor de NPS",
"field_label_passive_count": "Contagem de passivos",
"field_label_promoter_count": "Contagem de promotores",
"field_label_response_id": "ID da resposta",
"field_label_sentiment": "Sentimento",
"field_label_source_name": "Nome da fonte",
"field_label_source_type": "Tipo de fonte",
"field_label_topic": "Tópico",
"field_label_user_identifier": "Identificador do usuário",
"filters": "Filtros",
"filters_toggle_description": "Incluir apenas dados que atendam às seguintes condições.",
"generate_chart": "Gerar gráfico",
"granularity": "Granularidade",
"granularity_day": "Dia",
"granularity_hour": "Hora",
"granularity_month": "Mês",
"granularity_quarter": "Trimestre",
"granularity_week": "Semana",
"granularity_year": "Ano",
"greater_than": "maior que",
"greater_than_or_equal": "maior ou igual a",
"group_by": "Agrupar por",
"group_by_description": "Selecione dimensões para detalhar seus dados. A ordem importa para gráficos multidimensionais.",
"guide_button": "Ver guia de campos",
"guide_chart_type": "Tipo de gráfico",
"guide_chart_type_desc": "Como os dados são visualizados: área, barra, linha, pizza ou número grande. Escolha com base no que você deseja mostrar (tendências, comparações, partes de um todo, etc.).",
"guide_dimensions": "Dimensões (agrupar por)",
"guide_dimensions_desc": "Como você divide ou agrupa os dados. Cada dimensão se torna uma categoria no gráfico (ex.: sentimento, tipo de origem, nome da pesquisa, canal, tópico). A ordem importa para gráficos multidimensionais.",
"guide_filters": "Filtros",
"guide_filters_desc": "Condições que limitam quais dados são incluídos. Cada filtro tem um campo, operador (igual a, contém, maior que, etc.) e valores. E = todos devem corresponder; Ou = qualquer um pode corresponder.",
"guide_measures": "Medidas (o que você conta ou agrega)",
"guide_measures_predefined": "Medidas predefinidas são métricas pré-construídas dos seus dados de feedback: contagem (total de respostas), contagem de promotores/detratores/passivos (segmentos NPS), pontuação NPS, pontuação média, taxa de conclusão.",
"guide_quick_ref": "Referência rápida",
"guide_term_dimension": "Campo categórico usado para agrupar ou dividir dados",
"guide_term_filter": "Condição que limita quais linhas são incluídas",
"guide_term_measure": "Valor numérico que você agrega (count, sum, avg, etc.)",
"guide_term_time": "Agrupamento baseado em tempo com granularidade e intervalo de datas",
"guide_time_dimension": "Dimensão de tempo",
"guide_time_dimension_desc": "Agrupamento baseado em tempo: escolha um campo de tempo (geralmente Coletado em), granularidade (Hora, Dia, Semana, Mês, etc.) e intervalo de datas (predefinido ou personalizado). Use para tendências ao longo do tempo.",
"guide_title": "Guia de campos do construtor de gráficos",
"is_not_set": "não está definido",
"is_set": "está definido",
"less_than": "menor que",
"less_than_or_equal": "menor ou igual a",
"measures": "Medidas",
"no_charts_found": "Nenhum gráfico encontrado.",
"no_dashboards_available": "Nenhum painel disponível",
"no_dashboards_create_first": "Crie um painel primeiro para adicionar gráficos a ele.",
"no_data_available": "Nenhum dado disponível",
"no_data_returned": "Nenhum dado retornado da consulta",
"no_data_returned_for_chart": "Nenhum dado retornado para o gráfico",
"no_grouping": "Nenhum (apenas filtro)",
"no_valid_data_to_display": "Nenhum dado válido para exibir",
"not_contains": "não contém",
"not_equals": "diferente de",
"open_chart": "Abrir gráfico {{name}}",
"open_options": "Abrir opções do gráfico",
"or_filter_logic": "OU",
"original": "Original",
"please_enter_chart_name": "Por favor, insira um nome para o gráfico",
"please_select_at_least_one_measure": "Por favor, selecione pelo menos uma medida",
"please_select_dashboard": "Por favor, selecione um painel",
"predefined_measures": "Medidas predefinidas",
"preset": "Predefinição",
"query_executed_successfully": "Consulta executada com sucesso",
"reset_to_ai_suggestion": "Redefinir para sugestão da IA",
"save_chart": "Salvar gráfico",
"save_chart_dialog_title": "Salvar gráfico",
"select_dimensions": "Selecionar dimensões...",
"select_field": "Selecionar campo",
"select_measures": "Selecionar medidas...",
"select_preset": "Selecionar predefinição",
"showing_first_n_of": "Mostrando os primeiros {{n}} de {{count}} registros",
"start_date": "Data inicial",
"time_dimension": "Dimensão temporal",
"time_dimension_toggle_description": "Adicione agrupamento baseado em tempo para visualizar tendências ao longo do tempo."
},
"dashboards": {
"create_dashboard": "Criar painel",
"create_dashboard_description": "Digite um nome para o seu novo painel.",
"create_failed": "Falha ao criar painel",
"create_success": "Painel criado com sucesso!",
"dashboard_name": "Nome do painel",
"dashboard_name_placeholder": "Meu painel",
"delete_confirmation": "Tem certeza de que deseja excluir este painel? Esta ação não pode ser desfeita.",
"delete_failed": "Falha ao excluir painel",
"delete_success": "Painel excluído com sucesso",
"description_optional": "Descrição (opcional)",
"description_placeholder": "Descrição do painel",
"duplicate_failed": "Falha ao duplicar painel",
"duplicate_success": "Painel duplicado com sucesso!",
"no_dashboards_found": "Nenhum painel encontrado.",
"please_enter_name": "Por favor, digite um nome para o painel"
}
},
"connect": {
"congrats": "Parabéns!",
"connection_successful_message": "Mandou bem! Estamos conectados.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Atualizar contatos",
"contacts_table_refresh_success": "Contatos atualizados com sucesso",
"create_attribute": "Criar atributo",
"create_key": "Criar chave",
"create_new_attribute": "Criar novo atributo",
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
"custom_attributes": "Atributos personalizados",
@@ -656,6 +841,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_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.}}",
"displays": "Exibições",
"edit_attribute": "Editar atributo",
"edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.",
"edit_attribute_values": "Editar atributos",
@@ -667,6 +853,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_date_format": "Formato de data inválido. Por favor, use uma data válida.",
"invalid_number_format": "Formato de número inválido. Por favor, insira um número válido.",
"no_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_surveys": "Sem pesquisas publicadas",
"no_responses_found": "Nenhuma resposta encontrada",
@@ -681,6 +868,8 @@
"select_a_survey": "Selecione uma pesquisa",
"select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo",
"survey_viewed": "Pesquisa visualizada",
"survey_viewed_at": "Visualizada em",
"system_attributes": "Atributos do sistema",
"unlock_contacts_description": "Gerencie contatos e envie pesquisas direcionadas",
"unlock_contacts_title": "Desbloqueie contatos com um plano superior",
@@ -752,7 +941,12 @@
"link_google_sheet": "Link da Planilha do Google",
"link_new_sheet": "Vincular nova planilha",
"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_hidden_fields": "Incluir Campos Ocultos",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimento e análise de dados (IA)",
"ai_data_analysis_enabled_description": "IA para extrair mais dos seus dados, configurar dashboards, gráficos, relatórios e muito mais. Acessa os dados da sua experiência.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Gerencie recursos com IA para esta organização.",
"ai_settings_updated_successfully": "Configurações de IA atualizadas com sucesso",
"ai_smart_tools_enabled": "Funcionalidades inteligentes (IA)",
"ai_smart_tools_enabled_description": "IA para ajudar você a conquistar mais em menos tempo. Nunca acessa dados coletados com o Formbricks. Usado apenas para, por exemplo, traduzir pesquisas para outros idiomas.",
"bulk_invite_warning_description": "Por favor, note que no Plano Gratuito, todos os membros da organização são automaticamente atribuídos ao papel de 'Owner', independentemente do papel especificado no arquivo CSV.",
"cannot_delete_only_organization": "Essa é sua única organização, não pode ser deletada. Crie uma nova organização primeiro.",
"cannot_leave_only_organization": "Você não pode sair dessa organização porque é a sua única. Crie uma nova organização primeiro.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Adicionar um texto padrão para mostrar se a pergunta for ignorada:",
"add_hidden_field_id": "Adicionar campo oculto ID",
"add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Aplica-se apenas a pesquisas no produto.",
"add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"",
"add_option": "Adicionar opção",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "Acompanhamento atualizado e será salvo assim que você salvar a pesquisa.",
"follow_ups_new": "Novo acompanhamento",
"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",
"four_points": "4 pontos",
"heading": "Título",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta",
"roundness": "Circularidade",
"roundness_description": "Controla o arredondamento dos cantos do cartão.",
"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.",
"rows": "linhas",
"save_and_close": "Salvar e Fechar",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Essa pesquisa gratuita e de código aberto foi encerrada",
"survey_display_settings": "Configurações de Exibição da Pesquisa",
"survey_placement": "Posicionamento da Pesquisa",
"survey_styling": "Estilização de Formulários",
"survey_trigger": "Gatilho de Pesquisa",
"switch_multi_language_on_to_get_started": "Ative o modo multilíngue para começar 👉",
"target_block_not_found": "Bloco de destino não encontrado",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Respostas filtradas (Excel)",
"generating_qr_code": "Gerando código QR",
"impressions": "Impressões",
"impressions_identified_only": "Mostrando apenas impressões de contatos identificados",
"impressions_tooltip": "Número de vezes que a pesquisa foi visualizada.",
"in_app": {
"connection_description": "A pesquisa será exibida para usuários do seu site, que atendam aos critérios listados abaixo",
@@ -1989,6 +2192,7 @@
"last_quarter": "Último trimestre",
"last_year": "Último ano",
"limit": "Limite",
"no_identified_impressions": "Nenhuma impressão de contatos identificados",
"no_responses_found": "Nenhuma resposta encontrada",
"other_values_found": "Outros valores encontrados",
"overall": "No geral",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.",
"advanced_styling_field_headline_weight": "Peso da fonte do título",
"advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_height": "Altura mínima",
"advanced_styling_field_indicator_bg": "Fundo do indicador",
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.",
"advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.",
"advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.",
"advanced_styling_field_input_height_description": "Controla a altura 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_y_description": "Adiciona espaço na parte superior e inferior.",
"advanced_styling_field_input_placeholder_opacity_description": "Esmaece o texto de dica do placeholder.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.",
"advanced_styling_field_option_bg": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border": "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_font_size_description": "Ajusta o tamanho do texto do rótulo da opção.",
"advanced_styling_field_option_label": "Cor do rótulo",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo atualizado com sucesso",
"suggest_colors": "Sugerir cores",
"suggested_colors_applied_please_save": "Cores sugeridas geradas com sucesso. Pressione \"Salvar\" para manter as alterações.",
"theme": "Tema",
"theme_settings_description": "Crie um tema de estilo para todas as pesquisas. Você pode ativar estilo personalizado para cada pesquisa."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer ficar por dentro?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_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!",
"prioritize_features_description": "Identifique os recursos que seus usuários mais e menos precisam.",
"prioritize_features_name": "Priorizar Funcionalidades",

View File

@@ -133,6 +133,7 @@
"allow": "Permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir que os utilizadores saiam se clicarem 'sair do questionário'",
"an_unknown_error_occurred_while_deleting_table_items": "Ocorreu um erro desconhecido ao eliminar {type}s",
"analysis": "Análise",
"and": "E",
"and_response_limit_of": "e limite de resposta de",
"anonymous": "Anónimo",
@@ -149,6 +150,8 @@
"bottom_right": "Inferior Direito",
"cancel": "Cancelar",
"centered_modal": "Modal Centralizado",
"chart": "Gráfico",
"charts": "Gráficos",
"choices": "Escolhas",
"choose_environment": "Escolha o ambiente",
"choose_organization": "Escolher organização",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} atributo} other {{value} atributos}}",
"count_contacts": "{value, plural, one {# contacto} other {# contactos} }",
"count_responses": "{value, plural, other {# respostas}}",
"create": "Criar",
"create_new_organization": "Criar nova organização",
"create_segment": "Criar segmento",
"create_survey": "Criar inquérito",
@@ -187,6 +191,8 @@
"created_by": "Criado por",
"customer_success": "Sucesso do Cliente",
"dark_overlay": "Sobreposição escura",
"dashboard": "Painel",
"dashboards": "Dashboards",
"date": "Data",
"days": "dias",
"default": "Padrão",
@@ -218,13 +224,16 @@
"error": "Erro",
"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_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_title": "Limite de Taxa Excedido",
"expand_rows": "Expandir linhas",
"failed_to_copy_to_clipboard": "Falha ao copiar para a área de transferência",
"failed_to_load_organizations": "Falha ao carregar organizações",
"failed_to_load_workspaces": "Falha ao carregar projetos",
"filter": "Filtro",
"finish": "Concluir",
"first_name": "Primeiro nome",
"follow_these": "Siga estes",
"formbricks_version": "Versão do Formbricks",
"full_name": "Nome completo",
@@ -236,7 +245,9 @@
"hidden": "Oculto",
"hidden_field": "Campo oculto",
"hidden_fields": "Campos ocultos",
"hide": "Ocultar",
"hide_column": "Ocultar coluna",
"id": "ID",
"image": "Imagem",
"images": "Imagens",
"import": "Importar",
@@ -254,6 +265,7 @@
"key": "Chave",
"label": "Etiqueta",
"language": "Idioma",
"last_name": "Apelido",
"learn_more": "Saiba mais",
"license_expired": "License Expired",
"light_overlay": "Sobreposição leve",
@@ -280,6 +292,7 @@
"move_down": "Mover para baixo",
"move_up": "Mover para cima",
"multiple_languages": "Várias línguas",
"my_product": "o meu produto",
"name": "Nome",
"new": "Novo",
"new_version_available": "Formbricks {version} está aqui. Atualize agora!",
@@ -303,6 +316,7 @@
"on": "Ligado",
"only_one_file_allowed": "Apenas um ficheiro é permitido",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Apenas proprietários e gestores podem realizar esta ação.",
"open_options": "Abrir opções",
"option_id": "ID de Opção",
"option_ids": "IDs de Opção",
"optional": "Opcional",
@@ -428,6 +442,7 @@
"top_right": "Superior Direito",
"try_again": "Tente novamente",
"type": "Tipo",
"unknown_survey": "Inquérito desconhecido",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "Atualizar",
"updated": "Atualizado",
@@ -444,6 +459,7 @@
"variables": "Variáveis",
"verified_email": "Email verificado",
"video": "Vídeo",
"view": "Ver",
"warning": "Aviso",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Não foi possível verificar a sua licença porque o servidor de licenças está inacessível.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "O seu inquérito seria mostrado neste URL.",
"your_survey_would_not_be_shown": "O seu inquérito não seria mostrado."
},
"analysis": {
"charts": {
"OR": "OU",
"add_chart_to_dashboard": "Adicionar gráfico ao painel",
"add_chart_to_dashboard_description": "Seleciona um painel para adicionar este gráfico. O gráfico será guardado automaticamente.",
"add_filter": "Adicionar filtro",
"add_to_dashboard": "Adicionar ao painel",
"advanced_chart_builder_config_prompt": "Configura o teu gráfico e clica em \"Executar consulta\" para pré-visualizar",
"ai_query_placeholder": "ex: Quantos utilizadores se registaram na semana passada?",
"ai_query_section_description": "Descreve o que queres ver e deixa a IA construir o gráfico.",
"ai_query_section_title": "Pergunta aos teus dados",
"apply_changes": "Aplicar alterações",
"chart": "Gráfico",
"chart_added_to_dashboard": "Gráfico adicionado ao painel!",
"chart_builder_choose_chart_type": "Escolher tipo de gráfico",
"chart_data": "Dados do gráfico",
"chart_data_tab": "Dados",
"chart_deleted_successfully": "Gráfico eliminado com sucesso",
"chart_deletion_error": "Falha ao eliminar gráfico",
"chart_duplicated_successfully": "Gráfico duplicado com sucesso",
"chart_duplication_error": "Falha ao duplicar gráfico",
"chart_name": "Nome do gráfico",
"chart_name_placeholder": "Nome do gráfico",
"chart_preview": "Pré-visualização do gráfico",
"chart_render_error": "Algo correu mal ao renderizar este gráfico.",
"chart_saved_successfully": "Gráfico guardado com sucesso!",
"chart_type_area": "Gráfico de área",
"chart_type_bar": "Gráfico de barras",
"chart_type_big_number": "Número grande",
"chart_type_line": "Gráfico de linhas",
"chart_type_not_supported": "Tipo de gráfico \"{{chartType}}\" ainda não suportado",
"chart_type_pie": "Gráfico circular",
"chart_updated_successfully": "Gráfico atualizado com sucesso!",
"configure_description": "Modifique o tipo de gráfico e outras definições para esta visualização.",
"configure_title": "Configurar gráfico",
"configure_type_label": "Tipo de gráfico",
"contains": "contém",
"create_chart": "Criar gráfico",
"create_chart_description": "Use IA para gerar um gráfico ou crie um manualmente.",
"custom_range": "Intervalo personalizado",
"dashboard": "Painel",
"dashboard_select_placeholder": "Selecione um painel",
"data_label": "Dados",
"date_preset_last_30_days": "Últimos 30 dias",
"date_preset_last_7_days": "Últimos 7 dias",
"date_preset_last_month": "Último mês",
"date_preset_this_month": "Este mês",
"date_preset_this_quarter": "Este trimestre",
"date_preset_this_year": "Este ano",
"date_preset_today": "Hoje",
"date_preset_yesterday": "Ontem",
"date_range": "Intervalo de datas",
"delete_chart_confirmation": "Tens a certeza de que queres eliminar este gráfico?",
"dimensions": "Dimensões",
"dimensions_toggle_description": "Agrupe dados por categorias. A ordem é importante para gráficos multidimensionais.",
"edit_chart_description": "Visualize e edite a configuração do seu gráfico.",
"edit_chart_title": "Editar gráfico",
"enable_time_dimension": "Ativar dimensão temporal",
"end_date": "Data de fim",
"enter_a_name_for_your_chart": "Introduza um nome para o seu gráfico para o guardar.",
"enter_value": "Introduza o valor",
"equals": "igual",
"failed_to_add_chart_to_dashboard": "Falha ao adicionar gráfico ao painel",
"failed_to_execute_query": "Falha ao executar consulta",
"failed_to_load_chart": "Falha ao carregar gráfico",
"failed_to_load_chart_data": "Falha ao carregar dados do gráfico",
"failed_to_save_chart": "Falha ao guardar gráfico",
"field": "Campo",
"field_label_average_score": "Pontuação média",
"field_label_collected_at": "Recolhido em",
"field_label_count": "Contagem",
"field_label_detractor_count": "Contagem de detratores",
"field_label_emotion": "Emoção",
"field_label_field_type": "Tipo de campo",
"field_label_nps_score": "Pontuação NPS",
"field_label_nps_value": "Valor NPS",
"field_label_passive_count": "Contagem de passivos",
"field_label_promoter_count": "Contagem de promotores",
"field_label_response_id": "ID de resposta",
"field_label_sentiment": "Sentimento",
"field_label_source_name": "Nome da origem",
"field_label_source_type": "Tipo de origem",
"field_label_topic": "Tópico",
"field_label_user_identifier": "Identificador de utilizador",
"filters": "Filtros",
"filters_toggle_description": "Incluir apenas dados que cumpram as seguintes condições.",
"generate_chart": "Gerar gráfico",
"granularity": "Granularidade",
"granularity_day": "Dia",
"granularity_hour": "Hora",
"granularity_month": "Mês",
"granularity_quarter": "Trimestre",
"granularity_week": "Semana",
"granularity_year": "Ano",
"greater_than": "maior que",
"greater_than_or_equal": "maior ou igual a",
"group_by": "Agrupar por",
"group_by_description": "Selecione dimensões para desagregar os seus dados. A ordem é importante para gráficos multidimensionais.",
"guide_button": "Ver guia de campos",
"guide_chart_type": "Tipo de gráfico",
"guide_chart_type_desc": "Como os dados são visualizados: área, barras, linhas, circular ou número grande. Escolha com base no que pretende mostrar (tendências, comparações, partes de um todo, etc.).",
"guide_dimensions": "Dimensões (agrupar por)",
"guide_dimensions_desc": "Como divide ou agrupa os dados. Cada dimensão torna-se uma categoria no gráfico (por exemplo, sentimento, tipo de origem, nome do inquérito, canal, tópico). A ordem é importante para gráficos multidimensionais.",
"guide_filters": "Filtros",
"guide_filters_desc": "Condições que limitam quais dados são incluídos. Cada filtro tem um campo, operador (igual, contém, maior que, etc.) e valores. E = todos devem corresponder; Ou = qualquer um pode corresponder.",
"guide_measures": "Medidas (o que conta ou agrega)",
"guide_measures_predefined": "As medidas predefinidas são métricas pré-construídas a partir dos seus dados de feedback: contagem (total de respostas), contagem de promotores/detratores/passivos (segmentos NPS), pontuação NPS, pontuação média, taxa de conclusão.",
"guide_quick_ref": "Referência rápida",
"guide_term_dimension": "Campo categórico usado para agrupar ou dividir dados",
"guide_term_filter": "Condição que limita quais linhas são incluídas",
"guide_term_measure": "Valor numérico que agrega (contar, somar, média, etc.)",
"guide_term_time": "Agrupamento baseado em tempo com granularidade e intervalo de datas",
"guide_time_dimension": "Dimensão temporal",
"guide_time_dimension_desc": "Agrupamento baseado em tempo: escolha um campo temporal (geralmente Recolhido em), granularidade (Hora, Dia, Semana, Mês, etc.) e intervalo de datas (predefinido ou personalizado). Use para tendências ao longo do tempo.",
"guide_title": "Guia de campos do criador de gráficos",
"is_not_set": "não está definido",
"is_set": "está definido",
"less_than": "menor que",
"less_than_or_equal": "menor ou igual a",
"measures": "Medidas",
"no_charts_found": "Nenhum gráfico encontrado.",
"no_dashboards_available": "Nenhum painel disponível",
"no_dashboards_create_first": "Cria primeiro um painel para adicionar gráficos.",
"no_data_available": "Nenhum dado disponível",
"no_data_returned": "Nenhum dado devolvido pela consulta",
"no_data_returned_for_chart": "Nenhum dado devolvido para o gráfico",
"no_grouping": "Nenhum (apenas filtro)",
"no_valid_data_to_display": "Nenhum dado válido para exibir",
"not_contains": "não contém",
"not_equals": "não é igual a",
"open_chart": "Abrir gráfico {{name}}",
"open_options": "Abrir opções do gráfico",
"or_filter_logic": "OU",
"original": "Original",
"please_enter_chart_name": "Por favor, introduz um nome para o gráfico",
"please_select_at_least_one_measure": "Por favor, seleciona pelo menos uma medida",
"please_select_dashboard": "Por favor, seleciona um painel",
"predefined_measures": "Medidas predefinidas",
"preset": "Predefinição",
"query_executed_successfully": "Consulta executada com sucesso",
"reset_to_ai_suggestion": "Repor sugestão da IA",
"save_chart": "Guardar gráfico",
"save_chart_dialog_title": "Guardar gráfico",
"select_dimensions": "Selecionar dimensões...",
"select_field": "Selecionar campo",
"select_measures": "Selecionar medidas...",
"select_preset": "Selecionar predefinição",
"showing_first_n_of": "A mostrar as primeiras {{n}} de {{count}} linhas",
"start_date": "Data de início",
"time_dimension": "Dimensão temporal",
"time_dimension_toggle_description": "Adicionar agrupamento temporal para tendências ao longo do tempo."
},
"dashboards": {
"create_dashboard": "Criar painel",
"create_dashboard_description": "Introduza um nome para o seu novo painel.",
"create_failed": "Falha ao criar painel",
"create_success": "Painel criado com sucesso!",
"dashboard_name": "Nome do painel",
"dashboard_name_placeholder": "O meu painel",
"delete_confirmation": "Tem a certeza de que pretende eliminar este painel? Esta ação não pode ser revertida.",
"delete_failed": "Falha ao eliminar painel",
"delete_success": "Painel eliminado com sucesso",
"description_optional": "Descrição (opcional)",
"description_placeholder": "Descrição do painel",
"duplicate_failed": "Falha ao duplicar painel",
"duplicate_success": "Painel duplicado com sucesso!",
"no_dashboards_found": "Nenhum painel encontrado.",
"please_enter_name": "Por favor, introduza um nome para o painel"
}
},
"connect": {
"congrats": "Parabéns!",
"connection_successful_message": "Muito bem! Estamos ligados.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Atualizar contactos",
"contacts_table_refresh_success": "Contactos atualizados com sucesso",
"create_attribute": "Criar atributo",
"create_key": "Criar chave",
"create_new_attribute": "Criar novo atributo",
"create_new_attribute_description": "Crie um novo atributo para fins de segmentação.",
"custom_attributes": "Atributos personalizados",
@@ -656,6 +841,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_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.}}",
"displays": "Visualizações",
"edit_attribute": "Editar atributo",
"edit_attribute_description": "Atualize a etiqueta e a descrição deste atributo.",
"edit_attribute_values": "Editar atributos",
@@ -667,6 +853,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_date_format": "Formato de data inválido. Por favor, usa uma data válida.",
"invalid_number_format": "Formato de número inválido. Por favor, introduz um número válido.",
"no_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_surveys": "Sem inquéritos publicados",
"no_responses_found": "Nenhuma resposta encontrada",
@@ -681,6 +868,8 @@
"select_a_survey": "Selecione um inquérito",
"select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo",
"survey_viewed": "Inquérito visualizado",
"survey_viewed_at": "Visualizado em",
"system_attributes": "Atributos do sistema",
"unlock_contacts_description": "Gerir contactos e enviar inquéritos direcionados",
"unlock_contacts_title": "Desbloqueie os contactos com um plano superior",
@@ -752,7 +941,12 @@
"link_google_sheet": "Ligar Folha do Google",
"link_new_sheet": "Ligar nova Folha",
"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_hidden_fields": "Incluir Campos Ocultos",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimento e análise de dados (IA)",
"ai_data_analysis_enabled_description": "IA para tirar mais partido dos teus dados, configurar dashboards, gráficos, relatórios e muito mais. Acede aos dados da tua experiência.",
"ai_enabled": "IA da Formbricks",
"ai_enabled_description": "Gerir funcionalidades com IA para esta organização.",
"ai_settings_updated_successfully": "Definições de IA atualizadas com sucesso",
"ai_smart_tools_enabled": "Funcionalidade inteligente (IA)",
"ai_smart_tools_enabled_description": "IA para te ajudar a alcançar mais em menos tempo. Nunca acede aos dados recolhidos com o Formbricks. Apenas usado para, por exemplo, traduzir inquéritos para outros idiomas.",
"bulk_invite_warning_description": "No plano gratuito, todos os membros da organização são sempre atribuídos ao papel de \"Proprietário\".",
"cannot_delete_only_organization": "Esta é a sua única organização, não pode ser eliminada. Crie uma nova organização primeiro.",
"cannot_leave_only_organization": "Não pode sair desta organização, pois é a sua única organização. Crie uma nova organização primeiro.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Adicionar um espaço reservado para mostrar se não houver valor para recordar.",
"add_hidden_field_id": "Adicionar ID do campo oculto",
"add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Aplica-se apenas a inquéritos no produto.",
"add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"",
"add_option": "Adicionar opção",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "Seguimento atualizado e será guardado assim que guardar o questionário.",
"follow_ups_new": "Novo acompanhamento",
"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",
"four_points": "4 pontos",
"heading": "Cabeçalho",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta",
"roundness": "Arredondamento",
"roundness_description": "Controla o arredondamento dos cantos do cartão.",
"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.",
"rows": "Linhas",
"save_and_close": "Guardar e Fechar",
@@ -1649,6 +1850,7 @@
"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_placement": "Colocação do Inquérito",
"survey_styling": "Estilo do formulário",
"survey_trigger": "Desencadeador de Inquérito",
"switch_multi_language_on_to_get_started": "Ative o modo multilingue para começar 👉",
"target_block_not_found": "Bloco de destino não encontrado",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Respostas filtradas (Excel)",
"generating_qr_code": "A gerar código QR",
"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.",
"in_app": {
"connection_description": "O questionário será exibido aos utilizadores do seu website que correspondam aos critérios listados abaixo",
@@ -1989,6 +2192,7 @@
"last_quarter": "Último trimestre",
"last_year": "Ano passado",
"limit": "Limite",
"no_identified_impressions": "Sem impressões de contactos identificados",
"no_responses_found": "Nenhuma resposta encontrada",
"other_values_found": "Outros valores encontrados",
"overall": "Geral",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.",
"advanced_styling_field_headline_weight": "Peso da fonte do título",
"advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_height": "Altura mínima",
"advanced_styling_field_indicator_bg": "Fundo do indicador",
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.",
"advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.",
"advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.",
"advanced_styling_field_input_height_description": "Controla a altura 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_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.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.",
"advanced_styling_field_option_bg": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border": "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_font_size_description": "Ajusta o tamanho do texto da etiqueta da opção.",
"advanced_styling_field_option_label": "Cor da etiqueta",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo atualizado com sucesso",
"suggest_colors": "Sugerir cores",
"suggested_colors_applied_please_save": "Cores sugeridas geradas com sucesso. Pressiona \"Guardar\" para manter as alterações.",
"theme": "Tema",
"theme_settings_description": "Crie um tema de estilo para todos os inquéritos. Pode ativar estilos personalizados para cada inquérito."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer manter-se atualizado?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_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!",
"prioritize_features_description": "Identifique as funcionalidades que os seus utilizadores precisam mais e menos.",
"prioritize_features_name": "Priorizar Funcionalidades",

View File

@@ -133,6 +133,7 @@
"allow": "Permite",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permite utilizatorilor să iasă făcând clic în afara sondajului",
"an_unknown_error_occurred_while_deleting_table_items": "A apărut o eroare necunoscută la ștergerea elementelor de tipul {type}",
"analysis": "Analiză",
"and": "Și",
"and_response_limit_of": "și limită răspuns",
"anonymous": "Anonim",
@@ -149,6 +150,8 @@
"bottom_right": "Dreapta Jos",
"cancel": "Anulare",
"centered_modal": "Modală centralizată",
"chart": "Grafic",
"charts": "Grafice",
"choices": "Alegeri",
"choose_environment": "Alege mediul",
"choose_organization": "Alege organizația",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} atribut} few {{value} atribute} other {{value} de atribute}}",
"count_contacts": "{value, plural, one {# contact} other {# contacte} }",
"count_responses": "{value, plural, one {# răspuns} other {# răspunsuri} }",
"create": "Creează",
"create_new_organization": "Creează organizație nouă",
"create_segment": "Creați segment",
"create_survey": "Creează sondaj",
@@ -187,6 +191,8 @@
"created_by": "Creat de",
"customer_success": "Succesul Clientului",
"dark_overlay": "Suprapunere întunecată",
"dashboard": "Tablou de bord",
"dashboards": "Tablouri de bord",
"date": "Dată",
"days": "zile",
"default": "Implicit",
@@ -218,13 +224,16 @@
"error": "Eroare",
"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_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_title": "Limită de cereri depășită",
"expand_rows": "Extinde rândurile",
"failed_to_copy_to_clipboard": "Nu s-a reușit copierea în clipboard",
"failed_to_load_organizations": "Nu s-a reușit încărcarea organizațiilor",
"failed_to_load_workspaces": "Nu s-au putut încărca workspaces",
"filter": "Filtru",
"finish": "Finalizează",
"first_name": "Prenume",
"follow_these": "Urmați acestea",
"formbricks_version": "Versiunea Formbricks",
"full_name": "Nume complet",
@@ -236,7 +245,9 @@
"hidden": "Ascuns",
"hidden_field": "Câmp ascuns",
"hidden_fields": "Câmpuri ascunse",
"hide": "Ascunde",
"hide_column": "Ascunde coloana",
"id": "ID",
"image": "Imagine",
"images": "Imagini",
"import": "Import",
@@ -254,6 +265,7 @@
"key": "Cheie",
"label": "Etichetă",
"language": "Limba",
"last_name": "Nume de familie",
"learn_more": "Află mai multe",
"license_expired": "License Expired",
"light_overlay": "Suprapunere ușoară",
@@ -280,6 +292,7 @@
"move_down": "Mută în jos",
"move_up": "Mută sus",
"multiple_languages": "Mai multe limbi",
"my_product": "produsul meu",
"name": "Nume",
"new": "Nou",
"new_version_available": "Formbricks {version} este disponibil. Actualizați acum!",
@@ -303,6 +316,7 @@
"on": "Pe",
"only_one_file_allowed": "Este permis doar un fișier",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Doar proprietarii și managerii pot efectua această acțiune.",
"open_options": "Deschide opțiunile",
"option_id": "ID opțiune",
"option_ids": "ID-uri opțiuni",
"optional": "Opțional",
@@ -428,6 +442,7 @@
"top_right": "Dreapta Sus",
"try_again": "Încearcă din nou",
"type": "Tip",
"unknown_survey": "Chestionar necunoscut",
"unlock_more_workspaces_with_a_higher_plan": "Deblochează mai multe workspaces cu un plan superior.",
"update": "Actualizare",
"updated": "Actualizat",
@@ -444,6 +459,7 @@
"variables": "Variante",
"verified_email": "Email verificat",
"video": "Video",
"view": "Vezi",
"warning": "Avertisment",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Nu am putut verifica licența dvs. deoarece serverul de licențe este inaccesibil.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Sondajul dumneavoastră ar fi afișat pe acest URL.",
"your_survey_would_not_be_shown": "Sondajul dumneavoastră nu va fi afișat."
},
"analysis": {
"charts": {
"OR": "SAU",
"add_chart_to_dashboard": "Adaugă grafic la Tablou de Bord",
"add_chart_to_dashboard_description": "Selectează un tablou de bord la care să adaugi acest grafic. Graficul va fi salvat automat.",
"add_filter": "Adaugă filtru",
"add_to_dashboard": "Adaugă la Tablou de Bord",
"advanced_chart_builder_config_prompt": "Configurează graficul și apasă pe \"Rulează interogarea\" pentru previzualizare",
"ai_query_placeholder": "ex: Câți utilizatori s-au înscris săptămâna trecută?",
"ai_query_section_description": "Descrie ce vrei să vezi și lasă AI-ul să construiască graficul.",
"ai_query_section_title": "Întreabă-ți datele",
"apply_changes": "Aplică modificările",
"chart": "Grafic",
"chart_added_to_dashboard": "Grafic adăugat la tablou de bord!",
"chart_builder_choose_chart_type": "Alege tipul de grafic",
"chart_data": "Datele graficului",
"chart_data_tab": "Date",
"chart_deleted_successfully": "Graficul a fost șters cu succes",
"chart_deletion_error": "Nu s-a putut șterge graficul",
"chart_duplicated_successfully": "Graficul a fost duplicat cu succes",
"chart_duplication_error": "Nu s-a putut duplica graficul",
"chart_name": "Numele graficului",
"chart_name_placeholder": "Numele graficului",
"chart_preview": "Previzualizare grafic",
"chart_render_error": "Ceva nu a mers bine la afișarea acestui grafic.",
"chart_saved_successfully": "Graficul a fost salvat cu succes!",
"chart_type_area": "Grafic de tip arie",
"chart_type_bar": "Grafic de tip bară",
"chart_type_big_number": "Număr mare",
"chart_type_line": "Grafic de tip linie",
"chart_type_not_supported": "Tipul de grafic \"{{chartType}}\" nu este încă suportat",
"chart_type_pie": "Grafic de tip plăcintă",
"chart_updated_successfully": "Graficul a fost actualizat cu succes!",
"configure_description": "Modifică tipul graficului și alte setări pentru această vizualizare.",
"configure_title": "Configurează graficul",
"configure_type_label": "Tip grafic",
"contains": "conține",
"create_chart": "Creează grafic",
"create_chart_description": "Folosește AI pentru a genera un grafic sau creează unul manual.",
"custom_range": "Interval personalizat",
"dashboard": "Tablou de bord",
"dashboard_select_placeholder": "Selectează un tablou de bord",
"data_label": "Date",
"date_preset_last_30_days": "Ultimele 30 de zile",
"date_preset_last_7_days": "Ultimele 7 zile",
"date_preset_last_month": "Ultima lună",
"date_preset_this_month": "Luna aceasta",
"date_preset_this_quarter": "Trimestrul acesta",
"date_preset_this_year": "Anul acesta",
"date_preset_today": "Astăzi",
"date_preset_yesterday": "Ieri",
"date_range": "Interval de date",
"delete_chart_confirmation": "Ești sigur că vrei să ștergi acest grafic?",
"dimensions": "Dimensiuni",
"dimensions_toggle_description": "Grupează datele pe categorii. Ordinea contează pentru graficele multidimensionale.",
"edit_chart_description": "Vezi și editează configurația graficului tău.",
"edit_chart_title": "Editează graficul",
"enable_time_dimension": "Activează dimensiunea de timp",
"end_date": "Data de sfârșit",
"enter_a_name_for_your_chart": "Introdu un nume pentru grafic ca să îl salvezi.",
"enter_value": "Introdu valoarea",
"equals": "egal",
"failed_to_add_chart_to_dashboard": "Nu s-a putut adăuga graficul în tablou de bord",
"failed_to_execute_query": "Nu s-a putut executa interogarea",
"failed_to_load_chart": "Nu s-a putut încărca graficul",
"failed_to_load_chart_data": "Nu s-au putut încărca datele graficului",
"failed_to_save_chart": "Nu s-a putut salva graficul",
"field": "Câmp",
"field_label_average_score": "Scor mediu",
"field_label_collected_at": "Colectat la",
"field_label_count": "Număr",
"field_label_detractor_count": "Număr de detractori",
"field_label_emotion": "Emoție",
"field_label_field_type": "Tip câmp",
"field_label_nps_score": "Scor NPS",
"field_label_nps_value": "Valoare NPS",
"field_label_passive_count": "Număr de pasivi",
"field_label_promoter_count": "Număr de promotori",
"field_label_response_id": "ID răspuns",
"field_label_sentiment": "Sentiment",
"field_label_source_name": "Nume sursă",
"field_label_source_type": "Tip sursă",
"field_label_topic": "Subiect",
"field_label_user_identifier": "Identificator utilizator",
"filters": "Filtre",
"filters_toggle_description": "Include doar datele care îndeplinesc următoarele condiții.",
"generate_chart": "Generează grafic",
"granularity": "Granularitate",
"granularity_day": "Zi",
"granularity_hour": "Oră",
"granularity_month": "Lună",
"granularity_quarter": "Trimestru",
"granularity_week": "Săptămână",
"granularity_year": "An",
"greater_than": "mai mare decât",
"greater_than_or_equal": "mai mare sau egal cu",
"group_by": "Grupează după",
"group_by_description": "Selectează dimensiunile după care să împarți datele. Ordinea contează pentru graficele multidimensionale.",
"guide_button": "Vezi ghidul de câmpuri",
"guide_chart_type": "Tip grafic",
"guide_chart_type_desc": "Cum sunt vizualizate datele: Zonă, Bară, Linie, Plăcintă sau Număr mare. Alege în funcție de ce vrei să arăți (tendințe, comparații, părți dintr-un întreg etc.).",
"guide_dimensions": "Dimensiuni (Grupează după)",
"guide_dimensions_desc": "Cum împarți sau grupezi datele. Fiecare dimensiune devine o categorie pe grafic (ex: Sentiment, Tip sursă, Nume sondaj, Canal, Subiect). Ordinea contează pentru graficele multidimensionale.",
"guide_filters": "Filtre",
"guide_filters_desc": "Condiții care limitează ce date sunt incluse. Fiecare filtru are un câmp, un operator (egal, conține, mai mare decât etc.) și valori. Și = toate trebuie să se potrivească; Sau = oricare poate să se potrivească.",
"guide_measures": "Măsuri (ce numeri sau agregi)",
"guide_measures_predefined": "Măsurile predefinite sunt metrici gata făcute din datele tale de feedback: Număr (răspunsuri totale), Număr Promoter/Detractor/Passive (segmente NPS), Scor NPS, Scor mediu, Rată de completare.",
"guide_quick_ref": "Referință rapidă",
"guide_term_dimension": "Câmp categorial folosit pentru a grupa sau împărți datele",
"guide_term_filter": "Condiție care limitează ce rânduri sunt incluse",
"guide_term_measure": "Valoare numerică pe care o agregi (count, sum, avg etc.)",
"guide_term_time": "Grupare pe bază de timp cu granularitate și interval de date",
"guide_time_dimension": "Dimensiune de timp",
"guide_time_dimension_desc": "Grupare pe bază de timp: alege un câmp de timp (de obicei Colectat la), granularitate (Oră, Zi, Săptămână, Lună etc.) și interval de date (presetat sau personalizat). Folosește pentru a vedea tendințe în timp.",
"guide_title": "Ghid pentru crearea graficelor",
"is_not_set": "nu este setat",
"is_set": "este setat",
"less_than": "mai mic decât",
"less_than_or_equal": "mai mic sau egal",
"measures": "Măsurători",
"no_charts_found": "Nu s-au găsit grafice.",
"no_dashboards_available": "Nu există tablouri de bord disponibile",
"no_dashboards_create_first": "Creează mai întâi un tablou de bord pentru a adăuga grafice.",
"no_data_available": "Nu există date disponibile",
"no_data_returned": "Nu au fost returnate date din interogare",
"no_data_returned_for_chart": "Nu au fost returnate date pentru grafic",
"no_grouping": "Niciuna (doar filtru)",
"no_valid_data_to_display": "Nu există date valide de afișat",
"not_contains": "nu conține",
"not_equals": "nu este egal",
"open_chart": "Deschide graficul {{name}}",
"open_options": "Deschide opțiunile graficului",
"or_filter_logic": "SAU",
"original": "Original",
"please_enter_chart_name": "Te rugăm să introduci un nume pentru grafic",
"please_select_at_least_one_measure": "Te rugăm să selectezi cel puțin o măsurătoare",
"please_select_dashboard": "Te rugăm să selectezi un tablou de bord",
"predefined_measures": "Măsurători predefinite",
"preset": "Presetare",
"query_executed_successfully": "Interogarea a fost executată cu succes",
"reset_to_ai_suggestion": "Resetează la sugestia AI",
"save_chart": "Salvează graficul",
"save_chart_dialog_title": "Salvează graficul",
"select_dimensions": "Selectează dimensiuni...",
"select_field": "Selectează câmpul",
"select_measures": "Selectează măsuri...",
"select_preset": "Selectează presetarea",
"showing_first_n_of": "Se afișează primele {{n}} din {{count}} rânduri",
"start_date": "Data de început",
"time_dimension": "Dimensiune temporală",
"time_dimension_toggle_description": "Adaugă grupare pe bază de timp pentru tendințe în timp."
},
"dashboards": {
"create_dashboard": "Creează tablou de bord",
"create_dashboard_description": "Introdu un nume pentru noul tău tablou de bord.",
"create_failed": "Crearea tabloului de bord a eșuat",
"create_success": "Tablou de bord creat cu succes!",
"dashboard_name": "Nume tablou de bord",
"dashboard_name_placeholder": "Tabloul meu de bord",
"delete_confirmation": "Ești sigur că vrei să ștergi acest tablou de bord? Această acțiune nu poate fi anulată.",
"delete_failed": "Ștergerea tabloului de bord a eșuat",
"delete_success": "Tablou de bord șters cu succes",
"description_optional": "Descriere (opțional)",
"description_placeholder": "Descriere tablou de bord",
"duplicate_failed": "Duplicarea tabloului de bord a eșuat",
"duplicate_success": "Tablou de bord duplicat cu succes!",
"no_dashboards_found": "Nu s-a găsit niciun tablou de bord.",
"please_enter_name": "Te rugăm să introduci un nume pentru tablou de bord"
}
},
"connect": {
"congrats": "Felicitări!",
"connection_successful_message": "Bravo! Suntem conectați.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Reîmprospătare contacte",
"contacts_table_refresh_success": "Contactele au fost actualizate cu succes",
"create_attribute": "Creează atribut",
"create_key": "Creează cheie",
"create_new_attribute": "Creează atribut nou",
"create_new_attribute_description": "Creează un atribut nou pentru segmentare.",
"custom_attributes": "Atribute personalizate",
@@ -656,6 +841,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_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.} }",
"displays": "Afișări",
"edit_attribute": "Editează atributul",
"edit_attribute_description": "Actualizează eticheta și descrierea acestui atribut.",
"edit_attribute_values": "Editează atributele",
@@ -667,6 +853,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_date_format": "Format de dată invalid. Te rugăm să folosești o dată validă.",
"invalid_number_format": "Format de număr invalid. Te rugăm să introduci un număr valid.",
"no_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_surveys": "Nu există sondaje publicate",
"no_responses_found": "Nu s-au găsit răspunsuri",
@@ -681,6 +868,8 @@
"select_a_survey": "Selectați un sondaj",
"select_attribute": "Selectează atributul",
"select_attribute_key": "Selectează cheia atributului",
"survey_viewed": "Chestionar vizualizat",
"survey_viewed_at": "Vizualizat la",
"system_attributes": "Atribute de sistem",
"unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite",
"unlock_contacts_title": "Deblocați contactele cu un plan superior.",
@@ -752,7 +941,12 @@
"link_google_sheet": "Leagă Google 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. ⏲️",
"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_hidden_fields": "Include câmpuri ascunse",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile."
},
"general": {
"ai_data_analysis_enabled": "Îmbogățire și analiză de date (AI)",
"ai_data_analysis_enabled_description": "AI pentru a obține mai mult din datele tale, configurare dashboard-uri, grafice, rapoarte și multe altele. Accesează datele tale de experiență.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Gestionează funcționalitățile bazate pe AI pentru această organizație.",
"ai_settings_updated_successfully": "Setările AI au fost actualizate cu succes",
"ai_smart_tools_enabled": "Funcționalitate inteligentă (AI)",
"ai_smart_tools_enabled_description": "AI care te ajută să faci mai mult în mai puțin timp. Nu accesează niciodată datele colectate cu Formbricks. Folosit doar, de exemplu, pentru a traduce chestionare în alte limbi.",
"bulk_invite_warning_description": "În planul gratuit, toți membrii organizației sunt întotdeauna alocați rolului „Proprietar”.",
"cannot_delete_only_organization": "Aceasta este singura ta organizație, nu poate fi ștearsă. Creează mai întâi o nouă organizație.",
"cannot_leave_only_organization": "Nu poți părăsi această organizație deoarece este singura ta organizație. Creează mai întâi o nouă organizație.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Adaugă un placeholder pentru a afișa dacă nu există valoare de reamintit",
"add_hidden_field_id": "Adăugați ID câmp ascuns",
"add_highlight_border": "Adaugă bordură evidențiată",
"add_highlight_border_description": "Se aplică doar sondajelor din produs.",
"add_logic": "Adaugă logică",
"add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"",
"add_option": "Adăugați opțiune",
@@ -1430,7 +1632,6 @@
"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_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",
"four_points": "4 puncte",
"heading": "Titlu",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.",
"response_options": "Opțiuni răspuns",
"roundness": "Rotunjire",
"roundness_description": "Controlează cât de rotunjite sunt colțurile cardului.",
"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.",
"rows": "Rânduri",
"save_and_close": "Salvează & Închide",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Acest sondaj gratuit și open-source a fost închis",
"survey_display_settings": "Setări de afișare a sondajului",
"survey_placement": "Amplasarea sondajului",
"survey_styling": "Stilizare formular",
"survey_trigger": "Declanșator sondaj",
"switch_multi_language_on_to_get_started": "Activați opțiunea multi-limbă pentru a începe 👉",
"target_block_not_found": "Blocul țintă nu a fost găsit",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Răspunsuri filtrate (Excel)",
"generating_qr_code": "Se generează codul QR",
"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.",
"in_app": {
"connection_description": "Sondajul va fi afișat utilizatorilor site-ului dvs. web, care îndeplinesc criteriile enumerate mai jos",
@@ -1989,6 +2192,7 @@
"last_quarter": "Ultimul trimestru",
"last_year": "Anul trecut",
"limit": "Limită",
"no_identified_impressions": "Nicio impresie de la contactele identificate",
"no_responses_found": "Nu s-au găsit răspunsuri",
"other_values_found": "Alte valori găsite",
"overall": "General",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Scalează textul titlului.",
"advanced_styling_field_headline_weight": "Grosime font titlu",
"advanced_styling_field_headline_weight_description": "Face textul titlului mai subțire sau mai îngroșat.",
"advanced_styling_field_height": "Înălțime",
"advanced_styling_field_height": "Înălțime minimă",
"advanced_styling_field_indicator_bg": "Fundal indicator",
"advanced_styling_field_indicator_bg_description": "Colorează partea umplută a barei.",
"advanced_styling_field_input_border_radius_description": "Rotunjește colțurile câmpurilor de introducere.",
"advanced_styling_field_input_font_size_description": "Scalează textul introdus în câmpuri.",
"advanced_styling_field_input_height_description": "Controlează înălțimea câmpului de introducere.",
"advanced_styling_field_input_height_description": "Controlează înălțimea minimă a câmpului de introducere.",
"advanced_styling_field_input_padding_x_description": "Adaugă spațiu la stânga și la dreapta.",
"advanced_styling_field_input_padding_y_description": "Adaugă spațiu deasupra și dedesubt.",
"advanced_styling_field_input_placeholder_opacity_description": "Estompează textul de sugestie din placeholder.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Colorează textul introdus în câmpuri.",
"advanced_styling_field_option_bg": "Fundal",
"advanced_styling_field_option_bg_description": "Umple elementele de opțiune.",
"advanced_styling_field_option_border": "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_font_size_description": "Redimensionează textul etichetei opțiunii.",
"advanced_styling_field_option_label": "Culoare etichetă",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Afișează semnătura „Powered by Formbricks”",
"styling_updated_successfully": "Stilizarea a fost actualizată cu succes",
"suggest_colors": "Sugerează culori",
"suggested_colors_applied_please_save": "Culorile sugerate au fost generate cu succes. Apasă pe „Salvează” pentru a păstra modificările.",
"theme": "Temă",
"theme_settings_description": "Creează o temă de stil pentru toate sondajele. Poți activa stilizare personalizată pentru fiecare sondaj."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Nu, mulţumesc!",
"preview_survey_question_2_headline": "Vrei să fii în temă?",
"preview_survey_question_2_subheader": "Aceasta este o descriere exemplu.",
"preview_survey_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!",
"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",

View File

@@ -133,6 +133,7 @@
"allow": "Разрешить",
"allow_users_to_exit_by_clicking_outside_the_survey": "Разрешить пользователям выходить, кликнув вне опроса",
"an_unknown_error_occurred_while_deleting_table_items": "Произошла неизвестная ошибка при удалении {type}ов",
"analysis": "Аналитика",
"and": "и",
"and_response_limit_of": "и лимит ответов",
"anonymous": "Аноним",
@@ -149,6 +150,8 @@
"bottom_right": "Внизу справа",
"cancel": "Отмена",
"centered_modal": "Центрированное модальное окно",
"chart": "График",
"charts": "Графики",
"choices": "Варианты",
"choose_environment": "Выберите среду",
"choose_organization": "Выберите организацию",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} атрибут} few {{value} атрибута} many {{value} атрибутов} other {{value} атрибута}}",
"count_contacts": "{value, plural, one {{value} контакт} few {{value} контакта} many {{value} контактов} other {{value} контактов}}",
"count_responses": "{value, plural, one {{value} ответ} few {{value} ответа} many {{value} ответов} other {{value} ответов}}",
"create": "Создать",
"create_new_organization": "Создать новую организацию",
"create_segment": "Создать сегмент",
"create_survey": "Создать опрос",
@@ -187,6 +191,8 @@
"created_by": "Создано пользователем",
"customer_success": "Customer Success",
"dark_overlay": "Тёмный оверлей",
"dashboard": "Панель управления",
"dashboards": "Дашборды",
"date": "Дата",
"days": "дни",
"default": "По умолчанию",
@@ -218,13 +224,16 @@
"error": "Ошибка",
"error_component_description": "Этот ресурс не существует или у вас нет необходимых прав для доступа к нему.",
"error_component_title": "Ошибка загрузки ресурсов",
"error_loading_data": "Ошибка загрузки данных",
"error_rate_limit_description": "Достигнуто максимальное количество запросов. Пожалуйста, попробуйте позже.",
"error_rate_limit_title": "Превышен лимит запросов",
"expand_rows": "Развернуть строки",
"failed_to_copy_to_clipboard": "Не удалось скопировать в буфер обмена",
"failed_to_load_organizations": "Не удалось загрузить организации",
"failed_to_load_workspaces": "Не удалось загрузить рабочие пространства",
"filter": "Фильтр",
"finish": "Завершить",
"first_name": "Имя",
"follow_these": "Выполните следующие действия",
"formbricks_version": "Версия Formbricks",
"full_name": "Полное имя",
@@ -236,7 +245,9 @@
"hidden": "Скрыто",
"hidden_field": "Скрытое поле",
"hidden_fields": "Скрытые поля",
"hide": "Скрыть",
"hide_column": "Скрыть столбец",
"id": "ID",
"image": "Изображение",
"images": "Изображения",
"import": "Импорт",
@@ -254,6 +265,7 @@
"key": "Ключ",
"label": "Метка",
"language": "Язык",
"last_name": "Фамилия",
"learn_more": "Подробнее",
"license_expired": "License Expired",
"light_overlay": "Светлый оверлей",
@@ -280,6 +292,7 @@
"move_down": "Переместить вниз",
"move_up": "Переместить вверх",
"multiple_languages": "Несколько языков",
"my_product": "мой продукт",
"name": "Имя",
"new": "Новый",
"new_version_available": "Formbricks {version} уже здесь. Обновитесь сейчас!",
@@ -303,6 +316,7 @@
"on": "Вкл.",
"only_one_file_allowed": "Разрешён только один файл",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Только владельцы и менеджеры могут выполнять это действие.",
"open_options": "Открыть параметры",
"option_id": "ID опции",
"option_ids": "ID опций",
"optional": "Необязательно",
@@ -428,6 +442,7 @@
"top_right": "Вверху справа",
"try_again": "Попробуйте ещё раз",
"type": "Тип",
"unknown_survey": "Неизвестный опрос",
"unlock_more_workspaces_with_a_higher_plan": "Откройте больше рабочих пространств с более высоким тарифом.",
"update": "Обновить",
"updated": "Обновлено",
@@ -444,6 +459,7 @@
"variables": "Переменные",
"verified_email": "Подтверждённый email",
"video": "Видео",
"view": "Просмотреть",
"warning": "Предупреждение",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Не удалось проверить вашу лицензию, так как сервер лицензий недоступен.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Ваш опрос будет отображаться по этому URL.",
"your_survey_would_not_be_shown": "Ваш опрос не будет отображаться."
},
"analysis": {
"charts": {
"OR": "ИЛИ",
"add_chart_to_dashboard": "Добавить график на панель",
"add_chart_to_dashboard_description": "Выбери панель, чтобы добавить на неё этот график. График будет сохранён автоматически.",
"add_filter": "Добавить фильтр",
"add_to_dashboard": "Добавить на панель",
"advanced_chart_builder_config_prompt": "Настрой график и нажми «Выполнить запрос», чтобы посмотреть предварительный просмотр",
"ai_query_placeholder": "например: Сколько пользователей зарегистрировались на прошлой неделе?",
"ai_query_section_description": "Опиши, что хочешь увидеть, и AI построит график.",
"ai_query_section_title": "Спроси свои данные",
"apply_changes": "Применить изменения",
"chart": "График",
"chart_added_to_dashboard": "График добавлен на панель!",
"chart_builder_choose_chart_type": "Выбери тип графика",
"chart_data": "Данные графика",
"chart_data_tab": "Данные",
"chart_deleted_successfully": "График успешно удалён",
"chart_deletion_error": "Не удалось удалить график",
"chart_duplicated_successfully": "График успешно дублирован",
"chart_duplication_error": "Не удалось дублировать график",
"chart_name": "Название графика",
"chart_name_placeholder": "Название графика",
"chart_preview": "Предпросмотр графика",
"chart_render_error": "Что-то пошло не так при отображении этой диаграммы.",
"chart_saved_successfully": "График успешно сохранён!",
"chart_type_area": "График областью",
"chart_type_bar": "Столбчатая диаграмма",
"chart_type_big_number": "Большое число",
"chart_type_line": "Линейный график",
"chart_type_not_supported": "Тип графика «{{chartType}}» пока не поддерживается",
"chart_type_pie": "Круговая диаграмма",
"chart_updated_successfully": "График успешно обновлён!",
"configure_description": "Измени тип графика и другие настройки этой визуализации.",
"configure_title": "Настроить график",
"configure_type_label": "Тип графика",
"contains": "содержит",
"create_chart": "Создать график",
"create_chart_description": "Используй ИИ для создания графика или собери его вручную.",
"custom_range": "Произвольный диапазон",
"dashboard": "Панель управления",
"dashboard_select_placeholder": "Выбери панель управления",
"data_label": "Данные",
"date_preset_last_30_days": "Последние 30 дней",
"date_preset_last_7_days": "Последние 7 дней",
"date_preset_last_month": "Прошлый месяц",
"date_preset_this_month": "В этом месяце",
"date_preset_this_quarter": "В этом квартале",
"date_preset_this_year": "В этом году",
"date_preset_today": "Сегодня",
"date_preset_yesterday": "Вчера",
"date_range": "Диапазон дат",
"delete_chart_confirmation": "Ты уверен, что хочешь удалить этот график?",
"dimensions": "Измерения",
"dimensions_toggle_description": "Группируй данные по категориям. Порядок важен для многомерных графиков.",
"edit_chart_description": "Просмотри и измени настройки своего графика.",
"edit_chart_title": "Редактировать график",
"enable_time_dimension": "Включить временное измерение",
"end_date": "Дата окончания",
"enter_a_name_for_your_chart": "Введи название для графика, чтобы сохранить его.",
"enter_value": "Введи значение",
"equals": "равно",
"failed_to_add_chart_to_dashboard": "Не удалось добавить график на панель управления",
"failed_to_execute_query": "Не удалось выполнить запрос",
"failed_to_load_chart": "Не удалось загрузить график",
"failed_to_load_chart_data": "Не удалось загрузить данные графика",
"failed_to_save_chart": "Не удалось сохранить график",
"field": "Поле",
"field_label_average_score": "Средний балл",
"field_label_collected_at": "Дата сбора",
"field_label_count": "Количество",
"field_label_detractor_count": "Количество критиков",
"field_label_emotion": "Эмоция",
"field_label_field_type": "Тип поля",
"field_label_nps_score": "Оценка NPS",
"field_label_nps_value": "Значение NPS",
"field_label_passive_count": "Количество пассивных",
"field_label_promoter_count": "Количество промоутеров",
"field_label_response_id": "ID ответа",
"field_label_sentiment": "Тональность",
"field_label_source_name": "Название источника",
"field_label_source_type": "Тип источника",
"field_label_topic": "Тема",
"field_label_user_identifier": "Идентификатор пользователя",
"filters": "Фильтры",
"filters_toggle_description": "Включай только те данные, которые соответствуют следующим условиям.",
"generate_chart": "Сгенерировать график",
"granularity": "Детализация",
"granularity_day": "День",
"granularity_hour": "Час",
"granularity_month": "Месяц",
"granularity_quarter": "Квартал",
"granularity_week": "Неделя",
"granularity_year": "Год",
"greater_than": "больше чем",
"greater_than_or_equal": "больше или равно",
"group_by": "Группировать по",
"group_by_description": "Выбери измерения для детализации данных. Порядок важен для многомерных графиков.",
"guide_button": "Открыть гайд по полям",
"guide_chart_type": "Тип графика",
"guide_chart_type_desc": "Как визуализируются данные: область, столбцы, линия, круговая диаграмма или большое число. Выбирай в зависимости от того, что хочешь показать (тренды, сравнения, части целого и т. д.).",
"guide_dimensions": "Измерения (Группировка)",
"guide_dimensions_desc": "Как ты разбиваешь или группируешь данные. Каждое измерение становится категорией на графике (например, настроение, тип источника, название опроса, канал, тема). Порядок важен для многомерных графиков.",
"guide_filters": "Фильтры",
"guide_filters_desc": "Условия, ограничивающие включаемые данные. Каждый фильтр состоит из поля, оператора (равно, содержит, больше чем и т. д.) и значений. И = все должны совпасть; Или = достаточно любого совпадения.",
"guide_measures": "Показатели (что считаешь или агрегируешь)",
"guide_measures_predefined": "Предустановленные показатели — это готовые метрики из твоих данных обратной связи: количество (всего ответов), количество промоутеров/детракторов/нейтралов (сегменты NPS), NPS-оценка, средний балл, процент завершения.",
"guide_quick_ref": "Быстрая справка",
"guide_term_dimension": "Категориальное поле для группировки или разбивки данных",
"guide_term_filter": "Условие, ограничивающее включаемые строки",
"guide_term_measure": "Числовое значение, которое агрегируется (count, сумма, среднее и т. д.)",
"guide_term_time": "Группировка по времени с выбором детализации и диапазона дат",
"guide_time_dimension": "Временное измерение",
"guide_time_dimension_desc": "Группировка по времени: выбери поле времени (обычно \"Собрано в\"), детализацию (час, день, неделя, месяц и т. д.) и диапазон дат (предустановленный или свой). Используй для анализа трендов во времени.",
"guide_title": "Путеводитель по конструктору графиков",
"is_not_set": "не задано",
"is_set": "задано",
"less_than": "меньше чем",
"less_than_or_equal": "меньше или равно",
"measures": "Показатели",
"no_charts_found": "Графики не найдены.",
"no_dashboards_available": "Нет доступных панелей управления",
"no_dashboards_create_first": "Сначала создай панель управления, чтобы добавить на неё графики.",
"no_data_available": "Нет доступных данных",
"no_data_returned": "Запрос не вернул данных",
"no_data_returned_for_chart": "Для графика не получено данных",
"no_grouping": "Нет (только фильтр)",
"no_valid_data_to_display": "Нет корректных данных для отображения",
"not_contains": "не содержит",
"not_equals": "не равно",
"open_chart": "Открыть график {{name}}",
"open_options": "Открыть настройки графика",
"or_filter_logic": "ИЛИ",
"original": "Оригинал",
"please_enter_chart_name": "Пожалуйста, введи название графика",
"please_select_at_least_one_measure": "Пожалуйста, выбери хотя бы один показатель",
"please_select_dashboard": "Пожалуйста, выбери панель управления",
"predefined_measures": "Предустановленные показатели",
"preset": "Пресет",
"query_executed_successfully": "Запрос успешно выполнен",
"reset_to_ai_suggestion": "Сбросить к предложению ИИ",
"save_chart": "Сохранить график",
"save_chart_dialog_title": "Сохранить график",
"select_dimensions": "Выберите измерения...",
"select_field": "Выберите поле",
"select_measures": "Выберите показатели...",
"select_preset": "Выберите пресет",
"showing_first_n_of": "Показаны первые {{n}} из {{count}} строк",
"start_date": "Дата начала",
"time_dimension": "Временное измерение",
"time_dimension_toggle_description": "Добавьте группировку по времени для анализа трендов."
},
"dashboards": {
"create_dashboard": "Создать панель управления",
"create_dashboard_description": "Введите название для новой панели управления.",
"create_failed": "Не удалось создать панель управления",
"create_success": "Панель управления успешно создана!",
"dashboard_name": "Название панели управления",
"dashboard_name_placeholder": "Моя панель управления",
"delete_confirmation": "Ты уверен, что хочешь удалить эту панель управления? Это действие нельзя отменить.",
"delete_failed": "Не удалось удалить панель управления",
"delete_success": "Панель управления успешно удалена",
"description_optional": "Описание (необязательно)",
"description_placeholder": "Описание панели управления",
"duplicate_failed": "Не удалось дублировать панель управления",
"duplicate_success": "Панель управления успешно продублирована!",
"no_dashboards_found": "Панели управления не найдены.",
"please_enter_name": "Пожалуйста, введите название панели управления"
}
},
"connect": {
"congrats": "Поздравляем!",
"connection_successful_message": "Отлично! Мы подключены.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Обновить контакты",
"contacts_table_refresh_success": "Контакты успешно обновлены",
"create_attribute": "Создать атрибут",
"create_key": "Создать ключ",
"create_new_attribute": "Создать новый атрибут",
"create_new_attribute_description": "Создайте новый атрибут для целей сегментации.",
"custom_attributes": "Пользовательские атрибуты",
@@ -656,6 +841,7 @@
"delete_attribute_confirmation": "{value, plural, one {Будет удалён выбранный атрибут. Все данные контактов, связанные с этим атрибутом, будут потеряны.} few {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} many {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.} other {Будут удалены выбранные атрибуты. Все данные контактов, связанные с этими атрибутами, будут потеряны.}}",
"delete_contact_confirmation": "Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны.",
"delete_contact_confirmation_with_quotas": "{value, plural, one {Это удалит все ответы на опросы и атрибуты контакта, связанные с этим контактом. Любая таргетинг и персонализация на основе данных этого контакта будут потеряны. Если у этого контакта есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} few {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} many {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.} other {Это удалит все ответы на опросы и атрибуты контактов, связанные с этими контактами. Любая таргетинг и персонализация на основе данных этих контактов будут потеряны. Если у этих контактов есть ответы, которые учитываются в квотах опроса, количество по квотам будет уменьшено, но лимиты квот останутся без изменений.}}",
"displays": "Показы",
"edit_attribute": "Редактировать атрибут",
"edit_attribute_description": "Обновите метку и описание для этого атрибута.",
"edit_attribute_values": "Редактировать атрибуты",
@@ -667,6 +853,7 @@
"invalid_csv_column_names": "Недопустимые имена столбцов в CSV: {columns}. Имена столбцов, которые станут новыми атрибутами, должны содержать только строчные буквы, цифры и подчёркивания, а также начинаться с буквы.",
"invalid_date_format": "Неверный формат даты. Пожалуйста, используйте корректную дату.",
"invalid_number_format": "Неверный формат числа. Пожалуйста, введите корректное число.",
"no_activity_yet": "Пока нет активности",
"no_published_link_surveys_available": "Нет доступных опубликованных опросов-ссылок. Пожалуйста, сначала опубликуйте опрос-ссылку.",
"no_published_surveys": "Нет опубликованных опросов",
"no_responses_found": "Ответы не найдены",
@@ -681,6 +868,8 @@
"select_a_survey": "Выберите опрос",
"select_attribute": "Выберите атрибут",
"select_attribute_key": "Выберите ключ атрибута",
"survey_viewed": "Опрос просмотрен",
"survey_viewed_at": "Просмотрено",
"system_attributes": "Системные атрибуты",
"unlock_contacts_description": "Управляйте контактами и отправляйте целевые опросы",
"unlock_contacts_title": "Откройте доступ к контактам с более высоким тарифом",
@@ -752,7 +941,12 @@
"link_google_sheet": "Связать с Google Sheet",
"link_new_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_hidden_fields": "Включить скрытые поля",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней."
},
"general": {
"ai_data_analysis_enabled": "Обогащение и анализ данных (ИИ)",
"ai_data_analysis_enabled_description": "ИИ для получения большего от твоих данных: настройка дашбордов, графиков, отчетов и не только. Работает с твоими данными об опыте.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Управляй функциями на базе ИИ для этой организации.",
"ai_settings_updated_successfully": "Настройки AI успешно обновлены",
"ai_smart_tools_enabled": "Умные функции (ИИ)",
"ai_smart_tools_enabled_description": "ИИ помогает тебе делать больше за меньшее время. Никогда не использует данные, собранные с помощью Formbricks. Применяется, например, для перевода опросов на другие языки.",
"bulk_invite_warning_description": "В бесплатном тарифе всем участникам организации всегда назначается роль \"Владелец\".",
"cannot_delete_only_organization": "Это ваша единственная организация, её нельзя удалить. Сначала создайте новую организацию.",
"cannot_leave_only_organization": "Вы не можете покинуть эту организацию, так как она у вас единственная. Сначала создайте новую организацию.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Добавить плейсхолдер, который будет показан, если нет значения для отображения.",
"add_hidden_field_id": "Добавить скрытый ID поля",
"add_highlight_border": "Добавить выделяющую рамку",
"add_highlight_border_description": "Применяется только к опросам внутри продукта.",
"add_logic": "Добавить логику",
"add_none_of_the_above": "Добавить вариант «Ничего из вышеперечисленного»",
"add_option": "Добавить вариант",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "Фоллоу-ап обновлён и будет сохранён после сохранения опроса.",
"follow_ups_new": "Новый фоллоу-ап",
"follow_ups_upgrade_button_text": "Обновите тариф для активации фоллоу-апов",
"form_styling": "Оформление формы",
"formbricks_sdk_is_not_connected": "Formbricks SDK не подключён",
"four_points": "4 балла",
"heading": "Заголовок",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Лимиты ответов, перенаправления и другое.",
"response_options": "Параметры ответа",
"roundness": "Скругление",
"roundness_description": "Определяет степень скругления углов карточки.",
"roundness_description": "Определяет степень скругления углов.",
"row_used_in_logic_error": "Эта строка используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите её из логики.",
"rows": "Строки",
"save_and_close": "Сохранить и закрыть",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Этот бесплатный и открытый опрос был закрыт",
"survey_display_settings": "Настройки отображения опроса",
"survey_placement": "Размещение опроса",
"survey_styling": "Оформление формы",
"survey_trigger": "Триггер опроса",
"switch_multi_language_on_to_get_started": "Включите многоязычный режим, чтобы начать 👉",
"target_block_not_found": "Целевой блок не найден",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Отфильтрованные ответы (Excel)",
"generating_qr_code": "Генерация QR-кода",
"impressions": "Просмотры",
"impressions_identified_only": "Показаны только показы от идентифицированных контактов",
"impressions_tooltip": "Количество раз, когда опрос был просмотрен.",
"in_app": {
"connection_description": "Опрос будет показан пользователям вашего сайта, которые соответствуют указанным ниже критериям",
@@ -1989,6 +2192,7 @@
"last_quarter": "Прошлый квартал",
"last_year": "Прошлый год",
"limit": "Лимит",
"no_identified_impressions": "Нет показов от идентифицированных контактов",
"no_responses_found": "Ответы не найдены",
"other_values_found": "Найдены другие значения",
"overall": "В целом",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Масштабирует текст заголовка.",
"advanced_styling_field_headline_weight": "Толщина шрифта заголовка",
"advanced_styling_field_headline_weight_description": "Делает текст заголовка тоньше или жирнее.",
"advanced_styling_field_height": "Высота",
"advanced_styling_field_height": "Минимальная высота",
"advanced_styling_field_indicator_bg": "Фон индикатора",
"advanced_styling_field_indicator_bg_description": "Задаёт цвет заполненной части полосы.",
"advanced_styling_field_input_border_radius_description": "Скругляет углы полей ввода.",
"advanced_styling_field_input_font_size_description": "Масштабирует введённый текст в полях ввода.",
"advanced_styling_field_input_height_description": "Определяет высоту поля ввода.",
"advanced_styling_field_input_height_description": "Управляет минимальной высотой поля ввода.",
"advanced_styling_field_input_padding_x_description": "Добавляет отступы слева и справа.",
"advanced_styling_field_input_padding_y_description": "Добавляет пространство сверху и снизу.",
"advanced_styling_field_input_placeholder_opacity_description": "Делает текст подсказки менее заметным.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Задаёт цвет введённого текста в полях.",
"advanced_styling_field_option_bg": "Фон",
"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_font_size_description": "Изменяет размер текста метки опции.",
"advanced_styling_field_option_label": "Цвет метки",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Показывать подпись «Работает на Formbricks»",
"styling_updated_successfully": "Стили успешно обновлены",
"suggest_colors": "Предложить цвета",
"suggested_colors_applied_please_save": "Рекомендованные цвета успешно сгенерированы. Нажми «Сохранить», чтобы применить изменения.",
"theme": "Тема",
"theme_settings_description": "Создайте стиль для всех опросов. Вы можете включить индивидуальное оформление для каждого опроса."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Нет, спасибо!",
"preview_survey_question_2_headline": "Хотите быть в курсе событий?",
"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": "Добро пожаловать!",
"prioritize_features_description": "Определите, какие функции наиболее и наименее важны для ваших пользователей.",
"prioritize_features_name": "Приоритизация функций",

View File

@@ -133,6 +133,7 @@
"allow": "Tillåt",
"allow_users_to_exit_by_clicking_outside_the_survey": "Tillåt användare att avsluta genom att klicka utanför enkäten",
"an_unknown_error_occurred_while_deleting_table_items": "Ett okänt fel uppstod vid borttagning av {type}",
"analysis": "Analys",
"and": "Och",
"and_response_limit_of": "och svarsgräns på",
"anonymous": "Anonym",
@@ -149,6 +150,8 @@
"bottom_right": "Nedre höger",
"cancel": "Avbryt",
"centered_modal": "Centrerad modal",
"chart": "Diagram",
"charts": "Diagram",
"choices": "Val",
"choose_environment": "Välj miljö",
"choose_organization": "Välj organisation",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} attribut} other {{value} attribut}}",
"count_contacts": "{value, plural, one {{value} kontakt} other {{value} kontakter}}",
"count_responses": "{value, plural, one {{value} svar} other {{value} svar}}",
"create": "Skapa",
"create_new_organization": "Skapa ny organisation",
"create_segment": "Skapa segment",
"create_survey": "Skapa enkät",
@@ -187,6 +191,8 @@
"created_by": "Skapad av",
"customer_success": "Kundframgång",
"dark_overlay": "Mörkt överlägg",
"dashboard": "Instrumentpanel",
"dashboards": "Instrumentpaneler",
"date": "Datum",
"days": "dagar",
"default": "Standard",
@@ -218,13 +224,16 @@
"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_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_title": "Begränsningsgräns överskriden",
"expand_rows": "Visa rader",
"failed_to_copy_to_clipboard": "Misslyckades att kopiera till urklipp",
"failed_to_load_organizations": "Misslyckades att ladda organisationer",
"failed_to_load_workspaces": "Det gick inte att ladda arbetsytor",
"filter": "Filter",
"finish": "Slutför",
"first_name": "Förnamn",
"follow_these": "Följ dessa",
"formbricks_version": "Formbricks-version",
"full_name": "Fullständigt namn",
@@ -236,7 +245,9 @@
"hidden": "Dold",
"hidden_field": "Dolt fält",
"hidden_fields": "Dolda fält",
"hide": "Dölj",
"hide_column": "Dölj kolumn",
"id": "ID",
"image": "Bild",
"images": "Bilder",
"import": "Importera",
@@ -254,6 +265,7 @@
"key": "Nyckel",
"label": "Etikett",
"language": "Språk",
"last_name": "Efternamn",
"learn_more": "Läs mer",
"license_expired": "License Expired",
"light_overlay": "Ljust överlägg",
@@ -280,6 +292,7 @@
"move_down": "Flytta ner",
"move_up": "Flytta upp",
"multiple_languages": "Flera språk",
"my_product": "min produkt",
"name": "Namn",
"new": "Ny",
"new_version_available": "Formbricks {version} är här. Uppgradera nu!",
@@ -303,6 +316,7 @@
"on": "På",
"only_one_file_allowed": "Endast en fil är tillåten",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "Endast ägare och chefer kan utföra denna åtgärd.",
"open_options": "Öppna alternativ",
"option_id": "Alternativ-ID",
"option_ids": "Alternativ-ID:n",
"optional": "Valfritt",
@@ -428,6 +442,7 @@
"top_right": "Övre höger",
"try_again": "Försök igen",
"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.",
"update": "Uppdatera",
"updated": "Uppdaterad",
@@ -444,6 +459,7 @@
"variables": "Variabler",
"verified_email": "Verifierad e-post",
"video": "Video",
"view": "Visa",
"warning": "Varning",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "Vi kunde inte verifiera din licens eftersom licensservern inte kan nås.",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "Din enkät skulle visas på denna URL.",
"your_survey_would_not_be_shown": "Din enkät skulle inte visas."
},
"analysis": {
"charts": {
"OR": "ELLER",
"add_chart_to_dashboard": "Lägg till diagram på instrumentpanelen",
"add_chart_to_dashboard_description": "Välj en instrumentpanel att lägga till det här diagrammet på. Diagrammet sparas automatiskt.",
"add_filter": "Lägg till filter",
"add_to_dashboard": "Lägg till på instrumentpanelen",
"advanced_chart_builder_config_prompt": "Konfigurera ditt diagram och klicka på \"Kör fråga\" för att förhandsgranska",
"ai_query_placeholder": "t.ex. Hur många användare registrerade sig förra veckan?",
"ai_query_section_description": "Beskriv vad du vill se så bygger AI diagrammet åt dig.",
"ai_query_section_title": "Fråga din data",
"apply_changes": "Verkställ ändringar",
"chart": "Diagram",
"chart_added_to_dashboard": "Diagram tillagt på instrumentpanelen!",
"chart_builder_choose_chart_type": "Välj diagramtyp",
"chart_data": "Diagramdata",
"chart_data_tab": "Data",
"chart_deleted_successfully": "Diagrammet har tagits bort",
"chart_deletion_error": "Det gick inte att ta bort diagrammet",
"chart_duplicated_successfully": "Diagrammet har duplicerats",
"chart_duplication_error": "Det gick inte att duplicera diagrammet",
"chart_name": "Diagramnamn",
"chart_name_placeholder": "Diagramnamn",
"chart_preview": "Förhandsgranska diagram",
"chart_render_error": "Något gick fel när diagrammet skulle visas.",
"chart_saved_successfully": "Diagram sparat!",
"chart_type_area": "Ytdiagram",
"chart_type_bar": "Stapeldiagram",
"chart_type_big_number": "Stort tal",
"chart_type_line": "Linjediagram",
"chart_type_not_supported": "Diagramtypen \"{{chartType}}\" stöds inte än",
"chart_type_pie": "Cirkeldiagram",
"chart_updated_successfully": "Diagram uppdaterat!",
"configure_description": "Ändra diagramtyp och andra inställningar för den här visualiseringen.",
"configure_title": "Konfigurera diagram",
"configure_type_label": "Diagramtyp",
"contains": "innehåller",
"create_chart": "Skapa diagram",
"create_chart_description": "Använd AI för att skapa ett diagram eller bygg ett manuellt.",
"custom_range": "Anpassat intervall",
"dashboard": "Instrumentpanel",
"dashboard_select_placeholder": "Välj en instrumentpanel",
"data_label": "Data",
"date_preset_last_30_days": "Senaste 30 dagarna",
"date_preset_last_7_days": "Senaste 7 dagarna",
"date_preset_last_month": "Senaste månaden",
"date_preset_this_month": "Denna månad",
"date_preset_this_quarter": "Detta kvartal",
"date_preset_this_year": "Detta år",
"date_preset_today": "Idag",
"date_preset_yesterday": "Igår",
"date_range": "Datumintervall",
"delete_chart_confirmation": "Är du säker på att du vill ta bort det här diagrammet?",
"dimensions": "Dimensioner",
"dimensions_toggle_description": "Gruppera data efter kategorier. Ordningen är viktig för flerdimensionella diagram.",
"edit_chart_description": "Visa och redigera din diagramkonfiguration.",
"edit_chart_title": "Redigera diagram",
"enable_time_dimension": "Aktivera tidsdimension",
"end_date": "Slutdatum",
"enter_a_name_for_your_chart": "Ange ett namn för ditt diagram för att spara det.",
"enter_value": "Ange värde",
"equals": "är lika med",
"failed_to_add_chart_to_dashboard": "Det gick inte att lägga till diagrammet i instrumentpanelen",
"failed_to_execute_query": "Det gick inte att köra frågan",
"failed_to_load_chart": "Det gick inte att ladda diagrammet",
"failed_to_load_chart_data": "Det gick inte att ladda diagramdata",
"failed_to_save_chart": "Det gick inte att spara diagrammet",
"field": "Fält",
"field_label_average_score": "Genomsnittligt betyg",
"field_label_collected_at": "Insamlad",
"field_label_count": "Antal",
"field_label_detractor_count": "Antal kritiker",
"field_label_emotion": "Känsla",
"field_label_field_type": "Fälttyp",
"field_label_nps_score": "NPS-poäng",
"field_label_nps_value": "NPS-värde",
"field_label_passive_count": "Antal passiva",
"field_label_promoter_count": "Antal förespråkare",
"field_label_response_id": "Svar-ID",
"field_label_sentiment": "Sentiment",
"field_label_source_name": "Källnamn",
"field_label_source_type": "Källtyp",
"field_label_topic": "Ämne",
"field_label_user_identifier": "Användar-ID",
"filters": "Filter",
"filters_toggle_description": "Inkludera bara data som uppfyller följande villkor.",
"generate_chart": "Generera diagram",
"granularity": "Detaljnivå",
"granularity_day": "Dag",
"granularity_hour": "timme",
"granularity_month": "månad",
"granularity_quarter": "kvartal",
"granularity_week": "vecka",
"granularity_year": "år",
"greater_than": "större än",
"greater_than_or_equal": "större än eller lika med",
"group_by": "Gruppera efter",
"group_by_description": "Välj dimensioner för att bryta ner din data. Ordningen är viktig för flerdimensionella diagram.",
"guide_button": "Visa fältguide",
"guide_chart_type": "Diagramtyp",
"guide_chart_type_desc": "Hur datan visualiseras: Yta, Stapel, Linje, Tårta eller Stort tal. Välj utifrån vad du vill visa (trender, jämförelser, delar av helhet, etc.).",
"guide_dimensions": "Dimensioner (Gruppera efter)",
"guide_dimensions_desc": "Hur du delar upp eller grupperar datan. Varje dimension blir en kategori i diagrammet (t.ex. Sentiment, Källtyp, Enkätnamn, Kanal, Ämne). Ordningen är viktig för flerdimensionella diagram.",
"guide_filters": "Filter",
"guide_filters_desc": "Villkor som begränsar vilken data som tas med. Varje filter har ett fält, en operator (lika med, innehåller, större än, etc.) och värden. Och = alla måste matcha; Eller = någon kan matcha.",
"guide_measures": "Mått (vad du räknar eller aggregerar)",
"guide_measures_predefined": "Fördefinierade mått är färdiga mätvärden från din feedbackdata: Antal (totala svar), Promotor/Detraktor/Passiv antal (NPS-segment), NPS-poäng, Medelpoäng, Slutförandegrad.",
"guide_quick_ref": "Snabbreferens",
"guide_term_dimension": "Kategorifält som används för att gruppera eller dela upp data",
"guide_term_filter": "Villkor som begränsar vilka rader som tas med",
"guide_term_measure": "Numeriskt värde du aggregerar (count, sum, avg, etc.)",
"guide_term_time": "Tidsbaserad gruppering med detaljnivå och datumintervall",
"guide_time_dimension": "Tidsdimension",
"guide_time_dimension_desc": "Tidsbaserad gruppering: välj ett tidsfält (oftast Insamlad vid), detaljnivå (timme, dag, vecka, månad osv.) och datumintervall (förinställt eller anpassat). Används för att visa trender över tid.",
"guide_title": "Fältguide för diagrambyggaren",
"is_not_set": "är inte satt",
"is_set": "är satt",
"less_than": "mindre än",
"less_than_or_equal": "mindre än eller lika med",
"measures": "Mått",
"no_charts_found": "Inga diagram hittades.",
"no_dashboards_available": "Inga instrumentpaneler tillgängliga",
"no_dashboards_create_first": "Skapa först en instrumentpanel för att lägga till diagram.",
"no_data_available": "Ingen data tillgänglig",
"no_data_returned": "Ingen data returnerades från frågan",
"no_data_returned_for_chart": "Ingen data returnerades för diagrammet",
"no_grouping": "Ingen (endast filter)",
"no_valid_data_to_display": "Ingen giltig data att visa",
"not_contains": "innehåller inte",
"not_equals": "är inte lika med",
"open_chart": "Öppna diagram {{name}}",
"open_options": "Öppna diagramalternativ",
"or_filter_logic": "ELLER",
"original": "Original",
"please_enter_chart_name": "Ange ett diagramnamn",
"please_select_at_least_one_measure": "Välj minst ett mått",
"please_select_dashboard": "Välj en instrumentpanel",
"predefined_measures": "Fördefinierade mått",
"preset": "Förinställning",
"query_executed_successfully": "Frågan kördes utan problem",
"reset_to_ai_suggestion": "Återställ till AI-förslag",
"save_chart": "Spara diagram",
"save_chart_dialog_title": "Spara diagram",
"select_dimensions": "Välj dimensioner...",
"select_field": "Välj fält",
"select_measures": "Välj mått...",
"select_preset": "Välj förinställning",
"showing_first_n_of": "Visar de första {{n}} av {{count}} raderna",
"start_date": "Startdatum",
"time_dimension": "Tidsdimension",
"time_dimension_toggle_description": "Lägg till tidsbaserad gruppering för trender över tid."
},
"dashboards": {
"create_dashboard": "Skapa instrumentpanel",
"create_dashboard_description": "Ange ett namn för din nya instrumentpanel.",
"create_failed": "Det gick inte att skapa instrumentpanelen",
"create_success": "Instrumentpanelen har skapats!",
"dashboard_name": "Instrumentpanelens namn",
"dashboard_name_placeholder": "Min instrumentpanel",
"delete_confirmation": "Är du säker på att du vill ta bort den här instrumentpanelen? Den här åtgärden kan inte ångras.",
"delete_failed": "Det gick inte att ta bort instrumentpanelen",
"delete_success": "Instrumentpanelen har tagits bort",
"description_optional": "Beskrivning (valfritt)",
"description_placeholder": "Beskrivning av instrumentpanelen",
"duplicate_failed": "Det gick inte att duplicera instrumentpanelen",
"duplicate_success": "Instrumentpanelen har duplicerats!",
"no_dashboards_found": "Inga instrumentpaneler hittades.",
"please_enter_name": "Ange ett namn på instrumentpanelen"
}
},
"connect": {
"congrats": "Grattis!",
"connection_successful_message": "Bra gjort! Vi är anslutna.",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "Uppdatera kontakter",
"contacts_table_refresh_success": "Kontakter uppdaterade",
"create_attribute": "Skapa attribut",
"create_key": "Skapa nyckel",
"create_new_attribute": "Skapa nytt attribut",
"create_new_attribute_description": "Skapa ett nytt attribut för segmenteringsändamål.",
"custom_attributes": "Anpassade attribut",
@@ -656,6 +841,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_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.}}",
"displays": "Visningar",
"edit_attribute": "Redigera attribut",
"edit_attribute_description": "Uppdatera etikett och beskrivning för detta attribut.",
"edit_attribute_values": "Redigera attribut",
@@ -667,6 +853,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_date_format": "Ogiltigt datumformat. Ange ett giltigt datum.",
"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_surveys": "Inga publicerade enkäter",
"no_responses_found": "Inga svar hittades",
@@ -681,6 +868,8 @@
"select_a_survey": "Välj en enkät",
"select_attribute": "Välj attribut",
"select_attribute_key": "Välj attributnyckel",
"survey_viewed": "Enkät visad",
"survey_viewed_at": "Visad kl.",
"system_attributes": "Systemattribut",
"unlock_contacts_description": "Hantera kontakter och skicka ut riktade enkäter",
"unlock_contacts_title": "Lås upp kontakter med en högre plan",
@@ -752,7 +941,12 @@
"link_google_sheet": "Länka Google 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. ⏲️",
"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_hidden_fields": "Inkludera dolda fält",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar."
},
"general": {
"ai_data_analysis_enabled": "Dataförbättring & analys (AI)",
"ai_data_analysis_enabled_description": "AI för att få ut mer av din data, skapa dashboards, diagram, rapporter och mer. Använder din upplevelsedata.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Hantera AI-drivna funktioner för den här organisationen.",
"ai_settings_updated_successfully": "AI-inställningarna har uppdaterats",
"ai_smart_tools_enabled": "Smarta funktioner (AI)",
"ai_smart_tools_enabled_description": "AI som hjälper dig att göra mer på kortare tid. Rör aldrig data som samlats in med Formbricks. Används bara till t.ex. att översätta enkäter till andra språk.",
"bulk_invite_warning_description": "På gratisplanen tilldelas alla organisationsmedlemmar alltid rollen \"Ägare\".",
"cannot_delete_only_organization": "Detta är din enda organisation, den kan inte tas bort. Skapa en ny organisation först.",
"cannot_leave_only_organization": "Du kan inte lämna denna organisation eftersom det är din enda organisation. Skapa en ny organisation först.",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "Lägg till en platshållare att visa om det inte finns något värde att återkalla.",
"add_hidden_field_id": "Lägg till dolt fält-ID",
"add_highlight_border": "Lägg till markerad kant",
"add_highlight_border_description": "Gäller bara för undersökningar i produkten.",
"add_logic": "Lägg till logik",
"add_none_of_the_above": "Lägg till \"Inget av ovanstående\"",
"add_option": "Lägg till alternativ",
@@ -1430,7 +1632,6 @@
"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_upgrade_button_text": "Uppgradera för att aktivera uppföljningar",
"form_styling": "Formulärstil",
"formbricks_sdk_is_not_connected": "Formbricks SDK är inte anslutet",
"four_points": "4 poäng",
"heading": "Rubrik",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "Svarsgränser, omdirigeringar och mer.",
"response_options": "Svarsalternativ",
"roundness": "Rundhet",
"roundness_description": "Styr hur rundade kortets hörn är.",
"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.",
"rows": "Rader",
"save_and_close": "Spara och stäng",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "Denna gratis och öppenkällkodsenkät har stängts",
"survey_display_settings": "Visningsinställningar för enkät",
"survey_placement": "Enkätplacering",
"survey_styling": "Formulärstil",
"survey_trigger": "Enkätutlösare",
"switch_multi_language_on_to_get_started": "Slå på flerspråkighet för att komma igång 👉",
"target_block_not_found": "Målblock hittades inte",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "Filtrerade svar (Excel)",
"generating_qr_code": "Genererar QR-kod",
"impressions": "Visningar",
"impressions_identified_only": "Visar bara visningar från identifierade kontakter",
"impressions_tooltip": "Antal gånger enkäten har visats.",
"in_app": {
"connection_description": "Enkäten kommer att visas för användare på din webbplats som matchar kriterierna nedan",
@@ -1989,6 +2192,7 @@
"last_quarter": "Senaste kvartalet",
"last_year": "Senaste året",
"limit": "Gräns",
"no_identified_impressions": "Inga visningar från identifierade kontakter",
"no_responses_found": "Inga svar hittades",
"other_values_found": "Andra värden hittades",
"overall": "Övergripande",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "Ändrar storleken på rubriken.",
"advanced_styling_field_headline_weight": "Rubrikens teckentjocklek",
"advanced_styling_field_headline_weight_description": "Gör rubriktexten tunnare eller fetare.",
"advanced_styling_field_height": "Höjd",
"advanced_styling_field_height": "Minsta höjd",
"advanced_styling_field_indicator_bg": "Indikatorns bakgrund",
"advanced_styling_field_indicator_bg_description": "Färglägger den fyllda delen av stapeln.",
"advanced_styling_field_input_border_radius_description": "Rundar av hörnen på inmatningsfält.",
"advanced_styling_field_input_font_size_description": "Ändrar storleken på texten i inmatningsfält.",
"advanced_styling_field_input_height_description": "Styr 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_y_description": "Lägger till utrymme upptill och nedtill.",
"advanced_styling_field_input_placeholder_opacity_description": "Tonar ut platshållartexten.",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "Färgar den inmatade texten i fälten.",
"advanced_styling_field_option_bg": "Bakgrund",
"advanced_styling_field_option_bg_description": "Fyller alternativraderna.",
"advanced_styling_field_option_border": "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_font_size_description": "Skalar textstorleken på alternativetiketten.",
"advanced_styling_field_option_label": "Etikettfärg",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "Visa 'Powered by Formbricks'-signatur",
"styling_updated_successfully": "Stiluppdatering lyckades",
"suggest_colors": "Föreslå färger",
"suggested_colors_applied_please_save": "Föreslagna färger har skapats. Tryck på \"Spara\" för att spara ändringarna.",
"theme": "Tema",
"theme_settings_description": "Skapa ett stilmall för alla undersökningar. Du kan aktivera anpassad stil för varje undersökning."
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "Nej, tack!",
"preview_survey_question_2_headline": "Vill du hållas uppdaterad?",
"preview_survey_question_2_subheader": "Det här är ett exempel på en beskrivning.",
"preview_survey_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!",
"prioritize_features_description": "Identifiera vilka funktioner dina användare behöver mest och minst.",
"prioritize_features_name": "Prioritera funktioner",

View File

@@ -133,6 +133,7 @@
"allow": "允许",
"allow_users_to_exit_by_clicking_outside_the_survey": "允许 用户 通过 点击 调查 外部 退出",
"an_unknown_error_occurred_while_deleting_table_items": "删除 {type} 时发生未知错误",
"analysis": "分析",
"and": "和",
"and_response_limit_of": "和 响应限制",
"anonymous": "匿名",
@@ -149,6 +150,8 @@
"bottom_right": "右下",
"cancel": "取消",
"centered_modal": "居中 模态",
"chart": "图表",
"charts": "图表",
"choices": "选项",
"choose_environment": "选择 环境",
"choose_organization": "选择 组织",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} 个属性} other {{value} 个属性}}",
"count_contacts": "{value, plural, other {{value} 联系人} }",
"count_responses": "{value, plural, other {{value} 回复} }",
"create": "创建",
"create_new_organization": "创建 新的 组织",
"create_segment": "创建 细分",
"create_survey": "创建 调查",
@@ -187,6 +191,8 @@
"created_by": "由 创建",
"customer_success": "客户成功",
"dark_overlay": "深色遮罩层",
"dashboard": "Dashboard",
"dashboards": "仪表盘",
"date": "日期",
"days": "天",
"default": "默认",
@@ -218,13 +224,16 @@
"error": "错误",
"error_component_description": "这个资源不存在或您没有权限访问它。",
"error_component_title": "错误 加载 资源",
"error_loading_data": "数据加载出错",
"error_rate_limit_description": "请求 达到 最大 上限 请 稍后 再试 。",
"error_rate_limit_title": "速率 限制 超过",
"expand_rows": "展开 行",
"failed_to_copy_to_clipboard": "复制到剪贴板失败",
"failed_to_load_organizations": "加载组织失败",
"failed_to_load_workspaces": "加载工作区失败",
"filter": "筛选",
"finish": "完成",
"first_name": "名字",
"follow_these": "遵循 这些",
"formbricks_version": "Formbricks 版本",
"full_name": "全名",
@@ -236,7 +245,9 @@
"hidden": "隐藏",
"hidden_field": "隐藏 字段",
"hidden_fields": "隐藏 字段",
"hide": "隐藏",
"hide_column": "隐藏 列",
"id": "ID",
"image": "图片",
"images": "图片",
"import": "导入",
@@ -254,6 +265,7 @@
"key": "键",
"label": "标签",
"language": "语言",
"last_name": "姓",
"learn_more": "了解 更多",
"license_expired": "License Expired",
"light_overlay": "浅色遮罩层",
@@ -280,6 +292,7 @@
"move_down": "下移",
"move_up": "上移",
"multiple_languages": "多种 语言",
"my_product": "我的产品",
"name": "名称",
"new": "新建",
"new_version_available": "Formbricks {version} 在 这里。立即 升级!",
@@ -303,6 +316,7 @@
"on": "开启",
"only_one_file_allowed": "只 允许 一个 文件",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "只有 所有者 和 管理者 可以 执行 此 操作。",
"open_options": "打开选项",
"option_id": "选项 ID",
"option_ids": "选项 ID",
"optional": "可选",
@@ -428,6 +442,7 @@
"top_right": "右上",
"try_again": "再试一次",
"type": "类型",
"unknown_survey": "未知调查",
"unlock_more_workspaces_with_a_higher_plan": "升级套餐以解锁更多工作区。",
"update": "更新",
"updated": "已更新",
@@ -444,6 +459,7 @@
"variables": "变量",
"verified_email": "已验证 电子邮件",
"video": "视频",
"view": "查看",
"warning": "警告",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "我们无法验证您的许可证,因为许可证服务器无法访问。",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "您的 调查 会 显示 在 此 URL 上",
"your_survey_would_not_be_shown": "您的 调查 不会 显示。"
},
"analysis": {
"charts": {
"OR": "或",
"add_chart_to_dashboard": "添加图表到 Dashboard",
"add_chart_to_dashboard_description": "选择一个 Dashboard将此图表添加进去。图表会自动保存。",
"add_filter": "添加过滤器",
"add_to_dashboard": "添加到 Dashboard",
"advanced_chart_builder_config_prompt": "配置你的图表,然后点击“运行查询”预览",
"ai_query_placeholder": "例如:上周有多少用户注册?",
"ai_query_section_description": "描述你想要看到的内容,让 AI 帮你生成图表。",
"ai_query_section_title": "向你的数据提问",
"apply_changes": "应用更改",
"chart": "图表",
"chart_added_to_dashboard": "图表已添加到 Dashboard",
"chart_builder_choose_chart_type": "选择图表类型",
"chart_data": "图表数据",
"chart_data_tab": "数据",
"chart_deleted_successfully": "图表删除成功",
"chart_deletion_error": "图表删除失败",
"chart_duplicated_successfully": "图表复制成功",
"chart_duplication_error": "图表复制失败",
"chart_name": "图表名称",
"chart_name_placeholder": "图表名称",
"chart_preview": "图表预览",
"chart_render_error": "渲染此图表时出现了问题。",
"chart_saved_successfully": "图表保存成功!",
"chart_type_area": "面积图",
"chart_type_bar": "柱状图",
"chart_type_big_number": "大数字",
"chart_type_line": "折线图",
"chart_type_not_supported": "暂不支持图表类型“{{chartType}}”",
"chart_type_pie": "饼图",
"chart_updated_successfully": "图表更新成功!",
"configure_description": "修改此可视化的图表类型和其他设置。",
"configure_title": "配置图表",
"configure_type_label": "图表类型",
"contains": "包含",
"create_chart": "创建图表",
"create_chart_description": "使用 AI 生成图表或手动创建。",
"custom_range": "自定义范围",
"dashboard": "Dashboard",
"dashboard_select_placeholder": "请选择一个 Dashboard",
"data_label": "数据",
"date_preset_last_30_days": "最近 30 天",
"date_preset_last_7_days": "最近 7 天",
"date_preset_last_month": "上个月",
"date_preset_this_month": "本月",
"date_preset_this_quarter": "本季度",
"date_preset_this_year": "今年",
"date_preset_today": "今天",
"date_preset_yesterday": "昨天",
"date_range": "日期范围",
"delete_chart_confirmation": "你确定要删除这个图表吗?",
"dimensions": "维度",
"dimensions_toggle_description": "按类别分组数据。对于多维图表,顺序很重要。",
"edit_chart_description": "查看并编辑你的图表配置。",
"edit_chart_title": "编辑图表",
"enable_time_dimension": "启用时间维度",
"end_date": "结束日期",
"enter_a_name_for_your_chart": "请输入图表名称以保存。",
"enter_value": "输入值",
"equals": "等于",
"failed_to_add_chart_to_dashboard": "添加图表到 Dashboard 失败",
"failed_to_execute_query": "查询执行失败",
"failed_to_load_chart": "加载图表失败",
"failed_to_load_chart_data": "加载图表数据失败",
"failed_to_save_chart": "图表保存失败",
"field": "字段",
"field_label_average_score": "平均分",
"field_label_collected_at": "收集时间",
"field_label_count": "数量",
"field_label_detractor_count": "贬损者数量",
"field_label_emotion": "情感",
"field_label_field_type": "字段类型",
"field_label_nps_score": "NPS 得分",
"field_label_nps_value": "NPS 值",
"field_label_passive_count": "中立者数量",
"field_label_promoter_count": "推荐者数量",
"field_label_response_id": "响应 ID",
"field_label_sentiment": "情绪",
"field_label_source_name": "来源名称",
"field_label_source_type": "来源类型",
"field_label_topic": "主题",
"field_label_user_identifier": "用户标识",
"filters": "筛选条件",
"filters_toggle_description": "仅包含符合以下条件的数据。",
"generate_chart": "生成图表",
"granularity": "粒度",
"granularity_day": "天",
"granularity_hour": "小时",
"granularity_month": "月",
"granularity_quarter": "季度",
"granularity_week": "周",
"granularity_year": "年",
"greater_than": "大于",
"greater_than_or_equal": "大于或等于",
"group_by": "分组依据",
"group_by_description": "选择用于细分数据的维度。对于多维图表,顺序很重要。",
"guide_button": "查看字段指南",
"guide_chart_type": "图表类型",
"guide_chart_type_desc": "数据的可视化方式:面积图、柱状图、折线图、饼图或大数字。根据你想展示的内容(趋势、对比、整体组成等)进行选择。",
"guide_dimensions": "维度(分组依据)",
"guide_dimensions_desc": "你如何拆分或分组数据。每个维度会成为图表上的一个类别(如情感、来源类型、问卷名称、渠道、主题)。对于多维图表,顺序很重要。",
"guide_filters": "筛选条件",
"guide_filters_desc": "限制包含哪些数据的条件。每个筛选包含字段、运算符等于、包含、大于等和数值。And = 全部满足Or = 满足任意一个即可。",
"guide_measures": "度量(你统计或汇总的内容)",
"guide_measures_predefined": "预设度量是从你的反馈数据中预先构建的指标:计数(总回复数)、推荐者/贬损者/中立者计数NPS分段、NPS得分、平均分、完成率。",
"guide_quick_ref": "快速参考",
"guide_term_dimension": "用于分组或拆分数据的分类字段",
"guide_term_filter": "限制包含哪些行的条件",
"guide_term_measure": "你要汇总的数值(计数、求和、平均等)",
"guide_term_time": "基于时间的分组,包含粒度和日期范围",
"guide_time_dimension": "时间维度",
"guide_time_dimension_desc": "基于时间的分组:选择一个时间字段(通常为“收集时间”),粒度(小时、天、周、月等)和日期范围(预设或自定义)。适用于查看随时间变化的趋势。",
"guide_title": "图表生成器使用指南",
"is_not_set": "未设置",
"is_set": "已设置",
"less_than": "小于",
"less_than_or_equal": "小于或等于",
"measures": "度量",
"no_charts_found": "未找到图表。",
"no_dashboards_available": "暂无可用 Dashboard",
"no_dashboards_create_first": "请先创建一个 Dashboard然后再添加图表。",
"no_data_available": "暂无数据",
"no_data_returned": "查询未返回数据",
"no_data_returned_for_chart": "该图表未返回数据",
"no_grouping": "无(仅筛选)",
"no_valid_data_to_display": "无有效数据可显示",
"not_contains": "不包含",
"not_equals": "不等于",
"open_chart": "打开图表 {{name}}",
"open_options": "打开图表选项",
"or_filter_logic": "或",
"original": "原始",
"please_enter_chart_name": "请输入图表名称",
"please_select_at_least_one_measure": "请至少选择一个度量",
"please_select_dashboard": "请选择一个 Dashboard",
"predefined_measures": "预设度量",
"preset": "预设",
"query_executed_successfully": "查询执行成功",
"reset_to_ai_suggestion": "重置为 AI 建议",
"save_chart": "保存图表",
"save_chart_dialog_title": "保存图表",
"select_dimensions": "选择维度...",
"select_field": "选择字段",
"select_measures": "选择度量...",
"select_preset": "选择预设",
"showing_first_n_of": "显示前 {{n}} 行,共 {{count}} 行",
"start_date": "开始日期",
"time_dimension": "时间维度",
"time_dimension_toggle_description": "添加基于时间的分组,用于趋势分析。"
},
"dashboards": {
"create_dashboard": "创建 Dashboard",
"create_dashboard_description": "请输入新 Dashboard 的名称。",
"create_failed": "创建 Dashboard 失败",
"create_success": "Dashboard 创建成功!",
"dashboard_name": "Dashboard 名称",
"dashboard_name_placeholder": "我的 Dashboard",
"delete_confirmation": "确定要删除此 Dashboard 吗?此操作无法撤销。",
"delete_failed": "删除 Dashboard 失败",
"delete_success": "Dashboard 删除成功",
"description_optional": "描述(可选)",
"description_placeholder": "Dashboard 描述",
"duplicate_failed": "复制 Dashboard 失败",
"duplicate_success": "Dashboard 复制成功!",
"no_dashboards_found": "未找到 Dashboard。",
"please_enter_name": "请输入 Dashboard 名称"
}
},
"connect": {
"congrats": "恭喜!",
"connection_successful_message": "做得好 !我们 已经 连接。",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "刷新 联系人",
"contacts_table_refresh_success": "联系人 已成功刷新",
"create_attribute": "创建属性",
"create_key": "创建键",
"create_new_attribute": "创建新属性",
"create_new_attribute_description": "为细分目的创建新属性。",
"custom_attributes": "自定义属性",
@@ -656,6 +841,7 @@
"delete_attribute_confirmation": "{value, plural, one {这将删除所选属性。与该属性相关的任何联系人数据都将丢失。} other {这将删除所选属性。与这些属性相关的任何联系人数据都将丢失。}}",
"delete_contact_confirmation": "这将删除与此联系人相关的所有调查问卷回复和联系人属性。基于此联系人数据的任何定位和个性化将会丢失。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {这将删除与此联系人相关的所有调查回复和联系人属性。基于此联系人数据的任何定位和个性化将丢失。如果此联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。} other {这将删除与这些联系人相关的所有调查回复和联系人属性。基于这些联系人数据的任何定位和个性化将丢失。如果这些联系人有影响调查配额的回复,配额数量将减少,但配额限制将保持不变。}}",
"displays": "展示次数",
"edit_attribute": "编辑属性",
"edit_attribute_description": "更新此属性的标签和描述。",
"edit_attribute_values": "编辑属性",
@@ -667,6 +853,7 @@
"invalid_csv_column_names": "无效的 CSV 列名:{columns}。作为新属性的列名只能包含小写字母、数字和下划线,并且必须以字母开头。",
"invalid_date_format": "日期格式无效。请使用有效日期。",
"invalid_number_format": "数字格式无效。请输入有效的数字。",
"no_activity_yet": "暂无活动",
"no_published_link_surveys_available": "没有可用的已发布链接调查。请先发布一个链接调查。",
"no_published_surveys": "没有已发布的调查",
"no_responses_found": "未找到 响应",
@@ -681,6 +868,8 @@
"select_a_survey": "选择一个调查",
"select_attribute": "选择 属性",
"select_attribute_key": "选择属性键",
"survey_viewed": "已查看调查",
"survey_viewed_at": "查看时间",
"system_attributes": "系统属性",
"unlock_contacts_description": "管理 联系人 并 发送 定向 调查",
"unlock_contacts_title": "通过 更 高级 划解锁 联系人",
@@ -752,7 +941,12 @@
"link_google_sheet": "链接 Google 表格",
"link_new_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_hidden_fields": "包括 隐藏 字段",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。"
},
"general": {
"ai_data_analysis_enabled": "数据增强与分析AI",
"ai_data_analysis_enabled_description": "使用 AI 深度挖掘你的数据,设置仪表盘、图表、报告等。会处理你的体验数据。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "管理该组织的 AI 驱动功能。",
"ai_settings_updated_successfully": "AI 设置已成功更新",
"ai_smart_tools_enabled": "智能功能AI",
"ai_smart_tools_enabled_description": "AI 帮你更高效地完成更多任务。绝不会接触 Formbricks 收集的数据,仅用于如问卷翻译等功能。",
"bulk_invite_warning_description": "在免费计划中,所有组织成员都会被分配为 \"Owner \"角色。",
"cannot_delete_only_organization": "这是 您 唯一的 组织,不可 删除。请 先 创建一个新的 组织。",
"cannot_leave_only_organization": "您 不能 离开 此 组织,因为 这是 您 唯一的 组织。请 先 创建一个新的 组织。",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "添加 占位符 显示 如果 没有 值以 回忆",
"add_hidden_field_id": "添加 隐藏 字段 ID",
"add_highlight_border": "添加 高亮 边框",
"add_highlight_border_description": "仅适用于产品内调查。",
"add_logic": "添加逻辑",
"add_none_of_the_above": "添加 “以上 都 不 是”",
"add_option": "添加 选项",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "后续 操作 已 更新, 并且 在 你 保存 调查 后 将 被 保存。",
"follow_ups_new": "新的跟进",
"follow_ups_upgrade_button_text": "升级 以启用 跟进",
"form_styling": "表单 样式",
"formbricks_sdk_is_not_connected": "Formbricks SDK 未连接",
"four_points": "4 分",
"heading": "标题",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "响应 限制 、 重定向 和 更多 。",
"response_options": "响应 选项",
"roundness": "圆度",
"roundness_description": "控制卡片角的圆润程度。",
"roundness_description": "控制圆角的弧度。",
"row_used_in_logic_error": "\"这个 行 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
"rows": "行",
"save_and_close": "保存 和 关闭",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "此 免费 & 开源 调查 已 关闭",
"survey_display_settings": "调查显示设置",
"survey_placement": "调查 放置",
"survey_styling": "表单 样式",
"survey_trigger": "调查 触发",
"switch_multi_language_on_to_get_started": "开启多语言以开始使用 👉",
"target_block_not_found": "未找到目标区块",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "过滤 反馈 Excel",
"generating_qr_code": "正在生成二维码",
"impressions": "印象",
"impressions_identified_only": "仅显示已识别联系人的展示次数",
"impressions_tooltip": "调查 被 查看 的 次数",
"in_app": {
"connection_description": "调查将显示给符合以下条件的您网站用户",
@@ -1989,6 +2192,7 @@
"last_quarter": "上季度",
"last_year": "去年",
"limit": "限额",
"no_identified_impressions": "没有已识别联系人的展示次数",
"no_responses_found": "未找到响应",
"other_values_found": "找到其他值",
"overall": "整体",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "调整主标题文字大小。",
"advanced_styling_field_headline_weight": "标题字体粗细",
"advanced_styling_field_headline_weight_description": "设置主标题文字的粗细。",
"advanced_styling_field_height": "高度",
"advanced_styling_field_height": "最小高度",
"advanced_styling_field_indicator_bg": "指示器背景",
"advanced_styling_field_indicator_bg_description": "设置进度条已填充部分的颜色。",
"advanced_styling_field_input_border_radius_description": "设置输入框圆角。",
"advanced_styling_field_input_font_size_description": "调整输入框内文字大小。",
"advanced_styling_field_input_height_description": "控制输入框高度。",
"advanced_styling_field_input_height_description": "控制输入框的最小高度。",
"advanced_styling_field_input_padding_x_description": "增加输入框左右间距。",
"advanced_styling_field_input_padding_y_description": "为输入框上下添加间距。",
"advanced_styling_field_input_placeholder_opacity_description": "调整占位提示文字的透明度。",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "设置输入框内已输入文字的颜色。",
"advanced_styling_field_option_bg": "背景色",
"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_font_size_description": "调整选项标签文字的大小。",
"advanced_styling_field_option_label": "标签颜色",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "显示“Powered by Formbricks”标识",
"styling_updated_successfully": "样式更新成功",
"suggest_colors": "推荐颜色",
"suggested_colors_applied_please_save": "已成功生成推荐配色。请点击“保存”以保留更改。",
"theme": "主题",
"theme_settings_description": "为所有问卷创建一个样式主题。你可以为每个问卷启用自定义样式。"
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "不,谢谢!",
"preview_survey_question_2_headline": "想 了解 最新信息吗?",
"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": "欢迎!",
"prioritize_features_description": "确定 用户 最 需要 和 最 不 需要 的 功能。",
"prioritize_features_name": "优先 功能",

View File

@@ -133,6 +133,7 @@
"allow": "允許",
"allow_users_to_exit_by_clicking_outside_the_survey": "允許使用者點擊問卷外退出",
"an_unknown_error_occurred_while_deleting_table_items": "刪除 '{'type'}' 時發生未知錯誤",
"analysis": "分析",
"and": "且",
"and_response_limit_of": "且回應上限為",
"anonymous": "匿名",
@@ -149,6 +150,8 @@
"bottom_right": "右下",
"cancel": "取消",
"centered_modal": "置中彈窗",
"chart": "圖表",
"charts": "圖表",
"choices": "選項",
"choose_environment": "選擇環境",
"choose_organization": "選擇 組織",
@@ -178,6 +181,7 @@
"count_attributes": "{value, plural, one {{value} 個屬性} other {{value} 個屬性}}",
"count_contacts": "{value, plural, other {{value} 聯絡人} }",
"count_responses": "{value, plural, other {{value} 回應} }",
"create": "建立",
"create_new_organization": "建立新組織",
"create_segment": "建立區隔",
"create_survey": "建立問卷",
@@ -187,6 +191,8 @@
"created_by": "建立者",
"customer_success": "客戶成功",
"dark_overlay": "深色覆蓋",
"dashboard": "儀表板",
"dashboards": "儀表板",
"date": "日期",
"days": "天",
"default": "預設",
@@ -218,13 +224,16 @@
"error": "錯誤",
"error_component_description": "此資源不存在或您沒有存取權限。",
"error_component_title": "載入資源錯誤",
"error_loading_data": "載入資料時發生錯誤",
"error_rate_limit_description": "已達 到最大 請求 次數。請 稍後 再試。",
"error_rate_limit_title": "限流超過",
"expand_rows": "展開列",
"failed_to_copy_to_clipboard": "無法複製到剪貼簿",
"failed_to_load_organizations": "無法載入組織",
"failed_to_load_workspaces": "載入工作區失敗",
"filter": "篩選",
"finish": "完成",
"first_name": "名字",
"follow_these": "按照這些步驟",
"formbricks_version": "Formbricks 版本",
"full_name": "全名",
@@ -236,7 +245,9 @@
"hidden": "隱藏",
"hidden_field": "隱藏欄位",
"hidden_fields": "隱藏欄位",
"hide": "隱藏",
"hide_column": "隱藏欄位",
"id": "ID",
"image": "圖片",
"images": "圖片",
"import": "匯入",
@@ -254,6 +265,7 @@
"key": "金鑰",
"label": "標籤",
"language": "語言",
"last_name": "姓氏",
"learn_more": "瞭解更多",
"license_expired": "License Expired",
"light_overlay": "淺色覆蓋",
@@ -280,6 +292,7 @@
"move_down": "下移",
"move_up": "上移",
"multiple_languages": "多種語言",
"my_product": "我的產品",
"name": "名稱",
"new": "新增",
"new_version_available": "Formbricks '{'version'}' 已推出。立即升級!",
@@ -303,6 +316,7 @@
"on": "開啟",
"only_one_file_allowed": "僅允許一個檔案",
"only_owners_managers_and_manage_access_members_can_perform_this_action": "只有擁有者、管理員和管理存取權限的成員才能執行此操作。",
"open_options": "開啟選項",
"option_id": "選項 ID",
"option_ids": "選項 IDs",
"optional": "選填",
@@ -428,6 +442,7 @@
"top_right": "右上",
"try_again": "再試一次",
"type": "類型",
"unknown_survey": "未知問卷",
"unlock_more_workspaces_with_a_higher_plan": "升級方案以解鎖更多工作區。",
"update": "更新",
"updated": "已更新",
@@ -444,6 +459,7 @@
"variables": "變數",
"verified_email": "已驗證的電子郵件",
"video": "影片",
"view": "檢視",
"warning": "警告",
"we_were_unable_to_verify_your_license_because_the_license_server_is_unreachable": "我們無法驗證您的授權,因為授權伺服器無法連線。",
"webhook": "Webhook",
@@ -607,6 +623,176 @@
"your_survey_would_be_shown_on_this_url": "您的問卷將顯示在此網址。",
"your_survey_would_not_be_shown": "您的問卷將不會顯示。"
},
"analysis": {
"charts": {
"OR": "或",
"add_chart_to_dashboard": "新增圖表到儀表板",
"add_chart_to_dashboard_description": "請選擇一個儀表板來新增此圖表。圖表會自動儲存。",
"add_filter": "新增篩選器",
"add_to_dashboard": "新增到儀表板",
"advanced_chart_builder_config_prompt": "設定你的圖表,然後點擊「執行查詢」預覽",
"ai_query_placeholder": "例如:上週有多少用戶註冊?",
"ai_query_section_description": "描述你想看到的內容,讓 AI 幫你建立圖表。",
"ai_query_section_title": "詢問你的數據",
"apply_changes": "套用變更",
"chart": "圖表",
"chart_added_to_dashboard": "圖表已新增到儀表板!",
"chart_builder_choose_chart_type": "選擇圖表類型",
"chart_data": "圖表資料",
"chart_data_tab": "資料",
"chart_deleted_successfully": "圖表已成功刪除",
"chart_deletion_error": "刪除圖表失敗",
"chart_duplicated_successfully": "圖表已成功複製",
"chart_duplication_error": "圖表複製失敗",
"chart_name": "圖表名稱",
"chart_name_placeholder": "圖表名稱",
"chart_preview": "圖表預覽",
"chart_render_error": "這個圖表在顯示時發生錯誤。",
"chart_saved_successfully": "圖表已成功儲存!",
"chart_type_area": "區域圖",
"chart_type_bar": "長條圖",
"chart_type_big_number": "大數字",
"chart_type_line": "折線圖",
"chart_type_not_supported": "尚未支援圖表類型「{{chartType}}」",
"chart_type_pie": "圓餅圖",
"chart_updated_successfully": "圖表已成功更新!",
"configure_description": "修改此視覺化的圖表類型及其他設定。",
"configure_title": "設定圖表",
"configure_type_label": "圖表類型",
"contains": "包含",
"create_chart": "建立圖表",
"create_chart_description": "使用 AI 產生圖表或手動建立。",
"custom_range": "自訂範圍",
"dashboard": "儀表板",
"dashboard_select_placeholder": "請選擇儀表板",
"data_label": "資料",
"date_preset_last_30_days": "過去 30 天",
"date_preset_last_7_days": "過去 7 天",
"date_preset_last_month": "上個月",
"date_preset_this_month": "本月",
"date_preset_this_quarter": "本季",
"date_preset_this_year": "今年",
"date_preset_today": "今天",
"date_preset_yesterday": "昨天",
"date_range": "日期範圍",
"delete_chart_confirmation": "你確定要刪除此圖表嗎?",
"dimensions": "維度",
"dimensions_toggle_description": "依類別分組資料。多維圖表時,順序會影響結果。",
"edit_chart_description": "檢視並編輯你的圖表設定。",
"edit_chart_title": "編輯圖表",
"enable_time_dimension": "啟用時間維度",
"end_date": "結束日期",
"enter_a_name_for_your_chart": "請輸入圖表名稱以儲存。",
"enter_value": "請輸入數值",
"equals": "等於",
"failed_to_add_chart_to_dashboard": "新增圖表到儀表板失敗",
"failed_to_execute_query": "查詢執行失敗",
"failed_to_load_chart": "載入圖表失敗",
"failed_to_load_chart_data": "載入圖表資料失敗",
"failed_to_save_chart": "儲存圖表失敗",
"field": "欄位",
"field_label_average_score": "平均分數",
"field_label_collected_at": "收集時間",
"field_label_count": "數量",
"field_label_detractor_count": "批評者數量",
"field_label_emotion": "情緒",
"field_label_field_type": "欄位類型",
"field_label_nps_score": "NPS 分數",
"field_label_nps_value": "NPS 值",
"field_label_passive_count": "中立者數量",
"field_label_promoter_count": "推廣者數量",
"field_label_response_id": "回應 ID",
"field_label_sentiment": "情感",
"field_label_source_name": "來源名稱",
"field_label_source_type": "來源類型",
"field_label_topic": "主題",
"field_label_user_identifier": "使用者識別碼",
"filters": "篩選條件",
"filters_toggle_description": "只包含符合下列條件的資料。",
"generate_chart": "產生圖表",
"granularity": "粒度",
"granularity_day": "天",
"granularity_hour": "小時",
"granularity_month": "月",
"granularity_quarter": "季",
"granularity_week": "週",
"granularity_year": "年",
"greater_than": "大於",
"greater_than_or_equal": "大於或等於",
"group_by": "分組依據",
"group_by_description": "選擇要拆解資料的維度。多維度圖表時,順序會影響結果。",
"guide_button": "查看欄位指南",
"guide_chart_type": "圖表類型",
"guide_chart_type_desc": "資料的視覺化方式:區域圖、長條圖、折線圖、圓餅圖或大數字。請根據你想呈現的內容(趨勢、比較、整體組成等)選擇。",
"guide_dimensions": "維度(分組依據)",
"guide_dimensions_desc": "你如何拆分或分組資料。每個維度都會成為圖表上的一個類別(例如:情緒、來源類型、問卷名稱、渠道、主題)。多維度圖表時,順序很重要。",
"guide_filters": "篩選條件",
"guide_filters_desc": "限制納入哪些資料的條件。每個篩選包含欄位、運算子等於、包含、大於等和數值。And全部符合Or任一符合即可。",
"guide_measures": "指標(你要計算或彙總的內容)",
"guide_measures_predefined": "預設指標是從回饋資料中預先建立的指標計數總回應數、推廣者貶抑者中立者計數NPS 分群、NPS 分數、平均分數、完成率。",
"guide_quick_ref": "快速參考",
"guide_term_dimension": "用來分組或拆分資料的類別欄位",
"guide_term_filter": "限制納入哪些資料列的條件",
"guide_term_measure": "你要彙總的數值(計數、總和、平均等)",
"guide_term_time": "依時間分組,包含粒度與日期範圍",
"guide_time_dimension": "時間維度",
"guide_time_dimension_desc": "依時間分組:選擇一個時間欄位(通常是收集時間)、粒度(小時、天、週、月等)和日期範圍(預設或自訂)。適合用來觀察趨勢變化。",
"guide_title": "圖表建立指南",
"is_not_set": "未設定",
"is_set": "已設定",
"less_than": "小於",
"less_than_or_equal": "小於或等於",
"measures": "指標",
"no_charts_found": "找不到圖表。",
"no_dashboards_available": "沒有可用的儀表板",
"no_dashboards_create_first": "請先建立儀表板,才能新增圖表。",
"no_data_available": "沒有可用資料",
"no_data_returned": "查詢沒有回傳資料",
"no_data_returned_for_chart": "此圖表沒有回傳資料",
"no_grouping": "無(僅篩選)",
"no_valid_data_to_display": "沒有可顯示的有效資料",
"not_contains": "不包含",
"not_equals": "不等於",
"open_chart": "開啟圖表 {{name}}",
"open_options": "開啟圖表選項",
"or_filter_logic": "或",
"original": "原始",
"please_enter_chart_name": "請輸入圖表名稱",
"please_select_at_least_one_measure": "請至少選擇一個指標",
"please_select_dashboard": "請選擇一個儀表板",
"predefined_measures": "預設指標",
"preset": "預設",
"query_executed_successfully": "查詢執行成功",
"reset_to_ai_suggestion": "重設為 AI 建議",
"save_chart": "儲存圖表",
"save_chart_dialog_title": "儲存圖表",
"select_dimensions": "選擇維度...",
"select_field": "選擇欄位",
"select_measures": "選擇指標...",
"select_preset": "選擇預設",
"showing_first_n_of": "顯示前 {{n}} 筆,共 {{count}} 筆資料",
"start_date": "開始日期",
"time_dimension": "時間維度",
"time_dimension_toggle_description": "新增以時間為基礎的分組,方便觀察趨勢變化。"
},
"dashboards": {
"create_dashboard": "建立儀表板",
"create_dashboard_description": "請輸入新儀表板的名稱。",
"create_failed": "建立儀表板失敗",
"create_success": "儀表板建立成功!",
"dashboard_name": "儀表板名稱",
"dashboard_name_placeholder": "我的儀表板",
"delete_confirmation": "你確定要刪除此儀表板嗎?此操作無法復原。",
"delete_failed": "刪除儀表板失敗",
"delete_success": "儀表板刪除成功",
"description_optional": "描述(選填)",
"description_placeholder": "儀表板描述",
"duplicate_failed": "複製儀表板失敗",
"duplicate_success": "儀表板複製成功!",
"no_dashboards_found": "找不到儀表板。",
"please_enter_name": "請輸入儀表板名稱"
}
},
"connect": {
"congrats": "恭喜!",
"connection_successful_message": "做得好!我們已連線。",
@@ -645,7 +831,6 @@
"contacts_table_refresh": "重新整理聯絡人",
"contacts_table_refresh_success": "聯絡人已成功重新整理",
"create_attribute": "建立屬性",
"create_key": "建立金鑰",
"create_new_attribute": "建立新屬性",
"create_new_attribute_description": "建立新屬性以進行分群用途。",
"custom_attributes": "自訂屬性",
@@ -656,6 +841,7 @@
"delete_attribute_confirmation": "{value, plural, one {這將刪除所選屬性。與此屬性相關的聯絡人資料將會遺失。} other {這將刪除所選屬性。與這些屬性相關的聯絡人資料將會遺失。}}",
"delete_contact_confirmation": "這將刪除與此聯繫人相關的所有調查回應和聯繫屬性。任何基於此聯繫人數據的定位和個性化將會丟失。",
"delete_contact_confirmation_with_quotas": "{value, plural, one {這將刪除與這個 contact 相關的所有調查響應和聯繫人屬性。基於這個 contact 數據的任何定向和個性化功能將會丟失。如果這個 contact 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。} other {這將刪除與這些 contacts 相關的所有調查響應和聯繫人屬性。基於這些 contacts 數據的任何定向和個性化功能將會丟失。如果這些 contacts 有作為調查配額依據的響應,配額計數將會減少,但配額限制將保持不變。}}",
"displays": "顯示次數",
"edit_attribute": "編輯屬性",
"edit_attribute_description": "更新此屬性的標籤與描述。",
"edit_attribute_values": "編輯屬性",
@@ -667,6 +853,7 @@
"invalid_csv_column_names": "無效的 CSV 欄位名稱:{columns}。作為新屬性的欄位名稱只能包含小寫字母、數字和底線,且必須以字母開頭。",
"invalid_date_format": "日期格式無效。請使用有效的日期。",
"invalid_number_format": "數字格式無效。請輸入有效的數字。",
"no_activity_yet": "尚無活動",
"no_published_link_surveys_available": "沒有可用的已發佈連結問卷。請先發佈一個連結問卷。",
"no_published_surveys": "沒有已發佈的問卷",
"no_responses_found": "找不到回應",
@@ -681,6 +868,8 @@
"select_a_survey": "選擇問卷",
"select_attribute": "選取屬性",
"select_attribute_key": "選取屬性鍵值",
"survey_viewed": "已查看問卷",
"survey_viewed_at": "查看時間",
"system_attributes": "系統屬性",
"unlock_contacts_description": "管理聯絡人並發送目標問卷",
"unlock_contacts_title": "使用更高等級的方案解鎖聯絡人",
@@ -752,7 +941,12 @@
"link_google_sheet": "連結 Google 試算表",
"link_new_sheet": "連結新試算表",
"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_hidden_fields": "包含隱藏欄位",
@@ -1049,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。"
},
"general": {
"ai_data_analysis_enabled": "資料增強與分析AI",
"ai_data_analysis_enabled_description": "利用 AI 深入分析你的資料,建立儀表板、圖表、報告等。會處理你的體驗資料。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "管理此組織的 AI 功能。",
"ai_settings_updated_successfully": "AI 設定已成功更新",
"ai_smart_tools_enabled": "智慧功能AI",
"ai_smart_tools_enabled_description": "AI 幫你更快完成更多事。絕不會接觸 Formbricks 收集的資料,只用於像是將問卷翻譯成其他語言等用途。",
"bulk_invite_warning_description": "在免費方案中,所有組織成員始終會被指派「擁有者」角色。",
"cannot_delete_only_organization": "這是您唯一的組織,無法刪除。請先建立新組織。",
"cannot_leave_only_organization": "您無法離開此組織,因為它是您唯一的組織。請先建立新組織。",
@@ -1232,6 +1433,7 @@
"add_fallback_placeholder": "新增 預設 以顯示是否沒 有 值 可 回憶 。",
"add_hidden_field_id": "新增隱藏欄位 ID",
"add_highlight_border": "新增醒目提示邊框",
"add_highlight_border_description": "僅適用於產品內調查。",
"add_logic": "新增邏輯",
"add_none_of_the_above": "新增 \"以上皆非\"",
"add_option": "新增選項",
@@ -1430,7 +1632,6 @@
"follow_ups_modal_updated_successfull_toast": "後續 動作 已 更新 並 將 在 你 儲存 調查 後 儲存",
"follow_ups_new": "新增後續追蹤",
"follow_ups_upgrade_button_text": "升級以啟用後續追蹤",
"form_styling": "表單樣式設定",
"formbricks_sdk_is_not_connected": "Formbricks SDK 未連線",
"four_points": "4 分",
"heading": "標題",
@@ -1603,7 +1804,7 @@
"response_limits_redirections_and_more": "回應限制、重新導向等。",
"response_options": "回應選項",
"roundness": "圓角",
"roundness_description": "調整卡片邊角的圓度。",
"roundness_description": "調整邊角的圓潤程度。",
"row_used_in_logic_error": "此 row 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
"rows": "列",
"save_and_close": "儲存並關閉",
@@ -1649,6 +1850,7 @@
"survey_completed_subheading": "此免費且開源的問卷已關閉",
"survey_display_settings": "問卷顯示設定",
"survey_placement": "問卷位置",
"survey_styling": "表單樣式設定",
"survey_trigger": "問卷觸發器",
"switch_multi_language_on_to_get_started": "請開啟多語言功能以開始使用 👉",
"target_block_not_found": "找不到目標區塊",
@@ -1947,6 +2149,7 @@
"filtered_responses_excel": "篩選回應 (Excel)",
"generating_qr_code": "正在生成 QR code",
"impressions": "曝光數",
"impressions_identified_only": "僅顯示已識別聯絡人的曝光次數",
"impressions_tooltip": "問卷已檢視的次數。",
"in_app": {
"connection_description": "調查將顯示給符合以下列出條件的網站用戶",
@@ -1989,6 +2192,7 @@
"last_quarter": "上一季",
"last_year": "去年",
"limit": "限制",
"no_identified_impressions": "沒有來自已識別聯絡人的曝光次數",
"no_responses_found": "找不到回應",
"other_values_found": "找到其他值",
"overall": "整體",
@@ -2153,12 +2357,12 @@
"advanced_styling_field_headline_size_description": "調整標題文字的大小。",
"advanced_styling_field_headline_weight": "標題字體粗細",
"advanced_styling_field_headline_weight_description": "讓標題文字變細或變粗。",
"advanced_styling_field_height": "高度",
"advanced_styling_field_height": "最小高度",
"advanced_styling_field_indicator_bg": "指示器背景",
"advanced_styling_field_indicator_bg_description": "設定進度條已填滿部分的顏色。",
"advanced_styling_field_input_border_radius_description": "調整輸入框的圓角。",
"advanced_styling_field_input_font_size_description": "調整輸入框內輸入文字的大小。",
"advanced_styling_field_input_height_description": "調整輸入欄的高度。",
"advanced_styling_field_input_height_description": "控制輸入欄的最小高度。",
"advanced_styling_field_input_padding_x_description": "在左右兩側增加間距。",
"advanced_styling_field_input_padding_y_description": "在上方和下方增加間距。",
"advanced_styling_field_input_placeholder_opacity_description": "讓提示文字變得更淡。",
@@ -2167,6 +2371,8 @@
"advanced_styling_field_input_text_description": "設定輸入文字的顏色。",
"advanced_styling_field_option_bg": "背景",
"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_font_size_description": "調整選項標籤文字的大小。",
"advanced_styling_field_option_label": "標籤顏色",
@@ -2221,6 +2427,7 @@
"show_powered_by_formbricks": "顯示「Powered by Formbricks」標記",
"styling_updated_successfully": "樣式已成功更新",
"suggest_colors": "建議顏色",
"suggested_colors_applied_please_save": "已成功產生建議色彩。請按「儲存」以保存變更。",
"theme": "主題",
"theme_settings_description": "為所有調查建立樣式主題。您可以為每個調查啟用自訂樣式。"
},
@@ -2985,6 +3192,9 @@
"preview_survey_question_2_choice_2_label": "不用了,謝謝!",
"preview_survey_question_2_headline": "想要緊跟最新動態嗎?",
"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": "歡迎!",
"prioritize_features_description": "找出您的使用者最需要和最不需要的功能。",
"prioritize_features_name": "優先排序功能",

View File

@@ -95,7 +95,7 @@ describe("validateResponseData", () => {
mockGetElementsFromBlocks.mockReturnValue(mockElements);
mockValidateBlockResponses.mockReturnValue({});
validateResponseData([], mockResponseData, "en", true, mockQuestions);
validateResponseData([], mockResponseData, "en", mockQuestions);
expect(mockTransformQuestionsToBlocks).toHaveBeenCalledWith(mockQuestions, []);
expect(mockGetElementsFromBlocks).toHaveBeenCalledWith(transformedBlocks);
@@ -105,15 +105,15 @@ describe("validateResponseData", () => {
mockGetElementsFromBlocks.mockReturnValue(mockElements);
mockValidateBlockResponses.mockReturnValue({});
validateResponseData(mockBlocks, mockResponseData, "en", true, mockQuestions);
validateResponseData(mockBlocks, mockResponseData, "en", mockQuestions);
expect(mockTransformQuestionsToBlocks).not.toHaveBeenCalled();
});
test("should return null when both blocks and questions are empty", () => {
expect(validateResponseData([], mockResponseData, "en", true, [])).toBeNull();
expect(validateResponseData(null, mockResponseData, "en", true, [])).toBeNull();
expect(validateResponseData(undefined, mockResponseData, "en", true, null)).toBeNull();
expect(validateResponseData([], mockResponseData, "en", [])).toBeNull();
expect(validateResponseData(null, mockResponseData, "en", [])).toBeNull();
expect(validateResponseData(undefined, mockResponseData, "en", null)).toBeNull();
});
test("should use default language code", () => {
@@ -125,25 +125,58 @@ describe("validateResponseData", () => {
expect(mockValidateBlockResponses).toHaveBeenCalledWith(mockElements, mockResponseData, "en");
});
test("should validate only present fields when finished is false", () => {
test("should validate only fields present in responseData", () => {
const partialResponseData: TResponseData = { element1: "test" };
const partialElements = [mockElements[0]];
const elementsToValidate = [mockElements[0]];
mockGetElementsFromBlocks.mockReturnValue(mockElements);
mockValidateBlockResponses.mockReturnValue({});
validateResponseData(mockBlocks, partialResponseData, "en", false);
validateResponseData(mockBlocks, partialResponseData, "en");
expect(mockValidateBlockResponses).toHaveBeenCalledWith(partialElements, partialResponseData, "en");
expect(mockValidateBlockResponses).toHaveBeenCalledWith(elementsToValidate, partialResponseData, "en");
});
test("should validate all fields when finished is true", () => {
const partialResponseData: TResponseData = { element1: "test" };
mockGetElementsFromBlocks.mockReturnValue(mockElements);
test("should never validate elements not in responseData", () => {
const blocksWithTwoElements: TSurveyBlock[] = [
...mockBlocks,
{
id: "block2",
name: "Block 2",
elements: [
{
id: "element2",
type: TSurveyElementTypeEnum.OpenText,
headline: { default: "Q2" },
required: true,
inputType: "text",
charLimit: { enabled: false },
},
],
},
];
const allElements = [
...mockElements,
{
id: "element2",
type: TSurveyElementTypeEnum.OpenText,
headline: { default: "Q2" },
required: true,
inputType: "text",
charLimit: { enabled: false },
},
];
const responseDataWithOnlyElement1: TResponseData = { element1: "test" };
mockGetElementsFromBlocks.mockReturnValue(allElements);
mockValidateBlockResponses.mockReturnValue({});
validateResponseData(mockBlocks, partialResponseData, "en", true);
validateResponseData(blocksWithTwoElements, responseDataWithOnlyElement1, "en");
expect(mockValidateBlockResponses).toHaveBeenCalledWith(mockElements, partialResponseData, "en");
// Only element1 should be validated, not element2 (even though it's required)
expect(mockValidateBlockResponses).toHaveBeenCalledWith(
[allElements[0]],
responseDataWithOnlyElement1,
"en"
);
});
});

View File

@@ -9,13 +9,13 @@ import { getElementsFromBlocks } from "@/lib/survey/utils";
import { ApiErrorDetails } from "@/modules/api/v2/types/api-error";
/**
* Validates response data against survey validation rules
* Handles partial responses (in-progress) by only validating present fields when finished is false
* Validates response data against survey validation rules.
* Only validates elements that have data in responseData - never validates
* all survey elements regardless of completion status.
*
* @param blocks - Survey blocks containing elements with validation rules (preferred)
* @param responseData - Response data to validate (keyed by element ID)
* @param languageCode - Language code for error messages (defaults to "en")
* @param finished - Whether the response is finished (defaults to true for management APIs)
* @param questions - Survey questions (legacy format, used as fallback if blocks are empty)
* @returns Validation error map keyed by element ID, or null if validation passes
*/
@@ -23,7 +23,6 @@ export const validateResponseData = (
blocks: TSurveyBlock[] | undefined | null,
responseData: TResponseData,
languageCode: string = "en",
finished: boolean = true,
questions?: TSurveyQuestion[] | undefined | null
): TValidationErrorMap | null => {
// Use blocks if available, otherwise transform questions to blocks
@@ -42,11 +41,8 @@ export const validateResponseData = (
// Extract elements from blocks
const allElements = getElementsFromBlocks(blocksToUse);
// If response is not finished, only validate elements that are present in the response data
// This prevents "required" errors for fields the user hasn't reached yet
const elementsToValidate = finished
? allElements
: allElements.filter((element) => Object.keys(responseData).includes(element.id));
// Always validate only elements that are present in responseData
const elementsToValidate = allElements.filter((element) => Object.keys(responseData).includes(element.id));
// Validate selected elements
const errorMap = validateBlockResponses(elementsToValidate, responseData, languageCode);

View File

@@ -12,7 +12,9 @@ type HasFindMany =
| Prisma.TeamFindManyArgs
| Prisma.ProjectTeamFindManyArgs
| Prisma.UserFindManyArgs
| Prisma.ContactAttributeKeyFindManyArgs;
| Prisma.ContactAttributeKeyFindManyArgs
| Prisma.ChartFindManyArgs
| Prisma.DashboardFindManyArgs;
export function buildCommonFilterQuery<T extends HasFindMany>(query: T, params: TGetFilter): T {
const { limit, skip, sortBy, order, startDate, endDate, filterDateField = "createdAt" } = params || {};

View File

@@ -15,7 +15,7 @@ import {
import { getSurveyQuestions } from "@/modules/api/v2/management/responses/[responseId]/lib/survey";
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import { resolveStorageUrlsInObject, validateFileUploads } from "@/modules/storage/utils";
import { ZResponseIdSchema, ZResponseUpdateSchema } from "./types/responses";
export const GET = async (request: Request, props: { params: Promise<{ responseId: string }> }) =>
@@ -51,7 +51,10 @@ export const GET = async (request: Request, props: { params: Promise<{ responseI
return handleApiError(request, response.error as ApiErrorResponseV2);
}
return responses.successResponse(response);
return responses.successResponse({
...response,
data: { ...response.data, data: resolveStorageUrlsInObject(response.data.data) },
});
},
});
@@ -198,7 +201,6 @@ export const PUT = (request: Request, props: { params: Promise<{ responseId: str
questionsResponse.data.blocks,
body.data,
body.language ?? "en",
body.finished,
questionsResponse.data.questions
);
@@ -244,7 +246,10 @@ export const PUT = (request: Request, props: { params: Promise<{ responseId: str
auditLog.newObject = response.data;
}
return responses.successResponse(response);
return responses.successResponse({
...response,
data: { ...response.data, data: resolveStorageUrlsInObject(response.data.data) },
});
},
action: "updated",
targetType: "response",

View File

@@ -12,7 +12,7 @@ import { getSurveyQuestions } from "@/modules/api/v2/management/responses/[respo
import { ZGetResponsesFilter, ZResponseInput } from "@/modules/api/v2/management/responses/types/responses";
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import { resolveStorageUrlsInObject, validateFileUploads } from "@/modules/storage/utils";
import { createResponseWithQuotaEvaluation, getResponses } from "./lib/response";
export const GET = async (request: NextRequest) =>
@@ -44,7 +44,9 @@ export const GET = async (request: NextRequest) =>
environmentResponses.push(...res.data.data);
return responses.successResponse({ data: environmentResponses });
return responses.successResponse({
data: environmentResponses.map((r) => ({ ...r, data: resolveStorageUrlsInObject(r.data) })),
});
},
});
@@ -134,7 +136,6 @@ export const POST = async (request: Request) =>
surveyQuestions.data.blocks,
body.data,
body.language ?? "en",
body.finished,
surveyQuestions.data.questions
);

View File

@@ -30,4 +30,4 @@ export const rateLimitConfigs = {
upload: { interval: 60, allowedPerInterval: 5, namespace: "storage:upload" }, // 5 per minute
delete: { interval: 60, allowedPerInterval: 5, namespace: "storage:delete" }, // 5 per minute
},
};
} as const;

View File

@@ -0,0 +1,43 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
const mockLoad = vi.fn();
const mockTablePivot = vi.fn();
vi.mock("@cubejs-client/core", () => ({
default: vi.fn(() => ({
load: mockLoad,
})),
}));
describe("executeQuery", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
const resultSet = { tablePivot: mockTablePivot };
mockLoad.mockResolvedValue(resultSet);
mockTablePivot.mockReturnValue([{ id: "1", count: 42 }]);
});
test("loads query and returns tablePivot result", async () => {
const { executeQuery } = await import("./cube-client");
const query = { measures: ["FeedbackRecords.count"] };
const result = await executeQuery(query);
expect(mockLoad).toHaveBeenCalledWith(query);
expect(mockTablePivot).toHaveBeenCalled();
expect(result).toEqual([{ id: "1", count: 42 }]);
});
test("preserves API URL when it already contains /cubejs-api/v1", async () => {
const fullUrl = "https://cube.example.com/cubejs-api/v1";
vi.stubEnv("CUBEJS_API_URL", fullUrl);
const { executeQuery } = await import("./cube-client");
await executeQuery({ measures: ["FeedbackRecords.count"] });
// eslint-disable-next-line @typescript-eslint/no-require-imports
const cubejs = ((await vi.importMock("@cubejs-client/core")) as any).default;
expect(cubejs).toHaveBeenCalledWith(expect.any(String), { apiUrl: fullUrl });
vi.unstubAllEnvs();
});
});

View File

@@ -0,0 +1,26 @@
import cubejs, { type CubeApi, type Query } from "@cubejs-client/core";
const getApiUrl = (): string => {
const baseUrl = process.env.CUBEJS_API_URL || "http://localhost:4000";
if (baseUrl.includes("/cubejs-api/v1")) {
return baseUrl;
}
return `${baseUrl.replace(/\/$/, "")}/cubejs-api/v1`;
};
let cubeClient: CubeApi | null = null;
function getCubeClient(): CubeApi {
if (!cubeClient) {
// TODO: This will fail silently if the token is not set. We need to fix this before going to production.
const token = process.env.CUBEJS_API_TOKEN ?? "";
cubeClient = cubejs(token, { apiUrl: getApiUrl() });
}
return cubeClient;
}
export async function executeQuery(query: Query) {
const client = getCubeClient();
const resultSet = await client.load(query);
return resultSet.tablePivot();
}

View File

@@ -0,0 +1,342 @@
"use server";
import { createOpenAI } from "@ai-sdk/openai";
import { Output, generateText } from "ai";
import { z } from "zod";
import { type TChartQuery, ZChartQuery } from "@formbricks/types/analysis";
import { ZId } from "@formbricks/types/common";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
import { executeQuery } from "@/modules/ee/analysis/api/lib/cube-client";
import { validateQueryMembers } from "@/modules/ee/analysis/charts/lib/chart-utils";
import {
createChart,
deleteChart,
duplicateChart,
getChart,
getCharts,
updateChart,
} from "@/modules/ee/analysis/charts/lib/charts";
import { checkProjectAccess } from "@/modules/ee/analysis/lib/access";
import { generateSchemaContext } from "@/modules/ee/analysis/lib/ai-schema-context";
import { ZChartCreateInput, ZChartType, ZChartUpdateInput } from "@/modules/ee/analysis/types/analysis";
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
/** Client-facing chart input (projectId and createdBy are resolved server-side) */
const ZChartCreateInputClient = ZChartCreateInput.omit({ projectId: true, createdBy: true });
const ZCreateChartAction = z.object({
environmentId: ZId,
chartInput: ZChartCreateInputClient,
});
export const createChartAction = authenticatedActionClient.schema(ZCreateChartAction).action(
withAuditLogging(
"created",
"chart",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZCreateChartAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const chart = await createChart({
...parsedInput.chartInput,
projectId,
createdBy: ctx.user.id,
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.chartId = chart.id;
ctx.auditLoggingCtx.newObject = chart;
return chart;
}
)
);
const ZUpdateChartAction = z.object({
environmentId: ZId,
chartId: ZId,
chartUpdateInput: ZChartUpdateInput,
});
export const updateChartAction = authenticatedActionClient.schema(ZUpdateChartAction).action(
withAuditLogging(
"updated",
"chart",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZUpdateChartAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const { chart, updatedChart } = await updateChart(
parsedInput.chartId,
projectId,
parsedInput.chartUpdateInput
);
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.chartId = parsedInput.chartId;
ctx.auditLoggingCtx.oldObject = chart;
ctx.auditLoggingCtx.newObject = updatedChart;
return updatedChart;
}
)
);
const ZDuplicateChartAction = z.object({
environmentId: ZId,
chartId: ZId,
});
export const duplicateChartAction = authenticatedActionClient.schema(ZDuplicateChartAction).action(
withAuditLogging(
"created",
"chart",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZDuplicateChartAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const duplicatedChart = await duplicateChart(parsedInput.chartId, projectId, ctx.user.id);
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.chartId = duplicatedChart.id;
ctx.auditLoggingCtx.newObject = duplicatedChart;
return duplicatedChart;
}
)
);
const ZDeleteChartAction = z.object({
environmentId: ZId,
chartId: ZId,
});
export const deleteChartAction = authenticatedActionClient.schema(ZDeleteChartAction).action(
withAuditLogging(
"deleted",
"chart",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZDeleteChartAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const chart = await deleteChart(parsedInput.chartId, projectId);
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.chartId = parsedInput.chartId;
ctx.auditLoggingCtx.oldObject = chart;
return { success: true };
}
)
);
const ZGetChartAction = z.object({
environmentId: ZId,
chartId: ZId,
});
export const getChartAction = authenticatedActionClient
.schema(ZGetChartAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZGetChartAction>;
}) => {
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
return getChart(parsedInput.chartId, projectId);
}
);
const ZGetChartsAction = z.object({
environmentId: ZId,
});
export const getChartsAction = authenticatedActionClient
.schema(ZGetChartsAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZGetChartsAction>;
}) => {
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
return getCharts(projectId);
}
);
// ── Charts UI specific actions (query execution & AI generation) ─────────────
const ZExecuteQueryAction = z.object({
environmentId: ZId,
query: ZChartQuery,
});
export const executeQueryAction = authenticatedActionClient
.schema(ZExecuteQueryAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZExecuteQueryAction>;
}) => {
await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
validateQueryMembers(parsedInput.query);
try {
return await executeQuery(parsedInput.query as Record<string, unknown>);
} catch (error) {
throw error instanceof Error ? error : new Error("Failed to execute query");
}
}
);
const CUBE_NAME = "FeedbackRecords";
const ZGenerateAIQueryResponse = z.object({
measures: z.array(z.string()),
dimensions: z.array(z.string()).nullable(),
timeDimensions: z
.array(
z.object({
dimension: z.string(),
granularity: z.enum(["hour", "day", "week", "month", "quarter", "year"]).nullable(),
dateRange: z.string().nullable(),
})
)
.nullable(),
chartType: ZChartType,
filters: z
.array(
z.object({
member: z.string(),
operator: z.enum([
"equals",
"notEquals",
"contains",
"notContains",
"set",
"notSet",
"gt",
"gte",
"lt",
"lte",
]),
values: z.array(z.string()).nullable(),
})
)
.nullable(),
});
const ZGenerateAIChartAction = z.object({
environmentId: ZId,
prompt: z.string().min(1).max(2000),
});
export const generateAIChartAction = authenticatedActionClient
.schema(ZGenerateAIChartAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZGenerateAIChartAction>;
}) => {
await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is not configured");
}
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const schemaContext = generateSchemaContext();
const { output } = await generateText({
model: openai("gpt-4o-mini"),
output: Output.object({ schema: ZGenerateAIQueryResponse }),
system: schemaContext,
prompt: `User request: "${parsedInput.prompt}"`,
});
const measures = output.measures.length > 0 ? output.measures : [`${CUBE_NAME}.count`];
const { chartType, ...cubeQuery } = { ...output, measures };
// Strip nulls/empty arrays so Cube.js receives only present fields
const cleanQuery: Record<string, unknown> = {
measures: cubeQuery.measures,
...(cubeQuery.dimensions?.length && { dimensions: cubeQuery.dimensions }),
...(cubeQuery.filters?.length && {
filters: cubeQuery.filters.map(({ member, operator, values }) => ({
member,
operator,
...(values != null && { values }),
})),
}),
...(cubeQuery.timeDimensions?.length && {
timeDimensions: cubeQuery.timeDimensions.map(({ dimension, granularity, dateRange }) => ({
dimension,
...(granularity != null && { granularity }),
...(dateRange != null && { dateRange }),
})),
}),
};
validateQueryMembers(cleanQuery as TChartQuery);
const data = await executeQuery(cleanQuery);
return {
query: cleanQuery,
chartType,
data: Array.isArray(data) ? data : [],
};
}
);

View File

@@ -0,0 +1,113 @@
"use client";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import {
Dialog,
DialogBody,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/modules/ui/components/dialog";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/modules/ui/components/select";
interface AddToDashboardDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
chartName: string;
onChartNameChange: (name: string) => void;
dashboards: Array<{ id: string; name: string }>;
selectedDashboardId: string | undefined;
onDashboardSelect: (id: string) => void;
onConfirm: () => void;
isSaving: boolean;
}
export function AddToDashboardDialog({
isOpen,
onOpenChange,
chartName,
onChartNameChange,
dashboards,
selectedDashboardId,
onDashboardSelect,
onConfirm,
isSaving,
}: Readonly<AddToDashboardDialogProps>) {
const { t } = useTranslation();
return (
<Dialog open={isOpen} onOpenChange={(open) => !isSaving && onOpenChange(open)}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t("environments.analysis.charts.add_chart_to_dashboard")}</DialogTitle>
<DialogDescription>
{t("environments.analysis.charts.add_chart_to_dashboard_description")}
</DialogDescription>
</DialogHeader>
<DialogBody>
<div className="space-y-4">
<div>
<Label htmlFor="chart-name">{t("environments.analysis.charts.chart_name")}</Label>
<Input
id="chart-name"
className="mt-2"
placeholder={t("environments.analysis.charts.chart_name_placeholder")}
value={chartName}
onChange={(e) => onChartNameChange(e.target.value)}
maxLength={255}
/>
</div>
<div>
<Label htmlFor="dashboard-select">{t("environments.analysis.charts.dashboard")}</Label>
<Select value={selectedDashboardId} onValueChange={onDashboardSelect}>
<SelectTrigger
id="dashboard-select"
className="mt-2 w-full"
disabled={dashboards.length === 0}>
<SelectValue
placeholder={
dashboards.length === 0
? t("environments.analysis.charts.no_dashboards_available")
: t("environments.analysis.charts.dashboard_select_placeholder")
}
/>
</SelectTrigger>
<SelectContent position="popper" className="max-h-[200px]">
{dashboards.map((dashboard) => (
<SelectItem key={dashboard.id} value={dashboard.id}>
{dashboard.name}
</SelectItem>
))}
</SelectContent>
</Select>
{dashboards.length === 0 && (
<p className="mt-1 text-xs text-gray-500">
{t("environments.analysis.charts.no_dashboards_create_first")}
</p>
)}
</div>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving}>
{t("common.cancel")}
</Button>
<Button onClick={onConfirm} loading={isSaving} disabled={!selectedDashboardId || !chartName.trim()}>
{t("environments.analysis.charts.add_to_dashboard")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,247 @@
"use client";
import { useEffect, useMemo, useReducer, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import type { TChartQuery } from "@formbricks/types/analysis";
import { AdvancedChartPreview } from "@/modules/ee/analysis/charts/components/advanced-chart-preview";
import { ChartBuilderGuide } from "@/modules/ee/analysis/charts/components/chart-builder-guide";
import { ChartTypeSelector } from "@/modules/ee/analysis/charts/components/chart-type-selector";
import { DimensionsPanel } from "@/modules/ee/analysis/charts/components/dimensions-panel";
import { FiltersPanel } from "@/modules/ee/analysis/charts/components/filters-panel";
import { MeasuresPanel } from "@/modules/ee/analysis/charts/components/measures-panel";
import { TimeDimensionPanel } from "@/modules/ee/analysis/charts/components/time-dimension-panel";
import { useChartQuery } from "@/modules/ee/analysis/charts/hooks/use-chart-query";
import {
type ChartBuilderState,
type FilterRow,
type TimeDimensionConfig,
buildCubeQuery,
parseQueryToState,
} from "@/modules/ee/analysis/lib/query-builder";
import { FEEDBACK_FIELDS } from "@/modules/ee/analysis/lib/schema-definition";
import type { AnalyticsResponse, TChartType } from "@/modules/ee/analysis/types/analysis";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
interface AdvancedChartBuilderProps {
environmentId: string;
chartType: TChartType;
initialQuery?: TChartQuery;
hidePreview?: boolean;
onChartGenerated?: (data: AnalyticsResponse) => void;
}
const ACTION = {
SET_MEASURES: "SET_MEASURES",
SET_DIMENSIONS: "SET_DIMENSIONS",
SET_FILTERS: "SET_FILTERS",
SET_FILTER_LOGIC: "SET_FILTER_LOGIC",
SET_TIME_DIMENSION: "SET_TIME_DIMENSION",
INIT_FROM_QUERY: "INIT_FROM_QUERY",
} as const;
type Action =
| { type: typeof ACTION.SET_MEASURES; payload: string[] }
| { type: typeof ACTION.SET_DIMENSIONS; payload: string[] }
| { type: typeof ACTION.SET_FILTERS; payload: FilterRow[] }
| { type: typeof ACTION.SET_FILTER_LOGIC; payload: "and" | "or" }
| { type: typeof ACTION.SET_TIME_DIMENSION; payload: TimeDimensionConfig | null }
| { type: typeof ACTION.INIT_FROM_QUERY; payload: Partial<ChartBuilderState> };
const initialState: ChartBuilderState = {
selectedMeasures: [],
selectedDimensions: [],
filters: [],
filterLogic: "and",
timeDimension: null,
};
const chartBuilderReducer = (state: ChartBuilderState, action: Action): ChartBuilderState => {
switch (action.type) {
case ACTION.SET_MEASURES:
return { ...state, selectedMeasures: action.payload };
case ACTION.SET_DIMENSIONS:
return { ...state, selectedDimensions: action.payload };
case ACTION.SET_FILTERS:
return { ...state, filters: action.payload };
case ACTION.SET_FILTER_LOGIC:
return { ...state, filterLogic: action.payload };
case ACTION.SET_TIME_DIMENSION:
return { ...state, timeDimension: action.payload };
case ACTION.INIT_FROM_QUERY:
return { ...state, ...action.payload };
default:
return state;
}
};
export function AdvancedChartBuilder({
environmentId,
chartType,
initialQuery,
hidePreview = false,
onChartGenerated,
}: Readonly<AdvancedChartBuilderProps>) {
const { t } = useTranslation();
const parsedInitial = initialQuery ? parseQueryToState(initialQuery) : null;
const [state, dispatch] = useReducer(
chartBuilderReducer,
initialQuery ? { ...initialState, ...parsedInitial } : initialState
);
const { chartData, query, isLoading, error, runQuery } = useChartQuery(environmentId, initialQuery);
const currentQuery = useMemo(() => buildCubeQuery(state), [state]);
const hasConfigChanged = useMemo(() => {
if (!query) return true;
return JSON.stringify(currentQuery) !== JSON.stringify(query);
}, [currentQuery, query]);
const appliedInitialQueryRef = useRef<TChartQuery | null>(null);
useEffect(() => {
if (!initialQuery) return;
if (appliedInitialQueryRef.current === initialQuery) return;
appliedInitialQueryRef.current = initialQuery;
const parsed = parseQueryToState(initialQuery);
dispatch({ type: ACTION.INIT_FROM_QUERY, payload: parsed });
setDimensionsOpen((parsed.selectedDimensions?.length ?? 0) > 0);
}, [initialQuery]);
const handleRunQuery = async () => {
if (state.selectedMeasures.length === 0) {
toast.error(t("environments.analysis.charts.please_select_at_least_one_measure"));
return;
}
const result = await runQuery(buildCubeQuery(state));
if (result) {
onChartGenerated?.({ ...result, chartType });
}
};
const [dimensionsOpen, setDimensionsOpen] = useState(
() => (parsedInitial?.selectedDimensions?.length ?? 0) > 0
);
const timeDimensionOpen = state.timeDimension != null;
const filtersOpen = state.filters.length > 0;
return (
<div className={hidePreview ? "space-y-2" : "grid gap-4 lg:grid-cols-2"}>
<div className="mx-1 space-y-2">
{!hidePreview && (
<>
<ChartBuilderGuide />
<ChartTypeSelector selectedChartType={chartType} onChartTypeSelect={() => {}} />
</>
)}
<div className="mt-4 flex w-full flex-col gap-3 overflow-hidden rounded-lg border bg-slate-50 p-4">
<MeasuresPanel
selectedMeasures={state.selectedMeasures}
onMeasuresChange={(measures) => dispatch({ type: ACTION.SET_MEASURES, payload: measures })}
/>
</div>
<AdvancedOptionToggle
isChecked={dimensionsOpen}
onToggle={(checked) => {
setDimensionsOpen(checked);
if (!checked) dispatch({ type: ACTION.SET_DIMENSIONS, payload: [] });
}}
htmlId="chart-dimensions-toggle"
title={t("environments.analysis.charts.dimensions")}
description={t("environments.analysis.charts.dimensions_toggle_description")}
customContainerClass="mt-2 px-0"
childrenContainerClass="flex-col gap-3 p-4"
childBorder>
<DimensionsPanel
hideTitle
selectedDimensions={state.selectedDimensions}
onDimensionsChange={(dimensions) =>
dispatch({ type: ACTION.SET_DIMENSIONS, payload: dimensions })
}
/>
</AdvancedOptionToggle>
<AdvancedOptionToggle
isChecked={timeDimensionOpen}
onToggle={() => {
if (timeDimensionOpen) dispatch({ type: ACTION.SET_TIME_DIMENSION, payload: null });
else if (!state.timeDimension) {
dispatch({
type: ACTION.SET_TIME_DIMENSION,
payload: {
dimension: "FeedbackRecords.collectedAt",
granularity: "day",
dateRange: "last 30 days",
},
});
}
}}
htmlId="chart-time-dimension-toggle"
title={t("environments.analysis.charts.time_dimension")}
description={t("environments.analysis.charts.time_dimension_toggle_description")}
customContainerClass="mt-2 px-0"
childrenContainerClass="flex-col gap-3 p-4"
childBorder>
<TimeDimensionPanel
hideTitle
timeDimension={state.timeDimension}
onTimeDimensionChange={(config) => dispatch({ type: ACTION.SET_TIME_DIMENSION, payload: config })}
/>
</AdvancedOptionToggle>
<AdvancedOptionToggle
isChecked={filtersOpen}
onToggle={() => {
if (filtersOpen) {
dispatch({ type: ACTION.SET_FILTERS, payload: [] });
} else if (state.filters.length === 0) {
const firstField = FEEDBACK_FIELDS.dimensions[0] ?? FEEDBACK_FIELDS.measures[0];
dispatch({
type: ACTION.SET_FILTERS,
payload: [
{
id: crypto.randomUUID(),
field: firstField?.id ?? "",
operator: "equals" as const,
values: null,
},
],
});
}
}}
htmlId="chart-filters-toggle"
title={t("environments.analysis.charts.filters")}
description={t("environments.analysis.charts.filters_toggle_description")}
customContainerClass="mt-2 px-0"
childrenContainerClass="flex-col gap-3 p-4"
childBorder>
<FiltersPanel
hideTitle
filters={state.filters}
filterLogic={state.filterLogic}
onFiltersChange={(filters) => dispatch({ type: ACTION.SET_FILTERS, payload: filters })}
onFilterLogicChange={(logic) => dispatch({ type: ACTION.SET_FILTER_LOGIC, payload: logic })}
/>
</AdvancedOptionToggle>
<Button onClick={handleRunQuery} disabled={isLoading || !hasConfigChanged}>
{isLoading ? <LoadingSpinner /> : t("environments.analysis.charts.generate_chart")}
</Button>
</div>
{!hidePreview && (
<AdvancedChartPreview
error={error}
isLoading={isLoading}
chartData={chartData}
chartType={chartType}
query={query}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,78 @@
"use client";
import * as Collapsible from "@radix-ui/react-collapsible";
import { DatabaseIcon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import type { TChartQuery } from "@formbricks/types/analysis";
import { ChartRenderer } from "@/modules/ee/analysis/charts/components/chart-renderer";
import { DataViewer } from "@/modules/ee/analysis/charts/components/data-viewer";
import type { TChartDataRow, TChartType } from "@/modules/ee/analysis/types/analysis";
import { Button } from "@/modules/ui/components/button";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
interface AdvancedChartPreviewProps {
error: string | null;
isLoading: boolean;
chartData: TChartDataRow[] | null;
chartType: TChartType;
query: TChartQuery | null;
}
export function AdvancedChartPreview({
error,
isLoading,
chartData,
chartType,
query,
}: Readonly<AdvancedChartPreviewProps>) {
const { t } = useTranslation();
const [showData, setShowData] = useState(false);
const hasData = chartData && chartData.length > 0 && !isLoading && chartType && query;
const isEmpty = !chartData && !isLoading && !error;
return (
<div className="space-y-2">
<h3 className="text-md font-semibold text-gray-900">
{t("environments.analysis.charts.chart_preview")}
</h3>
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800">{error}</div>
)}
{isLoading && (
<div className="flex h-64 items-center justify-center">
<LoadingSpinner />
</div>
)}
{hasData && (
<div className="space-y-2">
<div className="rounded-lg border border-gray-200 bg-white p-4">
<ChartRenderer chartType={chartType} data={chartData} query={query} />
</div>
<Collapsible.Root open={showData} onOpenChange={setShowData}>
<Collapsible.CollapsibleTrigger asChild>
<Button variant="outline" className="w-full justify-start">
<DatabaseIcon className="mr-2 h-4 w-4" />
{showData ? t("common.hide") : t("common.view")}{" "}
{t("environments.analysis.charts.data_label")}
</Button>
</Collapsible.CollapsibleTrigger>
<Collapsible.CollapsibleContent className="mt-2">
<DataViewer data={chartData} />
</Collapsible.CollapsibleContent>
</Collapsible.Root>
</div>
)}
{isEmpty && (
<div className="flex h-64 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-sm text-gray-500">
{t("environments.analysis.charts.advanced_chart_builder_config_prompt")}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,84 @@
"use client";
import { ActivityIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { generateAIChartAction } from "@/modules/ee/analysis/charts/actions";
import type { AnalyticsResponse } from "@/modules/ee/analysis/types/analysis";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
interface AIQuerySectionProps {
environmentId: string;
onChartGenerated: (data: AnalyticsResponse) => void;
}
export function AIQuerySection({ environmentId, onChartGenerated }: Readonly<AIQuerySectionProps>) {
const [userQuery, setUserQuery] = useState("");
const [isGenerating, setIsGenerating] = useState(false);
const { t } = useTranslation();
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!userQuery.trim()) return;
setIsGenerating(true);
try {
const result = await generateAIChartAction({
environmentId,
prompt: userQuery.trim(),
});
if (result?.data) {
onChartGenerated(result.data);
} else {
const errorMessage = getFormattedErrorMessage(result);
toast.error(errorMessage);
}
} catch (error: unknown) {
const message =
error instanceof Error ? error.message : t("common.something_went_wrong_please_try_again");
toast.error(message);
} finally {
setIsGenerating(false);
}
};
return (
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<div className="mb-4 flex items-center gap-2">
<div className="bg-brand-dark/10 flex h-8 w-8 items-center justify-center rounded-full">
<ActivityIcon className="text-brand-dark h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-gray-900">
{t("environments.analysis.charts.ai_query_section_title")}
</h2>
<p className="text-sm text-gray-500">
{t("environments.analysis.charts.ai_query_section_description")}
</p>
</div>
</div>
<form className="flex gap-4" onSubmit={handleSubmit}>
<Input
autoFocus
placeholder={t("environments.analysis.charts.ai_query_placeholder")}
value={userQuery}
onChange={(e) => setUserQuery(e.target.value)}
className="flex-1"
maxLength={2000}
disabled={isGenerating}
/>
<Button
type="submit"
disabled={!userQuery.trim() || isGenerating}
loading={isGenerating}
className="bg-brand-dark hover:bg-brand-dark/90">
{t("common.generate")}
</Button>
</form>
</div>
);
}

View File

@@ -0,0 +1,119 @@
"use client";
import { type ElementType, type ReactNode, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { CartesianGrid, XAxis, YAxis } from "recharts";
import {
CHART_BRAND_DARK,
formatCellValue,
formatXAxisTick,
} from "@/modules/ee/analysis/charts/lib/chart-utils";
import { formatCubeColumnHeader } from "@/modules/ee/analysis/lib/schema-definition";
import type { TChartDataRow } from "@/modules/ee/analysis/types/analysis";
import type { ChartConfig } from "@/modules/ui/components/chart";
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@/modules/ui/components/chart";
const ChartTooltipRow = ({
value,
dataKey,
color,
}: Readonly<{ value: unknown; dataKey: string; color?: string }>) => {
const { t } = useTranslation();
const indicatorColor = color ?? CHART_BRAND_DARK;
return (
<>
<div
className="h-2.5 w-2.5 shrink-0 rounded-[2px] border border-current"
style={{
backgroundColor: indicatorColor,
borderColor: indicatorColor,
}}
/>
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">{formatCubeColumnHeader(dataKey, t)}</span>
<span className="text-foreground font-mono font-medium tabular-nums">{formatCellValue(value)}</span>
</div>
</>
);
};
/** Creates a tooltip formatter bound to dataKey for Cartesian charts. Defined at module level to avoid Sonar "component in parent" warnings. */
const createTooltipFormatter = (dataKey: string) => {
const Formatter = (value: unknown) => <ChartTooltipRow value={value} dataKey={dataKey} />;
Formatter.displayName = "ChartTooltipFormatter";
return Formatter;
};
/** Tooltip content for single-measure Cartesian charts. */
const SingleMeasureTooltip = ({ dataKey }: Readonly<{ dataKey: string }>) => {
const formatter = useMemo(() => createTooltipFormatter(dataKey), [dataKey]);
return <ChartTooltipContent labelFormatter={formatXAxisTick} formatter={formatter} />;
};
/** Tooltip formatter for multi-measure charts; uses each payload item's dataKey and color. */
const multiMeasureTooltipFormatter = (
value: unknown,
name: string,
item: { dataKey?: string; color?: string; payload?: { fill?: string } }
) => {
const key = item?.dataKey ?? name;
const color = item?.color ?? item?.payload?.fill;
return <ChartTooltipRow value={value} dataKey={key} color={color} />;
};
export interface CartesianChartProps {
data: TChartDataRow[];
xAxisKey: string;
dataKeys: string[];
chartConfig: ChartConfig;
chart: ElementType;
children: ReactNode;
showLegend?: boolean;
chartProps?: Record<string, unknown>;
}
/** Shared layout for bar, line, and area charts. Supports single or multiple measures. */
export function CartesianChart({
data,
xAxisKey,
dataKeys,
chartConfig,
chart: Chart,
children,
showLegend = false,
chartProps = {},
}: Readonly<CartesianChartProps>) {
const isMultiMeasure = dataKeys.length > 1;
const tooltipContent = isMultiMeasure ? (
<ChartTooltipContent labelFormatter={formatXAxisTick} formatter={multiMeasureTooltipFormatter} />
) : (
<SingleMeasureTooltip dataKey={dataKeys[0]} />
);
return (
<div className="h-64 w-full">
<ChartContainer config={chartConfig} className="h-full w-full">
<Chart data={data} {...chartProps}>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis
dataKey={xAxisKey}
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={formatXAxisTick}
/>
<YAxis tickLine={false} axisLine={false} />
<ChartTooltip content={tooltipContent} />
{showLegend && <ChartLegend content={<ChartLegendContent />} verticalAlign="top" height={36} />}
{children}
</Chart>
</ChartContainer>
</div>
);
}

View File

@@ -0,0 +1,105 @@
"use client";
import { HelpCircle } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle } from "@/modules/ui/components/dialog";
interface ChartBuilderGuideProps {
/** Optional trigger; when not provided, caller renders their own */
trigger?: React.ReactNode;
}
export function ChartBuilderGuide({ trigger }: Readonly<ChartBuilderGuideProps>) {
const [isOpen, setIsOpen] = useState(false);
const { t } = useTranslation();
return (
<>
{trigger ?? (
<Button type="button" variant="ghost" size="sm" onClick={() => setIsOpen(true)}>
<HelpCircle className="mr-2 h-4 w-4" />
{t("environments.analysis.charts.guide_button")}
</Button>
)}
<Dialog open={isOpen} onOpenChange={(isOpen) => !isOpen && setIsOpen(false)}>
<DialogContent width="wide" className="max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{t("environments.analysis.charts.guide_title")}</DialogTitle>
</DialogHeader>
<DialogBody>
<div className="space-y-6">
<section>
<h3 className="text-md mb-1 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_chart_type")}
</h3>
<p className="text-gray-600">{t("environments.analysis.charts.guide_chart_type_desc")}</p>
</section>
<section>
<h3 className="text-md mb-1 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_measures")}
</h3>
<p className="text-sm text-gray-600">
{t("environments.analysis.charts.guide_measures_predefined")}
</p>
</section>
<section>
<h3 className="text-md mb-1 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_dimensions")}
</h3>
<p className="text-sm text-gray-600">
{t("environments.analysis.charts.guide_dimensions_desc")}
</p>
</section>
<section>
<h3 className="text-md mb-1 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_time_dimension")}
</h3>
<p className="text-sm text-gray-600">
{t("environments.analysis.charts.guide_time_dimension_desc")}
</p>
</section>
<section>
<h3 className="text-md mb-1 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_filters")}
</h3>
<p className="text-sm text-gray-600">
{t("environments.analysis.charts.guide_filters_desc")}
</p>
</section>
<section>
<h3 className="text-md mb-2 font-semibold text-gray-900">
{t("environments.analysis.charts.guide_quick_ref")}
</h3>
<dl className="space-y-1.5 text-sm text-gray-600">
<div>
<dt className="inline font-medium text-gray-900">Measure: </dt>
<dd className="inline">{t("environments.analysis.charts.guide_term_measure")}</dd>
</div>
<div>
<dt className="inline font-medium text-gray-900">Dimension: </dt>
<dd className="inline">{t("environments.analysis.charts.guide_term_dimension")}</dd>
</div>
<div>
<dt className="inline font-medium text-gray-900">Time dimension: </dt>
<dd className="inline">{t("environments.analysis.charts.guide_term_time")}</dd>
</div>
<div>
<dt className="inline font-medium text-gray-900">Filter: </dt>
<dd className="inline">{t("environments.analysis.charts.guide_term_filter")}</dd>
</div>
</dl>
</section>
</div>
</DialogBody>
</DialogContent>
</Dialog>
</>
);
}

View File

@@ -0,0 +1,32 @@
"use client";
import { PlusIcon, SaveIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import { DialogFooter } from "@/modules/ui/components/dialog";
interface ChartDialogFooterProps {
onSaveClick: () => void;
onAddToDashboardClick: () => void;
isSaving: boolean;
}
export function ChartDialogFooter({
onSaveClick,
onAddToDashboardClick,
isSaving,
}: Readonly<ChartDialogFooterProps>) {
const { t } = useTranslation();
return (
<DialogFooter>
<Button variant="outline" onClick={onAddToDashboardClick} disabled={isSaving}>
<PlusIcon className="mr-2 h-4 w-4" />
{t("environments.analysis.charts.add_to_dashboard")}
</Button>
<Button onClick={onSaveClick} disabled={isSaving}>
<SaveIcon className="mr-2 h-4 w-4" />
{t("environments.analysis.charts.save_chart")}
</Button>
</DialogFooter>
);
}

View File

@@ -0,0 +1,25 @@
"use client";
import { useTranslation } from "react-i18next";
import { Dialog, DialogContent, DialogTitle } from "@/modules/ui/components/dialog";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
interface ChartDialogLoadingViewProps {
open: boolean;
onClose: () => void;
}
export function ChartDialogLoadingView({ open, onClose }: Readonly<ChartDialogLoadingViewProps>) {
const { t } = useTranslation();
return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent width="wide">
<DialogTitle className="sr-only">{t("common.loading")}</DialogTitle>
<div className="flex h-64 items-center justify-center">
<LoadingSpinner />
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,129 @@
"use client";
import { CopyIcon, MoreVertical, SquarePenIcon, TrashIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { deleteChartAction, duplicateChartAction } from "@/modules/ee/analysis/charts/actions";
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
import { Button } from "@/modules/ui/components/button";
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
interface ChartDropdownMenuProps {
environmentId: string;
chart: TChartWithCreator;
onEdit?: () => void;
}
export function ChartDropdownMenu({ environmentId, chart, onEdit }: Readonly<ChartDropdownMenuProps>) {
const { t } = useTranslation();
const router = useRouter();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isDuplicating, setIsDuplicating] = useState(false);
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
const handleDeleteChart = async () => {
setIsDeleting(true);
try {
const result = await deleteChartAction({ environmentId, chartId: chart.id });
if (result?.data) {
toast.success(t("environments.analysis.charts.chart_deleted_successfully"));
setIsDeleteDialogOpen(false);
router.refresh();
} else {
const msg =
getFormattedErrorMessage(result) || t("environments.analysis.charts.chart_deletion_error");
toast.error(msg);
}
} catch {
toast.error(t("common.something_went_wrong_please_try_again"));
} finally {
setIsDeleting(false);
}
};
const handleDuplicateChart = async () => {
setIsDuplicating(true);
try {
const result = await duplicateChartAction({ environmentId, chartId: chart.id });
if (result?.data) {
toast.success(t("environments.analysis.charts.chart_duplicated_successfully"));
router.refresh();
} else {
toast.error(
getFormattedErrorMessage(result) || t("environments.analysis.charts.chart_duplication_error")
);
}
} catch {
toast.error(t("environments.analysis.charts.chart_duplication_error"));
} finally {
setIsDuplicating(false);
}
};
return (
<div id={`chart-${chart.id}-actions`} data-testid="chart-dropdown-menu">
<DropdownMenu open={isDropDownOpen} onOpenChange={setIsDropDownOpen}>
<DropdownMenuTrigger className="z-10" asChild>
<Button variant="outline" className="px-2" onClick={(e) => e.stopPropagation()}>
<span className="sr-only">{t("environments.analysis.charts.open_options")}</span>
<MoreVertical className="size-4" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="inline-block w-auto min-w-max" align="end">
<DropdownMenuGroup>
{onEdit && (
<DropdownMenuItem
icon={<SquarePenIcon className="size-4" />}
onClick={() => {
setIsDropDownOpen(false);
onEdit();
}}>
{t("common.edit")}
</DropdownMenuItem>
)}
<DropdownMenuItem
icon={<CopyIcon className="size-4" />}
onClick={() => {
setIsDropDownOpen(false);
handleDuplicateChart();
}}
disabled={isDuplicating}>
{t("common.duplicate")}
</DropdownMenuItem>
<DropdownMenuItem
icon={<TrashIcon className="size-4" />}
onClick={() => {
setIsDropDownOpen(false);
setIsDeleteDialogOpen(true);
}}
disabled={isDeleting}>
{t("common.delete")}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DeleteDialog
deleteWhat={t("common.chart")}
open={isDeleteDialogOpen}
setOpen={setIsDeleteDialogOpen}
onDelete={handleDeleteChart}
text={t("environments.analysis.charts.delete_chart_confirmation")}
isDeleting={isDeleting}
/>
</div>
);
}

View File

@@ -0,0 +1,39 @@
"use client";
import { Component, type ErrorInfo, type ReactNode } from "react";
interface ChartErrorBoundaryProps {
fallbackMessage: string;
children: ReactNode;
}
interface ChartErrorBoundaryState {
hasError: boolean;
}
export class ChartErrorBoundary extends Component<ChartErrorBoundaryProps, ChartErrorBoundaryState> {
constructor(props: ChartErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): ChartErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error("ChartRenderer error:", error, info.componentStack);
}
override render() {
if (this.state.hasError) {
return (
<div className="text-muted-foreground flex h-64 items-center justify-center text-sm">
{this.props.fallbackMessage}
</div>
);
}
return this.props.children;
}
}

View File

@@ -0,0 +1,88 @@
"use client";
import { BarChart, DatabaseIcon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ChartErrorBoundary } from "@/modules/ee/analysis/charts/components/chart-error-boundary";
import { ChartRenderer } from "@/modules/ee/analysis/charts/components/chart-renderer";
import { DataViewer } from "@/modules/ee/analysis/charts/components/data-viewer";
import { AnalyticsResponse } from "@/modules/ee/analysis/types/analysis";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/modules/ui/components/tabs";
interface ChartPreviewProps {
chartData: AnalyticsResponse | null;
isLoading?: boolean;
error?: string | null;
}
export function ChartPreview({ chartData, isLoading = false, error }: Readonly<ChartPreviewProps>) {
const [activeTab, setActiveTab] = useState<"chart" | "data">("chart");
const { t } = useTranslation();
const data = chartData?.data ?? [];
const handleTabChange = (value: string) => {
if (value === "chart" || value === "data") {
setActiveTab(value);
}
};
const renderContent = () => {
if (isLoading) {
return (
<div className="flex h-48 items-center justify-center">
<LoadingSpinner />
</div>
);
}
if (error || chartData?.error) {
return (
<div className="flex h-48 items-center justify-center text-sm text-red-600">
{error || chartData?.error}
</div>
);
}
if (!chartData) {
return (
<div className="flex h-48 items-center justify-center text-sm text-gray-500">
{t("environments.analysis.charts.no_data_available")}
</div>
);
}
return (
<Tabs value={activeTab} onValueChange={handleTabChange}>
<div className="mb-4 flex justify-end">
<TabsList>
<TabsTrigger value="chart" icon={<BarChart className="h-4 w-4" />}>
{t("environments.analysis.charts.chart")}
</TabsTrigger>
<TabsTrigger value="data" icon={<DatabaseIcon className="h-4 w-4" />}>
{t("environments.analysis.charts.chart_data_tab")}
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="chart" className="mt-0">
<ChartErrorBoundary fallbackMessage={t("environments.analysis.charts.chart_render_error")}>
<ChartRenderer chartType={chartData.chartType} data={data} query={chartData.query} />
</ChartErrorBoundary>
</TabsContent>
<TabsContent value="data" className="mt-0">
<DataViewer data={data} />
</TabsContent>
</Tabs>
);
};
return (
<div className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 font-semibold text-gray-900">{t("environments.analysis.charts.chart_preview")}</h3>
{renderContent()}
</div>
);
}

View File

@@ -0,0 +1,202 @@
"use client";
import { useTranslation } from "react-i18next";
import { Area, AreaChart, Bar, BarChart, Cell, Line, LineChart, Pie, PieChart } from "recharts";
import type { TChartQuery } from "@formbricks/types/analysis";
import { CartesianChart } from "@/modules/ee/analysis/charts/components/cartesian-chart";
import {
CHART_BRAND_DARK,
CHART_MEASURE_COLORS,
formatXAxisTick,
preparePieData,
} from "@/modules/ee/analysis/charts/lib/chart-utils";
import { formatCubeColumnHeader } from "@/modules/ee/analysis/lib/schema-definition";
import type { TChartDataRow, TChartType } from "@/modules/ee/analysis/types/analysis";
import type { ChartConfig } from "@/modules/ui/components/chart";
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/modules/ui/components/chart";
const formatPieLabel = ({ name, percent }: { name: string; percent?: number }): string => {
if (percent == null) return "";
return `${formatXAxisTick(name)}: ${(percent * 100).toFixed(0)}%`;
};
interface ChartRendererProps {
chartType: TChartType;
data: TChartDataRow[];
query: TChartQuery;
}
export function ChartRenderer({ chartType, data, query }: Readonly<ChartRendererProps>) {
const { t } = useTranslation();
if (!data || data.length === 0) {
return (
<div className="text-muted-foreground flex h-64 items-center justify-center">
{t("environments.analysis.charts.no_data_available")}
</div>
);
}
const rowKeys = Object.keys(data[0] ?? {});
const timeDim = query.timeDimensions?.[0];
const timeDimKey = timeDim?.granularity
? `${timeDim.dimension}.${timeDim.granularity}`
: timeDim?.dimension;
const xAxisKey = query.dimensions?.[0] ?? timeDimKey ?? rowKeys[0] ?? "key";
const measureIds = query.measures?.filter((m) => rowKeys.includes(m)) ?? [];
const dataKeys = measureIds.length > 0 ? measureIds : rowKeys.filter((k) => k !== xAxisKey);
if (dataKeys.length === 0) {
return (
<div className="text-muted-foreground flex h-64 items-center justify-center">
{t("environments.analysis.charts.no_data_available")}
</div>
);
}
const chartConfig: ChartConfig = Object.fromEntries(
dataKeys.map((key, i) => [
key,
{
label: formatCubeColumnHeader(key, t),
color: CHART_MEASURE_COLORS[i % CHART_MEASURE_COLORS.length],
},
])
);
const dataKey = dataKeys[0];
const isMultiMeasure = dataKeys.length > 1;
switch (chartType) {
case "bar":
return (
<CartesianChart
chart={BarChart}
data={data}
xAxisKey={xAxisKey}
dataKeys={dataKeys}
chartConfig={chartConfig}
showLegend={isMultiMeasure}
chartProps={isMultiMeasure ? { barCategoryGap: "20%" } : {}}>
{dataKeys.map((key, i) => (
<Bar
key={key}
dataKey={key}
fill={chartConfig[key]?.color ?? CHART_MEASURE_COLORS[i % CHART_MEASURE_COLORS.length]}
radius={4}
/>
))}
</CartesianChart>
);
case "line":
return (
<CartesianChart
chart={LineChart}
data={data}
xAxisKey={xAxisKey}
dataKeys={dataKeys}
chartConfig={chartConfig}
showLegend={isMultiMeasure}>
{dataKeys.map((key, i) => {
const color = chartConfig[key]?.color ?? CHART_MEASURE_COLORS[i % CHART_MEASURE_COLORS.length];
return (
<Line
key={key}
type="monotone"
dataKey={key}
stroke={color}
strokeWidth={3}
dot={{ fill: color, r: 4 }}
activeDot={{ r: 6 }}
/>
);
})}
</CartesianChart>
);
case "area":
return (
<CartesianChart
chart={AreaChart}
data={data}
xAxisKey={xAxisKey}
dataKeys={dataKeys}
chartConfig={chartConfig}
showLegend={isMultiMeasure}>
{dataKeys.map((key, i) => (
<Area
key={key}
type="monotone"
dataKey={key}
stroke={chartConfig[key]?.color ?? CHART_MEASURE_COLORS[i % CHART_MEASURE_COLORS.length]}
fill={chartConfig[key]?.color ?? CHART_MEASURE_COLORS[i % CHART_MEASURE_COLORS.length]}
fillOpacity={0.4}
strokeWidth={2}
/>
))}
</CartesianChart>
);
case "pie": {
const pieResult = preparePieData(data, dataKey);
if (!pieResult) {
return (
<div className="text-muted-foreground flex h-64 items-center justify-center">
{t("environments.analysis.charts.no_valid_data_to_display")}
</div>
);
}
const { processedData, colors } = pieResult;
return (
<div className="h-64 w-full min-w-0">
<ChartContainer config={chartConfig} className="h-full w-full min-w-0">
<PieChart>
<Pie
data={processedData}
dataKey={dataKey}
nameKey={xAxisKey}
cx="50%"
cy="50%"
outerRadius={80}
label={formatPieLabel}>
{processedData.map((row, index) => {
const rowKey = row[xAxisKey] ?? `row-${index}`;
const uniqueKey = `${xAxisKey}-${String(rowKey)}-${index}`;
return <Cell key={uniqueKey} fill={colors[index] || CHART_BRAND_DARK} />;
})}
</Pie>
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value, name) => [String(value), formatCubeColumnHeader(String(name), t)]}
/>
}
/>
</PieChart>
</ChartContainer>
</div>
);
}
case "big_number": {
const total =
data.length === 1
? Number(data[0]?.[dataKey]) || 0
: data.reduce((sum, row) => sum + (Number(row[dataKey]) || 0), 0);
const formatted = total.toLocaleString();
return (
<div className="flex h-64 items-center justify-center">
<div className="text-center">
<div className="text-foreground text-4xl font-bold">{formatted}</div>
<div className="text-muted-foreground mt-2 text-sm">{formatCubeColumnHeader(dataKey, t)}</div>
</div>
</div>
);
}
default:
return (
<div className="text-muted-foreground flex h-64 items-center justify-center">
{t("environments.analysis.charts.chart_type_not_supported", { chartType })}
</div>
);
}
}

View File

@@ -0,0 +1,97 @@
"use client";
import { BarChart3Icon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { convertDateString, timeSinceDate } from "@/lib/time";
import { ChartDropdownMenu } from "@/modules/ee/analysis/charts/components/chart-dropdown-menu";
import { CreateChartDialog } from "@/modules/ee/analysis/charts/components/create-chart-dialog";
import { CHART_TYPE_ICONS } from "@/modules/ee/analysis/charts/lib/chart-types";
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
interface ChartRowProps {
chart: TChartWithCreator;
environmentId: string;
isReadOnly: boolean;
}
export function ChartRow({ chart, environmentId, isReadOnly }: Readonly<ChartRowProps>) {
const { t } = useTranslation();
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const IconComponent = CHART_TYPE_ICONS[chart.type as keyof typeof CHART_TYPE_ICONS] ?? BarChart3Icon;
const handleChartClick = () => {
if (!isReadOnly) {
setIsEditDialogOpen(true);
}
};
const handleRowKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleChartClick();
}
};
return (
<>
{/* Cannot use native <button>; row contains dropdown trigger (nested interactive invalid) */}
{/* eslint-disable-next-line jsx-a11y/prefer-tag-over-role, jsx-a11y/no-static-element-interactions */}
<div
role={isReadOnly ? undefined : "button"}
tabIndex={isReadOnly ? undefined : 0}
onClick={isReadOnly ? undefined : handleChartClick}
onKeyDown={isReadOnly ? undefined : handleRowKeyDown}
aria-label={
isReadOnly ? undefined : t("environments.analysis.charts.open_chart", { name: chart.name })
}
className={`grid h-12 w-full grid-cols-7 content-center p-2 text-left transition-colors ease-in-out hover:bg-slate-100 ${isReadOnly ? "" : "cursor-pointer"}`}>
<div className="col-span-6 grid grid-cols-6 content-center">
<div className="col-span-3 flex items-center pl-6 text-sm">
<div className="flex items-center gap-4">
<div className="ph-no-capture w-8 flex-shrink-0 text-slate-500">
<IconComponent className="h-5 w-5" />
</div>
<div className="flex flex-col">
<div className="ph-no-capture font-medium text-slate-900">{chart.name}</div>
</div>
</div>
</div>
<div className="col-span-1 my-auto hidden whitespace-nowrap text-center text-sm text-slate-500 sm:block">
<div className="ph-no-capture text-slate-900">{chart.creator?.name ?? "-"}</div>
</div>
<div className="col-span-1 my-auto hidden whitespace-normal text-center text-sm text-slate-500 sm:block">
<div className="ph-no-capture text-slate-900">
{convertDateString(chart.createdAt.toISOString())}
</div>
</div>
<div className="col-span-1 my-auto hidden text-center text-sm text-slate-500 sm:block">
<div className="ph-no-capture text-slate-900">{timeSinceDate(new Date(chart.updatedAt))}</div>
</div>
</div>
<div // NOSONAR - stopPropagation wrapper to prevent row click when interacting with dropdown
className="col-span-1 my-auto flex items-center justify-end pr-6"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}>
{!isReadOnly && (
<ChartDropdownMenu
environmentId={environmentId}
chart={chart}
onEdit={() => setIsEditDialogOpen(true)}
/>
)}
</div>
</div>
{!isReadOnly && (
<CreateChartDialog
open={isEditDialogOpen}
onOpenChange={setIsEditDialogOpen}
environmentId={environmentId}
chartId={chart.id}
initialChart={chart}
onSuccess={() => setIsEditDialogOpen(false)}
/>
)}
</>
);
}

View File

@@ -0,0 +1,47 @@
"use client";
import { useTranslation } from "react-i18next";
import { getChartTypes } from "@/modules/ee/analysis/charts/lib/chart-types";
import type { TChartType } from "@/modules/ee/analysis/types/analysis";
interface ChartTypeSelectorProps {
selectedChartType: TChartType;
onChartTypeSelect: (chartType: TChartType) => void;
}
export function ChartTypeSelector({
selectedChartType,
onChartTypeSelect,
}: Readonly<ChartTypeSelectorProps>) {
const { t } = useTranslation();
const chartTypes = getChartTypes(t);
return (
<div className="space-y-2">
<h2 className="text-md font-semibold text-gray-900">
{t("environments.analysis.charts.chart_builder_choose_chart_type")}
</h2>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
{chartTypes.map((chart) => {
const isSelected = selectedChartType === chart.id;
return (
<button
key={chart.id}
type="button"
onClick={() => onChartTypeSelect(chart.id)}
className={`rounded-md border p-4 text-center transition-all hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
isSelected
? "border-brand-dark ring-brand-dark bg-brand-dark/5 ring-1"
: "border-gray-200 hover:border-gray-300"
}`}>
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded bg-gray-100">
<chart.icon className="h-6 w-6 text-gray-600" strokeWidth={1.5} />
</div>
<span className="text-sm font-medium text-gray-700">{chart.label}</span>
</button>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,63 @@
import { Delay } from "@suspensive/react";
import { Suspense, use } from "react";
import { getTranslate } from "@/lingodotdev/server";
import { ChartsList } from "@/modules/ee/analysis/charts/components/charts-list";
import { ChartsListSkeleton } from "@/modules/ee/analysis/charts/components/charts-list-skeleton";
import { CreateChartButton } from "@/modules/ee/analysis/charts/components/create-chart-button";
import { getChartsWithCreator } from "@/modules/ee/analysis/charts/lib/charts";
import { AnalysisPageLayout } from "@/modules/ee/analysis/components/analysis-page-layout";
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
interface ChartsListContentProps {
chartsPromise: Promise<TChartWithCreator[]>;
environmentId: string;
isReadOnly: boolean;
}
const ChartsListContent = ({
chartsPromise,
environmentId,
isReadOnly,
}: Readonly<ChartsListContentProps>) => {
const charts = use(chartsPromise);
return <ChartsList charts={charts} environmentId={environmentId} isReadOnly={isReadOnly} />;
};
interface ChartsListPageProps {
environmentId: string;
}
export async function ChartsListPage({ environmentId }: Readonly<ChartsListPageProps>) {
const t = await getTranslate();
const { project, isReadOnly } = await getEnvironmentAuth(environmentId);
const chartsPromise = getChartsWithCreator(project.id);
return (
<AnalysisPageLayout
pageTitle={t("common.analysis")}
environmentId={environmentId}
cta={isReadOnly ? undefined : <CreateChartButton environmentId={environmentId} />}>
<Suspense
fallback={
<Delay ms={200}>
<ChartsListSkeleton
columnHeaders={[
t("common.title"),
t("common.created_by"),
t("common.created_at"),
t("common.updated_at"),
]}
/>
</Delay>
}>
<ChartsListContent
chartsPromise={chartsPromise}
environmentId={environmentId}
isReadOnly={isReadOnly}
/>
</Suspense>
</AnalysisPageLayout>
);
}

View File

@@ -0,0 +1,43 @@
const SKELETON_ROWS = 3;
const SkeletonRow = () => {
return (
<div className="grid h-12 w-full animate-pulse grid-cols-7 content-center p-2">
<div className="col-span-3 flex items-center gap-4 pl-6">
<div className="h-5 w-5 rounded bg-gray-200" />
<div className="h-4 w-36 rounded bg-gray-200" />
</div>
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
<div className="h-4 w-20 rounded bg-gray-200" />
</div>
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
<div className="h-4 w-24 rounded bg-gray-200" />
</div>
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
<div className="h-4 w-20 rounded bg-gray-200" />
</div>
<div className="col-span-1" />
</div>
);
};
interface ChartsListSkeletonProps {
columnHeaders: [string, string, string, string];
}
export const ChartsListSkeleton = ({ columnHeaders }: Readonly<ChartsListSkeletonProps>) => {
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="grid h-12 grid-cols-7 content-center border-b text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6">{columnHeaders[0]}</div>
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[1]}</div>
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[2]}</div>
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[3]}</div>
<div className="col-span-1" />
</div>
{Array.from({ length: SKELETON_ROWS }).map((_, i) => (
<SkeletonRow key={`skeleton-row-${String(i)}`} />
))}
</div>
);
};

View File

@@ -0,0 +1,34 @@
import { getTranslate } from "@/lingodotdev/server";
import { ChartRow } from "@/modules/ee/analysis/charts/components/chart-row";
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
interface ChartsListProps {
charts: TChartWithCreator[];
environmentId: string;
isReadOnly: boolean;
}
export const ChartsList = async ({ charts, environmentId, isReadOnly }: Readonly<ChartsListProps>) => {
const t = await getTranslate();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="grid h-12 grid-cols-7 content-center border-b text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6">{t("common.title")}</div>
<div className="col-span-1 hidden text-center sm:block">{t("common.created_by")}</div>
<div className="col-span-1 hidden text-center sm:block">{t("common.created_at")}</div>
<div className="col-span-1 hidden text-center sm:block">{t("common.updated_at")}</div>
<div className="col-span-1" />
</div>
{charts.length === 0 ? (
<p className="py-6 text-center text-sm text-slate-400">
{t("environments.analysis.charts.no_charts_found")}
</p>
) : (
charts.map((chart) => (
<ChartRow key={chart.id} chart={chart} environmentId={environmentId} isReadOnly={isReadOnly} />
))
)}
</div>
);
};

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