chore: merge with epic/v5

This commit is contained in:
pandeymangg
2026-05-05 15:14:53 +05:30
44 changed files with 1479 additions and 534 deletions
+3
View File
@@ -64,6 +64,9 @@ DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=pu
HUB_API_KEY=dev-api-key
HUB_API_URL=http://localhost:8080
HUB_DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable
# Hub image tag used by docker-compose.dev.yml (hub + hub-migrate). Leave unset to use the
# pinned default in the compose file; override here when testing a specific Hub release.
# HUB_IMAGE_TAG=0.2.0
###########################
# CUBE ANALYTICS (XM V5) #
@@ -0,0 +1,121 @@
"use server";
import { z } from "zod";
import { ZId } from "@formbricks/types/common";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
import { getOrganizationIdFromWorkspaceId } from "@/lib/utils/helper";
import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-directory/lib/feedback-directory";
import { semanticSearchFeedbackRecords } from "@/modules/hub/service";
import type { SemanticSearchResultItem } from "@/modules/hub/types";
const TOPICS_PREVIEW_LIMIT = 10;
const SEARCH_CONCURRENCY = 4;
const ZSemanticSearchFeedbackRecordsAction = z.object({
workspaceId: ZId,
query: z.string().trim().min(1).max(500),
limit: z.number().min(1).max(50).optional(),
minScore: z.number().min(0).max(1).optional(),
});
export type TTopicsPreviewSearchResult = SemanticSearchResultItem & {
tenant_id: string;
directory_name: string;
};
export type TTopicsPreviewSearchActionResult = {
results: TTopicsPreviewSearchResult[];
unavailable: boolean;
unavailableMessage?: string;
};
const ensureReadAccess = async (userId: string, workspaceId: string): Promise<void> => {
const organizationId = await getOrganizationIdFromWorkspaceId(workspaceId);
await checkAuthorizationUpdated({
userId,
organizationId,
access: [
{
type: "organization",
roles: ["owner", "manager"],
},
{
type: "workspaceTeam",
minPermission: "read",
workspaceId,
},
],
});
};
export const semanticSearchFeedbackRecordsAction = authenticatedActionClient
.inputSchema(ZSemanticSearchFeedbackRecordsAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZSemanticSearchFeedbackRecordsAction>;
}): Promise<TTopicsPreviewSearchActionResult> => {
await ensureReadAccess(ctx.user.id, parsedInput.workspaceId);
const directories = await getFeedbackDirectoriesByWorkspaceId(parsedInput.workspaceId);
if (directories.length === 0) {
return { results: [], unavailable: false };
}
const limit = parsedInput.limit ?? TOPICS_PREVIEW_LIMIT;
const searches: {
directory: (typeof directories)[number];
result: Awaited<ReturnType<typeof semanticSearchFeedbackRecords>>;
}[] = [];
for (let i = 0; i < directories.length; i += SEARCH_CONCURRENCY) {
const chunk = directories.slice(i, i + SEARCH_CONCURRENCY);
const chunkResults = await Promise.all(
chunk.map(async (directory) => {
const result = await semanticSearchFeedbackRecords({
tenant_id: directory.id,
query: parsedInput.query,
limit,
min_score: parsedInput.minScore,
});
return { directory, result };
})
);
searches.push(...chunkResults);
}
const successfulResults = searches.flatMap(({ directory, result }) =>
(result.data?.data ?? []).map((item) => ({
...item,
tenant_id: directory.id,
directory_name: directory.name,
}))
);
if (successfulResults.length > 0) {
return {
results: successfulResults.toSorted((a, b) => b.score - a.score).slice(0, limit),
unavailable: false,
};
}
const firstError = searches.find(({ result }) => result.error)?.result.error;
if (firstError?.status === 0 || firstError?.status === 503) {
return {
results: [],
unavailable: true,
unavailableMessage: firstError.message,
};
}
if (firstError) {
throw new Error(firstError.message);
}
return { results: [], unavailable: false };
}
);
@@ -0,0 +1,192 @@
"use client";
import { SearchIcon, SparklesIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Badge } from "@/modules/ui/components/badge";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { UnifyConfigNavigation } from "../../components/UnifyConfigNavigation";
import { semanticSearchFeedbackRecordsAction } from "../actions";
import type { TTopicsPreviewSearchResult } from "../actions";
interface TopicsSubtopicsPreviewProps {
workspaceId: string;
directoryMap: Record<string, string>;
}
export const TopicsSubtopicsPreview = ({
workspaceId,
directoryMap,
}: Readonly<TopicsSubtopicsPreviewProps>) => {
const { t } = useTranslation();
const [query, setQuery] = useState("");
const [results, setResults] = useState<TTopicsPreviewSearchResult[]>([]);
const [hasSearched, setHasSearched] = useState(false);
const [isSearching, setIsSearching] = useState(false);
const [error, setError] = useState<string | null>(null);
const [unavailableMessage, setUnavailableMessage] = useState<string | null>(null);
const hasDirectories = Object.keys(directoryMap).length > 0;
const exampleSearches = [
t("workspace.unify.semantic_topics_example_slow_checkout"),
t("workspace.unify.semantic_topics_example_pricing_complaints"),
t("workspace.unify.semantic_topics_example_confusing_onboarding"),
];
const runSearch = async (searchQuery: string) => {
const trimmedQuery = searchQuery.trim();
if (!trimmedQuery || isSearching) return;
setQuery(trimmedQuery);
setIsSearching(true);
setHasSearched(true);
setError(null);
setUnavailableMessage(null);
try {
const response = await semanticSearchFeedbackRecordsAction({
workspaceId,
query: trimmedQuery,
limit: 10,
minScore: 0.7,
});
if (response?.data) {
setResults(response.data.results);
setUnavailableMessage(response.data.unavailable ? (response.data.unavailableMessage ?? "") : null);
} else {
setResults([]);
setError(getFormattedErrorMessage(response) ?? t("workspace.unify.semantic_search_failed"));
}
} catch {
setResults([]);
setError(t("workspace.unify.semantic_search_failed"));
} finally {
setIsSearching(false);
}
};
const handleSubmit = async (event: { preventDefault: () => void }) => {
event.preventDefault();
await runSearch(query);
};
return (
<PageContentWrapper>
<PageHeader pageTitle={t("workspace.unify.feedback_records")}>
<UnifyConfigNavigation workspaceId={workspaceId} activeId="topics-subtopics" />
</PageHeader>
<div className="space-y-4">
<div className="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="max-w-2xl space-y-2">
<div className="flex items-center gap-2">
<SparklesIcon className="h-5 w-5 text-slate-500" aria-hidden="true" />
<h2 className="text-lg font-semibold text-slate-900">
{t("workspace.unify.semantic_topics_preview_title")}
</h2>
<Badge text={t("common.preview")} type="gray" size="tiny" />
</div>
<p className="text-sm text-slate-600">
{t("workspace.unify.semantic_topics_preview_description")}
</p>
</div>
</div>
<form className="mt-5 flex flex-col gap-3 sm:flex-row" onSubmit={handleSubmit}>
<Input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder={t("workspace.unify.semantic_search_placeholder")}
disabled={!hasDirectories || isSearching}
aria-label={t("workspace.unify.semantic_search_input_label")}
/>
<Button type="submit" disabled={!query.trim() || !hasDirectories} loading={isSearching}>
<SearchIcon className="h-4 w-4" aria-hidden="true" />
{t("workspace.unify.search_feedback")}
</Button>
</form>
<div className="mt-4 flex flex-wrap items-center gap-2">
<span className="text-xs text-slate-500">{t("workspace.unify.try_searching_for")}</span>
{exampleSearches.map((label) => (
<Button
key={label}
type="button"
size="sm"
variant="secondary"
disabled={!hasDirectories || isSearching}
onClick={() => runSearch(label)}>
{label}
</Button>
))}
</div>
</div>
{!hasDirectories && (
<div className="rounded-xl border border-slate-200 bg-white p-6 text-center shadow-sm">
<p className="text-sm text-slate-600">{t("workspace.unify.semantic_search_no_directories")}</p>
<Button className="mt-4" size="sm" asChild>
<Link href={`/workspaces/${workspaceId}/feedback-sources`}>
{t("workspace.unify.manage_feedback_sources")}
</Link>
</Button>
</div>
)}
{error && (
<div className="rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">{error}</div>
)}
{unavailableMessage !== null && (
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
{t("workspace.unify.semantic_search_unavailable")}
</div>
)}
{hasSearched && !isSearching && !error && unavailableMessage === null && results.length === 0 && (
<div className="rounded-xl border border-slate-200 bg-white p-6 text-center shadow-sm">
<p className="text-sm text-slate-600">{t("workspace.unify.semantic_search_no_results")}</p>
</div>
)}
{results.length > 0 && (
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="border-b border-slate-200 px-4 py-3">
<p className="text-sm font-medium text-slate-900">
{t("workspace.unify.semantic_search_results_count", { count: results.length })}
</p>
</div>
<div className="divide-y divide-slate-100">
{results.map((result) => (
<div key={`${result.tenant_id}-${result.feedback_record_id}`} className="space-y-2 p-4">
<div className="flex flex-wrap items-center gap-2">
<Badge text={result.directory_name} type="gray" size="tiny" />
<span className="text-xs text-slate-500">
{t("workspace.unify.semantic_search_relevance", {
score: Math.round(result.score * 100),
})}
</span>
</div>
<p className="text-sm font-medium text-slate-900">
{result.field_label || t("workspace.unify.field_label")}
</p>
<p className="whitespace-pre-wrap text-sm text-slate-700">
{result.value_text || t("workspace.unify.semantic_search_missing_text")}
</p>
</div>
))}
</div>
</div>
)}
</div>
</PageContentWrapper>
);
};
@@ -0,0 +1,29 @@
import { notFound } from "next/navigation";
import { getTranslate } from "@/lingodotdev/server";
import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-directory/lib/feedback-directory";
import { getWorkspaceAuth } from "@/modules/workspaces/lib/utils";
import { TopicsSubtopicsPreview } from "./components/topics-subtopics-preview";
export default async function UnifyTopicsSubtopicsPage(
props: Readonly<{ params: Promise<{ workspaceId: string }> }>
) {
const t = await getTranslate();
const params = await props.params;
const { isOwner, isManager, hasReadAccess, hasReadWriteAccess, hasManageAccess, session } =
await getWorkspaceAuth(params.workspaceId);
if (!session) {
throw new Error(t("common.session_not_found"));
}
const hasAccess = isOwner || isManager || hasReadAccess || hasReadWriteAccess || hasManageAccess;
if (!hasAccess) {
return notFound();
}
const directories = await getFeedbackDirectoriesByWorkspaceId(params.workspaceId);
const directoryMap = Object.fromEntries(directories.map((directory) => [directory.id, directory.name]));
return <TopicsSubtopicsPreview workspaceId={params.workspaceId} directoryMap={directoryMap} />;
}
+1 -1
View File
@@ -5,7 +5,6 @@ import { TAuthenticationApiKey } from "@formbricks/types/auth";
import { authenticateRequest } from "@/app/api/v1/auth";
import { reportApiError } from "@/app/lib/api/api-error-reporter";
import { responses } from "@/app/lib/api/response";
import { getApiKeyFromHeaders } from "@/modules/api/lib/api-key-auth";
import {
AuthenticationMethod,
isClientSideApiRoute,
@@ -13,6 +12,7 @@ import {
isManagementApiRoute,
} from "@/app/middleware/endpoint-validator";
import { AUDIT_LOG_ENABLED } from "@/lib/constants";
import { getApiKeyFromHeaders } from "@/modules/api/lib/api-key-auth";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
TEnvoyRateLimitAuthType,
+16 -1
View File
@@ -407,7 +407,6 @@ checksums:
common/some_files_failed_to_upload: a0e26efeb29ae905257ecf93b112dff0
common/something_went_wrong: a3cd2f01c073f1f5ff436d4b132d39cf
common/something_went_wrong_please_try_again: c62a7718d9a1e9c4ffb707807550f836
common/soon: b12e79beb0aef9414a445a1b95dd4322
common/sort_by: 8adf3dbc5668379558957662f0c43563
common/start_free_trial: e346e4ed7d138dcc873db187922369da
common/status: 4e1fcce15854d824919b4a582c697c90
@@ -3579,6 +3578,7 @@ checksums:
workspace/unify/request_feedback_source: 51045caa2c81dee971d23a1841d19a7e
workspace/unify/required: 04d7fb6f37ffe0a6ca97d49e2a8b6eb5
workspace/unify/save_changes: 53dd9f4f0a4accc822fa5c1f2f6d118a
workspace/unify/search_feedback: db1e8dd05944bb928b96e3822aee3379
workspace/unify/select_a_survey_to_see_questions: 792eba3d2f6d210231a2266401111a20
workspace/unify/select_a_value: 115002bf2d9eec536165a7b7efc62862
workspace/unify/select_feedback_directory: 88afbf2c2a322249908ee5d00ec5f65d
@@ -3588,6 +3588,20 @@ checksums:
workspace/unify/select_survey: bac52e59c7847417bef6fe7b7096b475
workspace/unify/select_survey_and_questions: 53914988a2f48caecea23f3b3b868b9f
workspace/unify/select_survey_questions_description: 3386ed56085eabebefa3cc453269fc5b
workspace/unify/semantic_search_failed: 6adf5f85d453ef2923861ad7b188787a
workspace/unify/semantic_search_input_label: 3b8af322b080da8b8cb4fcce6c3f3d1e
workspace/unify/semantic_search_missing_text: e1ad1ba8f3ab2e05f4f73732543d0ed5
workspace/unify/semantic_search_no_directories: 2bcebe10f5898f5422ee17ed66295044
workspace/unify/semantic_search_no_results: 50f0572ad7584c91af5a0a28523f40f2
workspace/unify/semantic_search_placeholder: b5dbff2cdd334d7b86f18a12c56ffbb1
workspace/unify/semantic_search_relevance: ddd1a91cd29944d5af7b899168b988a2
workspace/unify/semantic_search_results_count: 199b822e4f709787b79dd42ccd70e58f
workspace/unify/semantic_search_unavailable: eb66fd42fc327627e74fe54a76f33b16
workspace/unify/semantic_topics_example_confusing_onboarding: ac612953829e6a7f58e34796a472ca71
workspace/unify/semantic_topics_example_pricing_complaints: fdf96d24d56620f79c31de48e6c1936b
workspace/unify/semantic_topics_example_slow_checkout: 42579a662e637ffe40de7078def55805
workspace/unify/semantic_topics_preview_description: 330871cf6f36128bfdbc7d9d20c500a4
workspace/unify/semantic_topics_preview_title: 2d59d672921b4807a40770e5d40b485e
workspace/unify/set_value: b8a86f8da957ebd599ece4b1b1936a78
workspace/unify/setup_connection: cce7d9c488d737d04e70bed929a46f8a
workspace/unify/showing_count_loaded: f443aae08223b65fbd5521d6e69534a4
@@ -3615,6 +3629,7 @@ checksums:
workspace/unify/submission_id: 02edf76883b47079dbe20f3f36b7c1a7
workspace/unify/survey_has_no_questions: c08514b6bce5eb464a4492239be5934d
workspace/unify/topics_and_subtopics: 1148eca01a1993fadca932efcdea7641
workspace/unify/try_searching_for: 8bc02885a2efdc53d7323aac26ae1110
workspace/unify/unify_feedback: cd68c8ce0445767e7dcfb4de789903d5
workspace/unify/update_mapping_description: 58d5966c0c9b406c037dff3aa8bcb396
workspace/unify/updated_at: 8fdb85248e591254973403755dcc3724
+4 -4
View File
@@ -3,17 +3,17 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import * as crypto from "@/lib/crypto";
import {
createGatewayServiceToken,
createFeedbackRecordsGatewayToken,
createEmailChangeToken,
createEmailToken,
createFeedbackRecordsGatewayToken,
createGatewayServiceToken,
createInviteToken,
createToken,
createTokenForLinkSurvey,
getEmailFromEmailToken,
verifyGatewayServiceToken,
verifyFeedbackRecordsGatewayToken,
verifyEmailChangeToken,
verifyFeedbackRecordsGatewayToken,
verifyGatewayServiceToken,
verifyInviteToken,
verifyToken,
verifyTokenForLinkSurvey,
+9 -3
View File
@@ -3,7 +3,7 @@ import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { ENCRYPTION_KEY, NEXTAUTH_SECRET } from "@/lib/constants";
import { symmetricDecrypt, symmetricEncrypt } from "@/lib/crypto";
import { getGatewayAuthServiceTokenPurpose, TGatewayAuthService } from "@/modules/gateway-auth/lib/service";
import { TGatewayAuthService, getGatewayAuthServiceTokenPurpose } from "@/modules/gateway-auth/lib/service";
const FEEDBACK_RECORDS_GATEWAY_TOKEN_TTL_SECONDS = 60 * 10;
@@ -29,7 +29,10 @@ export const createToken = (userId: string, options = {}): string => {
return jwt.sign({ id: encryptedUserId }, NEXTAUTH_SECRET, options);
};
export const createGatewayServiceToken = (userId: string, service: TGatewayAuthService): {
export const createGatewayServiceToken = (
userId: string,
service: TGatewayAuthService
): {
token: string;
expiresAt: string;
} => {
@@ -104,7 +107,10 @@ export const verifyEmailChangeToken = async (token: string): Promise<{ id: strin
};
};
export const verifyGatewayServiceToken = (token: string, service: TGatewayAuthService): {
export const verifyGatewayServiceToken = (
token: string,
service: TGatewayAuthService
): {
userId: string;
} => {
if (!NEXTAUTH_SECRET) {
+22 -7
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Einige Dateien konnten nicht hochgeladen werden",
"something_went_wrong": "Etwas ist schiefgelaufen",
"something_went_wrong_please_try_again": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
"soon": "Bald",
"sort_by": "Sortieren nach",
"start_free_trial": "Kostenlose Testversion starten",
"status": "Status",
@@ -1746,7 +1745,7 @@
"filter_data": "Daten filtern",
"filters": "Filter",
"filters_toggle_description": "Nur Daten einbeziehen, die die folgenden Bedingungen erfüllen.",
"go_to_feedback_directories": "Zu Feedback-Verzeichnissen",
"go_to_feedback_directories": "Zu Feedback-Verzeichnissen gehen",
"granularity": "Granularität",
"granularity_day": "Tag",
"granularity_hour": "Stunde",
@@ -1852,9 +1851,9 @@
"delete_api_key_confirmation": "Alle Anwendungen, die diesen Schlüssel verwenden, können nicht mehr auf deine Formbricks-Daten zugreifen.",
"duplicate_access": "Doppelter Workspace-Zugriff ist nicht erlaubt",
"duplicate_directory_access": "Doppelter Zugriff auf Feedback-Verzeichnis nicht erlaubt",
"feedback_directory_access": "Zugriff auf Feedback-Verzeichnis",
"feedback_directory_access": "Feedback-Verzeichnis-Zugriff",
"no_api_keys_yet": "Du hast noch keine API-Schlüssel",
"no_directory_permissions_found": "Keine Berechtigungen für Feedback-Verzeichnis gefunden",
"no_directory_permissions_found": "Keine Berechtigungen für Feedback-Verzeichnisse gefunden",
"no_workspace_permissions_found": "Keine Workspace-Berechtigungen gefunden",
"organization_access": "Organisations-Zugriff",
"organization_access_description": "Wähle Lese- oder Schreibrechte für organisationsweite Ressourcen aus.",
@@ -2547,10 +2546,10 @@
"archive_directory": "Verzeichnis archivieren",
"archive_not_allowed": "Du darfst dieses Verzeichnis nicht archivieren.",
"are_you_sure_you_want_to_archive": "Bist du sicher, dass du dieses Verzeichnis archivieren möchtest? Workspaces haben dann keinen Zugriff mehr darauf.",
"assign_workspaces_description": "Lege fest, welche Workspaces auf dieses Feedback-Verzeichnis zugreifen können.",
"assign_workspaces_description": "Steuere, welche Workspaces auf dieses Feedback-Verzeichnis zugreifen können.",
"connectors_description": "Connectoren, die Feedback-Datensätze an dieses Verzeichnis senden.",
"create_feedback_directory": "Feedback-Verzeichnis erstellen",
"description": "Verwalte Feedback-Verzeichnisse und ihre Workspace-Zuordnungen.",
"description": "Verwalte Feedback-Verzeichnisse und ihre Workspace-Zuweisungen.",
"directory_archived_successfully": "Verzeichnis erfolgreich archiviert",
"directory_created_successfully": "Verzeichnis erfolgreich erstellt",
"directory_id": "Verzeichnis-ID",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Schreibgeschützte Metadatenwerte (keine Zeichenfolge)",
"metadata_value": "Metadatenwert",
"missing_feedback_source_title": "Feedback-Quelle fehlt?",
"no_feedback_directory_available": "Diesem Workspace ist kein Feedback-Verzeichnis zugewiesen. Erstelle oder weise zuerst eines zu.",
"no_feedback_directory_available": "Diesem Workspace ist kein Feedback-Verzeichnis zugewiesen. Erstelle oder weise zuerst eins zu.",
"no_feedback_records": "Noch keine Feedback-Einträge vorhanden. Einträge erscheinen hier, sobald deine Konnektoren Daten senden.",
"no_source_fields_loaded": "Noch keine Quellfelder geladen",
"no_sources_connected": "Noch keine Quellen verbunden. Füge eine Quelle hinzu, um loszulegen.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Quellen-Integration anfragen",
"required": "Erforderlich",
"save_changes": "Änderungen speichern",
"search_feedback": "Feedback durchsuchen",
"select_a_survey_to_see_questions": "Wähle eine Umfrage aus, um ihre Fragen zu sehen",
"select_a_value": "Wähle einen Wert aus...",
"select_feedback_directory": "Verzeichnis auswählen",
@@ -3748,6 +3748,20 @@
"select_survey": "Umfrage auswählen",
"select_survey_and_questions": "Umfrage & Fragen auswählen",
"select_survey_questions_description": "Wähle aus, welche Umfragefragen FeedbackRecords erstellen sollen.",
"semantic_search_failed": "Suche nach Feedback-Einträgen fehlgeschlagen",
"semantic_search_input_label": "Feedback-Einträge nach Thema durchsuchen",
"semantic_search_missing_text": "Dieser Feedback-Eintrag hat keinen anzuzeigenden Text.",
"semantic_search_no_directories": "Diesem Workspace ist noch kein Feedback-Verzeichnis zugewiesen. Füge eine Feedback-Quelle hinzu, um Feedback nach Bedeutung zu durchsuchen.",
"semantic_search_no_results": "Keine passenden Feedback-Einträge gefunden. Versuche ein breiteres Thema oder eine andere Formulierung.",
"semantic_search_placeholder": "Suche nach einem Thema, z. B. Preisbeschwerden",
"semantic_search_relevance": "{score} % Relevanz",
"semantic_search_results_count": "{count, plural, one {# passender Feedback-Eintrag} other {# passende Feedback-Einträge}}",
"semantic_search_unavailable": "Semantische Suche ist noch nicht verfügbar. Konfiguriere Hub-Embeddings, um diese Vorschau zu nutzen.",
"semantic_topics_example_confusing_onboarding": "verwirrende Einführung",
"semantic_topics_example_pricing_complaints": "Preisbeschwerden",
"semantic_topics_example_slow_checkout": "langsamer Checkout",
"semantic_topics_preview_description": "Gib ein Thema oder einen Begriff ein, um Feedback-Einträge nach Bedeutung zu finden. Dies ist eine frühe Vorschau auf zukünftige Themen & Unterthemen.",
"semantic_topics_preview_title": "Feedback nach Thema durchsuchen",
"set_value": "Wert festlegen",
"setup_connection": "Verbindung einrichten",
"showing_count_loaded": "{count} Datensätze werden angezeigt",
@@ -3775,6 +3789,7 @@
"submission_id": "Einreichungs-ID",
"survey_has_no_questions": "Diese Umfrage hat keine Fragen",
"topics_and_subtopics": "Themen & Unterthemen",
"try_searching_for": "Versuche zu suchen nach",
"unify_feedback": "Feedback vereinheitlichen",
"update_mapping_description": "Aktualisiere die Zuordnungskonfiguration für diese Quelle.",
"updated_at": "Aktualisiert am",
+16 -1
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Some files failed to upload",
"something_went_wrong": "Something went wrong",
"something_went_wrong_please_try_again": "Something went wrong. Please try again.",
"soon": "Soon",
"sort_by": "Sort by",
"start_free_trial": "Start free trial",
"status": "Status",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Request source integration",
"required": "Required",
"save_changes": "Save changes",
"search_feedback": "Search feedback",
"select_a_survey_to_see_questions": "Select a survey to see its questions",
"select_a_value": "Select a value...",
"select_feedback_directory": "Select a directory",
@@ -3748,6 +3748,20 @@
"select_survey": "Select Survey",
"select_survey_and_questions": "Select Survey & Questions",
"select_survey_questions_description": "Choose which survey questions should create FeedbackRecords.",
"semantic_search_failed": "Failed to search feedback records",
"semantic_search_input_label": "Search feedback records by topic",
"semantic_search_missing_text": "This feedback record has no text to display.",
"semantic_search_no_directories": "No feedback record directory is assigned to this workspace yet. Add a feedback source to start searching feedback by meaning.",
"semantic_search_no_results": "No matching feedback records found. Try a broader topic or a different phrase.",
"semantic_search_placeholder": "Search for a topic, e.g. pricing complaints",
"semantic_search_relevance": "{score}% relevance",
"semantic_search_results_count": "{count, plural, one {# matching feedback record} other {# matching feedback records}}",
"semantic_search_unavailable": "Semantic search is not available yet. Configure Hub embeddings to use this preview.",
"semantic_topics_example_confusing_onboarding": "confusing onboarding",
"semantic_topics_example_pricing_complaints": "pricing complaints",
"semantic_topics_example_slow_checkout": "slow checkout",
"semantic_topics_preview_description": "Enter a topic or phrase to surface feedback records by meaning. This is an early preview of future Topics & Subtopics.",
"semantic_topics_preview_title": "Search feedback by topic",
"set_value": "set value",
"setup_connection": "Setup connection",
"showing_count_loaded": "Showing {count} records",
@@ -3775,6 +3789,7 @@
"submission_id": "Submission ID",
"survey_has_no_questions": "This survey has no questions",
"topics_and_subtopics": "Topics & Subtopics",
"try_searching_for": "Try searching for",
"unify_feedback": "Unify Feedback",
"update_mapping_description": "Update the mapping configuration for this source.",
"updated_at": "Updated at",
+25 -10
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Algunos archivos no se han podido subir",
"something_went_wrong": "Algo ha salido mal",
"something_went_wrong_please_try_again": "Algo ha salido mal. Por favor, inténtalo de nuevo.",
"soon": "Próximamente",
"sort_by": "Ordenar por",
"start_free_trial": "Iniciar prueba gratuita",
"status": "Estado",
@@ -1746,7 +1745,7 @@
"filter_data": "Filtrar datos",
"filters": "Filtros",
"filters_toggle_description": "Incluye solo los datos que cumplan las siguientes condiciones.",
"go_to_feedback_directories": "Ir a Directorios de Comentarios",
"go_to_feedback_directories": "Ir a directorios de feedback",
"granularity": "Granularidad",
"granularity_day": "Día",
"granularity_hour": "Hora",
@@ -1770,7 +1769,7 @@
"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_data_source_available": "No hay ningún directorio de comentarios asignado a este espacio de trabajo.",
"no_data_source_available": "No hay ningún directorio de feedback asignado a este espacio de trabajo.",
"no_grouping": "Ninguno (solo filtro)",
"no_valid_data_to_display": "No hay datos válidos para mostrar",
"not_contains": "no contiene",
@@ -1851,10 +1850,10 @@
"api_key_updated": "Clave API actualizada",
"delete_api_key_confirmation": "Cualquier aplicación que use esta clave ya no podrá acceder a tus datos de Formbricks.",
"duplicate_access": "No se permite el acceso duplicado al espacio de trabajo",
"duplicate_directory_access": "No se permite el acceso duplicado al directorio de comentarios",
"feedback_directory_access": "Acceso al Directorio de Comentarios",
"duplicate_directory_access": "No se permite el acceso duplicado al directorio de feedback",
"feedback_directory_access": "Acceso al directorio de feedback",
"no_api_keys_yet": "Aún no tienes ninguna clave API",
"no_directory_permissions_found": "No se encontraron permisos de directorio de comentarios",
"no_directory_permissions_found": "No se encontraron permisos para el directorio de feedback",
"no_workspace_permissions_found": "No se encontraron permisos del espacio de trabajo",
"organization_access": "Acceso a la organización",
"organization_access_description": "Selecciona privilegios de lectura o escritura para recursos de toda la organización.",
@@ -2566,13 +2565,13 @@
"error_directory_workspaces_invalid_org": "Algunos de los espacios de trabajo especificados no pertenecen a esta organización.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Directorios de Feedback",
"no_access": "No tienes permiso para gestionar los directorios de feedback.",
"no_access": "No tienes permiso para gestionar directorios de feedback.",
"no_connectors": "Aún no hay conectores vinculados a este directorio.",
"pause_connectors_confirmation_description": "Si pausas estos conectores, no se añadirán nuevos registros.",
"pause_connectors_confirmation_title": "¿Pausar conectores vinculados?",
"select_workspaces_placeholder": "Selecciona espacios de trabajo...",
"show_archived": "Mostrar archivados",
"title": "Directorios de Feedback",
"title": "Directorios de feedback",
"unarchive": "Desarchivar",
"unarchive_workspace_conflict": "No se puede desarchivar este directorio porque uno o más espacios de trabajo asignados están archivados.",
"upgrade_prompt_description": "Organiza los registros de feedback en directorios y dirige los datos al espacio de trabajo adecuado. Disponible en los planes Pro y Scale.",
@@ -3687,7 +3686,7 @@
"enum": "enum",
"failed_to_load_feedback_records": "Error al cargar los registros de comentarios",
"feedback_date": "Fecha actual",
"feedback_directory": "Directorio de Feedback",
"feedback_directory": "Directorio de feedback",
"feedback_record_created_successfully": "Registro de comentarios creado correctamente",
"feedback_record_details": "Detalles del registro de comentarios",
"feedback_record_details_description": "Revise y actualice los campos del registro de comentarios.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Valores de metadatos de solo lectura (no cadenas)",
"metadata_value": "Valor de metadatos",
"missing_feedback_source_title": "¿Falta alguna fuente de feedback?",
"no_feedback_directory_available": "No hay ningún directorio de feedback asignado a este espacio de trabajo. Crea o asigna uno primero.",
"no_feedback_directory_available": "No hay ningún directorio de feedback asignado a este espacio de trabajo. Primero crea o asigna uno.",
"no_feedback_records": "Aún no hay registros de comentarios. Los registros aparecerán aquí una vez que tus conectores empiecen a enviar datos.",
"no_source_fields_loaded": "Aún no se han cargado campos de origen",
"no_sources_connected": "Aún no hay fuentes conectadas. Añade una fuente para empezar.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Solicitar integración de fuente",
"required": "Obligatorio",
"save_changes": "Guardar cambios",
"search_feedback": "Buscar feedback",
"select_a_survey_to_see_questions": "Selecciona una encuesta para ver sus preguntas",
"select_a_value": "Selecciona un valor...",
"select_feedback_directory": "Selecciona un directorio",
@@ -3748,6 +3748,20 @@
"select_survey": "Seleccionar encuesta",
"select_survey_and_questions": "Seleccionar encuesta y preguntas",
"select_survey_questions_description": "Elige qué preguntas de la encuesta deben crear FeedbackRecords.",
"semantic_search_failed": "No se pudieron buscar los registros de feedback",
"semantic_search_input_label": "Buscar registros de feedback por tema",
"semantic_search_missing_text": "Este registro de feedback no tiene texto para mostrar.",
"semantic_search_no_directories": "Todavía no hay ningún directorio de registros de feedback asignado a este espacio de trabajo. Añade una fuente de feedback para empezar a buscar feedback por significado.",
"semantic_search_no_results": "No se encontraron registros de feedback coincidentes. Prueba con un tema más amplio o una frase diferente.",
"semantic_search_placeholder": "Busca un tema, por ejemplo, quejas sobre precios",
"semantic_search_relevance": "{score}% de relevancia",
"semantic_search_results_count": "{count, plural, one {# registro de feedback coincidente} other {# registros de feedback coincidentes}}",
"semantic_search_unavailable": "La búsqueda semántica aún no está disponible. Configura los embeddings del Hub para usar esta vista previa.",
"semantic_topics_example_confusing_onboarding": "onboarding confuso",
"semantic_topics_example_pricing_complaints": "quejas sobre precios",
"semantic_topics_example_slow_checkout": "proceso de pago lento",
"semantic_topics_preview_description": "Introduce un tema o frase para encontrar registros de comentarios por significado. Esta es una vista previa temprana de futuros Temas y Subtemas.",
"semantic_topics_preview_title": "Buscar comentarios por tema",
"set_value": "establecer valor",
"setup_connection": "Configurar conexión",
"showing_count_loaded": "Mostrando {count} registros",
@@ -3775,6 +3789,7 @@
"submission_id": "ID de envío",
"survey_has_no_questions": "Esta encuesta no tiene preguntas",
"topics_and_subtopics": "Temas y subtemas",
"try_searching_for": "Prueba a buscar",
"unify_feedback": "Unificar feedback",
"update_mapping_description": "Actualiza la configuración de mapeo para esta fuente.",
"updated_at": "Actualizado el",
+29 -14
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Certains fichiers n'ont pas pu être téléchargés",
"something_went_wrong": "Quelque chose s'est mal passé.",
"something_went_wrong_please_try_again": "Une erreur s'est produite. Veuillez réessayer.",
"soon": "Bientôt",
"sort_by": "Trier par",
"start_free_trial": "Commencer l'essai gratuit",
"status": "Statut",
@@ -1746,7 +1745,7 @@
"filter_data": "Filtrer les données",
"filters": "Filtres",
"filters_toggle_description": "Inclure uniquement les données qui répondent aux conditions suivantes.",
"go_to_feedback_directories": "Accéder aux répertoires de commentaires",
"go_to_feedback_directories": "Accéder aux répertoires de retours",
"granularity": "Granularité",
"granularity_day": "Jour",
"granularity_hour": "Heure",
@@ -1770,7 +1769,7 @@
"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_data_source_available": "Aucun répertoire de commentaires n'est attribué à cet espace de travail.",
"no_data_source_available": "Aucun répertoire de retours n'est attribué à cet espace de travail.",
"no_grouping": "Aucun (filtre uniquement)",
"no_valid_data_to_display": "Aucune donnée valide à afficher",
"not_contains": "ne contient pas",
@@ -1851,10 +1850,10 @@
"api_key_updated": "Clé API mise à jour",
"delete_api_key_confirmation": "Toute application utilisant cette clé ne pourra plus accéder à vos données Formbricks.",
"duplicate_access": "Accès en double à l'espace de travail non autorisé",
"duplicate_directory_access": "L'accès en double au répertoire de commentaires n'est pas autorisé",
"feedback_directory_access": "Accès au répertoire de commentaires",
"duplicate_directory_access": "L'accès en double au répertoire de retours n'est pas autorisé",
"feedback_directory_access": "Accès au répertoire de retours",
"no_api_keys_yet": "Vous n'avez pas encore de clés API",
"no_directory_permissions_found": "Aucune autorisation de répertoire de commentaires trouvée",
"no_directory_permissions_found": "Aucune autorisation de répertoire de retours trouvée",
"no_workspace_permissions_found": "Aucune autorisation d'espace de travail trouvée",
"organization_access": "Accès à l'organisation",
"organization_access_description": "Sélectionnez les privilèges de lecture ou d'écriture pour les ressources à l'échelle de l'organisation.",
@@ -2547,10 +2546,10 @@
"archive_directory": "Archiver le répertoire",
"archive_not_allowed": "Vous n'êtes pas autorisé à archiver ce répertoire.",
"are_you_sure_you_want_to_archive": "Es-tu sûr de vouloir archiver ce répertoire ? Les espaces de travail n'y auront plus accès.",
"assign_workspaces_description": "Contrôle quels espaces de travail peuvent accéder à ce répertoire de feedback.",
"assign_workspaces_description": "Contrôle quels espaces de travail peuvent accéder à ce répertoire de retours.",
"connectors_description": "Connecteurs qui envoient des enregistrements de retour d'expérience vers ce répertoire.",
"create_feedback_directory": "Créer un répertoire de commentaires",
"description": "Gère les répertoires de feedback et leurs affectations aux espaces de travail.",
"description": "Gère les répertoires de retours et leurs attributions d'espaces de travail.",
"directory_archived_successfully": "Répertoire archivé avec succès",
"directory_created_successfully": "Répertoire créé avec succès",
"directory_id": "ID du répertoire",
@@ -2559,20 +2558,20 @@
"directory_settings_title": "Paramètres de {directoryName}",
"directory_unarchived_successfully": "Répertoire désarchivé avec succès",
"directory_updated_successfully": "Répertoire mis à jour avec succès",
"empty_state": "Aucun répertoire de feedback trouvé. Crée-en un pour commencer.",
"empty_state": "Aucun répertoire de retours trouvé. Crée-en un pour commencer.",
"error_directory_has_connectors": "Impossible d'archiver un répertoire auquel des connecteurs sont liés. Supprimez d'abord tous les connecteurs.",
"error_directory_name_duplicate": "Un répertoire de feedback avec ce nom existe déjà.",
"error_directory_name_duplicate": "Un répertoire de retours avec ce nom existe déjà.",
"error_directory_name_required": "Le nom du répertoire est requis.",
"error_directory_workspaces_invalid_org": "Certains espaces de travail spécifiés n'appartiennent pas à cette organisation.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Répertoires de feedback",
"no_access": "Tu n'as pas la permission de gérer les répertoires de feedback.",
"no_access": "Tu n'as pas la permission de gérer les répertoires de retours.",
"no_connectors": "Aucun connecteur lié à ce répertoire pour le moment.",
"pause_connectors_confirmation_description": "Si vous mettez ces connecteurs en pause, aucun nouvel enregistrement ne sera ajouté.",
"pause_connectors_confirmation_title": "Mettre en pause les connecteurs liés ?",
"select_workspaces_placeholder": "Sélectionner des espaces de travail...",
"show_archived": "Afficher les éléments archivés",
"title": "Répertoires de feedback",
"title": "Répertoires de retours",
"unarchive": "Désarchiver",
"unarchive_workspace_conflict": "Impossible de désarchiver ce répertoire, car un ou plusieurs espaces de travail attribués sont archivés.",
"upgrade_prompt_description": "Organisez les enregistrements de feedback dans des répertoires et dirigez les données vers le bon espace de travail. Disponible avec les forfaits Pro et Scale.",
@@ -3687,7 +3686,7 @@
"enum": "enum",
"failed_to_load_feedback_records": "Échec du chargement des enregistrements de feedback",
"feedback_date": "Date actuelle",
"feedback_directory": "Répertoire de feedback",
"feedback_directory": "Répertoire de retours",
"feedback_record_created_successfully": "Enregistrement de commentaires créé avec succès",
"feedback_record_details": "Détails de l'enregistrement des commentaires",
"feedback_record_details_description": "Examiner et mettre à jour les champs denregistrement des commentaires.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Valeurs de métadonnées en lecture seule (non-chaîne)",
"metadata_value": "Valeur des métadonnées",
"missing_feedback_source_title": "Il manque une source de feedback ?",
"no_feedback_directory_available": "Aucun répertoire de feedback n'est assigné à cet espace de travail. Créez-en un ou assignez-en un d'abord.",
"no_feedback_directory_available": "Aucun répertoire de retours attribué à cet espace de travail. Crée-en un ou attribue-en un d'abord.",
"no_feedback_records": "Aucun enregistrement de feedback pour le moment. Les enregistrements apparaîtront ici une fois que vos connecteurs commenceront à envoyer des données.",
"no_source_fields_loaded": "Aucun champ source chargé pour le moment",
"no_sources_connected": "Aucune source connectée pour le moment. Ajoutez une source pour commencer.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Demander une intégration de source",
"required": "Requis",
"save_changes": "Enregistrer les modifications",
"search_feedback": "Rechercher des retours",
"select_a_survey_to_see_questions": "Sélectionnez une enquête pour voir ses questions",
"select_a_value": "Sélectionnez une valeur...",
"select_feedback_directory": "Sélectionner un répertoire",
@@ -3748,6 +3748,20 @@
"select_survey": "Sélectionner l'enquête",
"select_survey_and_questions": "Sélectionner l'enquête et les questions",
"select_survey_questions_description": "Choisissez quelles questions d'enquête doivent créer des FeedbackRecords.",
"semantic_search_failed": "Échec de la recherche des enregistrements de retours",
"semantic_search_input_label": "Recherche des enregistrements de retours par sujet",
"semantic_search_missing_text": "Cet enregistrement de retours n'a pas de texte à afficher.",
"semantic_search_no_directories": "Aucun répertoire d'enregistrements de retours n'est encore attribué à cet espace de travail. Ajoute une source de retours pour commencer à rechercher des retours par signification.",
"semantic_search_no_results": "Aucun enregistrement de retours correspondant trouvé. Essaie un sujet plus large ou une formulation différente.",
"semantic_search_placeholder": "Recherche un sujet, par ex. plaintes sur les prix",
"semantic_search_relevance": "{score} % de pertinence",
"semantic_search_results_count": "{count, plural, one {# enregistrement de retours correspondant} other {# enregistrements de retours correspondants}}",
"semantic_search_unavailable": "La recherche sémantique n'est pas encore disponible. Configure les embeddings Hub pour utiliser cet aperçu.",
"semantic_topics_example_confusing_onboarding": "intégration confuse",
"semantic_topics_example_pricing_complaints": "plaintes sur les prix",
"semantic_topics_example_slow_checkout": "passage en caisse lent",
"semantic_topics_preview_description": "Saisissez un sujet ou une phrase pour retrouver des retours clients par signification. Ceci est un aperçu des futurs Sujets et Sous-sujets.",
"semantic_topics_preview_title": "Rechercher des retours par sujet",
"set_value": "définir la valeur",
"setup_connection": "Configurer la connexion",
"showing_count_loaded": "Affichage de {count} enregistrements",
@@ -3775,6 +3789,7 @@
"submission_id": "ID de soumission",
"survey_has_no_questions": "Ce sondage n'a pas de questions",
"topics_and_subtopics": "Thèmes et sous-thèmes",
"try_searching_for": "Essayez de rechercher",
"unify_feedback": "Unifier les retours",
"update_mapping_description": "Mettre à jour la configuration de mappage pour cette source.",
"updated_at": "Mis à jour à",
+23 -8
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Néhány fájlt nem sikerült feltölteni",
"something_went_wrong": "Valami probléma történt",
"something_went_wrong_please_try_again": "Valami probléma történt. Próbálja meg újra.",
"soon": "Hamarosan",
"sort_by": "Rendezési sorrend",
"start_free_trial": "Ingyenes próbaidőszak indítása",
"status": "Állapot",
@@ -1770,7 +1769,7 @@
"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_data_source_available": "Ehhez a munkaterülethez nem tartozik visszajelzési könyvtár.",
"no_data_source_available": "Ehhez a munkaterülethez nincs visszajelzési könyvtár hozzárendelve.",
"no_grouping": "Nincs (csak szűrés)",
"no_valid_data_to_display": "Nincsenek megjeleníthető érvényes adatok",
"not_contains": "nem tartalmazza",
@@ -2559,20 +2558,20 @@
"directory_settings_title": "{directoryName} beállításai",
"directory_unarchived_successfully": "A könyvtár archiválása sikeresen visszavonva",
"directory_updated_successfully": "A könyvtár sikeresen frissítve",
"empty_state": "Nem található visszajelzési könyvtár. Hozzon létre egyet a kezdéshez.",
"empty_state": "Nem találhatók visszajelzési könyvtárak. Hozzon létre egyet a kezdéshez.",
"error_directory_has_connectors": "Nem archiválható olyan könyvtár, amelyhez csatlakozók vannak társítva. Először távolítson el minden csatlakozót.",
"error_directory_name_duplicate": "Ezzel a névvel már létezik visszajelzési könyvtár.",
"error_directory_name_duplicate": "Már létezik visszajelzési könyvtár ezzel a névvel.",
"error_directory_name_required": "A könyvtár neve kötelező megadni.",
"error_directory_workspaces_invalid_org": "Egyes megadott munkaterületek nem ehhez a szervezethez tartoznak.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Visszajelzési könyvtárak",
"no_access": "Nem rendelkezik jogosultsággal a visszajelzési könyvtárak kezeléséhez.",
"no_access": "Önnek nincs jogosultsága a visszajelzési könyvtárak kezeléséhez.",
"no_connectors": "Még nincsenek csatlakozók társítva ehhez a könyvtárhoz.",
"pause_connectors_confirmation_description": "Ha szünetelteti ezeket a csatlakozókat, nem kerülnek be új rekordok.",
"pause_connectors_confirmation_title": "Szünetelteti a kapcsolódó csatlakozókat?",
"select_workspaces_placeholder": "Munkaterületek kiválasztása...",
"show_archived": "Archivált elemek megjelenítése",
"title": "Visszajelzési Könyvtárak",
"title": "Visszajelzési könyvtárak",
"unarchive": "Archiválás visszavonása",
"unarchive_workspace_conflict": "A könyvtár nem állítható vissza, mert egy vagy több hozzárendelt munkaterület archiválva van.",
"upgrade_prompt_description": "Szervezze a visszajelzési rekordokat könyvtárakba, és irányítsa az adatokat a megfelelő munkaterületre. A Pro és Scale csomagokban érhető el.",
@@ -3687,7 +3686,7 @@
"enum": "felsorolás",
"failed_to_load_feedback_records": "Nem sikerült betölteni a visszajelzési rekordokat",
"feedback_date": "Aktuális dátum",
"feedback_directory": "Visszajelzési Könyvtár",
"feedback_directory": "Visszajelzési könyvtár",
"feedback_record_created_successfully": "A visszajelzési rekord sikeresen létrehozva",
"feedback_record_details": "A visszajelzési rekord részletei",
"feedback_record_details_description": "Tekintse át és frissítse a visszajelzési rekordmezőket.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Csak olvasható metaadatértékek (nem karakterlánc)",
"metadata_value": "A metaadat értéke",
"missing_feedback_source_title": "Hiányzik egy visszajelzési forrás?",
"no_feedback_directory_available": "Ehhez a munkaterülethez nem tartozik visszajelzési könyvtár. Először hozzon létre vagy rendeljen hozzá egyet.",
"no_feedback_directory_available": "Ehhez a munkaterülethez nincs visszajelzési könyvtár hozzárendelve. Hozzon létre vagy rendeljen hozzá egyet először.",
"no_feedback_records": "Még nincsenek visszajelzési rekordok. A rekordok itt fognak megjelenni, amint a csatlakozók elkezdik küldeni az adatokat.",
"no_source_fields_loaded": "Még nincsenek forrás mezők betöltve",
"no_sources_connected": "Még nincsenek források csatlakoztatva. Adj hozzá egy forrást a kezdéshez.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Forrásintegráció kérése",
"required": "Kötelező",
"save_changes": "Változtatások mentése",
"search_feedback": "Visszajelzések keresése",
"select_a_survey_to_see_questions": "Válassz egy kérdőívet a kérdések megtekintéséhez",
"select_a_value": "Válassz egy értéket...",
"select_feedback_directory": "Válasszon egy könyvtárat",
@@ -3748,6 +3748,20 @@
"select_survey": "Kérdőív kiválasztása",
"select_survey_and_questions": "Kérdőív és kérdések kiválasztása",
"select_survey_questions_description": "Válassza ki, mely kérdőívkérdések hozzanak létre visszajelzési rekordokat.",
"semantic_search_failed": "A visszajelzési rekordok keresése sikertelen",
"semantic_search_input_label": "Visszajelzési rekordok keresése téma szerint",
"semantic_search_missing_text": "Ez a visszajelzési rekord nem tartalmaz megjeleníthető szöveget.",
"semantic_search_no_directories": "Ehhez a munkaterülethez még nincs visszajelzési rekord könyvtár hozzárendelve. Adjon hozzá egy visszajelzési forrást a jelentés szerinti keresés megkezdéséhez.",
"semantic_search_no_results": "Nem találhatók egyező visszajelzési rekordok. Próbálkozzon tágabb témával vagy más kifejezéssel.",
"semantic_search_placeholder": "Keressen egy témát, például árazási panaszok",
"semantic_search_relevance": "{score}% relevancia",
"semantic_search_results_count": "{count, plural, one {# egyező visszajelzési rekord} other {# egyező visszajelzési rekord}}",
"semantic_search_unavailable": "A szemantikai keresés még nem érhető el. Konfigurálja a Hub beágyazásokat az előnézet használatához.",
"semantic_topics_example_confusing_onboarding": "zavaró regisztráció",
"semantic_topics_example_pricing_complaints": "árazási panaszok",
"semantic_topics_example_slow_checkout": "lassú pénztári folyamat",
"semantic_topics_preview_description": "Adjon meg egy témát vagy kifejezést, hogy tartalmilag kapcsolódó visszajelzési bejegyzéseket jelenítsen meg. Ez egy korai előnézet a jövőbeli Témák és Altémák funkcióról.",
"semantic_topics_preview_title": "Visszajelzések keresése téma szerint",
"set_value": "érték beállítása",
"setup_connection": "Kapcsolat beállítása",
"showing_count_loaded": "{count} rekord megjelenítése",
@@ -3775,6 +3789,7 @@
"submission_id": "Beküldés azonosítója",
"survey_has_no_questions": "Ez a felmérés nem tartalmaz kérdéseket",
"topics_and_subtopics": "Témák és altémák",
"try_searching_for": "Próbálja ki a következő keresést",
"unify_feedback": "Visszajelzések egyesítése",
"update_mapping_description": "Frissítse a leképezési konfigurációt ehhez a forráshoz.",
"updated_at": "Frissítve",
+21 -6
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "一部のファイルのアップロードに失敗しました",
"something_went_wrong": "問題が発生しました",
"something_went_wrong_please_try_again": "問題が発生しました。もう一度お試しください。",
"soon": "近日公開",
"sort_by": "並び替え",
"start_free_trial": "無料トライアルを開始",
"status": "ステータス",
@@ -1746,7 +1745,7 @@
"filter_data": "データをフィルター",
"filters": "フィルター",
"filters_toggle_description": "以下の条件を満たすデータのみを含めます。",
"go_to_feedback_directories": "フィードバックディレクトリ移動",
"go_to_feedback_directories": "フィードバックディレクトリ移動",
"granularity": "粒度",
"granularity_day": "日",
"granularity_hour": "時間",
@@ -2550,7 +2549,7 @@
"assign_workspaces_description": "このフィードバックディレクトリにアクセスできるワークスペースを管理します。",
"connectors_description": "このディレクトリにフィードバックレコードを送信するコネクタ。",
"create_feedback_directory": "フィードバックディレクトリを作成",
"description": "フィードバックディレクトリとワークスペースの割り当てを管理します。",
"description": "フィードバックディレクトリとワークスペースの割り当てを管理します。",
"directory_archived_successfully": "ディレクトリをアーカイブしました",
"directory_created_successfully": "ディレクトリを作成しました",
"directory_id": "ディレクトリID",
@@ -2559,9 +2558,9 @@
"directory_settings_title": "{directoryName}の設定",
"directory_unarchived_successfully": "ディレクトリのアーカイブを解除しました",
"directory_updated_successfully": "ディレクトリを更新しました",
"empty_state": "フィードバックディレクトリが見つかりません。最初のディレクトリを作成してください。",
"empty_state": "フィードバックディレクトリが見つかりません。作成して始めましょう。",
"error_directory_has_connectors": "コネクタがリンクされているディレクトリはアーカイブできません。まずすべてのコネクタを削除してください。",
"error_directory_name_duplicate": "この名前のフィードバックディレクトリはに存在します。",
"error_directory_name_duplicate": "この名前のフィードバックディレクトリはすでに存在します。",
"error_directory_name_required": "ディレクトリ名は必須です。",
"error_directory_workspaces_invalid_org": "指定されたワークスペースの一部がこの組織に属していません。",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "読み取り専用メタデータ値 (非文字列)",
"metadata_value": "メタデータ値",
"missing_feedback_source_title": "フィードバックソースが見つかりませんか?",
"no_feedback_directory_available": "このワークスペースにフィードバックディレクトリが割り当てられていません。まず作成または割り当てを行ってください。",
"no_feedback_directory_available": "このワークスペースにフィードバックディレクトリが割り当てられていません。まずディレクトリを作成または割り当ててください。",
"no_feedback_records": "フィードバックレコードはまだありません。コネクタがデータの送信を開始すると、ここにレコードが表示されます。",
"no_source_fields_loaded": "ソースフィールドがまだ読み込まれていません",
"no_sources_connected": "ソースがまだ接続されていません。開始するにはソースを追加してください。",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "ソース統合をリクエスト",
"required": "必須",
"save_changes": "変更を保存",
"search_feedback": "フィードバックを検索",
"select_a_survey_to_see_questions": "フォームを選択して質問を表示",
"select_a_value": "値を選択...",
"select_feedback_directory": "ディレクトリを選択",
@@ -3748,6 +3748,20 @@
"select_survey": "フォームを選択",
"select_survey_and_questions": "フォームと質問を選択",
"select_survey_questions_description": "フィードバックレコードを作成するフォームの質問を選択してください。",
"semantic_search_failed": "フィードバック記録の検索に失敗しました",
"semantic_search_input_label": "トピック別にフィードバック記録を検索",
"semantic_search_missing_text": "このフィードバック記録には表示するテキストがありません。",
"semantic_search_no_directories": "このワークスペースにはまだフィードバック記録ディレクトリが割り当てられていません。フィードバックソースを追加して、意味による検索を開始しましょう。",
"semantic_search_no_results": "一致するフィードバック記録が見つかりませんでした。より広いトピックや別のフレーズを試してください。",
"semantic_search_placeholder": "トピックを検索、例: 価格に関する苦情",
"semantic_search_relevance": "関連性 {score}%",
"semantic_search_results_count": "{count, plural, other {# 件のフィードバック記録}}",
"semantic_search_unavailable": "セマンティック検索はまだ利用できません。このプレビューを使用するには、Hub埋め込みを設定してください。",
"semantic_topics_example_confusing_onboarding": "わかりにくいオンボーディング",
"semantic_topics_example_pricing_complaints": "価格に関する苦情",
"semantic_topics_example_slow_checkout": "チェックアウトが遅い",
"semantic_topics_preview_description": "トピックやフレーズを入力すると、意味に基づいてフィードバック記録を表示できます。これは、今後のトピックとサブトピック機能の早期プレビューです。",
"semantic_topics_preview_title": "トピック別にフィードバックを検索",
"set_value": "値を設定",
"setup_connection": "接続を設定",
"showing_count_loaded": "{count}件のレコードを表示中",
@@ -3775,6 +3789,7 @@
"submission_id": "提出ID",
"survey_has_no_questions": "このアンケートには質問がありません",
"topics_and_subtopics": "トピックとサブトピック",
"try_searching_for": "検索してみる",
"unify_feedback": "フィードバックを統合",
"update_mapping_description": "このソースのマッピング設定を更新します。",
"updated_at": "更新日時",
+18 -3
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Sommige bestanden konden niet worden geüpload",
"something_went_wrong": "Er is iets misgegaan",
"something_went_wrong_please_try_again": "Er is iets misgegaan. Probeer het opnieuw.",
"soon": "Binnenkort",
"sort_by": "Sorteer op",
"start_free_trial": "Start gratis proefperiode",
"status": "Status",
@@ -1746,7 +1745,7 @@
"filter_data": "Data filteren",
"filters": "Filters",
"filters_toggle_description": "Neem alleen gegevens op die aan de volgende voorwaarden voldoen.",
"go_to_feedback_directories": "Ga naar Feedbackmappen",
"go_to_feedback_directories": "Ga naar feedbackmappen",
"granularity": "Granulariteit",
"granularity_day": "Dag",
"granularity_hour": "Uur",
@@ -2550,7 +2549,7 @@
"assign_workspaces_description": "Bepaal welke workspaces toegang hebben tot deze feedbackmap.",
"connectors_description": "Connectoren die feedbackrecords naar deze map sturen.",
"create_feedback_directory": "Feedbackmap maken",
"description": "Beheer feedbackmappen en hun workspace-toewijzingen.",
"description": "Beheer feedbackmappen en hun workspacetoewijzingen.",
"directory_archived_successfully": "Map succesvol gearchiveerd",
"directory_created_successfully": "Map succesvol aangemaakt",
"directory_id": "Map-ID",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Bronintegratie aanvragen",
"required": "Vereist",
"save_changes": "Wijzigingen opslaan",
"search_feedback": "Zoek feedback",
"select_a_survey_to_see_questions": "Selecteer een enquête om de vragen te zien",
"select_a_value": "Selecteer een waarde...",
"select_feedback_directory": "Selecteer een map",
@@ -3748,6 +3748,20 @@
"select_survey": "Selecteer enquête",
"select_survey_and_questions": "Selecteer enquête & vragen",
"select_survey_questions_description": "Kies welke enquêtevragen FeedbackRecords moeten aanmaken.",
"semantic_search_failed": "Feedbackrecords zoeken is mislukt",
"semantic_search_input_label": "Zoek feedbackrecords op onderwerp",
"semantic_search_missing_text": "Dit feedbackrecord heeft geen tekst om weer te geven.",
"semantic_search_no_directories": "Er is nog geen feedbackmap toegewezen aan deze workspace. Voeg een feedbackbron toe om te beginnen met zoeken op betekenis.",
"semantic_search_no_results": "Geen overeenkomende feedbackrecords gevonden. Probeer een breder onderwerp of een andere zoekopdracht.",
"semantic_search_placeholder": "Zoek naar een onderwerp, bijv. klachten over prijzen",
"semantic_search_relevance": "{score}% relevant",
"semantic_search_results_count": "{count, plural, one {# overeenkomend feedbackrecord} other {# overeenkomende feedbackrecords}}",
"semantic_search_unavailable": "Semantisch zoeken is nog niet beschikbaar. Configureer Hub-embeddings om deze preview te gebruiken.",
"semantic_topics_example_confusing_onboarding": "verwarrende onboarding",
"semantic_topics_example_pricing_complaints": "klachten over prijzen",
"semantic_topics_example_slow_checkout": "trage afrekening",
"semantic_topics_preview_description": "Voer een onderwerp of zin in om feedbackrecords op betekenis te vinden. Dit is een vroege preview van toekomstige Onderwerpen & Subonderwerpen.",
"semantic_topics_preview_title": "Zoek feedback op onderwerp",
"set_value": "waarde instellen",
"setup_connection": "Verbinding instellen",
"showing_count_loaded": "Er worden {count} records weergegeven",
@@ -3775,6 +3789,7 @@
"submission_id": "Inzendings-ID",
"survey_has_no_questions": "Deze enquête heeft geen vragen",
"topics_and_subtopics": "Onderwerpen en subonderwerpen",
"try_searching_for": "Probeer te zoeken naar",
"unify_feedback": "Feedback verenigen",
"update_mapping_description": "Werk de mappingconfiguratie voor deze bron bij.",
"updated_at": "Bijgewerkt op",
+18 -3
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Alguns arquivos falharam ao enviar",
"something_went_wrong": "Algo deu errado",
"something_went_wrong_please_try_again": "Algo deu errado. Tente novamente.",
"soon": "Em breve",
"sort_by": "Ordenar por",
"start_free_trial": "Iniciar teste gratuito",
"status": "status",
@@ -2547,10 +2546,10 @@
"archive_directory": "Arquivar Diretório",
"archive_not_allowed": "Você não tem permissão para arquivar este diretório.",
"are_you_sure_you_want_to_archive": "Tem certeza de que deseja arquivar este diretório? Os espaços de trabalho não terão mais acesso a ele.",
"assign_workspaces_description": "Controle quais espaços de trabalho podem acessar este diretório de feedback.",
"assign_workspaces_description": "Controle quais workspaces podem acessar este diretório de feedback.",
"connectors_description": "Conectores que enviam registros de feedback para este diretório.",
"create_feedback_directory": "Criar diretório de feedback",
"description": "Gerencie diretórios de feedback e suas atribuições de espaços de trabalho.",
"description": "Gerencie diretórios de feedback e suas atribuições de workspace.",
"directory_archived_successfully": "Diretório arquivado com sucesso",
"directory_created_successfully": "Diretório criado com sucesso",
"directory_id": "ID do Diretório",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Solicitar integração de fonte",
"required": "Obrigatório",
"save_changes": "Salvar alterações",
"search_feedback": "Buscar feedback",
"select_a_survey_to_see_questions": "Selecione uma pesquisa para ver suas perguntas",
"select_a_value": "Selecione um valor...",
"select_feedback_directory": "Selecione um diretório",
@@ -3748,6 +3748,20 @@
"select_survey": "Selecionar pesquisa",
"select_survey_and_questions": "Selecionar pesquisa e perguntas",
"select_survey_questions_description": "Escolha quais perguntas da pesquisa devem criar FeedbackRecords.",
"semantic_search_failed": "Falha ao buscar registros de feedback",
"semantic_search_input_label": "Busque registros de feedback por tópico",
"semantic_search_missing_text": "Este registro de feedback não possui texto para exibir.",
"semantic_search_no_directories": "Nenhum diretório de registro de feedback está atribuído a este workspace ainda. Adicione uma fonte de feedback para começar a buscar feedback por significado.",
"semantic_search_no_results": "Nenhum registro de feedback correspondente encontrado. Tente um tópico mais amplo ou uma frase diferente.",
"semantic_search_placeholder": "Busque por um tópico, ex.: reclamações sobre preço",
"semantic_search_relevance": "{score}% de relevância",
"semantic_search_results_count": "{count, plural, one {# registro de feedback correspondente} other {# registros de feedback correspondentes}}",
"semantic_search_unavailable": "A busca semântica ainda não está disponível. Configure os embeddings do Hub para usar esta prévia.",
"semantic_topics_example_confusing_onboarding": "onboarding confuso",
"semantic_topics_example_pricing_complaints": "reclamações sobre preço",
"semantic_topics_example_slow_checkout": "checkout lento",
"semantic_topics_preview_description": "Digite um tópico ou frase para encontrar registros de feedback por significado. Esta é uma prévia antecipada dos futuros Tópicos e Subtópicos.",
"semantic_topics_preview_title": "Buscar feedback por tópico",
"set_value": "definir valor",
"setup_connection": "Configurar conexão",
"showing_count_loaded": "Mostrando {count} registros",
@@ -3775,6 +3789,7 @@
"submission_id": "ID de envio",
"survey_has_no_questions": "Esta pesquisa não possui perguntas",
"topics_and_subtopics": "Tópicos e subtópicos",
"try_searching_for": "Tente buscar por",
"unify_feedback": "Unificar feedback",
"update_mapping_description": "Atualize a configuração de mapeamento para esta fonte.",
"updated_at": "Atualizado em",
+22 -7
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Alguns ficheiros falharam ao carregar",
"something_went_wrong": "Algo correu mal",
"something_went_wrong_please_try_again": "Algo correu mal. Por favor, tente novamente.",
"soon": "Em breve",
"sort_by": "Ordem",
"start_free_trial": "Iniciar teste gratuito",
"status": "Estado",
@@ -1770,7 +1769,7 @@
"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_data_source_available": "Nenhum diretório de feedback está atribuído a este espaço de trabalho.",
"no_data_source_available": "Nenhum diretório de feedback está atribuído a este workspace.",
"no_grouping": "Nenhum (apenas filtro)",
"no_valid_data_to_display": "Nenhum dado válido para exibir",
"not_contains": "não contém",
@@ -1854,7 +1853,7 @@
"duplicate_directory_access": "Não é permitido acesso duplicado ao diretório de feedback",
"feedback_directory_access": "Acesso ao Diretório de Feedback",
"no_api_keys_yet": "Ainda não tem nenhuma chave de API",
"no_directory_permissions_found": "Não foram encontradas permissões de diretório de feedback",
"no_directory_permissions_found": "Nenhuma permissão de diretório de feedback encontrada",
"no_workspace_permissions_found": "Não foram encontradas permissões de Espaço de Trabalho",
"organization_access": "Acesso à organização",
"organization_access_description": "Selecione privilégios de leitura ou escrita para recursos de toda a organização.",
@@ -2547,10 +2546,10 @@
"archive_directory": "Arquivar Diretório",
"archive_not_allowed": "Não tens permissão para arquivar este diretório.",
"are_you_sure_you_want_to_archive": "Tens a certeza de que queres arquivar este diretório? Os espaços de trabalho deixarão de ter acesso ao mesmo.",
"assign_workspaces_description": "Controla quais os espaços de trabalho que podem aceder a este diretório de feedback.",
"assign_workspaces_description": "Controla quais workspaces podem aceder a este diretório de feedback.",
"connectors_description": "Conectores que enviam registos de feedback para este diretório.",
"create_feedback_directory": "Criar diretório de feedback",
"description": "Gere diretórios de feedback e as suas atribuições de espaços de trabalho.",
"description": "Gere diretórios de feedback e as suas atribuições de workspace.",
"directory_archived_successfully": "Diretório arquivado com sucesso",
"directory_created_successfully": "Diretório criado com sucesso",
"directory_id": "ID do Diretório",
@@ -2559,7 +2558,7 @@
"directory_settings_title": "Definições de {directoryName}",
"directory_unarchived_successfully": "Diretório desarquivado com sucesso",
"directory_updated_successfully": "Diretório atualizado com sucesso",
"empty_state": "Não foram encontrados diretórios de feedback. Cria um para começar.",
"empty_state": "Nenhum diretório de feedback encontrado. Cria um para começar.",
"error_directory_has_connectors": "Não é possível arquivar um diretório que tem conectores associados. Remove todos os conectores primeiro.",
"error_directory_name_duplicate": "Já existe um diretório de feedback com este nome.",
"error_directory_name_required": "O nome do diretório é obrigatório.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Valores de metadados somente leitura (sem string)",
"metadata_value": "Valor dos metadados",
"missing_feedback_source_title": "Falta alguma fonte de feedback?",
"no_feedback_directory_available": "Não há nenhum diretório de feedback atribuído a este espaço de trabalho. Cria ou atribui um primeiro.",
"no_feedback_directory_available": "Nenhum diretório de feedback atribuído a este workspace. Cria ou atribui um primeiro.",
"no_feedback_records": "Ainda não há registos de feedback. Os registos aparecerão aqui assim que os teus conectores começarem a enviar dados.",
"no_source_fields_loaded": "Ainda não foram carregados campos de origem",
"no_sources_connected": "Ainda não há origens ligadas. Adicione uma origem para começar.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Solicitar integração de fonte",
"required": "Obrigatório",
"save_changes": "Guardar alterações",
"search_feedback": "Pesquisar feedback",
"select_a_survey_to_see_questions": "Selecione um inquérito para ver as suas perguntas",
"select_a_value": "Selecione um valor...",
"select_feedback_directory": "Selecionar um diretório",
@@ -3748,6 +3748,20 @@
"select_survey": "Selecionar inquérito",
"select_survey_and_questions": "Selecionar inquérito e perguntas",
"select_survey_questions_description": "Escolha quais perguntas do inquérito devem criar FeedbackRecords.",
"semantic_search_failed": "Falha ao pesquisar registos de feedback",
"semantic_search_input_label": "Pesquisar registos de feedback por tópico",
"semantic_search_missing_text": "Este registo de feedback não tem texto para mostrar.",
"semantic_search_no_directories": "Ainda não há nenhum diretório de registos de feedback atribuído a este workspace. Adiciona uma fonte de feedback para começar a pesquisar por significado.",
"semantic_search_no_results": "Nenhum registo de feedback correspondente encontrado. Tenta um tópico mais abrangente ou uma frase diferente.",
"semantic_search_placeholder": "Pesquisar por um tópico, ex: reclamações sobre preços",
"semantic_search_relevance": "{score}% de relevância",
"semantic_search_results_count": "{count, plural, one {# registo de feedback correspondente} other {# registos de feedback correspondentes}}",
"semantic_search_unavailable": "A pesquisa semântica ainda não está disponível. Configura os embeddings do Hub para usar esta pré-visualização.",
"semantic_topics_example_confusing_onboarding": "onboarding confuso",
"semantic_topics_example_pricing_complaints": "reclamações sobre preços",
"semantic_topics_example_slow_checkout": "checkout lento",
"semantic_topics_preview_description": "Introduz um tópico ou frase para encontrar registos de feedback por significado. Esta é uma pré-visualização de futuros Tópicos e Subtópicos.",
"semantic_topics_preview_title": "Pesquisar feedback por tópico",
"set_value": "definir valor",
"setup_connection": "Configurar ligação",
"showing_count_loaded": "A mostrar {count} registos",
@@ -3775,6 +3789,7 @@
"submission_id": "ID de envio",
"survey_has_no_questions": "Este inquérito não tem perguntas",
"topics_and_subtopics": "Tópicos e subtópicos",
"try_searching_for": "Experimenta pesquisar por",
"unify_feedback": "Unificar feedback",
"update_mapping_description": "Atualiza a configuração de mapeamento para esta origem.",
"updated_at": "Atualizado em",
+21 -6
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Unele fișiere nu au reușit să se încarce",
"something_went_wrong": "Ceva nu a mers bine",
"something_went_wrong_please_try_again": "Ceva nu a mers bine. Vă rugăm să încercați din nou.",
"soon": "În curând",
"sort_by": "Sortare după",
"start_free_trial": "Începe perioada de probă gratuită",
"status": "Stare",
@@ -1746,7 +1745,7 @@
"filter_data": "Filtrează datele",
"filters": "Filtre",
"filters_toggle_description": "Include doar datele care îndeplinesc următoarele condiții.",
"go_to_feedback_directories": "Mergi la Directoarele de Feedback",
"go_to_feedback_directories": "Mergi la Directoare de Feedback",
"granularity": "Granularitate",
"granularity_day": "Zi",
"granularity_hour": "Oră",
@@ -1854,7 +1853,7 @@
"duplicate_directory_access": "Accesul duplicat la directorul de feedback nu este permis",
"feedback_directory_access": "Acces la Directorul de Feedback",
"no_api_keys_yet": "Nu ai încă nicio cheie API",
"no_directory_permissions_found": "Nu s-au găsit permisiuni pentru directorul de feedback",
"no_directory_permissions_found": "Nu au fost găsite permisiuni pentru directoare de feedback",
"no_workspace_permissions_found": "Nu s-au găsit permisiuni pentru Workspace",
"organization_access": "Acces organizație",
"organization_access_description": "Selectează privilegii de citire sau scriere pentru resursele la nivel de organizație.",
@@ -2547,7 +2546,7 @@
"archive_directory": "Arhivează directorul",
"archive_not_allowed": "Nu ai permisiunea să arhivezi acest director.",
"are_you_sure_you_want_to_archive": "Ești sigur că vrei să arhivezi acest director? Spațiile de lucru nu vor mai avea acces la el.",
"assign_workspaces_description": "Controlează care spații de lucru pot accesa acest director de feedback.",
"assign_workspaces_description": "Controlează ce spații de lucru pot accesa acest director de feedback.",
"connectors_description": "Conectori care trimit înregistrări de feedback către acest director.",
"create_feedback_directory": "Creează director de feedback",
"description": "Gestionează directoarele de feedback și atribuirile lor la spații de lucru.",
@@ -3687,7 +3686,7 @@
"enum": "enum",
"failed_to_load_feedback_records": "Nu s-au putut încărca înregistrările de feedback",
"feedback_date": "Data curentă",
"feedback_directory": "Director de feedback",
"feedback_directory": "Director de Feedback",
"feedback_record_created_successfully": "Înregistrare de feedback creată cu succes",
"feedback_record_details": "Detaliile înregistrării feedback-ului",
"feedback_record_details_description": "Examinați și actualizați câmpurile pentru înregistrarea de feedback.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Valori de metadate numai pentru citire (fără șir)",
"metadata_value": "Valoarea metadatelor",
"missing_feedback_source_title": "Lipsește o sursă de feedback?",
"no_feedback_directory_available": "Niciun director de feedback atribuit acestui spațiu de lucru. Creează sau atribuie unul mai întâi.",
"no_feedback_directory_available": "Niciun director de feedback nu este atribuit acestui spațiu de lucru. Creează sau atribuie unul mai întâi.",
"no_feedback_records": "Nu există încă înregistrări de feedback. Înregistrările vor apărea aici după ce conectorii tăi vor începe să trimită date.",
"no_source_fields_loaded": "Nu au fost încă încărcate câmpuri sursă",
"no_sources_connected": "Nicio sursă conectată încă. Adaugă o sursă pentru a începe.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Solicită integrarea sursei",
"required": "Obligatoriu",
"save_changes": "Salvează modificările",
"search_feedback": "Caută feedback",
"select_a_survey_to_see_questions": "Selectează un chestionar pentru a vedea întrebările",
"select_a_value": "Selectează o valoare...",
"select_feedback_directory": "Selectează un director",
@@ -3748,6 +3748,20 @@
"select_survey": "Selectează chestionar",
"select_survey_and_questions": "Selectează chestionar și întrebări",
"select_survey_questions_description": "Alege ce întrebări din chestionar vor crea FeedbackRecords.",
"semantic_search_failed": "Căutarea înregistrărilor de feedback a eșuat",
"semantic_search_input_label": "Caută înregistrări de feedback după subiect",
"semantic_search_missing_text": "Această înregistrare de feedback nu conține text de afișat.",
"semantic_search_no_directories": "Încă nu este atribuit niciun director de înregistrări de feedback acestui spațiu de lucru. Adaugă o sursă de feedback pentru a începe căutarea după înțeles.",
"semantic_search_no_results": "Nu au fost găsite înregistrări de feedback corespunzătoare. Încearcă un subiect mai general sau o altă formulare.",
"semantic_search_placeholder": "Caută un subiect, de ex. plângeri despre prețuri",
"semantic_search_relevance": "{score}% relevanță",
"semantic_search_results_count": "{count, plural, one {# înregistrare de feedback corespunzătoare} few {# înregistrări de feedback corespunzătoare} other {# de înregistrări de feedback corespunzătoare}}",
"semantic_search_unavailable": "Căutarea semantică nu este disponibilă încă. Configurează încorporările Hub pentru a folosi această previzualizare.",
"semantic_topics_example_confusing_onboarding": "onboarding confuz",
"semantic_topics_example_pricing_complaints": "plângeri despre prețuri",
"semantic_topics_example_slow_checkout": "finalizare lentă",
"semantic_topics_preview_description": "Introdu un subiect sau o frază pentru a identifica înregistrările de feedback după sens. Aceasta este o previzualizare timpurie a viitoarelor Subiecte și Subsubiecte.",
"semantic_topics_preview_title": "Caută feedback după subiect",
"set_value": "setează valoare",
"setup_connection": "Configurează conexiunea",
"showing_count_loaded": "Se afișează {count} înregistrări",
@@ -3775,6 +3789,7 @@
"submission_id": "ID-ul trimiterii",
"survey_has_no_questions": "Acest sondaj nu are întrebări",
"topics_and_subtopics": "Subiecte și subiecte secundare",
"try_searching_for": "Încearcă să cauți",
"unify_feedback": "Unify Feedback",
"update_mapping_description": "Actualizează configurația de mapare pentru această sursă.",
"updated_at": "Actualizat la",
+28 -13
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Не удалось загрузить некоторые файлы",
"something_went_wrong": "Что-то пошло не так",
"something_went_wrong_please_try_again": "Что-то пошло не так. Пожалуйста, попробуйте ещё раз.",
"soon": "Скоро",
"sort_by": "Сортировать по",
"start_free_trial": "Начать бесплатный пробный период",
"status": "Статус",
@@ -1746,7 +1745,7 @@
"filter_data": "Фильтровать данные",
"filters": "Фильтры",
"filters_toggle_description": "Включай только те данные, которые соответствуют следующим условиям.",
"go_to_feedback_directories": "Перейти к директориям отзывов",
"go_to_feedback_directories": "Перейти к директориям обратной связи",
"granularity": "Детализация",
"granularity_day": "День",
"granularity_hour": "Час",
@@ -1770,7 +1769,7 @@
"no_data_available": "Нет доступных данных",
"no_data_returned": "Запрос не вернул данных",
"no_data_returned_for_chart": "Для графика не получено данных",
"no_data_source_available": "К этому рабочему пространству не назначена директория отзывов.",
"no_data_source_available": "К этому рабочему пространству не привязана директория обратной связи.",
"no_grouping": "Нет (только фильтр)",
"no_valid_data_to_display": "Нет корректных данных для отображения",
"not_contains": "не содержит",
@@ -1851,10 +1850,10 @@
"api_key_updated": "API-ключ обновлён",
"delete_api_key_confirmation": "Любые приложения, использующие этот ключ, больше не смогут получить доступ к вашим данным Formbricks.",
"duplicate_access": "Дублированный доступ к рабочему пространству не разрешён",
"duplicate_directory_access": "Дублирование доступа к директории отзывов не разрешено",
"feedback_directory_access": "Доступ к директории отзывов",
"duplicate_directory_access": "Дублирование доступа к директории обратной связи запрещено",
"feedback_directory_access": "Доступ к директории обратной связи",
"no_api_keys_yet": "У вас ещё нет API-ключей",
"no_directory_permissions_found": "Разрешения для директории отзывов не найдены",
"no_directory_permissions_found": "Разрешения для директорий обратной связи не найдены",
"no_workspace_permissions_found": "Разрешения для рабочего пространства не найдены",
"organization_access": "Доступ к организации",
"organization_access_description": "Выберите права на чтение или запись для ресурсов всей организации.",
@@ -2547,10 +2546,10 @@
"archive_directory": "Архивировать каталог",
"archive_not_allowed": "У тебя нет прав для архивирования этого каталога.",
"are_you_sure_you_want_to_archive": "Ты уверен, что хочешь архивировать этот каталог? Рабочие пространства больше не будут иметь к нему доступа.",
"assign_workspaces_description": "Управляй тем, какие рабочие пространства могут получить доступ к этому каталогу отзывов.",
"assign_workspaces_description": "Управляй, какие рабочие пространства могут получить доступ к этой директории обратной связи.",
"connectors_description": "Коннекторы, которые отправляют записи обратной связи в этот каталог.",
"create_feedback_directory": "Создать директорию для отзывов",
"description": "Управляй каталогами отзывов и их назначением рабочим пространствам.",
"description": "Управляй директориями обратной связи и их привязками к рабочим пространствам.",
"directory_archived_successfully": "Каталог успешно архивирован",
"directory_created_successfully": "Каталог успешно создан",
"directory_id": "ID каталога",
@@ -2559,14 +2558,14 @@
"directory_settings_title": "Настройки {directoryName}",
"directory_unarchived_successfully": "Каталог успешно разархивирован",
"directory_updated_successfully": "Каталог успешно обновлён",
"empty_state": "Каталоги отзывов не найдены. Создай один, чтобы начать.",
"empty_state": "Директории обратной связи не найдены. Создай одну, чтобы начать.",
"error_directory_has_connectors": "Невозможно архивировать каталог, к которому привязаны коннекторы. Сначала удалите все коннекторы.",
"error_directory_name_duplicate": "Директория обратной связи с таким именем уже существует.",
"error_directory_name_duplicate": "Директория обратной связи с таким названием уже существует.",
"error_directory_name_required": "Необходимо указать имя директории.",
"error_directory_workspaces_invalid_org": "Некоторые указанные рабочие пространства не принадлежат этой организации.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Каталоги отзывов",
"no_access": "У тебя нет прав для управления каталогами отзывов.",
"no_access": "У тебя нет прав для управления директориями обратной связи.",
"no_connectors": "К этому каталогу пока не привязано ни одного коннектора.",
"pause_connectors_confirmation_description": "Если приостановить эти коннекторы, новые записи больше не будут добавляться.",
"pause_connectors_confirmation_title": "Приостановить связанные коннекторы?",
@@ -3687,7 +3686,7 @@
"enum": "enum",
"failed_to_load_feedback_records": "Не удалось загрузить отзывы",
"feedback_date": "Текущая дата",
"feedback_directory": "Каталог обратной связи",
"feedback_directory": "Директория обратной связи",
"feedback_record_created_successfully": "Запись отзыва успешно создана",
"feedback_record_details": "Детали записи обратной связи",
"feedback_record_details_description": "Просмотрите и обновите поля записи отзыва.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Значения метаданных только для чтения (нестроковые)",
"metadata_value": "Значение метаданных",
"missing_feedback_source_title": "Не нашли нужный источник обратной связи?",
"no_feedback_directory_available": "К этому рабочему пространству не назначен каталог обратной связи. Сначала создайте или назначьте каталог.",
"no_feedback_directory_available": "К этому рабочему пространству не привязана директория обратной связи. Сначала создай или привяжи её.",
"no_feedback_records": "Пока нет записей отзывов. Они появятся здесь, когда коннекторы начнут отправлять данные.",
"no_source_fields_loaded": "Поля источника ещё не загружены",
"no_sources_connected": "Нет подключённых источников. Добавьте источник, чтобы начать.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Запросить интеграцию источника",
"required": "Обязательно",
"save_changes": "Сохранить изменения",
"search_feedback": "Искать обратную связь",
"select_a_survey_to_see_questions": "Выберите опрос, чтобы увидеть его вопросы",
"select_a_value": "Выберите значение...",
"select_feedback_directory": "Выберите каталог",
@@ -3748,6 +3748,20 @@
"select_survey": "Выбрать опрос",
"select_survey_and_questions": "Выбрать опрос и вопросы",
"select_survey_questions_description": "Выберите, какие вопросы опроса должны создавать FeedbackRecords.",
"semantic_search_failed": "Не удалось найти записи обратной связи",
"semantic_search_input_label": "Ищи записи обратной связи по теме",
"semantic_search_missing_text": "В этой записи обратной связи нет текста для отображения.",
"semantic_search_no_directories": "К этому рабочему пространству пока не привязана директория с записями обратной связи. Добавь источник обратной связи, чтобы начать поиск по смыслу.",
"semantic_search_no_results": "Подходящие записи обратной связи не найдены. Попробуй более широкую тему или другую фразу.",
"semantic_search_placeholder": "Ищи по теме, например, жалобы на цены",
"semantic_search_relevance": "Релевантность {score}%",
"semantic_search_results_count": "{count, plural, one {# подходящая запись обратной связи} few {# подходящие записи обратной связи} many {# подходящих записей обратной связи} other {# подходящих записей обратной связи}}",
"semantic_search_unavailable": "Семантический поиск пока недоступен. Настрой эмбеддинги Hub, чтобы использовать эту функцию.",
"semantic_topics_example_confusing_onboarding": "запутанная регистрация",
"semantic_topics_example_pricing_complaints": "жалобы на цены",
"semantic_topics_example_slow_checkout": "медленное оформление заказа",
"semantic_topics_preview_description": "Введите тему или фразу, чтобы найти записи отзывов по смыслу. Это ранний предварительный просмотр будущих Тем и Подтем.",
"semantic_topics_preview_title": "Поиск отзывов по теме",
"set_value": "установить значение",
"setup_connection": "Настроить подключение",
"showing_count_loaded": "Показано записей: {count}",
@@ -3775,6 +3789,7 @@
"submission_id": "Идентификатор отправки",
"survey_has_no_questions": "В этом опросе нет вопросов",
"topics_and_subtopics": "Темы и подтемы",
"try_searching_for": "Попробуйте поискать",
"unify_feedback": "Обратная связь Unify",
"update_mapping_description": "Обнови настройки сопоставления для этого источника.",
"updated_at": "Обновлено",
+29 -14
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Några filer misslyckades att laddas upp",
"something_went_wrong": "Något gick fel",
"something_went_wrong_please_try_again": "Något gick fel. Försök igen.",
"soon": "Snart",
"sort_by": "Sortera efter",
"start_free_trial": "Starta gratis provperiod",
"status": "Status",
@@ -1746,7 +1745,7 @@
"filter_data": "Filtrera data",
"filters": "Filter",
"filters_toggle_description": "Inkludera bara data som uppfyller följande villkor.",
"go_to_feedback_directories": "Gå till Feedbackkataloger",
"go_to_feedback_directories": "Gå till feedback-kataloger",
"granularity": "Detaljnivå",
"granularity_day": "Dag",
"granularity_hour": "timme",
@@ -1770,7 +1769,7 @@
"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_data_source_available": "Ingen feedbackkatalog är tilldelad till den här arbetsytan.",
"no_data_source_available": "Ingen feedback-katalog är tilldelad till denna arbetsyta.",
"no_grouping": "Ingen (endast filter)",
"no_valid_data_to_display": "Ingen giltig data att visa",
"not_contains": "innehåller inte",
@@ -1851,10 +1850,10 @@
"api_key_updated": "API-nyckel uppdaterad",
"delete_api_key_confirmation": "Alla applikationer som använder denna nyckel kommer inte längre att kunna komma åt din Formbricks-data.",
"duplicate_access": "Duplicerad arbetsyteåtkomst är inte tillåten",
"duplicate_directory_access": "Duplicerad feedbackkatalogåtkomst är inte tillåten",
"feedback_directory_access": "Feedbackkatalogåtkomst",
"duplicate_directory_access": "Duplicerad åtkomst till feedback-katalog är inte tillåten",
"feedback_directory_access": "Åtkomst till feedback-katalog",
"no_api_keys_yet": "Du har inga API-nycklar ännu",
"no_directory_permissions_found": "Inga feedbackkatalogbehörigheter hittades",
"no_directory_permissions_found": "Inga behörigheter för feedback-katalog hittades",
"no_workspace_permissions_found": "Inga behörigheter för arbetsytan hittades",
"organization_access": "Organisationsåtkomst",
"organization_access_description": "Välj läs- eller skrivbehörighet för resurser på organisationsnivå.",
@@ -2547,10 +2546,10 @@
"archive_directory": "Arkivera katalog",
"archive_not_allowed": "Du har inte behörighet att arkivera den här katalogen.",
"are_you_sure_you_want_to_archive": "Är du säker på att du vill arkivera den här katalogen? Arbetsytor kommer inte längre ha tillgång till den.",
"assign_workspaces_description": "Styr vilka arbetsytor som kan komma åt den här feedbackkatalogen.",
"assign_workspaces_description": "Styr vilka arbetsytor som kan komma åt denna feedback-katalog.",
"connectors_description": "Kopplingar som skickar feedbackposter till den här katalogen.",
"create_feedback_directory": "Skapa feedbackkatalog",
"description": "Hantera feedbackkataloger och deras arbetsytstilldelningar.",
"description": "Hantera feedback-kataloger och deras arbetsytetilldelningar.",
"directory_archived_successfully": "Katalogen arkiverades",
"directory_created_successfully": "Katalogen skapades",
"directory_id": "Katalog-ID",
@@ -2559,20 +2558,20 @@
"directory_settings_title": "Inställningar för {directoryName}",
"directory_unarchived_successfully": "Katalogen återställdes från arkivet",
"directory_updated_successfully": "Katalogen uppdaterades",
"empty_state": "Inga feedbackkataloger hittades. Skapa en för att komma igång.",
"empty_state": "Inga feedback-kataloger hittades. Skapa en för att komma igång.",
"error_directory_has_connectors": "Kan inte arkivera en katalog som har kopplingar kopplade till den. Ta bort alla kopplingar först.",
"error_directory_name_duplicate": "En feedbackkatalog med detta namn finns redan.",
"error_directory_name_duplicate": "En feedback-katalog med detta namn finns redan.",
"error_directory_name_required": "Katalognamn krävs.",
"error_directory_workspaces_invalid_org": "Vissa angivna arbetsytor tillhör inte denna organisation.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Feedbackkataloger",
"no_access": "Du har inte behörighet att hantera feedbackkataloger.",
"no_access": "Du har inte behörighet att hantera feedback-kataloger.",
"no_connectors": "Inga kopplingar länkade till den här katalogen ännu.",
"pause_connectors_confirmation_description": "Om du pausar dessa kopplingar kommer inga nya poster att läggas till.",
"pause_connectors_confirmation_title": "Pausa länkade kopplingar?",
"select_workspaces_placeholder": "Välj arbetsytor...",
"show_archived": "Visa arkiverade",
"title": "Feedbackkataloger",
"title": "Feedback-kataloger",
"unarchive": "Avarkivera",
"unarchive_workspace_conflict": "Den här katalogen kan inte avarkiveras eftersom en eller flera tilldelade arbetsytor är arkiverade.",
"upgrade_prompt_description": "Organisera feedbackposter i kataloger och dirigera data till rätt arbetsyta. Tillgängligt på Pro- och Scale-planerna.",
@@ -3687,7 +3686,7 @@
"enum": "enum",
"failed_to_load_feedback_records": "Det gick inte att ladda feedbackposter",
"feedback_date": "Aktuellt datum",
"feedback_directory": "Feedbackkatalog",
"feedback_directory": "Feedback-katalog",
"feedback_record_created_successfully": "Feedbackposten har skapats",
"feedback_record_details": "Feedbackpostdetaljer",
"feedback_record_details_description": "Granska och uppdatera fält för feedbackposter.",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Skrivskyddade metadatavärden (icke-sträng)",
"metadata_value": "Metadatavärde",
"missing_feedback_source_title": "Missing feedback source?",
"no_feedback_directory_available": "Ingen feedbackkatalog tilldelad till den här arbetsytan. Skapa eller tilldela en först.",
"no_feedback_directory_available": "Ingen feedback-katalog är tilldelad till denna arbetsyta. Skapa eller tilldela en först.",
"no_feedback_records": "Inga feedbackposter ännu. Poster visas här när dina connectors börjar skicka data.",
"no_source_fields_loaded": "Inga källfält har laddats än",
"no_sources_connected": "Inga källor är anslutna än. Lägg till en källa för att komma igång.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Request source integration",
"required": "Obligatoriskt",
"save_changes": "Spara ändringar",
"search_feedback": "Sök feedback",
"select_a_survey_to_see_questions": "Välj en enkät för att se dess frågor",
"select_a_value": "Välj ett värde...",
"select_feedback_directory": "Välj en katalog",
@@ -3748,6 +3748,20 @@
"select_survey": "Välj enkät",
"select_survey_and_questions": "Välj enkät & frågor",
"select_survey_questions_description": "Välj vilka enkätfrågor som ska skapa FeedbackRecords.",
"semantic_search_failed": "Misslyckades med att söka i feedback-poster",
"semantic_search_input_label": "Sök feedback-poster efter ämne",
"semantic_search_missing_text": "Denna feedback-post har ingen text att visa.",
"semantic_search_no_directories": "Ingen feedback-katalog är tilldelad till denna arbetsyta ännu. Lägg till en feedback-källa för att börja söka feedback efter mening.",
"semantic_search_no_results": "Inga matchande feedback-poster hittades. Prova ett bredare ämne eller en annan fras.",
"semantic_search_placeholder": "Sök efter ett ämne, t.ex. klagomål om priser",
"semantic_search_relevance": "{score}% relevans",
"semantic_search_results_count": "{count, plural, one {# matchande feedback-post} other {# matchande feedback-poster}}",
"semantic_search_unavailable": "Semantisk sökning är inte tillgänglig ännu. Konfigurera Hub-inbäddningar för att använda denna förhandsvisning.",
"semantic_topics_example_confusing_onboarding": "förvirrande onboarding",
"semantic_topics_example_pricing_complaints": "klagomål om priser",
"semantic_topics_example_slow_checkout": "långsam utcheckning",
"semantic_topics_preview_description": "Ange ett ämne eller en fras för att hitta feedbackposter baserat på betydelse. Detta är en tidig förhandsgranskning av framtida Ämnen & Underämnen.",
"semantic_topics_preview_title": "Sök feedback efter ämne",
"set_value": "ange värde",
"setup_connection": "Ställ in anslutning",
"showing_count_loaded": "Visar {count} poster",
@@ -3775,6 +3789,7 @@
"submission_id": "Inlämnings-ID",
"survey_has_no_questions": "Den här enkäten har inga frågor",
"topics_and_subtopics": "Ämnen och delämnen",
"try_searching_for": "Prova att söka efter",
"unify_feedback": "Samla feedback",
"update_mapping_description": "Uppdatera mappningskonfigurationen för den här källan.",
"updated_at": "Uppdaterad",
+19 -4
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "Bazı dosyalar yüklenemedi",
"something_went_wrong": "Bir şeyler ters gitti",
"something_went_wrong_please_try_again": "Bir sorun oluştu. Lütfen tekrar deneyin.",
"soon": "Yakında",
"sort_by": "Sıralama",
"start_free_trial": "Ücretsiz denemeyi başlat",
"status": "Durum",
@@ -2561,12 +2560,12 @@
"directory_updated_successfully": "Dizin başarıyla güncellendi",
"empty_state": "Geri bildirim dizini bulunamadı. Başlamak için bir tane oluştur.",
"error_directory_has_connectors": "Bağlayıcıları bağlı olan bir dizin arşivlenemez. Önce tüm bağlayıcıları kaldır.",
"error_directory_name_duplicate": "Bu ada sahip bir geri bildirim dizini zaten mevcut.",
"error_directory_name_duplicate": "Bu adda bir geri bildirim dizini zaten mevcut.",
"error_directory_name_required": "Dizin adı gereklidir.",
"error_directory_workspaces_invalid_org": "Belirtilen çalışma alanlarından bazıları bu organizasyona ait değil.",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "Geri Bildirim Dizinleri",
"no_access": "Geri bildirim dizinlerini yönetme izniniz yok.",
"no_access": "Geri bildirim dizinlerini yönetme yetkin yok.",
"no_connectors": "Bu dizine henüz bağlı bağlayıcı yok.",
"pause_connectors_confirmation_description": "Bu bağlayıcıları duraklatırsanız yeni kayıtlar eklenmez.",
"pause_connectors_confirmation_title": "Bağlı bağlayıcılar duraklatılsın mı?",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "Salt okunur meta veri değerleri (dize dışı)",
"metadata_value": "Meta veri değeri",
"missing_feedback_source_title": "Missing feedback source?",
"no_feedback_directory_available": "Bu çalışma alanına atanmış bir geri bildirim dizini yok. Önce bir tane oluştur veya ata.",
"no_feedback_directory_available": "Bu çalışma alanına atanmış geri bildirim dizini yok. Önce bir tane oluştur veya ata.",
"no_feedback_records": "Henüz geri bildirim kaydı yok. Bağlayıcıların veri göndermeye başlamasıyla kayıtlar burada görünecek.",
"no_source_fields_loaded": "Henüz kaynak alan yüklenmedi",
"no_sources_connected": "Henüz bağlı kaynak yok. Başlamak için bir kaynak ekle.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Request source integration",
"required": "Gerekli",
"save_changes": "Değişiklikleri kaydet",
"search_feedback": "Geri bildirim ara",
"select_a_survey_to_see_questions": "Sorularını görmek için bir anket seç",
"select_a_value": "Bir değer seç...",
"select_feedback_directory": "Bir dizin seç",
@@ -3748,6 +3748,20 @@
"select_survey": "Anket Seç",
"select_survey_and_questions": "Anket ve Soruları Seç",
"select_survey_questions_description": "Hangi anket sorularının GeriBildirimKayıtları oluşturması gerektiğini seçin.",
"semantic_search_failed": "Geri bildirim kayıtları aranamadı",
"semantic_search_input_label": "Geri bildirim kayıtlarını konuya göre ara",
"semantic_search_missing_text": "Bu geri bildirim kaydında görüntülenecek metin yok.",
"semantic_search_no_directories": "Bu çalışma alanına henüz geri bildirim kayıt dizini atanmamış. Anlamsal aramaya başlamak için bir geri bildirim kaynağı ekle.",
"semantic_search_no_results": "Eşleşen geri bildirim kaydı bulunamadı. Daha geniş bir konu veya farklı bir ifade dene.",
"semantic_search_placeholder": "Bir konu ara, örn. fiyatlandırma şikayetleri",
"semantic_search_relevance": "%{score} uygunluk",
"semantic_search_results_count": "{count, plural, one {# eşleşen geri bildirim kaydı} other {# eşleşen geri bildirim kaydı}}",
"semantic_search_unavailable": "Anlamsal arama henüz kullanıma sunulmadı. Bu önizlemeyi kullanmak için Hub gömme ayarlarını yapılandır.",
"semantic_topics_example_confusing_onboarding": "kafa karıştırıcı onboarding",
"semantic_topics_example_pricing_complaints": "fiyatlandırma şikayetleri",
"semantic_topics_example_slow_checkout": "yavaş ödeme",
"semantic_topics_preview_description": "Anlam bazında geri bildirim kayıtlarını ortaya çıkarmak için bir konu veya ifade gir. Bu, gelecekteki Konular ve Alt Konular özelliğinin erken bir ön izlemesidir.",
"semantic_topics_preview_title": "Konuya göre geri bildirim ara",
"set_value": "değer belirle",
"setup_connection": "Bağlantıyı kur",
"showing_count_loaded": "{count} kayıt gösteriliyor",
@@ -3775,6 +3789,7 @@
"submission_id": "Gönderim Kimliği",
"survey_has_no_questions": "Bu ankette soru yok",
"topics_and_subtopics": "Konular ve alt konular",
"try_searching_for": "Şunu aramayı dene",
"unify_feedback": "Geri Bildirimleri Birleştir",
"update_mapping_description": "Bu kaynak için eşleme yapılandırmasını güncelle.",
"updated_at": "Güncellenme tarihi",
+20 -5
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "某些文件上传失败",
"something_went_wrong": "出错了",
"something_went_wrong_please_try_again": "出错了 。请 尝试 再次 操作 。",
"soon": "即将推出",
"sort_by": "排序 依据",
"start_free_trial": "开始免费试用",
"status": "状态",
@@ -1851,8 +1850,8 @@
"api_key_updated": "API 密钥已更新",
"delete_api_key_confirmation": "使用此密钥的任何应用将无法再访问您的 Formbricks 数据。",
"duplicate_access": "不允许重复的工作区访问权限",
"duplicate_directory_access": "不允许重复的反馈目录访问",
"feedback_directory_access": "反馈目录访问",
"duplicate_directory_access": "不允许重复的反馈目录访问权限",
"feedback_directory_access": "反馈目录访问权限",
"no_api_keys_yet": "您还没有任何 API 密钥",
"no_directory_permissions_found": "未找到反馈目录权限",
"no_workspace_permissions_found": "未找到工作区权限",
@@ -2559,9 +2558,9 @@
"directory_settings_title": "{directoryName} 设置",
"directory_unarchived_successfully": "目录已成功取消归档",
"directory_updated_successfully": "目录已成功更新",
"empty_state": "未找到反馈目录。创建一个开始使用。",
"empty_state": "未找到反馈目录。创建一个开始使用。",
"error_directory_has_connectors": "无法归档已链接连接器的目录。请先移除所有连接器。",
"error_directory_name_duplicate": "已存在同名的反馈目录。",
"error_directory_name_duplicate": "已存在使用此名称的反馈目录。",
"error_directory_name_required": "目录名称为必填项。",
"error_directory_workspaces_invalid_org": "某些指定的工作区不属于此组织。",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Request source integration",
"required": "必填",
"save_changes": "保存更改",
"search_feedback": "搜索反馈",
"select_a_survey_to_see_questions": "请选择一个调查以查看其问题",
"select_a_value": "选择一个值...",
"select_feedback_directory": "选择目录",
@@ -3748,6 +3748,20 @@
"select_survey": "选择调查",
"select_survey_and_questions": "选择调查和问题",
"select_survey_questions_description": "选择哪些调查问题会创建反馈记录。",
"semantic_search_failed": "搜索反馈记录失败",
"semantic_search_input_label": "按主题搜索反馈记录",
"semantic_search_missing_text": "此反馈记录没有可显示的文本。",
"semantic_search_no_directories": "此工作区尚未分配反馈记录目录。添加反馈来源以开始按含义搜索反馈。",
"semantic_search_no_results": "未找到匹配的反馈记录。尝试使用更宽泛的主题或不同的短语。",
"semantic_search_placeholder": "搜索主题,例如:定价投诉",
"semantic_search_relevance": "相关度 {score}%",
"semantic_search_results_count": "{count, plural, other {# 条匹配的反馈记录}}",
"semantic_search_unavailable": "语义搜索暂不可用。配置 Hub 嵌入以使用此预览功能。",
"semantic_topics_example_confusing_onboarding": "令人困惑的引导流程",
"semantic_topics_example_pricing_complaints": "定价投诉",
"semantic_topics_example_slow_checkout": "结账慢",
"semantic_topics_preview_description": "输入主题或短语,按含义查找反馈记录。这是未来主题和子主题功能的早期预览。",
"semantic_topics_preview_title": "按主题搜索反馈",
"set_value": "设置值",
"setup_connection": "设置连接",
"showing_count_loaded": "显示 {count} 条记录",
@@ -3775,6 +3789,7 @@
"submission_id": "提交ID",
"survey_has_no_questions": "该调查没有任何问题",
"topics_and_subtopics": "主题和子主题",
"try_searching_for": "尝试搜索",
"unify_feedback": "统一反馈",
"update_mapping_description": "更新此来源的映射配置。",
"updated_at": "更新于",
+21 -6
View File
@@ -434,7 +434,6 @@
"some_files_failed_to_upload": "部分檔案上傳失敗",
"something_went_wrong": "發生錯誤",
"something_went_wrong_please_try_again": "發生錯誤。請再試一次。",
"soon": "即將推出",
"sort_by": "排序方式",
"start_free_trial": "開始免費試用",
"status": "狀態",
@@ -1770,7 +1769,7 @@
"no_data_available": "沒有可用資料",
"no_data_returned": "查詢沒有回傳資料",
"no_data_returned_for_chart": "此圖表沒有回傳資料",
"no_data_source_available": "此工作區未指派意見回饋目錄。",
"no_data_source_available": "此工作區未指派任何意見回饋目錄。",
"no_grouping": "無(僅篩選)",
"no_valid_data_to_display": "沒有可顯示的有效資料",
"not_contains": "不包含",
@@ -2550,7 +2549,7 @@
"assign_workspaces_description": "控制哪些工作區可以存取此意見回饋目錄。",
"connectors_description": "將意見回饋記錄傳送至此目錄的連接器。",
"create_feedback_directory": "建立意見回饋目錄",
"description": "管理意見回饋目錄及其工作區配置。",
"description": "管理意見回饋目錄及其工作區指派。",
"directory_archived_successfully": "目錄已成功封存",
"directory_created_successfully": "目錄已成功建立",
"directory_id": "目錄 ID",
@@ -2559,14 +2558,14 @@
"directory_settings_title": "{directoryName} 設定",
"directory_unarchived_successfully": "目錄已成功取消封存",
"directory_updated_successfully": "目錄已成功更新",
"empty_state": "找不到任何意見回饋目錄。建立一個開始使用。",
"empty_state": "找不到意見回饋目錄。建立一個開始使用。",
"error_directory_has_connectors": "無法封存已連結連接器的目錄。請先移除所有連接器。",
"error_directory_name_duplicate": "已存在同名的意見回饋目錄。",
"error_directory_name_required": "目錄名稱為必填項目。",
"error_directory_workspaces_invalid_org": "部分指定的工作區不屬於此組織。",
"error_workspace_already_assigned": "One or more workspaces are already linked to a different active directory. Reassign them first.",
"nav_label": "意見回饋目錄",
"no_access": "沒有權限管理意見回饋目錄。",
"no_access": "沒有權限管理意見回饋目錄。",
"no_connectors": "此目錄尚未連結任何連接器。",
"pause_connectors_confirmation_description": "暫停這些連接器後,將不會再新增新紀錄。",
"pause_connectors_confirmation_title": "暫停已連結的連接器?",
@@ -3727,7 +3726,7 @@
"metadata_read_only_entries": "唯讀元資料值(非字串)",
"metadata_value": "元資料值",
"missing_feedback_source_title": "Missing feedback source?",
"no_feedback_directory_available": "此工作區未指派意見回饋目錄。請先建立或指派一個目錄。",
"no_feedback_directory_available": "此工作區未指派意見回饋目錄。請先建立或指派一個。",
"no_feedback_records": "目前尚無回饋紀錄。當你的連接器開始傳送資料時,紀錄會顯示在這裡。",
"no_source_fields_loaded": "尚未載入來源欄位",
"no_sources_connected": "尚未連接任何來源。請新增來源以開始使用。",
@@ -3739,6 +3738,7 @@
"request_feedback_source": "Request source integration",
"required": "必填",
"save_changes": "儲存變更",
"search_feedback": "搜尋意見回饋",
"select_a_survey_to_see_questions": "請選擇問卷以查看其問題",
"select_a_value": "請選擇一個值...",
"select_feedback_directory": "選擇目錄",
@@ -3748,6 +3748,20 @@
"select_survey": "選擇問卷",
"select_survey_and_questions": "選擇問卷與問題",
"select_survey_questions_description": "請選擇哪些問卷問題要建立 FeedbackRecords。",
"semantic_search_failed": "搜尋意見回饋記錄失敗",
"semantic_search_input_label": "依主題搜尋意見回饋記錄",
"semantic_search_missing_text": "此意見回饋記錄沒有可顯示的文字。",
"semantic_search_no_directories": "此工作區尚未指派意見回饋記錄目錄。新增意見回饋來源以開始依語意搜尋意見回饋。",
"semantic_search_no_results": "找不到相符的意見回饋記錄。試試更廣泛的主題或不同的描述方式。",
"semantic_search_placeholder": "搜尋主題,例如:價格投訴",
"semantic_search_relevance": "相關度 {score}%",
"semantic_search_results_count": "{count, plural, other {找到 # 筆相符的意見回饋記錄}}",
"semantic_search_unavailable": "語意搜尋尚無法使用。請設定 Hub 嵌入功能以使用此預覽功能。",
"semantic_topics_example_confusing_onboarding": "令人困惑的入門流程",
"semantic_topics_example_pricing_complaints": "價格投訴",
"semantic_topics_example_slow_checkout": "結帳速度慢",
"semantic_topics_preview_description": "輸入主題或詞彙,以語意方式找出相關的意見回饋記錄。這是未來主題與子主題功能的早期預覽版本。",
"semantic_topics_preview_title": "依主題搜尋意見回饋",
"set_value": "設定值",
"setup_connection": "設定連線",
"showing_count_loaded": "顯示 {count} 筆記錄",
@@ -3775,6 +3789,7 @@
"submission_id": "提交ID",
"survey_has_no_questions": "此問卷沒有任何題目",
"topics_and_subtopics": "主題與子主題",
"try_searching_for": "試試搜尋",
"unify_feedback": "整合回饋",
"update_mapping_description": "更新此來源的對應設定。",
"updated_at": "更新時間",
@@ -31,10 +31,10 @@ export const UnifyConfigNavigation = ({
label: (
<span className="inline-flex items-center gap-2">
{t("workspace.unify.topics_and_subtopics")}
<Badge text={t("common.soon")} type="gray" size="tiny" />
<Badge text={t("common.preview")} type="gray" size="tiny" />
</span>
),
disabled: true,
href: `${baseHref}/topics-subtopics`,
},
];
+2 -4
View File
@@ -2,10 +2,10 @@ import "server-only";
import { NextRequest } from "next/server";
import { feedbackRecordsEnvoyAuthorizer } from "@/modules/hub/feedback-records-gateway";
import {
TEnvoyRequestAuthorizer,
authenticateEnvoyRequest,
buildStatusResponse,
parseEnvoyRequestMetadata,
TEnvoyRequestAuthorizer,
} from "./shared";
const envoyAuthorizers: TEnvoyRequestAuthorizer[] = [feedbackRecordsEnvoyAuthorizer];
@@ -16,9 +16,7 @@ export const authorizeEnvoyRequest = async (request: NextRequest): Promise<Respo
return requestMetadata.errorResponse;
}
const authorizer = envoyAuthorizers.find((candidate) =>
candidate.matches(requestMetadata.originalRequest)
);
const authorizer = envoyAuthorizers.find((candidate) => candidate.matches(requestMetadata.originalRequest));
if (!authorizer) {
return buildStatusResponse(400, "Unsupported Envoy auth route");
}
@@ -1,5 +1,5 @@
import { describe, expect, test } from "vitest";
import { getGatewayAuthServiceTokenPurpose, ZGatewayAuthService } from "./service";
import { ZGatewayAuthService, getGatewayAuthServiceTokenPurpose } from "./service";
describe("gateway auth service registry", () => {
test("returns the configured token purpose for feedbackRecords", () => {
+96 -10
View File
@@ -1,5 +1,15 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { createCacheKey } from "@formbricks/cache";
import FormbricksHub from "@formbricks/hub";
import {
createFeedbackRecord,
createFeedbackRecordsBatch,
getFeedbackRecordTenant,
listFeedbackRecords,
retrieveFeedbackRecord,
semanticSearchFeedbackRecords,
updateFeedbackRecord,
} from "./service";
import type { FeedbackRecordCreateParams } from "./types";
vi.mock("@formbricks/logger", () => ({
@@ -31,14 +41,6 @@ vi.mock("@/lib/cache", () => ({
const { getHubClient } = await import("./hub-client");
const { cache } = await import("@/lib/cache");
const {
createFeedbackRecord,
createFeedbackRecordsBatch,
getFeedbackRecordTenant,
listFeedbackRecords,
retrieveFeedbackRecord,
updateFeedbackRecord,
} = await import("./service");
const sampleInput: FeedbackRecordCreateParams = {
field_id: "el-1",
@@ -49,6 +51,8 @@ const sampleInput: FeedbackRecordCreateParams = {
field_label: "Question?",
value_number: 5,
collected_at: "2026-02-24T10:00:00.000Z",
submission_id: "sub-1",
tenant_id: "tenant-1",
};
describe("hub service", () => {
@@ -117,7 +121,7 @@ describe("hub service", () => {
feedbackRecords: { list: vi.fn().mockResolvedValue(listResponse) },
} as any);
const result = await listFeedbackRecords({ tenant_id: "env-1", limit: 50, offset: 0 });
const result = await listFeedbackRecords({ tenant_id: "env-1", limit: 50 });
expect(result.error).toBeNull();
expect(result.data).toEqual(listResponse);
@@ -133,7 +137,6 @@ describe("hub service", () => {
expect(result.data).toBeNull();
expect(result.error).toMatchObject({ status: 0, message: "Network error" });
});
});
describe("retrieveFeedbackRecord", () => {
@@ -164,6 +167,89 @@ describe("hub service", () => {
});
});
describe("semanticSearchFeedbackRecords", () => {
test("returns error result when getHubClient returns null", async () => {
vi.mocked(getHubClient).mockReturnValue(null);
const result = await semanticSearchFeedbackRecords({
tenant_id: "env-1",
query: "slow checkout",
});
expect(result.data).toBeNull();
expect(result.error).toMatchObject({
status: 0,
message: "HUB_API_KEY is not set; Hub integration is disabled.",
});
});
test("returns data when client.search.performSemanticSearch succeeds", async () => {
const searchResponse = {
data: [
{
feedback_record_id: "018e1234-5678-9abc-def0-123456789abc",
score: 0.91,
field_label: "What can we improve?",
value_text: "Checkout feels slow.",
},
],
limit: 10,
};
const performSemanticSearch = vi.fn().mockResolvedValue(searchResponse);
vi.mocked(getHubClient).mockReturnValue({
feedbackRecords: { search: { performSemanticSearch } },
} as any);
const input = {
tenant_id: "env-1",
query: "slow checkout",
limit: 10,
min_score: 0.7,
};
const result = await semanticSearchFeedbackRecords(input);
expect(result.error).toBeNull();
expect(result.data).toEqual(searchResponse);
expect(performSemanticSearch).toHaveBeenCalledWith(input);
});
test("returns error with status when client.search.performSemanticSearch throws APIError", async () => {
const apiError = new (FormbricksHub.APIError as any)("Embeddings are not configured", 503);
vi.mocked(getHubClient).mockReturnValue({
feedbackRecords: {
search: { performSemanticSearch: vi.fn().mockRejectedValue(apiError) },
},
} as any);
const result = await semanticSearchFeedbackRecords({
tenant_id: "env-1",
query: "slow checkout",
});
expect(result.data).toBeNull();
expect(result.error).toMatchObject({
status: 503,
message: "Embeddings are not configured",
});
});
test("returns error result when call throws non-API error", async () => {
vi.mocked(getHubClient).mockReturnValue({
feedbackRecords: {
search: { performSemanticSearch: vi.fn().mockRejectedValue(new Error("Network error")) },
},
} as any);
const result = await semanticSearchFeedbackRecords({
tenant_id: "env-1",
query: "slow checkout",
});
expect(result.data).toBeNull();
expect(result.error).toMatchObject({ status: 0, message: "Network error" });
});
});
describe("updateFeedbackRecord", () => {
test("returns error when client is null", async () => {
vi.mocked(getHubClient).mockReturnValue(null);
+34 -3
View File
@@ -1,6 +1,6 @@
import "server-only";
import FormbricksHub from "@formbricks/hub";
import { createCacheKey } from "@formbricks/cache";
import FormbricksHub from "@formbricks/hub";
import { logger } from "@formbricks/logger";
import { cache } from "@/lib/cache";
import { getHubClient } from "./hub-client";
@@ -10,6 +10,8 @@ import type {
FeedbackRecordListParams,
FeedbackRecordListResponse,
FeedbackRecordUpdateParams,
SemanticSearchInput,
SemanticSearchResponse,
} from "./types";
type HubError = { status: number; message: string; detail: string };
@@ -25,9 +27,15 @@ const NO_CONFIG_ERROR = {
detail: "HUB_API_KEY is not set; Hub integration is disabled.",
} as const;
const getErrorMessage = (err: unknown): string => {
if (err instanceof Error) return err.message;
if (typeof err === "string") return err;
return "Unknown error";
};
const createResultFromError = (err: unknown): HubFeedbackRecordResult => {
const status = err instanceof FormbricksHub.APIError ? err.status : 0;
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
return { data: null, error: { status, message, detail: message } };
};
@@ -95,6 +103,11 @@ export type ListFeedbackRecordsResult = {
error: HubError | null;
};
export type SemanticSearchFeedbackRecordsResult = {
data: SemanticSearchResponse | null;
error: HubError | null;
};
export type FeedbackRecordTenantResult = {
data: { tenantId: string } | null;
error: { status: number; message: string; detail: string } | null;
@@ -116,7 +129,25 @@ export const listFeedbackRecords = async (
} catch (err) {
logger.warn({ err }, "Hub: listFeedbackRecords failed");
const status = err instanceof FormbricksHub.APIError ? err.status : 0;
const message = err instanceof Error ? err.message : String(err);
const message = getErrorMessage(err);
return { data: null, error: { status, message, detail: message } };
}
};
export const semanticSearchFeedbackRecords = async (
input: SemanticSearchInput
): Promise<SemanticSearchFeedbackRecordsResult> => {
const client = getHubClient();
if (!client) {
return { data: null, error: { ...NO_CONFIG_ERROR } };
}
try {
const data = await client.feedbackRecords.search.performSemanticSearch(input);
return { data, error: null };
} catch (err) {
logger.warn({ err, tenantId: input.tenant_id }, "Hub: semanticSearchFeedbackRecords failed");
const status = err instanceof FormbricksHub.APIError ? err.status : 0;
const message = getErrorMessage(err);
return { data: null, error: { status, message, detail: message } };
}
};
+4
View File
@@ -5,3 +5,7 @@ export type FeedbackRecordData = FormbricksHub.FeedbackRecordData;
export type FeedbackRecordListParams = FormbricksHub.FeedbackRecordListParams;
export type FeedbackRecordListResponse = FormbricksHub.FeedbackRecordListResponse;
export type FeedbackRecordUpdateParams = FormbricksHub.FeedbackRecordUpdateParams;
export type SemanticSearchInput = FormbricksHub.FeedbackRecords.SearchPerformSemanticSearchParams;
export type SemanticSearchResponse = FormbricksHub.FeedbackRecords.SearchPerformSemanticSearchResponse;
export type SemanticSearchResultItem = FormbricksHub.FeedbackRecords.SearchPerformSemanticSearchResponse.Data;
+2 -1
View File
@@ -142,9 +142,10 @@ This chart does not deploy Cube.js. XM Suite v5 dashboard and analysis features
| hub.enabled | bool | `true` | |
| hub.env | object | `{}` | |
| hub.existingSecret | string | `""` | |
| hub.image.digest | string | `"sha256:14db7b3d285b6e9165b55693f9b83d08beff840a255fd77dd12882ee0a62f5cb"` | When set, takes precedence over tag (immutable pin). |
| hub.image.pullPolicy | string | `"IfNotPresent"` | |
| hub.image.repository | string | `"ghcr.io/formbricks/hub"` | |
| hub.image.tag | string | `"1.0.0"` | |
| hub.image.tag | string | `"0.2.0"` | Fallback when digest is empty. |
| hub.migration.activeDeadlineSeconds | int | `900` | |
| hub.migration.backoffLimit | int | `3` | |
| hub.migration.ttlSecondsAfterFinished | int | `300` | |
+13
View File
@@ -101,6 +101,19 @@ If `namespaceOverride` is provided, it will be used; otherwise, it defaults to `
{{- default (include "formbricks.appSecretName" .) .Values.hub.existingSecret -}}
{{- end }}
{{/*
Hub image reference. Pin by digest in production (hub.image.digest = "sha256:..."); falls back to
hub.image.tag for local/dev. All Hub workloads (deployment, init container, migration job, future
hub-worker) must use this helper so they cannot drift apart.
*/}}
{{- define "formbricks.hubImage" -}}
{{- if .Values.hub.image.digest -}}
{{- printf "%s@%s" .Values.hub.image.repository .Values.hub.image.digest -}}
{{- else -}}
{{- printf "%s:%s" .Values.hub.image.repository (.Values.hub.image.tag | default "latest") -}}
{{- end -}}
{{- end }}
{{- define "formbricks.postgresAdminPassword" -}}
{{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "formbricks.appSecretName" .)) }}
@@ -32,7 +32,7 @@ spec:
{{- end }}
initContainers:
- name: hub-migrate
image: {{ .Values.hub.image.repository }}:{{ .Values.hub.image.tag | default "latest" }}
image: {{ include "formbricks.hubImage" . }}
imagePullPolicy: {{ .Values.hub.image.pullPolicy }}
securityContext:
readOnlyRootFilesystem: true
@@ -48,7 +48,7 @@ spec:
name: {{ include "formbricks.hubSecretName" . }}
containers:
- name: hub
image: {{ .Values.hub.image.repository }}:{{ .Values.hub.image.tag | default "latest" }}
image: {{ include "formbricks.hubImage" . }}
imagePullPolicy: {{ .Values.hub.image.pullPolicy }}
securityContext:
readOnlyRootFilesystem: true
@@ -37,7 +37,7 @@ spec:
{{- end }}
containers:
- name: hub-migrate
image: {{ .Values.hub.image.repository }}:{{ .Values.hub.image.tag | default "latest" }}
image: {{ include "formbricks.hubImage" . }}
imagePullPolicy: {{ .Values.hub.image.pullPolicy }}
securityContext:
readOnlyRootFilesystem: true
+7 -2
View File
@@ -568,8 +568,13 @@ hub:
image:
repository: "ghcr.io/formbricks/hub"
# Pin to a semver tag for reproducible deployments; update on each Hub release.
tag: "1.0.0"
# Pinned by digest for immutable, reproducible deployments. When digest is set it takes
# precedence over tag, and deployment, init container, and migration job all resolve to the
# same immutable image. Update on each Hub release.
# Current digest corresponds to ghcr.io/formbricks/hub:0.2.0.
digest: "sha256:14db7b3d285b6e9165b55693f9b83d08beff840a255fd77dd12882ee0a62f5cb"
# Tag is a fallback for dev/non-prod when digest is cleared; keep aligned with the digest above.
tag: "0.2.0"
pullPolicy: IfNotPresent
# Optional override for the secret Hub reads from.
+5 -2
View File
@@ -47,8 +47,11 @@ services:
- minio-data:/data
# Run Hub DB migrations (goose + river) before the API starts. Idempotent; runs on every compose up.
# Hub image pinned via HUB_IMAGE_TAG so docker does not silently reuse a stale :latest cache.
# Keep hub, hub-migrate, and any future hub-worker on the same tag — they share one image and
# drift breaks migrations or job processing.
hub-migrate:
image: ghcr.io/formbricks/hub:latest
image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-0.2.0}
restart: "no"
entrypoint: ["sh", "-c"]
command:
@@ -63,7 +66,7 @@ services:
# Formbricks Hub API (ghcr.io/formbricks/hub). Shares the same Postgres database as Formbricks by default.
hub:
image: ghcr.io/formbricks/hub:latest
image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-0.2.0}
depends_on:
hub-migrate:
condition: service_completed_successfully
+2 -2
View File
@@ -33,7 +33,7 @@ That's it! After running the command and providing the required information, vis
The stack includes the [Formbricks Hub](https://github.com/formbricks/hub) API (`ghcr.io/formbricks/hub`) and a bundled Cube.js service for XM Suite v5 analytics. Hub and Cube share the same database as Formbricks by default.
- **Migrations**: A `hub-migrate` service runs Hub's database migrations (goose + river) before the Hub API starts. It runs on every `docker compose up` and is idempotent.
- **Production** (`docker/docker-compose.yml`): Set `HUB_API_KEY` and `CUBEJS_API_SECRET` (required). `HUB_API_URL` defaults to `http://hub:8080` and `CUBEJS_API_URL` defaults to `http://cube:4000` so the Formbricks app can reach both services inside the compose network. Override `HUB_DATABASE_URL` and `CUBEJS_DB_*` only if Hub or Cube should use a separate database.
- **Development** (`docker-compose.dev.yml`): Hub and Cube use the same local Postgres database. `HUB_API_KEY` defaults to `dev-api-key`, `CUBEJS_API_URL` defaults to `http://localhost:4000`, and `pnpm dev:setup` generates `CUBEJS_API_SECRET` in the repo root `.env`.
- **Production** (`docker/docker-compose.yml`): Set `HUB_API_KEY` and `CUBEJS_API_SECRET` (required). `HUB_API_URL` defaults to `http://hub:8080` and `CUBEJS_API_URL` defaults to `http://cube:4000` so the Formbricks app can reach both services inside the compose network. Override `HUB_DATABASE_URL` and `CUBEJS_DB_*` only if Hub or Cube should use a separate database. The Hub image tracks `:latest` by default so `formbricks.sh update` advances Hub in lockstep with the app. `hub` and `hub-migrate` always resolve to the same image. To pin to an immutable reference, set `HUB_IMAGE_REF` in `docker/.env` to either a tag (e.g. `:0.2.0`) or a digest (e.g. `@sha256:14db7b3d…`).
- **Development** (`docker-compose.dev.yml`): Hub and Cube use the same local Postgres database. `HUB_API_KEY` defaults to `dev-api-key`, `CUBEJS_API_URL` defaults to `http://localhost:4000`, and `pnpm dev:setup` generates `CUBEJS_API_SECRET` in the repo root `.env`. The Hub image is pinned to a semver tag (`hub` and `hub-migrate` share the same value); override `HUB_IMAGE_TAG` in the repo root `.env` to test a specific Hub release.
In development, Hub is exposed locally on port **8080** and Cube on **4000** (with the Cube playground on **4001**). In production Docker Compose, Hub and Cube stay internal to the compose network and are reached via `http://hub:8080` and `http://cube:4000`.
+5 -2
View File
@@ -262,8 +262,11 @@ services:
<<: *environment
# Run Hub DB migrations (goose + river) before the API starts. Uses same image; migrations are idempotent.
# Default tracks :latest so self-host updates (compose pull) advance Hub alongside the app image.
# Operators who want an immutable pin can set HUB_IMAGE_REF in docker/.env to either ":<tag>"
# (e.g. ":0.2.0") or "@sha256:<digest>". hub and hub-migrate share the same value — no drift.
hub-migrate:
image: ghcr.io/formbricks/hub:latest
image: ghcr.io/formbricks/hub${HUB_IMAGE_REF:-:latest}
restart: "no"
entrypoint: ["sh", "-c"]
command:
@@ -279,7 +282,7 @@ services:
# Formbricks Hub API (ghcr.io/formbricks/hub). Set HUB_API_KEY. By default shares the Formbricks database; set HUB_DATABASE_URL to use a separate DB.
hub:
restart: always
image: ghcr.io/formbricks/hub:latest
image: ghcr.io/formbricks/hub${HUB_IMAGE_REF:-:latest}
depends_on:
hub-migrate:
condition: service_completed_successfully
+235 -11
View File
@@ -6,22 +6,245 @@ icon: "arrow-right"
## v5
**Rate Limit**
Formbricks v5 changes the self-hosted runtime contract. If you are upgrading an existing Formbricks 4.x
deployment, review this section before starting the new version.
Formbricks v5 changes how rate limiting is enforced:
### What Changes In v5
- several public and API-key routes are no longer rate-limited inside the application server
- those routes are now expected to be protected by Envoy Gateway or an equivalent edge rate limiter
- the remaining session-based routes, server actions, and uncovered APIs still use the in-app limiter
- **Formbricks Hub is now mandatory** for self-hosted Formbricks v5 deployments.
- **Edge rate limiting is now required** for specific public and API-key routes. Those routes are no longer
throttled inside the application server.
- **AI features are configured at the instance level** via `AI_*` environment variables.
- **XM Suite v5 analytics depends on Cube.js**. The Docker and one-click stack bundle it, while Helm
deployments still need a separate reachable Cube.js instance and `CUBEJS_API_SECRET`.
<Warning>
If you self-host Formbricks without Envoy or another equivalent edge rate limiter, upgrade planning for v5
must include new edge protection for the covered routes. Otherwise those routes will no longer be throttled
by the application server after the upgrade.
Formbricks v5 removes application-level rate limiting for several routes that are now expected to be
protected by Envoy Gateway or an equivalent edge rate limiter. If your self-hosted instance does not
already have equivalent edge protection, add it before exposing the v5 stack.
</Warning>
See the [rate-limiting guide](/self-hosting/advanced/rate-limiting) for the exact covered route groups, thresholds,
and the remaining app-enforced limits.
### Before You Upgrade
Before you restart your instance on Formbricks v5:
- back up your database
- identify your current deployment type: one-click, manual Docker Compose, or Kubernetes/Helm
- confirm Redis/Valkey and your file storage setup are already healthy from your v4 baseline
- identify whether file uploads use external S3-compatible storage or a legacy bundled MinIO service
- decide whether this instance needs AI features, dashboards/analysis, or only core survey flows
- verify whether you already run Envoy Gateway or another equivalent edge rate limiter for the covered routes
### Required Config And Infrastructure Changes
#### Formbricks Hub
Formbricks v5 expects Hub to be part of the self-hosted stack.
- `HUB_API_KEY` is required
- `HUB_API_URL` must point to the Hub service the Formbricks app can reach
- `HUB_DATABASE_URL` is optional; when unset, Hub can share the same PostgreSQL database as Formbricks
- bundled Docker and Helm assets run Hub database migrations automatically during startup or upgrade, and the
migration steps are idempotent
<Note>
Hub-specific source code and standalone deployment assets live in the
[Formbricks Hub repository](https://github.com/formbricks/hub). For a normal Formbricks v5 upgrade, use that
repository as reference material only; the canonical migration steps stay in these Formbricks docs.
</Note>
#### Edge Rate Limiting
Formbricks v5 splits rate limiting across two layers:
- Envoy Gateway, or an equivalent edge rate limiter, for covered public and API-key routes
- the application server for the remaining session-authenticated routes, server actions, and uncovered APIs
Keep Redis/Valkey enabled for the remaining application-enforced limits. For the covered route groups and exact
thresholds, use the [rate-limiting guide](/self-hosting/advanced/rate-limiting) as the source of truth.
#### AI Features
AI features are optional and only need instance-level configuration if you want to enable the related
enterprise functionality.
- `AI_PROVIDER` and `AI_MODEL` are the base settings
- `AI_PROVIDER=aws` requires `AI_AWS_REGION`, `AI_AWS_ACCESS_KEY_ID`, and `AI_AWS_SECRET_ACCESS_KEY`
- `AI_PROVIDER=google` requires `AI_GOOGLE_CLOUD_PROJECT`, `AI_GOOGLE_CLOUD_LOCATION`, and either
`AI_GOOGLE_CLOUD_CREDENTIALS_JSON` or `AI_GOOGLE_CLOUD_APPLICATION_CREDENTIALS`
- `AI_PROVIDER=azure` requires `AI_AZURE_API_KEY` and either `AI_AZURE_BASE_URL` or
`AI_AZURE_RESOURCE_NAME`
#### Cube.js Analytics
XM Suite v5 dashboard and analysis features require Cube.js.
- the Docker and one-click stack bundle the `cube` service and expect `CUBEJS_API_SECRET`
- Helm deployments still need a separate reachable Cube.js instance
- the Formbricks app expects `CUBEJS_API_URL` and `CUBEJS_API_SECRET`
- if you run Cube yourself, you may also need to override `CUBEJS_DB_*` values for the Cube service
### Upgrade Steps By Deployment Type
**1. Back up your database**
<Tabs>
<Tab title="One-Click">
From the `formbricks` directory created by the installer:
```bash
cd formbricks
docker compose exec postgres pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v5_$(date +%Y%m%d_%H%M%S).dump
```
<Info>
If your PostgreSQL service name differs, run <code>docker compose ps</code> first and adjust the command.
</Info>
</Tab>
<Tab title="Docker Compose">
From the directory that contains your `docker-compose.yml`:
```bash
docker compose exec postgres pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v5_$(date +%Y%m%d_%H%M%S).dump
```
<Info>
If your service is not named <code>postgres</code>, use <code>docker compose ps</code> to find the correct
name.
</Info>
</Tab>
<Tab title="Kubernetes">
If you are using the in-cluster PostgreSQL released by the Helm chart:
```bash
kubectl exec -n formbricks formbricks-postgresql-0 -- pg_dump -Fc -U formbricks -d formbricks > formbricks_pre_v5_$(date +%Y%m%d_%H%M%S).dump
```
If you use a managed PostgreSQL service, create a provider snapshot or run `pg_dump` directly against the
external host before continuing.
</Tab>
</Tabs>
**2. Update your deployment config and restart on v5**
<Tabs>
<Tab title="One-Click">
The one-click `./formbricks.sh update` command only pulls new images. It does **not** rewrite your existing
`formbricks/docker-compose.yml`, so you must merge the v5 stack changes before the first v5 restart.
```bash
curl -fsSL -o formbricks/docker-compose.v5.yml https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/docker-compose.yml
```
Then compare `formbricks/docker-compose.v5.yml` with your existing `formbricks/docker-compose.yml` and merge
the v5 additions:
- add a non-empty `HUB_API_KEY` and reuse the same value wherever your deployment resolves Hub auth
- keep `HUB_API_URL` at `http://hub:8080` unless Hub runs elsewhere
- include the bundled `hub-migrate` and `hub` services
- if you use the bundled XM Suite v5 analytics stack, sync `formbricks/cube/cube.js` and
`formbricks/cube/schema/FeedbackRecords.js` from the current release and ensure
`formbricks/.env` contains `CUBEJS_API_SECRET`
- if your older setup still uses bundled MinIO for uploads, review that storage path separately before the
first v5 restart; newer self-hosting updates move the bundled object-storage path to RustFS, while
external S3-compatible storage keeps the same `S3_*` app contract
- add any `AI_*` variables you need
- if you do not run the bundled Docker analytics path, point `CUBEJS_API_URL` at your external Cube.js
instance and provide the matching `CUBEJS_API_SECRET`
After the compose file is updated and your edge rate limiter is in place:
```bash
./formbricks.sh update
```
</Tab>
<Tab title="Docker Compose">
Pull the current production compose file and merge the v5 changes into your existing deployment config before
only updating images:
```bash
curl -fsSL -o docker-compose.v5.yml https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/docker-compose.yml
```
At minimum, confirm:
- `HUB_API_KEY` is configured and the same value is available wherever your deployment resolves Hub auth
- `HUB_API_URL` points to the Hub service the app can reach
- the compose stack includes `hub-migrate` and `hub`
- the XM Suite v5 Docker stack also includes `cube`, `cube/cube.js`, and
`cube/schema/FeedbackRecords.js`, with `CUBEJS_API_SECRET` available through your `.env` or shell
environment
- if your legacy Compose file still includes bundled MinIO for uploads, treat that as a separate storage
review when comparing files; newer bundled storage guidance uses RustFS, while external S3-compatible
storage keeps the same `S3_*` app contract
- any `AI_*` variables you need are set
- if you override the bundled analytics path, point `CUBEJS_API_URL` at your external Cube.js instance and
supply the matching `CUBEJS_API_SECRET`
Then restart the stack:
```bash
docker compose pull
docker compose down
docker compose up -d
```
<Info>
The XM Suite v5 Docker Compose stack bundles Hub and Cube.js. Keep the bundled `cube/` config files in
sync with `docker-compose.yml` when you update this path.
</Info>
</Tab>
<Tab title="Kubernetes">
Upgrade using the current Formbricks chart:
```bash
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
-n formbricks \
-f values.yaml
```
Before running the upgrade, confirm these v5 expectations in your values:
- keep `hub.enabled=true`; Hub is mandatory in v5
- if you want the bundled Envoy path, configure `envoy.enabled=true` and then choose:
- `envoy.controller.enabled=true` for the bundled controller mode
- `envoy.controller.enabled=false` when the cluster already has a compatible Envoy Gateway controller
- if you use bundled Envoy rate limiting, enable a dedicated backend with `envoyRedis.enabled=true`
- if you already have an equivalent edge rate limiter outside the chart, keep that protection in place
- if the instance needs XM Suite v5 analytics or dashboards, provide `CUBEJS_API_URL` and
`CUBEJS_API_SECRET` for the external Cube.js deployment
</Tab>
</Tabs>
### Post-Upgrade Verification
After the upgrade:
- confirm the Formbricks app starts and `GET /health` returns successfully
- confirm the Hub service is healthy and reachable from the Formbricks app
- verify any Hub-backed connector or feedback flows you use
- verify covered routes are rate-limited at the edge layer
- verify AI features only if you configured the required `AI_*` variables
- verify dashboards and analysis flows only if your deployment path includes Cube.js or points to an external
Cube.js instance
### Troubleshooting And Rollback
Common upgrade issues:
- **Missing `HUB_API_KEY`**: Formbricks cannot authenticate to Hub, and Hub itself may fail to start
- **Bad `HUB_API_URL`**: the app starts, but Hub-backed features fail because the service cannot be reached
- **No edge rate limiter in front of covered routes**: the v5 deployment runs, but those routes are no longer
protected by the legacy in-app limiter
- **Missing AI credentials**: AI features remain unavailable until `AI_PROVIDER`, `AI_MODEL`, and the matching
provider credentials are set correctly
- **Cube not configured**: dashboards or analysis queries fail even though the core Formbricks app is healthy
If you need to roll back:
- restore the pre-upgrade database backup if schema or data changes require it
- revert your image tags or Helm release values to the last known-good Formbricks 4.x version
- revert compose or Helm configuration changes introduced for the v5 rollout
**Workspaces and Environment IDs**
@@ -1372,7 +1595,8 @@ For a seamless migration, below is a shell script for your self-hosted instance
### Docker & Single Script Setup
Now that these variables can be defined at runtime, you can append them inside your `x-environment` in the `docker-compose.yml` itself.
For a more detailed guide on these environment variables, please refer to the [Important Runtime Variables](/self-hosting/setup/docker#important-run-time-variables) section.
For a more detailed guide on these environment variables, please refer to the
[Environment Variables](/self-hosting/configuration/environment-variables) page.
```yaml docker-compose.yml
version: "3.3"
+6 -1
View File
@@ -18,6 +18,11 @@ Starting with Formbricks v5, rate limiting is split across two layers:
those routes will no longer be throttled by the application server after upgrading.
</Warning>
<Info>
For the full self-hosted upgrade checklist, use this page together with the
[v5 migration guide](/self-hosting/advanced/migration#v5).
</Info>
Rate limits are scoped by identifier, depending on the endpoint and enforcement layer:
- IP hash (for unauthenticated/client-side routes and public actions)
@@ -36,7 +41,7 @@ Before upgrading to Formbricks v5:
- expect covered routes to emit gateway `429`s instead of the legacy app JSON `429`s
For the current source of truth on covered routes and thresholds, use this page together with your deployment
configuration.
configuration and the [v5 migration guide](/self-hosting/advanced/migration#v5).
## Envoy-Managed Limits
@@ -8,102 +8,108 @@ icon: "code"
These variables are present inside your machine's docker-compose file. Restart the docker containers if you change any variables for them to take effect.
<Note>
Upgrading from Formbricks 4.x to 5.0? Read the [migration guide](/self-hosting/advanced/migration#v5) first.
Formbricks v5 makes Hub part of the standard self-hosted runtime and changes how rate limiting is enforced.
</Note>
For `AI_PROVIDER=google`, use a Gemini model ID such as `gemini-2.5-flash` together with Google Cloud credentials. Formbricks uses Google Cloud naming here, even though the underlying SDK still talks to Vertex AI endpoints for Gemini model access.
| Variable | Description | Required | Default |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| WEBAPP_URL | Base URL of the site. | required | http://localhost:3000 |
| PUBLIC_URL | Base URL for the public domain where surveys and public-facing content are served. If not set, uses WEBAPP_URL. | optional | WEBAPP_URL |
| NEXTAUTH_URL | Location of the auth server. This should normally be the same as WEBAPP_URL | required | http://localhost:3000 |
| DATABASE_URL | Database URL with credentials. | required | |
| NEXTAUTH_SECRET | Secret for NextAuth, used for session signing and encryption. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| ENCRYPTION_KEY | Secret used by Formbricks for data encryption and audit log hashing. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| CRON_SECRET | API Secret for running cron jobs. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| LOG_LEVEL | Minimum log level (debug, info, warn, error, fatal) | optional | info |
| S3_ACCESS_KEY | Access key for S3. | optional | (resolved by the AWS SDK) |
| S3_SECRET_KEY | Secret key for S3. | optional | (resolved by the AWS SDK) |
| S3_REGION | Region for S3. | optional | (resolved by the AWS SDK) |
| S3_BUCKET_NAME | S3 bucket name for data storage. Formbricks enables S3 storage when this is set. | optional (required if S3 is enabled) | |
| S3_ENDPOINT_URL | Endpoint for S3. | optional | (resolved by the AWS SDK) |
| SAML_DATABASE_URL | Database URL for SAML. | optional | postgres://postgres:@localhost:5432/formbricks-saml |
| PRIVACY_URL | URL for privacy policy. | optional | |
| TERMS_URL | URL for terms of service. | optional | |
| IMPRINT_URL | URL for imprint. | optional | |
| IMPRINT_ADDRESS | Address for imprint. | optional | |
| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to 1. | optional | |
| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to 1. | optional | |
| PASSWORD_RESET_TOKEN_LIFETIME_MINUTES | Configures how long password reset links remain valid in minutes. Accepted values are integers from 5 to 120. | optional | 30 |
| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to 1. | optional | |
| RATE_LIMITING_DISABLED | Disables rate limiting if set to 1. | optional | |
| TELEMETRY_DISABLED | Disables telemetry reporting if set to 1. Ignored when an Enterprise License is active. | optional | |
| DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS | Allows webhook URLs to point to internal/private network addresses (e.g. localhost, 192.168.x.x) if set to 1. Useful for self-hosted instances that need to send webhooks to internal services. | optional | |
| INVITE_DISABLED | Disables the ability for invited users to create an account if set to 1. | optional | |
| MAIL_FROM | Email address to send emails from. | optional (required if email services are to be enabled) | |
| MAIL_FROM_NAME | Email name/title to send emails from. | optional (required if email services are to be enabled) | |
| SMTP_HOST | Host URL of your SMTP server. | optional (required if email services are to be enabled) | |
| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | |
| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | |
| SMTP_PASSWORD | Password for your SMTP Server. | optional (required if email services are to be enabled) | |
| SMTP_AUTHENTICATED | If set to 0, the server will not require SMTP_USER and SMTP_PASSWORD(default is 1) | optional | |
| SMTP_SECURE_ENABLED | SMTP secure connection. For using TLS, set to 1 else to 0. | optional (required if email services are to be enabled) | |
| SMTP_REJECT_UNAUTHORIZED_TLS | If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs. | optional | 1 |
| TURNSTILE_SITE_KEY | Site key for Turnstile. | optional | |
| TURNSTILE_SECRET_KEY | Secret key for Turnstile. | optional | |
| RECAPTCHA_SITE_KEY | Site key for survey responses recaptcha bot protection | optional | |
| RECAPTCHA_SECRET_KEY | Secret key for recaptcha bot protection. | optional | |
| GITHUB_ID | Client ID for GitHub. | optional (required if GitHub auth is enabled) | |
| GITHUB_SECRET | Secret for GitHub. | optional (required if GitHub auth is enabled) | |
| GOOGLE_CLIENT_ID | Client ID for Google. | optional (required if Google auth is enabled) | |
| GOOGLE_CLIENT_SECRET | Secret for Google. | optional (required if Google auth is enabled) | |
| AI_PROVIDER | Instance-level AI provider used in the background. Supported values: `aws`, `google`, `azure`. | optional (required if AI is enabled) | |
| AI_MODEL | Instance-level AI model or deployment name used by the active provider. | optional (required if `AI_PROVIDER` is set) | |
| AI_GOOGLE_CLOUD_PROJECT | Google Cloud project ID for the `google` AI provider. | optional (required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_LOCATION | Google Cloud location for `google` AI requests. | optional (required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_CREDENTIALS_JSON | Service account credentials JSON for the `google` AI provider. | optional (one of this or `AI_GOOGLE_CLOUD_APPLICATION_CREDENTIALS` required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_APPLICATION_CREDENTIALS | Path to Google Application Default Credentials used by the `google` AI provider. | optional (one of this or `AI_GOOGLE_CLOUD_CREDENTIALS_JSON` required if `AI_PROVIDER=google`) | |
| AI_AWS_REGION | AWS region for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_ACCESS_KEY_ID | AWS access key ID for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_SECRET_ACCESS_KEY | AWS secret access key for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_SESSION_TOKEN | AWS session token for Amazon Bedrock temporary credentials. | optional | |
| AI_AZURE_BASE_URL | Azure OpenAI / Foundry base URL. When set, this is preferred over `AI_AZURE_RESOURCE_NAME`. | optional | |
| AI_AZURE_RESOURCE_NAME | Azure resource name used to assemble the Azure OpenAI URL. | optional | |
| AI_AZURE_API_KEY | API key for Azure OpenAI / Foundry. | optional (required if `AI_PROVIDER=azure`) | |
| AI_AZURE_API_VERSION | Azure API version for OpenAI-compatible calls. | optional | v1 |
| STRIPE_SECRET_KEY | Secret key for Stripe integration. | optional | |
| STRIPE_WEBHOOK_SECRET | Webhook secret for Stripe integration. | optional | |
| DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | #64748b |
| DEFAULT_ORGANIZATION_ID | Automatically assign new users to a specific organization when joining | optional | |
| OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | |
| OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
| OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
| OIDC_ISSUER | Issuer URL for Custom OpenID Connect Provider (should have .well-known configured at this) | optional (required if OIDC auth is enabled) | |
| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | RS256 |
| OTEL_EXPORTER_OTLP_ENDPOINT | Base OTLP HTTP endpoint for traces and metrics export (e.g. http://collector:4318). | optional | |
| OTEL_EXPORTER_OTLP_PROTOCOL | OTLP protocol to use for export. | optional | http/protobuf |
| OTEL_SERVICE_NAME | Service name reported in OpenTelemetry resource attributes. | optional | formbricks |
| OTEL_RESOURCE_ATTRIBUTES | Comma-separated resource attributes in OTel format (`key=value,key2=value2`). | optional | |
| OTEL_TRACES_SAMPLER | Trace sampler strategy (`always_on`, `always_off`, `traceidratio`, `parentbased_traceidratio`). | optional | always_on |
| OTEL_TRACES_SAMPLER_ARG | Sampling argument used by ratio-based samplers (`0` to `1`). | optional | |
| PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | |
| PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 |
| DEFAULT_TEAM_ID | Default team ID for new users. | optional | |
| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional | |
| SENTRY_ENVIRONMENT | Set this to identify the environment in Sentry | optional | |
| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional | |
| SESSION_MAX_AGE | Configure the maximum age for the session in seconds. | optional | 86400 (24 hours) |
| USER_MANAGEMENT_MINIMUM_ROLE | Set this to control which roles can access user management features. Accepted values: "owner", "manager", "disabled" | optional | manager |
| REDIS_URL | Redis URL for caching, rate limiting, and audit logging. Application will not start without this. | required | redis://localhost:6379 |
| AUDIT_LOG_ENABLED | Set this to 1 to enable audit logging. Requires Redis to be configured with the REDIS_URL env variable. | optional | 0 |
| AUDIT_LOG_GET_USER_IP | Set to 1 to include user IP addresses in audit logs from request headers | optional | 0 |
| Variable | Description | Required | Default |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| WEBAPP_URL | Base URL of the site. | required | http://localhost:3000 |
| PUBLIC_URL | Base URL for the public domain where surveys and public-facing content are served. If not set, uses WEBAPP_URL. | optional | WEBAPP_URL |
| NEXTAUTH_URL | Location of the auth server. This should normally be the same as WEBAPP_URL | required | http://localhost:3000 |
| DATABASE_URL | Database URL with credentials. | required | |
| NEXTAUTH_SECRET | Secret for NextAuth, used for session signing and encryption. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| ENCRYPTION_KEY | Secret used by Formbricks for data encryption and audit log hashing. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| CRON_SECRET | API Secret for running cron jobs. | required | (Generated by the user, must not exceed 32 bytes, `openssl rand -hex 32`) |
| LOG_LEVEL | Minimum log level (debug, info, warn, error, fatal) | optional | info |
| S3_ACCESS_KEY | Access key for S3. | optional | (resolved by the AWS SDK) |
| S3_SECRET_KEY | Secret key for S3. | optional | (resolved by the AWS SDK) |
| S3_REGION | Region for S3. | optional | (resolved by the AWS SDK) |
| S3_BUCKET_NAME | S3 bucket name for data storage. Formbricks enables S3 storage when this is set. | optional (required if S3 is enabled) | |
| S3_ENDPOINT_URL | Endpoint for S3. | optional | (resolved by the AWS SDK) |
| SAML_DATABASE_URL | Database URL for SAML. | optional | postgres://postgres:@localhost:5432/formbricks-saml |
| PRIVACY_URL | URL for privacy policy. | optional | |
| TERMS_URL | URL for terms of service. | optional | |
| IMPRINT_URL | URL for imprint. | optional | |
| IMPRINT_ADDRESS | Address for imprint. | optional | |
| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to 1. | optional | |
| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to 1. | optional | |
| PASSWORD_RESET_TOKEN_LIFETIME_MINUTES | Configures how long password reset links remain valid in minutes. Accepted values are integers from 5 to 120. | optional | 30 |
| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to 1. | optional | |
| RATE_LIMITING_DISABLED | Disables only the application-level rate limiter if set to 1. It does not disable Envoy or an equivalent edge rate limiter. | optional | |
| TELEMETRY_DISABLED | Disables telemetry reporting if set to 1. Ignored when an Enterprise License is active. | optional | |
| DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS | Allows webhook URLs to point to internal/private network addresses (e.g. localhost, 192.168.x.x) if set to 1. Useful for self-hosted instances that need to send webhooks to internal services. | optional | |
| INVITE_DISABLED | Disables the ability for invited users to create an account if set to 1. | optional | |
| MAIL_FROM | Email address to send emails from. | optional (required if email services are to be enabled) | |
| MAIL_FROM_NAME | Email name/title to send emails from. | optional (required if email services are to be enabled) | |
| SMTP_HOST | Host URL of your SMTP server. | optional (required if email services are to be enabled) | |
| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | |
| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | |
| SMTP_PASSWORD | Password for your SMTP Server. | optional (required if email services are to be enabled) | |
| SMTP_AUTHENTICATED | If set to 0, the server will not require SMTP_USER and SMTP_PASSWORD(default is 1) | optional | |
| SMTP_SECURE_ENABLED | SMTP secure connection. For using TLS, set to 1 else to 0. | optional (required if email services are to be enabled) | |
| SMTP_REJECT_UNAUTHORIZED_TLS | If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs. | optional | 1 |
| TURNSTILE_SITE_KEY | Site key for Turnstile. | optional | |
| TURNSTILE_SECRET_KEY | Secret key for Turnstile. | optional | |
| RECAPTCHA_SITE_KEY | Site key for survey responses recaptcha bot protection | optional | |
| RECAPTCHA_SECRET_KEY | Secret key for recaptcha bot protection. | optional | |
| GITHUB_ID | Client ID for GitHub. | optional (required if GitHub auth is enabled) | |
| GITHUB_SECRET | Secret for GitHub. | optional (required if GitHub auth is enabled) | |
| GOOGLE_CLIENT_ID | Client ID for Google. | optional (required if Google auth is enabled) | |
| GOOGLE_CLIENT_SECRET | Secret for Google. | optional (required if Google auth is enabled) | |
| AI_PROVIDER | Instance-level AI provider used in the background. Supported values: `aws`, `google`, `azure`. | optional (required if AI is enabled) | |
| AI_MODEL | Instance-level AI model or deployment name used by the active provider. | optional (required if `AI_PROVIDER` is set) | |
| AI_GOOGLE_CLOUD_PROJECT | Google Cloud project ID for the `google` AI provider. | optional (required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_LOCATION | Google Cloud location for `google` AI requests. | optional (required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_CREDENTIALS_JSON | Service account credentials JSON for the `google` AI provider. | optional (one of this or `AI_GOOGLE_CLOUD_APPLICATION_CREDENTIALS` required if `AI_PROVIDER=google`) | |
| AI_GOOGLE_CLOUD_APPLICATION_CREDENTIALS | Path to Google Application Default Credentials used by the `google` AI provider. | optional (one of this or `AI_GOOGLE_CLOUD_CREDENTIALS_JSON` required if `AI_PROVIDER=google`) | |
| AI_AWS_REGION | AWS region for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_ACCESS_KEY_ID | AWS access key ID for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_SECRET_ACCESS_KEY | AWS secret access key for Amazon Bedrock. | optional (required if `AI_PROVIDER=aws`) | |
| AI_AWS_SESSION_TOKEN | AWS session token for Amazon Bedrock temporary credentials. | optional | |
| AI_AZURE_BASE_URL | Azure OpenAI / Foundry base URL. When set, this is preferred over `AI_AZURE_RESOURCE_NAME`. | optional (one of this or `AI_AZURE_RESOURCE_NAME` required if `AI_PROVIDER=azure`) | |
| AI_AZURE_RESOURCE_NAME | Azure resource name used to assemble the Azure OpenAI URL. | optional (one of this or `AI_AZURE_BASE_URL` required if `AI_PROVIDER=azure`) | |
| AI_AZURE_API_KEY | API key for Azure OpenAI / Foundry. | optional (required if `AI_PROVIDER=azure`) | |
| AI_AZURE_API_VERSION | Azure API version for OpenAI-compatible calls. | optional | v1 |
| STRIPE_SECRET_KEY | Secret key for Stripe integration. | optional | |
| STRIPE_WEBHOOK_SECRET | Webhook secret for Stripe integration. | optional | |
| DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | #64748b |
| DEFAULT_ORGANIZATION_ID | Automatically assign new users to a specific organization when joining | optional | |
| OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | |
| OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
| OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
| OIDC_ISSUER | Issuer URL for Custom OpenID Connect Provider (should have .well-known configured at this) | optional (required if OIDC auth is enabled) | |
| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | RS256 |
| OTEL_EXPORTER_OTLP_ENDPOINT | Base OTLP HTTP endpoint for traces and metrics export (e.g. http://collector:4318). | optional | |
| OTEL_EXPORTER_OTLP_PROTOCOL | OTLP protocol to use for export. | optional | http/protobuf |
| OTEL_SERVICE_NAME | Service name reported in OpenTelemetry resource attributes. | optional | formbricks |
| OTEL_RESOURCE_ATTRIBUTES | Comma-separated resource attributes in OTel format (`key=value,key2=value2`). | optional | |
| OTEL_TRACES_SAMPLER | Trace sampler strategy (`always_on`, `always_off`, `traceidratio`, `parentbased_traceidratio`). | optional | always_on |
| OTEL_TRACES_SAMPLER_ARG | Sampling argument used by ratio-based samplers (`0` to `1`). | optional | |
| PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | |
| PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 |
| DEFAULT_TEAM_ID | Default team ID for new users. | optional | |
| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional | |
| SENTRY_ENVIRONMENT | Set this to identify the environment in Sentry | optional | |
| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional | |
| SESSION_MAX_AGE | Configure the maximum age for the session in seconds. | optional | 86400 (24 hours) |
| USER_MANAGEMENT_MINIMUM_ROLE | Set this to control which roles can access user management features. Accepted values: "owner", "manager", "disabled" | optional | manager |
| REDIS_URL | Redis URL for caching, rate limiting, and audit logging. Application will not start without this. | required | redis://localhost:6379 |
| AUDIT_LOG_ENABLED | Set this to 1 to enable audit logging. Requires Redis to be configured with the REDIS_URL env variable. | optional | 0 |
| AUDIT_LOG_GET_USER_IP | Set to 1 to include user IP addresses in audit logs from request headers | optional | 0 |
#### Formbricks Hub
When running the stack with [Formbricks Hub](https://github.com/formbricks/hub) (for example via Docker Compose or Helm), the following variables apply:
Starting with Formbricks v5, Hub is part of the standard self-hosted runtime. When you run Formbricks with the
bundled Docker Compose or Helm assets, the following variables apply:
| Variable | Description | Required | Default |
| ---------------- | ---------------------------------------------------------------------------------- | -------- | --------------------------------------------------- |
| HUB_API_KEY | API key used by the Formbricks Hub API (port 8080). | required | (e.g. `openssl rand -hex 32`) |
| HUB_API_URL | Base URL the Formbricks app uses to call Hub. Use `http://localhost:8080` locally. | required | `http://localhost:8080` in local dev |
| HUB_API_KEY | API key used by the Formbricks Hub API. Generate a strong secret and use the same value wherever your deployment supplies Hub auth configuration. | required | (e.g. `openssl rand -hex 32`) |
| HUB_API_URL | Base URL the Formbricks app uses to call Hub. With the bundled Docker stack, keep this at `http://hub:8080` unless Hub runs elsewhere. | required | `http://hub:8080` (bundled Docker), `http://localhost:8080` (local dev) |
| HUB_DATABASE_URL | PostgreSQL connection URL for Hub. Omit to use the same database as Formbricks. | optional | Same as Formbricks `DATABASE_URL` (shared database) |
#### Cube.js Analytics for XM Suite v5
@@ -113,8 +119,8 @@ Cube JWT from `CUBEJS_API_SECRET`, so `CUBEJS_API_TOKEN` is not part of the supp
| Variable | Description | Required | Default |
| ----------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------ |
| CUBEJS_API_URL | Base URL the Formbricks app uses to call Cube. Use `http://localhost:4000` locally. | required for XM Suite v5 analytics | `http://localhost:4000` in local dev |
| CUBEJS_API_SECRET | Shared secret Formbricks uses to sign Cube API JWTs. Generate with `openssl rand -hex 32`. | required for XM Suite v5 analytics | |
| CUBEJS_API_URL | Base URL the Formbricks app uses to call Cube. Use `http://localhost:4000` locally. | required for XM Suite v5 analytics | `http://localhost:4000` in local dev |
| CUBEJS_API_SECRET | Shared secret Formbricks uses to sign Cube API JWTs. Generate with `openssl rand -hex 32`. | required for XM Suite v5 analytics | |
| CUBEJS_DB_HOST | Database host for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_PORT | Database port for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_NAME | Database name for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
+90 -125
View File
@@ -1,197 +1,162 @@
---
title: "Cluster Setup"
description: "How to set up Formbricks in a High-Availability Cluster"
description: "Run Formbricks in a high-availability cluster."
icon: "circle-nodes"
---
## Overview
Running Formbricks as a cluster of multiple instances offers several key advantages:
Running Formbricks as a cluster of multiple instances gives you:
- **High Availability**: Ensure your surveys remain accessible even if some instances fail
- **Load Distribution**: Handle higher traffic by distributing requests across multiple instances
- **Scalability**: Easily scale horizontally by adding more instances as your needs grow
- **Zero-Downtime Updates**: Rolling updates without service interruption
- **High Availability**: surveys remain accessible even if one app pod becomes unavailable
- **Load Distribution**: traffic can be spread across multiple stateless Formbricks app instances
- **Scalability**: you can scale app replicas horizontally as usage grows
- **Zero-Downtime Updates**: rolling deployments are possible with the right orchestration setup
## Requirements
To run Formbricks in a cluster setup, you'll need:
For a Formbricks v5 cluster setup, plan for:
- Shared PostgreSQL database
- Shared Redis cache for session management and caching
- Load balancer to distribute traffic
- shared PostgreSQL for Formbricks and Hub, with `pgvector` support if Hub shares the same database
- shared Redis/Valkey for caching, rate limiting, and audit-related flows
- shared S3-compatible storage if you use file uploads or organization branding assets
- a load balancer or ingress layer in front of the app
- Formbricks Hub as part of the runtime
- Envoy Gateway or an equivalent external edge rate limiter for the v5-covered public and API-key routes
## Architecture
The Formbricks cluster setup consists of multiple components working together to provide a scalable and highly available system. Here's a detailed overview of the architecture:
```mermaid
graph TD
subgraph Load Balancer
LB[Load Balancer/Ingress]
subgraph Edge
LB["Load Balancer / Ingress"]
RL["Envoy Or Equivalent Edge Rate Limiter"]
end
subgraph Formbricks Cluster
FB1[Formbricks Instance 1]
FB2[Formbricks Instance 2]
FB3[Formbricks Instance n]
FB1["Formbricks App 1"]
FB2["Formbricks App 2"]
FB3["Formbricks App n"]
HUB["Formbricks Hub"]
end
subgraph Data Storage
subgraph PostgreSQL HA
PSQL_P[(PostgreSQL Primary)]
PSQL_R[(PostgreSQL Replica)]
end
subgraph Redis Cluster
RC_P[(Redis Primary)]
RC_R[(Redis Replica)]
end
S3[S3 Compatible Storage]
PSQL["PostgreSQL"]
REDIS["Redis / Valkey"]
S3["S3 Compatible Storage"]
end
%% Connections
LB --> FB1
LB --> FB2
LB --> FB3
LB --> RL
RL --> FB1
RL --> FB2
RL --> FB3
FB1 --> PSQL_P
FB2 --> PSQL_P
FB3 --> PSQL_P
PSQL_P --> PSQL_R
FB1 --> PSQL
FB2 --> PSQL
FB3 --> PSQL
HUB --> PSQL
FB1 --> RC_P
FB2 --> RC_P
FB3 --> RC_P
RC_P --> RC_R
FB1 --> REDIS
FB2 --> REDIS
FB3 --> REDIS
FB1 --> HUB
FB2 --> HUB
FB3 --> HUB
FB1 --> S3
FB2 --> S3
FB3 --> S3
style PSQL_P fill:#00C4B8,color:#ffffff
style PSQL_R fill:#00C4B8,color:#ffffff
style RC_P fill:#FF6B6B,color:#ffffff
style RC_R fill:#FF6B6B,color:#ffffff
style S3 fill:#FFA94D,color:#ffffff
style FB1,FB2,FB3 fill:#0D9373,color:#ffffff
style LB fill:#4C6EF5,color:#ffffff
```
### Component Description
1. **Formbricks Cluster**
1. **Formbricks App Replicas**
- Multiple Formbricks instances (1..n) running in parallel
- Each instance is stateless and can handle any incoming request
- Automatic failover if any instance becomes unavailable
- stateless application instances that serve the UI, APIs, and survey flows
- can be scaled horizontally behind a load balancer
2. **PostgreSQL Database**
2. **Formbricks Hub**
- Primary database storing all survey, response, and contact data
- Optional high-availability setup with primary-replica configuration
- Handles all persistent data storage needs
- required in Formbricks v5
- stores and serves Hub-backed feedback record data
- can share the same PostgreSQL database as the main app when configured that way
3. **Redis Cluster**
3. **PostgreSQL**
- Acts as a distributed cache layer
- Improves performance by caching frequently accessed data
- Can be configured in HA mode with primary-replica setup
- Handles session management and real-time features
- primary persistent store for the Formbricks app and, by default, Hub
- should be backed up and monitored like any other stateful production dependency
4. **S3 Compatible Storage**
4. **Redis / Valkey**
- Stores file uploads and attachments
- Can be any S3-compatible storage service (AWS S3, MinIO, etc.)
- Provides reliable and scalable file storage
- required for caching, remaining application-enforced rate limits, and audit-related flows
- should be shared across all app replicas
5. **Load Balancer**
- Distributes incoming traffic across all Formbricks instances
- Performs health checks and removes unhealthy instances
- Ensures even load distribution and high availability
5. **S3-Compatible Storage**
- used for file uploads and media-related features
- should be shared across all replicas
6. **Edge Layer**
- terminates or routes incoming traffic
- enforces rate limiting for the route groups that moved out of the application server in v5
## Redis Configuration
<Note>
Redis is required for Formbricks to function. The application will not start without a Redis URL configured.
Redis/Valkey is required for Formbricks to function. The application will not start without `REDIS_URL`.
</Note>
Configure Redis by adding the following **required** environment variable to your instances:
```sh env
REDIS_URL=redis://your-redis-host:6379
```
## S3 Configuration
Configure S3 storage by adding the following environment variables to your instances:
```sh env
# Required for file uploads in serverless environments
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_REGION=your-region
S3_BUCKET_NAME=your-bucket-name
# For S3-compatible storage (e.g., StorJ, MinIO)
# Leave empty for Amazon S3
S3_ENDPOINT_URL=https://your-s3-compatible-endpoint
# Enable for S3-compatible storage that requires path style
# 0 for disabled, 1 for enabled
S3_FORCE_PATH_STYLE=0
```
When using S3 in a cluster setup, ensure that:
When using S3 in a cluster setup, ensure:
- All Formbricks instances have access to the same S3 bucket
- The bucket has appropriate CORS settings configured
- IAM roles/users have sufficient permissions for read/write operations
- all replicas use the same bucket
- the bucket has the required CORS settings
- the credentials have read/write access for the assets you expect Formbricks to manage
## v5 Cluster Notes
### Hub Is Mandatory
Formbricks v5 self-hosting requires Hub. Do not plan a cluster upgrade that keeps Hub disabled.
### Edge Rate Limiting Must Exist Somewhere
You do not have to use the Formbricks Helm chart's Envoy bundle, but you do need equivalent edge protection for
the v5-covered public and API-key routes. Use the
[rate-limiting guide](/self-hosting/advanced/rate-limiting) for the exact route coverage.
### Cube Is Optional
Cube is only needed for analytics dashboards or other analysis flows that depend on Cube queries. It is not part
of the baseline Formbricks v5 cluster runtime.
## Kubernetes Setup
Formbricks provides an official Helm chart for deploying the entire cluster stack on Kubernetes. The Helm chart is available in the [Formbricks GitHub repository](https://github.com/formbricks/formbricks/tree/main/helm-chart).
### Features of the Helm Chart
The Helm chart provides a complete deployment solution that includes:
- Formbricks application with configurable replicas
- PostgreSQL database (with optional HA configuration)
- Redis cluster for caching
- Optional Traefik ingress controller for routing and SSL termination
- Automatic configuration of dependencies and networking
### Installation Steps
1. Add the Formbricks Helm repository:
The current Kubernetes deployment path uses the OCI chart published from
[`charts/formbricks`](https://github.com/formbricks/formbricks/tree/main/charts/formbricks):
```sh
helm repo add formbricks https://raw.githubusercontent.com/formbricks/formbricks/main/helm-chart
helm repo update
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
-n formbricks \
--create-namespace \
-f values.yaml
```
2. Install the chart:
```sh
helm install formbricks formbricks/formbricks
```
### Configuration Options
The Helm chart can be customized using a `values.yaml` file to configure:
- Number of Formbricks replicas
- Resource limits and requests
- Database configuration
- Redis settings
- Ingress rules and TLS
- Environment variables and secrets
Refer to the [Helm chart documentation](https://github.com/formbricks/formbricks/tree/main/helm-chart) for detailed configuration options and examples.
For the Kubernetes-specific installation flow, mandatory Hub behavior, and Envoy bundle modes, use the dedicated
[Kubernetes deployment guide](/self-hosting/setup/kubernetes).
+38 -1
View File
@@ -15,6 +15,13 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
Docker documentation.
</Note>
<Info>
Starting with Formbricks v5, the production Docker Compose stack includes Formbricks Hub and the XM Suite v5
Cube.js services. Generate `HUB_API_KEY` and `CUBEJS_API_SECRET` during setup, keep `HUB_API_URL` at its
internal default unless Hub runs elsewhere, and use the [migration guide](/self-hosting/advanced/migration#v5)
when upgrading an existing 4.x instance.
</Info>
## Start
1. **Create a New Directory for Formbricks**
@@ -95,6 +102,29 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
sed -i '' "s/CRON_SECRET:.*/CRON_SECRET: $(openssl rand -hex 32)/" docker-compose.yml
```
1. **Generate Hub API Key**
Formbricks v5 requires a Hub API key for the bundled Hub service.
For Linux:
```bash
sed -i "/HUB_API_KEY:$/s/HUB_API_KEY:.*/HUB_API_KEY: $(openssl rand -hex 32)/" docker-compose.yml
```
For macOS:
```bash
sed -i '' "s/HUB_API_KEY:.*/HUB_API_KEY: $(openssl rand -hex 32)/" docker-compose.yml
```
<Info>
The bundled production stack already sets <code>HUB_API_URL</code> to <code>http://hub:8080</code>. Only
change that value if your Formbricks app needs to reach Hub at a different address. If your deployment also
resolves Compose variables from a shell environment or <code>.env</code> file, keep the same
<code>HUB_API_KEY</code> available there as well.
</Info>
1. **Start the Docker Setup**
Now, you're ready to run Formbricks with Docker. Use the command below to start Formbricks together with PostgreSQL, Redis, Formbricks Hub, and Cube.js:
@@ -118,6 +148,12 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
Please take a look at our [migration guide](/self-hosting/advanced/migration) for version specific steps to update Formbricks.
<Info>
For a major migration such as Formbricks 4.x to 5.0, update your compose structure and configuration first.
Pulling images alone is not enough if your stack does not yet include Hub, `HUB_API_KEY`, the bundled
`cube/` config files plus `CUBEJS_API_SECRET`, or the new edge rate-limiting setup.
</Info>
1. Pull the latest Formbricks image
```bash
@@ -163,7 +199,8 @@ The fastest way to test MinIO with Formbricks is to use the included `docker-com
docker compose -f docker-compose.dev.yml up -d
```
This starts PostgreSQL, Valkey (Redis), MinIO, and Mailhog.
This starts PostgreSQL, Valkey (Redis), MinIO, Mailhog, Formbricks Hub, and a local Cube instance for
analytics testing.
2. **Access MinIO Console**
+112 -156
View File
@@ -1,211 +1,167 @@
---
title: "Kubernetes Deployment"
description: "Deploy the new Helm chart on a Kubernetes cluster using Helm."
description: "Deploy Formbricks on Kubernetes with the current OCI Helm chart."
icon: "circle-nodes"
---
Deploy Formbricks on Kubernetes using the current OCI Helm chart published from the `charts/formbricks`
directory in the Formbricks repository.
<Info>
Formbricks v5 self-hosting expects Hub to be part of the runtime. The chart handles that by default. Use the
[migration guide](/self-hosting/advanced/migration#v5) before upgrading an existing 4.x deployment.
</Info>
## Prerequisites
Ensure you have the following before proceeding:
- A running Kubernetes cluster (EKS, GKE, AKS, Minikube, etc.)
- An **Ingress Controller** (e.g., Traefik, Nginx)
- **Helm installed** on your local machine
- **Production setup requires managed PostgreSQL and Redis services**
- a running Kubernetes cluster
- Helm 3 installed locally
- a public hostname for `formbricks.webappUrl`
- a plan for PostgreSQL and Redis/Valkey, either in-cluster or managed externally
- an edge rate-limiting plan for the v5-covered routes: the chart's Envoy bundle or an equivalent external edge
solution
> **Note:** Redis is required for **session handling** when deploying multiple pods.
---
## 1. Installation Steps
## 1. Install The Chart
<Steps>
<Step title="Install with Default Configuration">
```sh
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace
```
> **Note:** To specify specific version use `--version` flag. E.g., `--version 1.0.0`. Starting from 3.5.0, the chart is available on the GitHub Container Registry (GHCR).
<Step title="Create A Minimal values.yaml">
By default:
- PostgreSQL and Redis are deployed within the cluster.
- Secrets are dynamically generated and stored as Kubernetes Secrets.
```yaml
formbricks:
webappUrl: https://surveys.example.com
```
Add any additional overrides you need for ingress, external services, secrets, or Enterprise license features.
</Step>
<Step title="Install with an Enterprise License">
<Step title="Install Formbricks">
```sh
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace --set enterprise.licenseKey="YOUR_LICENSE_KEY"
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
-n formbricks \
--create-namespace \
-f values.yaml
```
By default, the chart deploys:
- the Formbricks application
- Formbricks Hub
- PostgreSQL
- Redis
- generated Kubernetes Secrets
</Step>
</Steps>
---
## 2. Configure Secrets And External Services
## 2. Configuring Secrets
### Using Generated Secrets
### Using Kubernetes Secrets (Default)
By default, **secrets are stored as Kubernetes Secrets**.
The chart automatically generates **random values** for required secrets.
The default chart path keeps `secret.enabled: true`, which lets the chart generate the required application
secrets for you.
Modify `values.yaml`:
```yaml
secret:
enabled: true
```
### Using Managed PostgreSQL And Redis
---
For production workloads, many teams prefer managed services:
### Using External Secrets (AWS Secrets Manager, Vault, etc.)
To use an **external secrets manager**, enable `externalSecret` in `values.yaml`:
```yaml
secret:
enabled: false # Disable default secret generation
externalSecret:
enabled: true
secretStore:
name: aws-secrets-manager
kind: ClusterSecretStore
refreshInterval: "1h"
files:
redis:
data:
REDIS_PASSWORD:
remoteRef:
key: "prod/formbricks/secrets"
property: REDIS_PASSWORD
postgres:
data:
POSTGRES_ADMIN_PASSWORD:
remoteRef:
key: "prod/formbricks/secrets"
property: POSTGRES_ADMIN_PASSWORD
POSTGRES_USER_PASSWORD:
remoteRef:
key: "prod/formbricks/secrets"
property: POSTGRES_USER_PASSWORD
app-secrets:
data:
DATABASE_URL:
remoteRef:
key: "prod/formbricks/secrets"
property: DATABASE_URL
REDIS_URL:
remoteRef:
key: "prod/formbricks/secrets"
property: REDIS_URL
ENCRYPTION_KEY:
remoteRef:
key: "prod/formbricks/secrets"
property: ENCRYPTION_KEY
```
**Ensure ExternalSecrets Operator is installed:**
[https://external-secrets.io/latest/](https://external-secrets.io/latest/)
Install with:
```sh
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
```
---
## 3. Configuring PostgreSQL and Redis
### Using Managed PostgreSQL and Redis
For production, we recommend using **managed database and cache services**.
Modify `values.yaml`:
```yaml
postgresql:
enabled: false
externalDatabaseUrl: "postgresql://user:password@your-postgres-host:5432/mydb"
externalDatabaseUrl: "postgresql://user:password@your-postgres-host:5432/formbricks"
redis:
enabled: false
externalRedisUrl: "redis://your-redis-host:6379"
```
Install with:
```sh
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
```
---
### Using External Secrets
### Using In-Cluster PostgreSQL and Redis (Default)
By default, PostgreSQL and Redis are **deployed inside the cluster**.
To **ensure in-cluster deployment**, use:
If your cluster already uses an external secret manager, enable `externalSecret` and point it at your existing
SecretStore. Ensure the resulting app secret exposes the values your deployment needs, including `DATABASE_URL`,
`REDIS_URL`, and `HUB_API_KEY`.
## 3. v5-Specific Deployment Notes
### Hub Is Mandatory
Formbricks v5 does not support `hub.enabled=false`. Keep the default `hub.enabled=true` behavior in place.
Use `hub.image.tag`, `hub.resources`, and `hub.existingSecret` only when you need to pin or customize the Hub
deployment details.
### Envoy Bundle Modes
The chart supports three edge patterns for the v5-covered routes:
- **Bundled Envoy controller**: set `envoy.enabled=true` and `envoy.controller.enabled=true`
- **Existing cluster Envoy controller**: set `envoy.enabled=true` and `envoy.controller.enabled=false`
- **Equivalent external edge protection**: keep using your platform's own ingress or gateway layer if it already
provides equivalent rate-limiting coverage
If you use the chart-managed Envoy rate-limiting path, enable a dedicated backend with:
```yaml
postgresql:
enabled: true
redis:
envoyRedis:
enabled: true
```
Apply with:
This keeps Envoy rate-limiting state separate from the application's own Redis traffic.
### Cube Is Optional
Cube is only needed for analytics dashboards or other analysis flows that depend on Cube queries.
- deploy Cube separately when you need it
- configure `CUBEJS_API_URL` and `CUBEJS_API_SECRET` for the Formbricks app
- do not expect the main Formbricks chart to provision Cube automatically
## 4. Upgrade The Deployment
For normal chart upgrades:
```sh
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
-n formbricks \
-f values.yaml
```
---
For a Formbricks 4.x to 5.0 migration, confirm the following before running the upgrade:
## 4. Upgrading the Deployment
To apply changes:
```sh
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks
```
- Hub remains enabled
- `HUB_API_KEY` is present
- your edge rate-limiting plan is in place
- any required `AI_*` variables are added
- Cube is configured only if this instance needs analytics dashboards or analysis queries
### Scaling Resources
```sh
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --set deployment.resources.limits.cpu=2 --set deployment.resources.limits.memory=4Gi
```
## 5. Key Values
### Enabling Autoscaling
```sh
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --set autoscaling.enabled=true --set autoscaling.minReplicas=3 --set autoscaling.maxReplicas=10
```
| Field | Description |
| ----------------------------- | ------------------------------------------------------------- |
| `formbricks.webappUrl` | Public base URL for the Formbricks app |
| `deployment.image.tag` | Formbricks image tag override |
| `hub.enabled` | Must stay `true` in Formbricks v5 |
| `hub.image.tag` | Hub image tag override |
| `envoy.enabled` | Enables chart-managed Envoy Gateway resources |
| `envoy.controller.enabled` | Installs the bundled Envoy controller when `true` |
| `envoyRedis.enabled` | Deploys a dedicated Redis backend for Envoy rate limiting |
| `postgresql.externalDatabaseUrl` | Uses an external PostgreSQL service instead of in-cluster |
| `redis.externalRedisUrl` | Uses an external Redis/Valkey service instead of in-cluster |
---
For the complete values surface, refer to the chart README in the repository:
[charts/formbricks/README.md](https://github.com/formbricks/formbricks/tree/main/charts/formbricks).
## 5. Key Configuration Values
## 6. Uninstalling The Deployment
| Field | Description | Default Value |
|--------------------------------|--------------------------------------|--------------|
| `deployment.replicas` | Number of application replicas | `1` |
| `deployment.image.repository` | Docker image repository | `"ghcr.io/formbricks/formbricks"` |
| `deployment.image.tag` | Docker image tag | `"latest"` |
| `autoscaling.enabled` | Enable autoscaling | `false` |
| `postgresql.enabled` | Deploy PostgreSQL in cluster | `true` |
| `postgresql.externalDatabaseUrl` | External PostgreSQL URL | `""` |
| `redis.enabled` | Deploy Redis in cluster | `true` |
| `redis.externalRedisUrl` | External Redis URL | `""` |
| `externalSecret.enabled` | Enable external secrets manager | `false` |
**Refer to the Helm chart repository for full configuration options.**
---
## 6. Uninstalling the Deployment
To remove the deployment:
```sh
helm uninstall formbricks -n formbricks
```
### Removing Persistent Volumes (PVCs)
By default, **PVCs are not deleted** with Helm.
To manually remove them:
If you also want to remove in-cluster persistent volumes:
```sh
kubectl delete pvc --all -n formbricks
```
To completely delete the namespace:
```sh
kubectl delete namespace formbricks
```
---
## Additional Notes
- **Ingress Setup:** If using an ingress controller, make sure to configure `ingress.enabled: true` in `values.yaml`.
- **Environment Variables:** Pass custom environment variables via `envFrom` in `values.yaml`.
- **Backup Strategy:** Ensure you have a backup policy for PostgreSQL if running in-cluster.
**Your Formbricks deployment is now ready!**
+19 -2
View File
@@ -32,6 +32,14 @@ Run this command in your terminal:
curl -fsSL https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/formbricks.sh -o formbricks.sh && chmod +x formbricks.sh && ./formbricks.sh install
```
<Info>
The current v5 one-click stack is based on the production Docker Compose file and includes Formbricks Hub plus
the bundled XM Suite v5 Cube.js files under `formbricks/cube/`. Ensure your generated
`formbricks/docker-compose.yml` contains a non-empty `HUB_API_KEY` and that `formbricks/.env` contains
`CUBEJS_API_SECRET` before treating the v5 stack as ready. If either value is missing after the script
finishes, add it manually. `HUB_API_URL` should normally stay at `http://hub:8080`.
</Info>
### Script Prompts
During installation, the script will prompt you to provide some details:
@@ -284,13 +292,22 @@ Y
## Update
To update Formbricks, simply run the following command:
To update Formbricks for a minor or patch release, run:
```
./formbricks.sh update
```
The script will automatically pull the latest version of Formbricks from GitHub Container Registry and restart the containers.
The script pulls the latest images, stops the running containers, and starts the stack again.
<Warning>
`./formbricks.sh update` does **not** rewrite your existing `formbricks/docker-compose.yml`. For a major
migration such as Formbricks 4.x to 5.0, follow the [migration guide](/self-hosting/advanced/migration#v5)
first, merge the current v5 Compose changes into your existing deployment, confirm `HUB_API_KEY` is set, and
only then run the update command. If your older one-click install also uses bundled MinIO for file uploads,
review that storage path separately before the first v5 restart; newer self-hosting updates move the bundled
object-storage path to RustFS, while external S3-compatible storage keeps the same `S3_*` app contract.
</Warning>
## Stop