Compare commits

...

89 Commits

Author SHA1 Message Date
Cursor Agent 6fb754bf87 chore: update pnpm-lock.yaml for vitest dependency 2026-02-13 01:25:01 +00:00
Cursor Agent e1cdbf7bc5 style: use test instead of it in validation tests
- Fixed linting issues by following @vitest/consistent-test-it rule
- Changed all 'it' to 'test' for consistency
2026-02-13 01:24:28 +00:00
Cursor Agent b52f1a19d4 test: add comprehensive tests for findLanguageCodesForDuplicateLabels
- Added test coverage for undefined value handling
- Tests verify fix for TypeError when language keys are missing
- Added test script to types package.json
- All 7 tests passing
2026-02-13 01:21:21 +00:00
Cursor Agent f55b9de3a2 fix: prevent TypeError when accessing undefined TI18nString values in findLanguageCodesForDuplicateLabels
Fixes FORMBRICKS-JM

- Updated findLanguageCodesForDuplicateLabels in both validation.ts and elements-validation.ts
- Changed to safely filter undefined values before calling .trim()
- Now filters for string type first, then trims, then filters empty strings
- Prevents 'undefined is not an object (evaluating e[r].trim)' error when survey labels are incomplete
2026-02-13 01:19:23 +00:00
Bhagya Amarasinghe 18a7b233f0 fix: distributed lock for license fetch when Redis cache is cold (#7225)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 21:01:21 +00:00
Bhagya Amarasinghe b52627b3e9 feat: integrate OpenTelemetry for enhanced monitoring and tracing (#7235)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:33:52 +00:00
Dhruwang Jariwala 73e8e2f899 feat: license status for self hosters (#7236) 2026-02-12 08:41:00 +00:00
Dhruwang Jariwala fb0ef2fa82 chore: 7114 improve ux in team settings (#7237)
Co-authored-by: Johannes <johannes@formbricks.com>
2026-02-12 06:18:05 +00:00
Dhruwang Jariwala 8ab8adc3d0 fix: onboarding preview (#7238) 2026-02-11 14:46:23 +00:00
Bhagya Amarasinghe fad55e3486 feat: add behavior configuration for autoscaling in values.yaml (#7239) 2026-02-11 13:13:20 +00:00
Theodór Tómas a5c92bbc7b fix: prevent expected auth errors from being reported to Sentry (#7215)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 08:43:08 +00:00
Theodór Tómas 48eff5b547 feat: advance css vars (#7135)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-10 17:34:25 +00:00
Anshuman Pandey ff10ca7d6a fix: allows local ip images (#7189)
Co-authored-by: pandeymangg <pandeyman@Anshumans-MacBook-Air.local>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
2026-02-10 17:29:27 +01:00
Theodór Tómas 04c2b030f1 chore: inject rules in agents-md (#7203)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-10 13:36:44 +00:00
Dhruwang Jariwala 256b223925 fix: update welcome card toggle logic to set active element when enabled (#7230) 2026-02-10 08:21:37 +00:00
Dhruwang Jariwala f3ff4c9951 fix: added next-env.d.ts to gitignore (#7220) 2026-02-10 08:21:15 +00:00
Dhruwang Jariwala 2a590ef315 chore: improved action searching (#7234) 2026-02-10 08:19:24 +00:00
Dhruwang Jariwala 07a6cd6c0e chore: survey ui console warnings (#7228) 2026-02-09 07:39:30 +00:00
Dhruwang Jariwala 335da2f1f5 fix: webhook data not being sent (#7219) 2026-02-09 06:06:30 +00:00
bharath kumar 13b9db915b fix(js-core): invert expiration logic for SDK error state (#7190) (#7202)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-09 05:08:19 +00:00
AndresAIFR 76b25476b3 fix: check serverError before showing success toast (#7185)
Co-authored-by: Andres Cruciani <AndresAIFR@users.noreply.github.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-02-09 04:49:36 +00:00
Dhruwang Jariwala 04220902b4 fix: external links are not working in picture selection question and ending card (#7221) 2026-02-06 18:08:00 +00:00
Theodór Tómas 4649a2de3e fix: fixing issue with saving follow ups (#7218)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-06 10:42:35 +00:00
Dhruwang Jariwala 56ce05fb94 fix: validation in client api (#7206)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-02-06 06:55:41 +00:00
Anshuman Pandey 1b81e68106 feat: overlay close (#7197) 2026-02-06 06:08:19 +00:00
Theodór Tómas 202958cac2 fix: replace @vercel/og with next/og (#7208) 2026-02-06 04:53:42 +00:00
Harsh Bhat 8e901fb3c9 docs: Validation Rules (#7213) 2026-02-05 14:51:26 +00:00
Harsh Bhat 29afb3e4e9 docs: Formbricks Hubspot integration (#7212) 2026-02-05 12:30:05 +00:00
Matti Nannt 38a3b31761 fix: upgrade preact to fix JSON VNode Injection vulnerability (#7209)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:10:12 +00:00
Dhruwang Jariwala 2bfb79d999 fix: translation github action (#7207) 2026-02-05 11:06:21 +00:00
Matti Nannt 7971b9b312 fix(security): upgrade pnpm and AWS SDK to fix vulnerabilities (#7192)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:29:17 +00:00
Johannes 1143f58ba5 fix: refresh invite expiration when sharing link (#7198)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-04 13:28:25 +00:00
Balázs Úr 47fe3c73dd fix: Hungarian translations (#7199)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-04 13:26:27 +00:00
Dhruwang Jariwala 727e586b16 feat: responseID in response table (#7195) 2026-02-04 09:59:37 +00:00
Theodór Tómas 4a9b4d52ca fix: resolve infinite re-render loop in Survey Editor (#7142) 2026-02-04 05:03:09 +00:00
Sadiq Mohammed cbb0166419 chore(docker): add healthchecks and wait for postgres and redis readiness (#7121) 2026-02-03 13:37:53 +00:00
Balázs Úr 4b0c518683 chore: use Unicode punctuation, remove contractions, make wording consistent (#7049)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-03 10:37:49 +00:00
devin-ai-integration[bot] 5f05f8d36b chore: remove unused icon components (#7170)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Johannes <johannes@formbricks.com>
2026-02-03 08:40:15 +00:00
Balázs Úr f7558a7497 feat: Add Hungarian language support (#7175)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-02-03 08:22:41 +00:00
Dhruwang Jariwala 009beba866 feat: dropdown ui for multi select (#7191) 2026-02-03 05:16:03 +00:00
Bhagya Amarasinghe c3ec5ddc3a fix: optimize license check flow to prevent Redis hammering and OOM crashes (#7180) 2026-02-02 13:50:35 +00:00
Matti Nannt 9573ae19e6 fix(security): upgrade next and lodash to fix vulnerabilities (#7179)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 06:51:37 +00:00
Matti Nannt 7b3f841c5e fix(security): upgrade qs to fix DoS vulnerability (#7178)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 05:53:59 +00:00
Dhruwang Jariwala 8f7d225d6a fix: jerky animation behaviour (#7158) 2026-01-23 12:26:57 +00:00
Anshuman Pandey 094b6dedba fix: fixes response card UI for cta question (#7157) 2026-01-23 10:29:01 +00:00
Anshuman Pandey 36f0be07c4 fix: handle server errors in survey publish flow (#7156) 2026-01-23 08:54:11 +00:00
Bhagya Amarasinghe e079055a43 fix(helm): DB migration job (#7152) 2026-01-23 07:58:54 +00:00
Bhagya Amarasinghe 9ae9a3a9fc fix(helm): update ExternalSecret API version to v1 (#7153) 2026-01-23 07:03:50 +00:00
Dhruwang Jariwala b4606c0113 fix: nps & rating rtl UI (#7154) 2026-01-23 06:46:41 +00:00
Dhruwang Jariwala 6be654ab60 fix: language variants not working for app surveys (#7151) 2026-01-23 06:46:21 +00:00
dependabot[bot] 95c2e24416 chore(deps): bump the npm_and_yarn group across 2 directories with 1 update (#7149)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
2026-01-22 12:06:12 +00:00
Theodór Tómas 5b86dd3a8f feat: question delete dialog (#7144) 2026-01-22 09:41:54 +00:00
Dhruwang Jariwala 0da083a214 fix: billing checks (#7137) 2026-01-22 09:24:13 +00:00
Dhruwang Jariwala 379a86cf46 fix: survey card animation issue (#7150) 2026-01-22 07:58:18 +00:00
Johannes bed78716f0 fix: add validation for variable name conflicts with hidden fields (#7148) 2026-01-22 07:36:09 +00:00
Johannes 6167c3d9e6 fix: make redirect wait for successful response completion (#7146) 2026-01-22 06:55:54 +00:00
Dhruwang Jariwala 1db1271e7f feat: validation rules (#7140) 2026-01-21 15:23:09 +00:00
Matti Nannt 9ec1964106 fix(security): upgrade react-email packages to fix transitive next.js vulnerability (#7145) 2026-01-21 16:00:33 +01:00
Dhruwang Jariwala d5a70796dd chore: tweaked validation of ending card url (#7139)
Co-authored-by: Johannes <johannes@formbricks.com>
2026-01-21 14:41:36 +00:00
Dhruwang Jariwala 246351b3e6 fix: quotas not working for multi lang surveys (#7141) 2026-01-21 14:23:16 +00:00
Dhruwang Jariwala 22ea7302bb fix: removed validation from button labels (#7138) 2026-01-21 14:14:22 +00:00
Dhruwang Jariwala 8d47ab9709 fix: rtl tweaks (#7136) 2026-01-21 07:08:22 +00:00
Matti Nannt 8f6d27c1ef fix: upgrade next.js and preact to fix high-severity vulnerabilities (#7134) 2026-01-20 11:22:01 +00:00
Dhruwang Jariwala a37815b831 fix: breaking email embed preview for single select question (#7133) 2026-01-20 06:42:15 +00:00
Dhruwang Jariwala 2b526a87ca fix: email locale in invite accepted email (#7124) 2026-01-19 13:32:01 +00:00
Dhruwang Jariwala 047750967c fix: console warnings in survey ui package (#7130) 2026-01-19 07:19:13 +00:00
Johannes a54356c3b0 docs: add CSAT and update Survey Cooldown (#7128)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-01-19 07:06:16 +00:00
Matti Nannt 38ea5ed6ae perf: remove redundant database indexes (#7104) 2026-01-16 10:17:05 +00:00
Dhruwang Jariwala 6e19de32f7 fix: org managers not able to access api keys (#7123) 2026-01-16 09:54:54 +00:00
Johannes 957a4432f4 feat: introduce language variations (#7082)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2026-01-16 08:51:20 +00:00
Matti Nannt 22a5d4bb7d chore: consolidate agent instructions and remove Cursor rules (#7096) 2026-01-16 08:20:23 +00:00
Matti Nannt 226dff0344 fix: upgrade storybook to v10.1.11 (#7120) 2026-01-16 07:19:18 +00:00
Dhruwang Jariwala d474a94a21 fix: multi lang button label issue (#7117) 2026-01-15 17:57:50 +00:00
dependabot[bot] c1a4cc308b chore(deps): bump the npm_and_yarn group across 2 directories with 1 update (#7081)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
2026-01-15 15:10:33 +01:00
Dhruwang Jariwala 210da98b69 fix: scrolling in project breadcrumb dropdown (#7118) 2026-01-15 11:59:17 +00:00
Matti Nannt 2fc183d384 chore: update pre-commit hook to address husky warning (#7106) 2026-01-15 07:42:37 +00:00
Dhruwang Jariwala 78fb111610 fix: syntax issue in pr check size github action (#7116) 2026-01-15 06:43:59 +00:00
Bhagya Amarasinghe 11c0cb4b61 fix: add required WEBAPP_URL/NEXTAUTH_URL config and improve helm chart (#7107) 2026-01-14 18:26:40 +00:00
Johannes 95831f7c7f feat: add auto-save for draft surveys and Cmd+S hotkey (#7087)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-01-14 17:23:34 +00:00
Anshuman Pandey a31e7bfaa5 feat: security signup ui (#7088)
Co-authored-by: Johannes <johannes@formbricks.com>
2026-01-14 16:45:21 +00:00
Matti Nannt 6e35fc1769 fix: update systeminformation to 5.27.14 (#7105) 2026-01-14 11:04:43 +00:00
Theodór Tómas 48cded1646 perf: decouple constants from zod and add bundle analyzer (#7101) 2026-01-14 09:50:05 +00:00
Dhruwang Jariwala db752cee15 feat: add support for mp3 file extension and corresponding MIME type (#7103)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2026-01-13 12:19:22 +00:00
Dhruwang Jariwala b33aae0a73 fix: missing Russian langauge in language select dropdown (#7099) 2026-01-13 10:08:50 +00:00
Matti Nannt 72126ad736 fix: required label not being translated (#7092) 2026-01-13 10:05:11 +00:00
Theodór Tómas 4a2eeac90b perf: reduce bundle size (#7094) 2026-01-12 16:57:12 +00:00
Anshuman Pandey 46be3e7d70 feat: webhook secret (#7084) 2026-01-09 12:31:29 +00:00
Dhruwang Jariwala 6d140532a7 feat: add IP address capture functionality to surveys (#7079) 2026-01-09 11:28:05 +00:00
Dhruwang Jariwala 8c4a7f1518 fix: remove subheader field from survey element presets (#7078) 2026-01-09 08:28:48 +00:00
525 changed files with 29208 additions and 12717 deletions
-61
View File
@@ -1,61 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Build & Deployment Best Practices
## Build Process
### Running Builds
- Use `pnpm build` from project root for full build
- Monitor for React hooks warnings and fix them immediately
- Ensure all TypeScript errors are resolved before deployment
### Common Build Issues & Fixes
#### React Hooks Warnings
- Capture ref values in variables within useEffect cleanup
- Avoid accessing `.current` directly in cleanup functions
- Pattern for fixing ref cleanup warnings:
```typescript
useEffect(() => {
const currentRef = myRef.current;
return () => {
if (currentRef) {
currentRef.cleanup();
}
};
}, []);
```
#### Test Failures During Build
- Ensure all test mocks include required constants like `SESSION_MAX_AGE`
- Mock Next.js navigation hooks properly: `useParams`, `useRouter`, `useSearchParams`
- Remove unused imports and constants from test files
- Use literal values instead of imported constants when the constant isn't actually needed
### Test Execution
- Run `pnpm test` to execute all tests
- Use `pnpm test -- --run filename.test.tsx` for specific test files
- Fix test failures before merging code
- Ensure 100% test coverage for new components
### Performance Monitoring
- Monitor build times and optimize if necessary
- Watch for memory usage during builds
- Use proper caching strategies for faster rebuilds
### Deployment Checklist
1. All tests passing
2. Build completes without warnings
3. TypeScript compilation successful
4. No linter errors
5. Database migrations applied (if any)
6. Environment variables configured
### EKS Deployment Considerations
- Ensure latest code is deployed to all pods
- Monitor AWS RDS Performance Insights for database issues
- Verify environment-specific configurations
- Check pod health and resource usage
-415
View File
@@ -1,415 +0,0 @@
---
description: Caching rules for performance improvements
globs:
alwaysApply: false
---
# Cache Optimization Patterns for Formbricks
## Cache Strategy Overview
Formbricks uses a **hybrid caching approach** optimized for enterprise scale:
- **Redis** for persistent cross-request caching
- **React `cache()`** for request-level deduplication
- **NO Next.js `unstable_cache()`** - avoid for reliability
## Key Files
### Core Cache Infrastructure
- [packages/cache/src/service.ts](mdc:packages/cache/src/service.ts) - Redis cache service
- [packages/cache/src/client.ts](mdc:packages/cache/src/client.ts) - Cache client initialization and singleton management
- [apps/web/lib/cache/index.ts](mdc:apps/web/lib/cache/index.ts) - Cache service proxy for web app
- [packages/cache/src/index.ts](mdc:packages/cache/src/index.ts) - Cache package exports and utilities
### Environment State Caching (Critical Endpoint)
- [apps/web/app/api/v1/client/[environmentId]/environment/route.ts](mdc:apps/web/app/api/v1/client/[environmentId]/environment/route.ts) - Main endpoint serving hundreds of thousands of SDK clients
- [apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts](mdc:apps/web/app/api/v1/client/[environmentId]/environment/lib/data.ts) - Optimized data layer with caching
## Enterprise-Grade Cache Key Patterns
**Always use** the `createCacheKey` utilities from the cache package:
```typescript
// ✅ Correct patterns
createCacheKey.environment.state(environmentId) // "fb:env:abc123:state"
createCacheKey.organization.billing(organizationId) // "fb:org:xyz789:billing"
createCacheKey.license.status(organizationId) // "fb:license:org123:status"
createCacheKey.user.permissions(userId, orgId) // "fb:user:456:org:123:permissions"
// ❌ Never use flat keys - collision-prone
"environment_abc123"
"user_data_456"
```
## When to Use Each Cache Type
### Use React `cache()` for Request Deduplication
```typescript
// ✅ Prevents multiple calls within same request
export const getEnterpriseLicense = reactCache(async () => {
// Complex license validation logic
});
```
### Use `cache.withCache()` for Simple Database Queries
```typescript
// ✅ Simple caching with automatic fallback (TTL in milliseconds)
export const getActionClasses = (environmentId: string) => {
return cache.withCache(() => fetchActionClassesFromDB(environmentId),
createCacheKey.environment.actionClasses(environmentId),
60 * 30 * 1000 // 30 minutes in milliseconds
);
};
```
### Use Explicit Redis Cache for Complex Business Logic
```typescript
// ✅ Full control for high-stakes endpoints
export const getEnvironmentState = async (environmentId: string) => {
const cached = await environmentStateCache.getEnvironmentState(environmentId);
if (cached) return cached;
const fresh = await buildComplexState(environmentId);
await environmentStateCache.setEnvironmentState(environmentId, fresh);
return fresh;
};
```
## Caching Decision Framework
### When TO Add Caching
```typescript
// ✅ Expensive operations that benefit from caching
- Database queries (>10ms typical)
- External API calls (>50ms typical)
- Complex computations (>5ms)
- File system operations
- Heavy data transformations
// Example: Database query with complex joins (TTL in milliseconds)
export const getEnvironmentWithDetails = withCache(
async (environmentId: string) => {
return prisma.environment.findUnique({
where: { id: environmentId },
include: { /* complex joins */ }
});
},
{ key: createCacheKey.environment.details(environmentId), ttl: 60 * 30 * 1000 } // 30 minutes
)();
```
### When NOT to Add Caching
```typescript
// ❌ Don't cache these operations - minimal overhead
- Simple property access (<0.1ms)
- Basic transformations (<1ms)
- Functions that just call already-cached functions
- Pure computation without I/O
// ❌ Bad example: Redundant caching
const getCachedLicenseFeatures = withCache(
async () => {
const license = await getEnterpriseLicense(); // Already cached!
return license.active ? license.features : null; // Just property access
},
{ key: "license-features", ttl: 1800 * 1000 } // 30 minutes in milliseconds
);
// ✅ Good example: Simple and efficient
const getLicenseFeatures = async () => {
const license = await getEnterpriseLicense(); // Already cached
return license.active ? license.features : null; // 0.1ms overhead
};
```
### Computational Overhead Analysis
Before adding caching, analyze the overhead:
```typescript
// ✅ High overhead - CACHE IT
- Database queries: ~10-100ms
- External APIs: ~50-500ms
- File I/O: ~5-50ms
- Complex algorithms: >5ms
// ❌ Low overhead - DON'T CACHE
- Property access: ~0.001ms
- Simple lookups: ~0.1ms
- Basic validation: ~1ms
- Type checks: ~0.01ms
// Example decision tree:
const expensiveOperation = async () => {
return prisma.query(); // 50ms - CACHE IT
};
const cheapOperation = (data: any) => {
return data.property; // 0.001ms - DON'T CACHE
};
```
### Avoid Cache Wrapper Anti-Pattern
```typescript
// ❌ Don't create wrapper functions just for caching
const getCachedUserPermissions = withCache(
async (userId: string) => getUserPermissions(userId),
{ key: createCacheKey.user.permissions(userId), ttl: 3600 * 1000 } // 1 hour in milliseconds
);
// ✅ Add caching directly to the original function
export const getUserPermissions = withCache(
async (userId: string) => {
return prisma.user.findUnique({
where: { id: userId },
include: { permissions: true }
});
},
{ key: createCacheKey.user.permissions(userId), ttl: 3600 * 1000 } // 1 hour in milliseconds
);
```
## TTL Coordination Strategy
### Multi-Layer Cache Coordination
For endpoints serving client SDKs, coordinate TTLs across layers:
```typescript
// Client SDK cache (expiresAt) - longest TTL for fewer requests
const CLIENT_TTL = 60; // 1 minute (seconds for client)
// Server Redis cache - shorter TTL ensures fresh data for clients
const SERVER_TTL = 60 * 1000; // 1 minutes in milliseconds
// HTTP cache headers (seconds)
const BROWSER_TTL = 60; // 1 minute (max-age)
const CDN_TTL = 60; // 1 minute (s-maxage)
const CORS_TTL = 60 * 60; // 1 hour (balanced approach)
```
### Standard TTL Guidelines (in milliseconds for cache-manager + Keyv)
```typescript
// Configuration data - rarely changes
const CONFIG_TTL = 60 * 60 * 24 * 1000; // 24 hours
// User data - moderate frequency
const USER_TTL = 60 * 60 * 2 * 1000; // 2 hours
// Survey data - changes moderately
const SURVEY_TTL = 60 * 15 * 1000; // 15 minutes
// Billing data - expensive to compute
const BILLING_TTL = 60 * 30 * 1000; // 30 minutes
// Action classes - infrequent changes
const ACTION_CLASS_TTL = 60 * 30 * 1000; // 30 minutes
```
## High-Frequency Endpoint Optimization
### Performance Patterns for High-Volume Endpoints
```typescript
// ✅ Optimized high-frequency endpoint pattern
export const GET = async (request: NextRequest, props: { params: Promise<{ id: string }> }) => {
const params = await props.params;
try {
// Simple validation (avoid Zod for high-frequency)
if (!params.id || typeof params.id !== 'string') {
return responses.badRequestResponse("ID is required", undefined, true);
}
// Single optimized query with caching
const data = await getOptimizedData(params.id);
return responses.successResponse(
{
data,
expiresAt: new Date(Date.now() + CLIENT_TTL * 1000), // SDK cache duration
},
true,
"public, s-maxage=1800, max-age=3600, stale-while-revalidate=1800, stale-if-error=3600"
);
} catch (err) {
// Simplified error handling for performance
if (err instanceof ResourceNotFoundError) {
return responses.notFoundResponse(err.resourceType, err.resourceId);
}
logger.error({ error: err, url: request.url }, "Error in high-frequency endpoint");
return responses.internalServerErrorResponse(err.message, true);
}
};
```
### Avoid These Performance Anti-Patterns
```typescript
// ❌ Avoid for high-frequency endpoints
const inputValidation = ZodSchema.safeParse(input); // Too slow
const startTime = Date.now(); logger.debug(...); // Logging overhead
const { data, revalidateEnvironment } = await get(); // Complex return types
```
### CORS Optimization
```typescript
// ✅ Balanced CORS caching (not too aggressive)
export const OPTIONS = async (): Promise<Response> => {
return responses.successResponse(
{},
true,
"public, s-maxage=3600, max-age=3600" // 1 hour balanced approach
);
};
```
## Redis Cache Migration from Next.js
### Avoid Legacy Next.js Patterns
```typescript
// ❌ Old Next.js unstable_cache pattern (avoid)
const getCachedData = unstable_cache(
async (id) => fetchData(id),
['cache-key'],
{ tags: ['environment'], revalidate: 900 }
);
// ❌ Don't use revalidateEnvironment flags with Redis
return { data, revalidateEnvironment: true }; // This gets cached incorrectly!
// ✅ New Redis pattern with withCache (TTL in milliseconds)
export const getCachedData = (id: string) =>
withCache(
() => fetchData(id),
{
key: createCacheKey.environment.data(id),
ttl: 60 * 15 * 1000, // 15 minutes in milliseconds
}
)();
```
### Remove Revalidation Logic
When migrating from Next.js `unstable_cache`:
- Remove `revalidateEnvironment` or similar flags
- Remove tag-based invalidation logic
- Use TTL-based expiration instead
- Handle one-time updates (like `appSetupCompleted`) directly in cache
## Data Layer Optimization
### Single Query Pattern
```typescript
// ✅ Optimize with single database query
export const getOptimizedEnvironmentData = async (environmentId: string) => {
return prisma.environment.findUniqueOrThrow({
where: { id: environmentId },
include: {
project: {
select: { id: true, recontactDays: true, /* ... */ }
},
organization: {
select: { id: true, billing: true }
},
surveys: {
where: { status: "inProgress" },
select: { id: true, name: true, /* ... */ }
},
actionClasses: {
select: { id: true, name: true, /* ... */ }
}
}
});
};
// ❌ Avoid multiple separate queries
const environment = await getEnvironment(id);
const organization = await getOrganization(environment.organizationId);
const surveys = await getSurveys(id);
const actionClasses = await getActionClasses(id);
```
## Invalidation Best Practices
**Always use explicit key-based invalidation:**
```typescript
// ✅ Clear and debuggable
await invalidateCache(createCacheKey.environment.state(environmentId));
await invalidateCache([
createCacheKey.environment.surveys(environmentId),
createCacheKey.environment.actionClasses(environmentId)
]);
// ❌ Avoid complex tag systems
await invalidateByTags(["environment", "survey"]); // Don't do this
```
## Critical Performance Targets
### High-Frequency Endpoint Goals
- **Cache hit ratio**: >85%
- **Response time P95**: <200ms
- **Database load reduction**: >60%
- **HTTP cache duration**: 1hr browser, 30min Cloudflare
- **SDK refresh interval**: 1 hour with 30min server cache
### Performance Monitoring
- Use **existing elastic cache analytics** for metrics
- Log cache errors and warnings (not debug info)
- Track database query reduction
- Monitor response times for cached endpoints
- **Avoid performance logging** in high-frequency endpoints
## Error Handling Pattern
Always provide fallback to fresh data on cache errors:
```typescript
try {
const cached = await cache.get(key);
if (cached) return cached;
const fresh = await fetchFresh();
await cache.set(key, fresh, ttl); // ttl in milliseconds
return fresh;
} catch (error) {
// ✅ Always fallback to fresh data
logger.warn("Cache error, fetching fresh", { key, error });
return fetchFresh();
}
```
## Common Pitfalls to Avoid
1. **Never use Next.js `unstable_cache()`** - unreliable in production
2. **Don't use revalidation flags with Redis** - they get cached incorrectly
3. **Avoid Zod validation** for simple parameters in high-frequency endpoints
4. **Don't add performance logging** to high-frequency endpoints
5. **Coordinate TTLs** between client and server caches
6. **Don't over-engineer** with complex tag systems
7. **Avoid caching rapidly changing data** (real-time metrics)
8. **Always validate cache keys** to prevent collisions
9. **Don't add redundant caching layers** - analyze computational overhead first
10. **Avoid cache wrapper functions** - add caching directly to expensive operations
11. **Don't cache property access or simple transformations** - overhead is negligible
12. **Analyze the full call chain** before adding caching to avoid double-caching
13. **Remember TTL is in milliseconds** for cache-manager + Keyv stack (not seconds)
## Monitoring Strategy
- Use **existing elastic cache analytics** for metrics
- Log cache errors and warnings
- Track database query reduction
- Monitor response times for cached endpoints
- **Don't add custom metrics** that duplicate existing monitoring
## Important Notes
### TTL Units
- **cache-manager + Keyv**: TTL in **milliseconds**
- **Direct Redis commands**: TTL in **seconds** (EXPIRE, SETEX) or **milliseconds** (PEXPIRE, PSETEX)
- **HTTP cache headers**: TTL in **seconds** (max-age, s-maxage)
- **Client SDK**: TTL in **seconds** (expiresAt calculation)
-41
View File
@@ -1,41 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Database Performance & Prisma Best Practices
## Critical Performance Rules
### Response Count Queries
- **NEVER** use `skip`/`offset` with `prisma.response.count()` - this causes expensive subqueries with OFFSET
- Always use only `where` clauses for count operations: `prisma.response.count({ where: { ... } })`
- For pagination, separate count queries from data queries
- Reference: [apps/web/lib/response/service.ts](mdc:apps/web/lib/response/service.ts) line 654-686
### Prisma Query Optimization
- Use proper indexes defined in [packages/database/schema.prisma](mdc:packages/database/schema.prisma)
- Leverage existing indexes: `@@index([surveyId, createdAt])`, `@@index([createdAt])`
- Use cursor-based pagination for large datasets instead of offset-based
- Cache frequently accessed data using React Cache and custom cache tags
### Date Range Filtering
- When filtering by `createdAt`, always use indexed queries
- Combine with `surveyId` for optimal performance: `{ surveyId, createdAt: { gte: start, lt: end } }`
- Avoid complex WHERE clauses that can't utilize indexes
### Count vs Data Separation
- Always separate count queries from data fetching queries
- Use `Promise.all()` to run count and data queries in parallel
- Example pattern from [apps/web/modules/api/v2/management/responses/lib/response.ts](mdc:apps/web/modules/api/v2/management/responses/lib/response.ts):
```typescript
const [responses, totalCount] = await Promise.all([
prisma.response.findMany(query),
prisma.response.count({ where: whereClause }),
]);
```
### Monitoring & Debugging
- Monitor AWS RDS Performance Insights for problematic queries
- Look for queries with OFFSET in count operations - these indicate performance issues
- Use proper error handling with `DatabaseError` for Prisma exceptions
-105
View File
@@ -1,105 +0,0 @@
---
description: >
globs: schema.prisma
alwaysApply: false
---
# Formbricks Database Schema Reference
This rule provides a reference to the Formbricks database structure. For the most up-to-date and complete schema definitions, please refer to the schema.prisma file directly.
## Database Overview
Formbricks uses PostgreSQL with Prisma ORM. The schema is designed for multi-tenancy with strong data isolation between organizations.
### Core Hierarchy
```
Organization
└── Project
└── Environment (production/development)
├── Survey
├── Contact
├── ActionClass
└── Integration
```
## Schema Reference
For the complete and up-to-date database schema, please refer to:
- Main schema: `packages/database/schema.prisma`
- JSON type definitions: `packages/database/json-types.ts`
The schema.prisma file contains all model definitions, relationships, enums, and field types. The json-types.ts file contains TypeScript type definitions for JSON fields.
## Data Access Patterns
### Multi-tenancy
- All data is scoped by Organization
- Environment-level isolation for surveys and contacts
- Project-level grouping for related surveys
### Soft Deletion
Some models use soft deletion patterns:
- Check `isActive` fields where present
- Use proper filtering in queries
### Cascading Deletes
Configured cascade relationships:
- Organization deletion cascades to all child entities
- Survey deletion removes responses, displays, triggers
- Contact deletion removes attributes and responses
## Common Query Patterns
### Survey with Responses
```typescript
// Include response count and latest responses
const survey = await prisma.survey.findUnique({
where: { id: surveyId },
include: {
responses: {
take: 10,
orderBy: { createdAt: "desc" },
},
_count: {
select: { responses: true },
},
},
});
```
### Environment Scoping
```typescript
// Always scope by environment
const surveys = await prisma.survey.findMany({
where: {
environmentId: environmentId,
// Additional filters...
},
});
```
### Contact with Attributes
```typescript
const contact = await prisma.contact.findUnique({
where: { id: contactId },
include: {
attributes: {
include: {
attributeKey: true,
},
},
},
});
```
This schema supports Formbricks' core functionality: multi-tenant survey management, user targeting, response collection, and analysis, all while maintaining strict data isolation and security.
-28
View File
@@ -1,28 +0,0 @@
---
description: Guideline for writing end-user facing documentation in the apps/docs folder
globs:
alwaysApply: false
---
Follow these instructions and guidelines when asked to write documentation in the apps/docs folder
Follow this structure to write the title, describtion and pick a matching icon and insert it at the top of the MDX file:
---
title: "FEATURE NAME"
description: "1 concise sentence to describe WHEN the feature is being used and FOR WHAT BENEFIT."
icon: "link"
---
- Description: 1 concise sentence to describe WHEN the feature is being used and FOR WHAT BENEFIT.
- Make ample use of the Mintlify components you can find here https://mintlify.com/docs/llms.txt - e.g. if docs describe consecutive steps, always use Mintlify Step component.
- In all Headlines, only capitalize the current feature and nothing else, to Camel Case.
- The page should never start with H1 headline, because it's already part of the template.
- Tonality: Keep it concise and to the point. Avoid Jargon where possible.
- If a feature is part of the Enterprise Edition, use this note:
<Note>
FEATURE NAME is part of the [Enterprise Edition](/self-hosting/advanced/license)
</Note>
-332
View File
@@ -1,332 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Formbricks Architecture & Patterns
## Monorepo Structure
### Apps Directory
- `apps/web/` - Main Next.js web application
- `packages/` - Shared packages and utilities
### Key Directories in Web App
```
apps/web/
├── app/ # Next.js 13+ app directory
│ ├── (app)/ # Main application routes
│ ├── (auth)/ # Authentication routes
│ ├── api/ # API routes
├── components/ # Shared components
├── lib/ # Utility functions and services
└── modules/ # Feature-specific modules
```
## Routing Patterns
### App Router Structure
The application uses Next.js 13+ app router with route groups:
```
(app)/environments/[environmentId]/
├── surveys/[surveyId]/
│ ├── (analysis)/ # Analysis views
│ │ ├── responses/ # Response management
│ │ ├── summary/ # Survey summary
│ │ └── hooks/ # Analysis-specific hooks
│ ├── edit/ # Survey editing
│ └── settings/ # Survey settings
```
### Dynamic Routes
- `[environmentId]` - Environment-specific routes
- `[surveyId]` - Survey-specific routes
## Service Layer Pattern
### Service Organization
Services are organized by domain in `apps/web/lib/`:
```typescript
// Example: Response service
// apps/web/lib/response/service.ts
export const getResponseCountAction = async ({
surveyId,
filterCriteria,
}: {
surveyId: string;
filterCriteria: any;
}) => {
// Service implementation
};
```
### Action Pattern
Server actions follow a consistent pattern:
```typescript
// Action wrapper for service calls
export const getResponseCountAction = async (params) => {
try {
const result = await responseService.getCount(params);
return { data: result };
} catch (error) {
return { error: error.message };
}
};
```
## Context Patterns
### Provider Structure
Context providers follow a consistent pattern:
```typescript
// Provider component
export const ResponseFilterProvider = ({ children }: { children: React.ReactNode }) => {
const [selectedFilter, setSelectedFilter] = useState(defaultFilter);
const value = {
selectedFilter,
setSelectedFilter,
// ... other state and methods
};
return (
<ResponseFilterContext.Provider value={value}>
{children}
</ResponseFilterContext.Provider>
);
};
// Hook for consuming context
export const useResponseFilter = () => {
const context = useContext(ResponseFilterContext);
if (!context) {
throw new Error('useResponseFilter must be used within ResponseFilterProvider');
}
return context;
};
```
### Context Composition
Multiple contexts are often composed together:
```typescript
// Layout component with multiple providers
export default function AnalysisLayout({ children }: { children: React.ReactNode }) {
return (
<ResponseFilterProvider>
<ResponseCountProvider>
{children}
</ResponseCountProvider>
</ResponseFilterProvider>
);
}
```
## Component Patterns
### Page Components
Page components are located in the app directory and follow this pattern:
```typescript
// apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx
export default function ResponsesPage() {
return (
<div>
<ResponsesTable />
<ResponsesPagination />
</div>
);
}
```
### Component Organization
- **Pages** - Route components in app directory
- **Components** - Reusable UI components
- **Modules** - Feature-specific components and logic
### Shared Components
Common components are in `apps/web/components/`:
- UI components (buttons, inputs, modals)
- Layout components (headers, sidebars)
- Data display components (tables, charts)
## Hook Patterns
### Custom Hook Structure
Custom hooks follow consistent patterns:
```typescript
export const useResponseCount = ({
survey,
initialCount
}: {
survey: TSurvey;
initialCount?: number;
}) => {
const [responseCount, setResponseCount] = useState(initialCount ?? 0);
const [isLoading, setIsLoading] = useState(false);
// Hook logic...
return {
responseCount,
isLoading,
refetch,
};
};
```
### Hook Dependencies
- Use context hooks for shared state
- Implement proper cleanup with AbortController
- Optimize dependency arrays to prevent unnecessary re-renders
## Data Fetching Patterns
### Server Actions
The app uses Next.js server actions for data fetching:
```typescript
// Server action
export async function getResponsesAction(params: GetResponsesParams) {
const responses = await getResponses(params);
return { data: responses };
}
// Client usage
const { data } = await getResponsesAction(params);
```
### Error Handling
Consistent error handling across the application:
```typescript
try {
const result = await apiCall();
return { data: result };
} catch (error) {
console.error("Operation failed:", error);
return { error: error.message };
}
```
## Type Safety
### Type Organization
Types are organized in packages:
- `@formbricks/types` - Shared type definitions
- Local types in component/hook files
### Common Types
```typescript
import { TSurvey } from "@formbricks/types/surveys/types";
import { TResponse } from "@formbricks/types/responses";
import { TEnvironment } from "@formbricks/types/environment";
```
## State Management
### Local State
- Use `useState` for component-specific state
- Use `useReducer` for complex state logic
- Use refs for mutable values that don't trigger re-renders
### Global State
- React Context for feature-specific shared state
- URL state for filters and pagination
- Server state through server actions
## Performance Considerations
### Code Splitting
- Dynamic imports for heavy components
- Route-based code splitting with app router
- Lazy loading for non-critical features
### Caching Strategy
- Server-side caching for database queries
- Client-side caching with React Query (where applicable)
- Static generation for public pages
## Testing Strategy
### Test Organization
```
component/
├── Component.tsx
├── Component.test.tsx
└── hooks/
├── useHook.ts
└── useHook.test.tsx
```
### Test Patterns
- Unit tests for utilities and services
- Integration tests for components with context
- Hook tests with proper mocking
## Build & Deployment
### Build Process
- TypeScript compilation
- Next.js build optimization
- Asset optimization and bundling
### Environment Configuration
- Environment-specific configurations
- Feature flags for gradual rollouts
- Database connection management
## Security Patterns
### Authentication
- Session-based authentication
- Environment-based access control
- API route protection
### Data Validation
- Input validation on both client and server
- Type-safe API contracts
- Sanitization of user inputs
## Monitoring & Observability
### Error Tracking
- Client-side error boundaries
- Server-side error logging
- Performance monitoring
### Analytics
- User interaction tracking
- Performance metrics
- Database query monitoring
## Best Practices Summary
### Code Organization
- ✅ Follow the established directory structure
- ✅ Use consistent naming conventions
- ✅ Separate concerns (UI, logic, data)
- ✅ Keep components focused and small
### Performance
- ✅ Implement proper loading states
- ✅ Use AbortController for async operations
- ✅ Optimize database queries
- ✅ Implement proper caching strategies
### Type Safety
- ✅ Use TypeScript throughout
- ✅ Define proper interfaces for props
- ✅ Use type guards for runtime validation
- ✅ Leverage shared type packages
### Testing
- ✅ Write tests for critical functionality
- ✅ Mock external dependencies properly
- ✅ Test error scenarios and edge cases
- ✅ Maintain good test coverage
-232
View File
@@ -1,232 +0,0 @@
---
description: Security best practices and guidelines for writing GitHub Actions and workflows
globs: .github/workflows/*.yml,.github/workflows/*.yaml,.github/actions/*/action.yml,.github/actions/*/action.yaml
---
# GitHub Actions Security Best Practices
## Required Security Measures
### 1. Set Minimum GITHUB_TOKEN Permissions
Always explicitly set the minimum required permissions for GITHUB_TOKEN:
```yaml
permissions:
contents: read
# Only add additional permissions if absolutely necessary:
# pull-requests: write # for commenting on PRs
# issues: write # for creating/updating issues
# checks: write # for publishing check results
```
### 2. Add Harden-Runner as First Step
For **every job** on `ubuntu-latest`, add Harden-Runner as the first step:
```yaml
- name: Harden the runner
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit # or 'block' for stricter security
```
### 3. Pin Actions to Full Commit SHA
**Always** pin third-party actions to their full commit SHA, not tags:
```yaml
# ❌ BAD - uses mutable tag
- uses: actions/checkout@v4
# ✅ GOOD - pinned to immutable commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
```
### 4. Secure Variable Handling
Prevent command injection by properly quoting variables:
```yaml
# ❌ BAD - potential command injection
run: echo "Processing ${{ inputs.user_input }}"
# ✅ GOOD - properly quoted
env:
USER_INPUT: ${{ inputs.user_input }}
run: echo "Processing ${USER_INPUT}"
```
Use `${VARIABLE}` syntax in shell scripts instead of `$VARIABLE`.
### 5. Environment Variables for Secrets
Store sensitive data in environment variables, not inline:
```yaml
# ❌ BAD
run: curl -H "Authorization: Bearer ${{ secrets.TOKEN }}" api.example.com
# ✅ GOOD
env:
API_TOKEN: ${{ secrets.TOKEN }}
run: curl -H "Authorization: Bearer ${API_TOKEN}" api.example.com
```
## Workflow Structure Best Practices
### Required Workflow Elements
```yaml
name: "Descriptive Workflow Name"
on:
# Define specific triggers
push:
branches: [main]
pull_request:
branches: [main]
# Always set explicit permissions
permissions:
contents: read
jobs:
job-name:
name: "Descriptive Job Name"
runs-on: ubuntu-latest
timeout-minutes: 30 # tune per job; standardize repo-wide
# Set job-level permissions if different from workflow level
permissions:
contents: read
steps:
# Always start with Harden-Runner on ubuntu-latest
- name: Harden the runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
# Pin all actions to commit SHA
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
```
### Input Validation for Actions
For composite actions, always validate inputs:
```yaml
inputs:
user_input:
description: "User provided input"
required: true
runs:
using: "composite"
steps:
- name: Validate input
shell: bash
run: |
# Harden shell and validate input format/content before use
set -euo pipefail
USER_INPUT="${{ inputs.user_input }}"
if [[ ! "${USER_INPUT}" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "❌ Invalid input format"
exit 1
fi
```
## Docker Security in Actions
### Pin Docker Images to Digests
```yaml
# ❌ BAD - mutable tag
container: node:18
# ✅ GOOD - pinned to digest
container: node:18@sha256:a1ba21bf0c92931d02a8416f0a54daad66cb36a85d6a37b82dfe1604c4c09cad
```
## Common Patterns
### Secure File Operations
```yaml
- name: Process files securely
shell: bash
env:
FILE_PATH: ${{ inputs.file_path }}
run: |
set -euo pipefail # Fail on errors, undefined vars, pipe failures
# Use absolute paths and validate
SAFE_PATH=$(realpath "${FILE_PATH}")
if [[ "$SAFE_PATH" != "${GITHUB_WORKSPACE}"/* ]]; then
echo "❌ Path outside workspace"
exit 1
fi
```
### Artifact Handling
```yaml
- name: Upload artifacts securely
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: build-artifacts
path: |
dist/
!dist/**/*.log # Exclude sensitive files
retention-days: 30
```
### GHCR authentication for pulls/scans
```yaml
# Minimal permissions required for GHCR pulls/scans
permissions:
contents: read
packages: read
steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
## Security Checklist
- [ ] Minimum GITHUB_TOKEN permissions set
- [ ] Harden-Runner added to all ubuntu-latest jobs
- [ ] All third-party actions pinned to commit SHA
- [ ] Input validation implemented for custom actions
- [ ] Variables properly quoted in shell scripts
- [ ] Secrets stored in environment variables
- [ ] Docker images pinned to digests (if used)
- [ ] Error handling with `set -euo pipefail`
- [ ] File paths validated and sanitized
- [ ] No sensitive data in logs or outputs
- [ ] GHCR login performed before pulls/scans (packages: read)
- [ ] Job timeouts configured (`timeout-minutes`)
## Recommended Additional Workflows
Consider adding these security-focused workflows to your repository:
1. **CodeQL Analysis** - Static Application Security Testing (SAST)
2. **Dependency Review** - Scan for vulnerable dependencies in PRs
3. **Dependabot Configuration** - Automated dependency updates
## Resources
- [GitHub Security Hardening Guide](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
- [Step Security Harden-Runner](https://github.com/step-security/harden-runner)
- [Secure-Repo Best Practices](https://github.com/step-security/secure-repo)
-457
View File
@@ -1,457 +0,0 @@
---
title: i18n Management with Lingo.dev
description: Guidelines for managing internationalization (i18n) with Lingo.dev, including translation workflow, key validation, and best practices
---
# i18n Management with Lingo.dev
This rule defines the workflow and best practices for managing internationalization (i18n) in the Formbricks project using Lingo.dev.
## Overview
Formbricks uses [Lingo.dev](https://lingo.dev) for managing translations across multiple languages. The translation workflow includes:
1. **Translation Keys**: Defined in code using the `t()` function from `react-i18next`
2. **Translation Files**: JSON files stored in `apps/web/locales/` for each supported language
3. **Validation**: Automated scanning to detect missing and unused translation keys
4. **CI/CD**: Pre-commit hooks and GitHub Actions to enforce translation quality
## Translation Workflow
### 1. Using Translations in Code
When adding translatable text in the web app, use the `t()` function or `<Trans>` component:
**Using the `t()` function:**
```tsx
import { useTranslate } from "@/lib/i18n/translate";
const MyComponent = () => {
const { t } = useTranslate();
return (
<div>
<h1>{t("common.welcome")}</h1>
<p>{t("pages.dashboard.description")}</p>
</div>
);
};
```
**Using the `<Trans>` component (for text with HTML elements):**
```tsx
import { Trans } from "react-i18next";
const MyComponent = () => {
return (
<div>
<p>
<Trans
i18nKey="auth.terms_agreement"
components={{
link: <a href="/terms" />,
b: <b />
}}
/>
</p>
</div>
);
};
```
**Key Naming Conventions:**
- Use dot notation for nested keys: `section.subsection.key`
- Use descriptive names: `auth.login.success_message` not `auth.msg1`
- Group related keys together: `auth.*`, `errors.*`, `common.*`
- Use lowercase with underscores: `user_profile_settings` not `UserProfileSettings`
### 2. Translation File Structure
Translation files are located in `apps/web/locales/` and use the following naming convention:
- `en-US.json` (English - United States, default)
- `de-DE.json` (German)
- `fr-FR.json` (French)
- `pt-BR.json` (Portuguese - Brazil)
- etc.
**File Structure:**
```json
{
"common": {
"welcome": "Welcome",
"save": "Save",
"cancel": "Cancel"
},
"auth": {
"login": {
"title": "Login",
"email_placeholder": "Enter your email",
"password_placeholder": "Enter your password"
}
}
}
```
### 3. Adding New Translation Keys
When adding new translation keys:
1. **Add the key in your code** using `t("your.new.key")`
2. **Add translation for that key in en-US.json file**
3. **Run the translation workflow:**
```bash
pnpm i18n
```
This will:
- Generate translations for all languages using Lingo.dev
- Validate that all keys are present and used
4. **Review and commit** the generated translation files
### 4. Available Scripts
```bash
# Generate translations using Lingo.dev
pnpm generate-translations
# Scan and validate translation keys
pnpm scan-translations
# Full workflow: generate + validate
pnpm i18n
# Validate only (without generation)
pnpm i18n:validate
```
## Translation Key Validation
### Automated Validation
The project includes automated validation that runs:
- **Pre-commit hook**: Validates translations before allowing commits (when `LINGODOTDEV_API_KEY` is set)
- **GitHub Actions**: Validates translations on every PR and push to main
### Validation Rules
The validation script (`scan-translations.ts`) checks for:
1. **Missing Keys**: Translation keys used in code but not present in translation files
2. **Unused Keys**: Translation keys present in translation files but not used in code
3. **Incomplete Translations**: Keys that exist in the default language (`en-US`) but are missing in target languages
**What gets scanned:**
- All `.ts` and `.tsx` files in `apps/web/`
- Both `t()` function calls and `<Trans i18nKey="">` components
- All locale files (`de-DE.json`, `fr-FR.json`, `ja-JP.json`, etc.)
**What gets excluded:**
- Test files (`*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx`)
- Build directories (`node_modules`, `dist`, `build`, `.next`, `coverage`)
- Locale files themselves (from code scanning)
**Note:** Test files are excluded because they often use mock or example translation keys for testing purposes that don't need to exist in production translation files.
### Fixing Validation Errors
#### Missing Keys
If you encounter missing key errors:
```
❌ MISSING KEYS (2):
These keys are used in code but not found in translation files:
• auth.signup.email_required
• settings.profile.update_success
```
**Resolution:**
1. Ensure that translations for those keys are present in en-US.json .
2. Run `pnpm generate-translations` to have Lingo.dev generate the missing translations
3. OR manually add the keys to `apps/web/locales/en-US.json`:
```json
{
"auth": {
"signup": {
"email_required": "Email is required"
}
},
"settings": {
"profile": {
"update_success": "Profile updated successfully"
}
}
}
```
3. Run `pnpm scan-translations` to verify
4. Commit the changes
#### Unused Keys
If you encounter unused key errors:
```
⚠️ UNUSED KEYS (1):
These keys exist in translation files but are not used in code:
• old.deprecated.key
```
**Resolution:**
1. If the key is truly unused, remove it from all translation files
2. If the key should be used, add it to your code using `t("old.deprecated.key")`
3. Run `pnpm scan-translations` to verify
4. Commit the changes
#### Incomplete Translations
If you encounter incomplete translation errors:
```
⚠️ INCOMPLETE TRANSLATIONS:
Some keys from en-US are missing in target languages:
📝 de-DE (5 missing keys):
• auth.new_feature.title
• auth.new_feature.description
• settings.advanced.option
... and 2 more
```
**Resolution:**
1. **Recommended:** Run `pnpm generate-translations` to have Lingo.dev automatically translate the missing keys
2. **Manual:** Add the missing keys to the target language files:
```bash
# Copy the structure from en-US.json and translate the values
# For example, in de-DE.json:
{
"auth": {
"new_feature": {
"title": "Neues Feature",
"description": "Beschreibung des neuen Features"
}
}
}
```
3. Run `pnpm scan-translations` to verify all translations are complete
4. Commit the changes
## Pre-commit Hook Behavior
The pre-commit hook will:
1. Run `lint-staged` for code formatting
2. If `LINGODOTDEV_API_KEY` is set:
- Generate translations using Lingo.dev
- Validate translation keys
- Auto-add updated locale files to the commit
- **Block the commit** if validation fails
3. If `LINGODOTDEV_API_KEY` is not set:
- Skip translation validation (for community contributors)
- Show a warning message
## Environment Variables
### LINGODOTDEV_API_KEY
This is the API key for Lingo.dev integration.
**For Core Team:**
- Add to your local `.env` file
- Required for running translation generation
**For Community Contributors:**
- Not required for local development
- Translation validation will be skipped
- The CI will still validate translations
## Best Practices
### 1. Keep Keys Organized
Group related keys together:
```json
{
"auth": {
"login": { ... },
"signup": { ... },
"forgot_password": { ... }
},
"dashboard": {
"header": { ... },
"sidebar": { ... }
}
}
```
### 2. Avoid Hardcoded Strings
**❌ Bad:**
```tsx
<button>Click here</button>
```
**✅ Good:**
```tsx
<button>{t("common.click_here")}</button>
```
### 3. Use Interpolation for Dynamic Content
**❌ Bad:**
```tsx
{t("welcome")} {userName}!
```
**✅ Good:**
```tsx
{t("auth.welcome_message", { userName })}
```
With translation:
```json
{
"auth": {
"welcome_message": "Welcome, {userName}!"
}
}
```
### 4. Avoid Dynamic Key Construction
**❌ Bad:**
```tsx
const key = `errors.${errorCode}`;
t(key);
```
**✅ Good:**
```tsx
switch (errorCode) {
case "401":
return t("errors.unauthorized");
case "404":
return t("errors.not_found");
default:
return t("errors.unknown");
}
```
### 5. Test Translation Keys
When adding new features:
1. Add translation keys
2. Test in multiple languages using the language switcher
3. Ensure text doesn't overflow in longer translations (German, French)
4. Run `pnpm scan-translations` before committing
## Troubleshooting
### Issue: Pre-commit hook fails with validation errors
**Solution:**
```bash
# Run the full i18n workflow
pnpm i18n
# Fix any missing or unused keys
# Then commit again
git add .
git commit -m "your message"
```
### Issue: Translation validation passes locally but fails in CI
**Solution:**
- Ensure all translation files are committed
- Check that `scan-translations.ts` hasn't been modified
- Verify that locale files are properly formatted JSON
### Issue: Cannot commit because of missing translations
**Solution:**
```bash
# If you have LINGODOTDEV_API_KEY:
pnpm generate-translations
# If you don't have the API key (community contributor):
# Manually add the missing keys to en-US.json
# Then run validation:
pnpm scan-translations
```
### Issue: Getting "unused keys" for keys that are used
**Solution:**
- The script scans `.ts` and `.tsx` files only
- If keys are used in other file types, they may be flagged
- Verify the key is actually used with `grep -r "your.key" apps/web/`
- If it's a false positive, consider updating the scanning patterns in `scan-translations.ts`
## AI Assistant Guidelines
When assisting with i18n-related tasks, always:
1. **Use the `t()` function** for all user-facing text
2. **Follow key naming conventions** (lowercase, dots for nesting)
3. **Run validation** after making changes: `pnpm scan-translations`
4. **Fix missing keys** by adding them to `en-US.json`
5. **Remove unused keys** from all translation files
6. **Test the pre-commit hook** if making changes to translation workflow
7. **Update this rule file** if translation workflow changes
### Fixing Missing Translation Keys
When the AI encounters missing translation key errors:
1. Identify the missing keys from the error output
2. Determine the appropriate section and naming for each key
3. Add the keys to `apps/web/locales/en-US.json` with meaningful English text
4. Ensure proper JSON structure and nesting
5. Run `pnpm scan-translations` to verify
6. Inform the user that other language files will be updated via Lingo.dev
**Example:**
```typescript
// Error: Missing key "settings.api.rate_limit_exceeded"
// Add to en-US.json:
{
"settings": {
"api": {
"rate_limit_exceeded": "API rate limit exceeded. Please try again later."
}
}
}
```
### Removing Unused Translation Keys
When the AI encounters unused translation key errors:
1. Verify the keys are truly unused by searching the codebase
2. Remove the keys from `apps/web/locales/en-US.json`
3. Note that removal from other language files can be handled via Lingo.dev
4. Run `pnpm scan-translations` to verify
## Migration Notes
This project previously used Tolgee for translations. As of this migration:
- **Old scripts**: `tolgee-pull` is deprecated (kept for reference)
- **New scripts**: Use `pnpm i18n` or `pnpm generate-translations`
- **Old workflows**: `tolgee.yml` and `tolgee-missing-key-check.yml` removed
- **New workflow**: `translation-check.yml` handles all validation
---
**Last Updated:** October 14, 2025
**Related Files:**
- `scan-translations.ts` - Translation validation script
- `.husky/pre-commit` - Pre-commit hook with i18n validation
- `.github/workflows/translation-check.yml` - CI workflow for translation validation
- `apps/web/locales/*.json` - Translation files
-52
View File
@@ -1,52 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# React Context & Provider Patterns
## Context Provider Best Practices
### Provider Implementation
- Use TypeScript interfaces for provider props with optional `initialCount` for testing
- Implement proper cleanup in `useEffect` to avoid React hooks warnings
- Reference: [apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/ResponseCountProvider.tsx](mdc:apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/ResponseCountProvider.tsx)
### Cleanup Pattern for Refs
```typescript
useEffect(() => {
const currentPendingRequests = pendingRequests.current;
const currentAbortController = abortController.current;
return () => {
if (currentAbortController) {
currentAbortController.abort();
}
currentPendingRequests.clear();
};
}, []);
```
### Testing Context Providers
- Always wrap components using context in the provider during tests
- Use `initialCount` prop for predictable test scenarios
- Mock context dependencies like `useParams`, `useResponseFilter`
- Example from [apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx](mdc:apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx):
```typescript
render(
<ResponseCountProvider survey={dummySurvey} initialCount={5}>
<ComponentUnderTest />
</ResponseCountProvider>
);
```
### Required Mocks for Context Testing
- Mock `next/navigation` with `useParams` returning environment and survey IDs
- Mock response filter context and actions
- Mock API actions that the provider depends on
### Context Hook Usage
- Create custom hooks like `useResponseCountContext()` for consuming context
- Provide meaningful error messages when context is used outside provider
- Use context for shared state that multiple components need to access
-179
View File
@@ -1,179 +0,0 @@
---
description: Apply these quality standards before finalizing code changes to ensure DRY principles, React best practices, TypeScript conventions, and maintainable code.
globs:
alwaysApply: false
---
# Review & Refine
Before finalizing any code changes, review your implementation against these quality standards:
## Core Principles
### DRY (Don't Repeat Yourself)
- Extract duplicated logic into reusable functions or hooks
- If the same code appears in multiple places, consolidate it
- Create helper functions at appropriate scope (component-level, module-level, or utility files)
- Avoid copy-pasting code blocks
### Code Reduction
- Remove unnecessary code, comments, and abstractions
- Prefer built-in solutions over custom implementations
- Consolidate similar logic
- Remove dead code and unused imports
- Question if every line of code is truly needed
## React Best Practices
### Component Design
- Keep components focused on a single responsibility
- Extract complex logic into custom hooks
- Prefer composition over prop drilling
- Use children props and render props when appropriate
- Keep component files under 300 lines when possible
### Hooks Usage
- Follow Rules of Hooks (only call at top level, only in React functions)
- Extract complex `useEffect` logic into custom hooks
- Use `useMemo` and `useCallback` only when you have a measured performance issue
- Declare dependencies arrays correctly - don't ignore exhaustive-deps warnings
- Keep `useEffect` focused on a single concern
### State Management
- Colocate state as close as possible to where it's used
- Lift state only when necessary
- Use `useReducer` for complex state logic with multiple sub-values
- Avoid derived state - compute values during render instead
- Don't store values in state that can be computed from props
### Event Handlers
- Name event handlers with `handle` prefix (e.g., `handleClick`, `handleSubmit`)
- Extract complex event handler logic into separate functions
- Avoid inline arrow functions in JSX when they contain complex logic
## TypeScript Best Practices
### Type Safety
- Prefer type inference over explicit types when possible
- Use `const` assertions for literal types
- Avoid `any` - use `unknown` if type is truly unknown
- Use discriminated unions for complex conditional logic
- Leverage type guards and narrowing
### Interface & Type Usage
- Use existing types from `@formbricks/types` - don't recreate them
- Prefer `interface` for object shapes that might be extended
- Prefer `type` for unions, intersections, and mapped types
- Define types close to where they're used unless they're shared
- Export types from index files for shared types
### Type Assertions
- Avoid type assertions (`as`) when possible
- Use type guards instead of assertions
- Only assert when you have more information than TypeScript
## Code Organization
### Separation of Concerns
- Separate business logic from UI rendering
- Extract API calls into separate functions or modules
- Keep data transformation separate from component logic
- Use custom hooks for stateful logic that doesn't render UI
### Function Clarity
- Functions should do one thing well
- Name functions clearly and descriptively
- Keep functions small (aim for under 20 lines)
- Extract complex conditionals into named boolean variables or functions
- Avoid deep nesting (max 3 levels)
### File Structure
- Group related functions together
- Order declarations logically (types → hooks → helpers → component)
- Keep imports organized (external → internal → relative)
- Consider splitting large files by concern
## Additional Quality Checks
### Performance
- Don't optimize prematurely - measure first
- Avoid creating new objects/arrays/functions in render unnecessarily
- Use keys properly in lists (stable, unique identifiers)
- Lazy load heavy components when appropriate
### Accessibility
- Use semantic HTML elements
- Include ARIA labels where needed
- Ensure keyboard navigation works
- Check color contrast and focus states
### Error Handling
- Handle error states in components
- Provide user feedback for failed operations
- Use error boundaries for component errors
- Log errors appropriately (avoid swallowing errors silently)
### Naming Conventions
- Use descriptive names (avoid abbreviations unless very common)
- Boolean variables/props should sound like yes/no questions (`isLoading`, `hasError`, `canEdit`)
- Arrays should be plural (`users`, `choices`, `items`)
- Event handlers: `handleX` in components, `onX` for props
- Constants in UPPER_SNAKE_CASE only for true constants
### Code Readability
- Prefer early returns to reduce nesting
- Use destructuring to make code clearer
- Break complex expressions into named variables
- Add comments only when code can't be made self-explanatory
- Use whitespace to group related code
### Testing Considerations
- Write code that's easy to test (pure functions, clear inputs/outputs)
- Avoid hard-to-mock dependencies when possible
- Keep side effects at the edges of your code
## Review Checklist
Before submitting your changes, ask yourself:
1. **DRY**: Is there any duplicated logic I can extract?
2. **Clarity**: Would another developer understand this code easily?
3. **Simplicity**: Is this the simplest solution that works?
4. **Types**: Am I using TypeScript effectively?
5. **React**: Am I following React idioms and best practices?
6. **Performance**: Are there obvious performance issues?
7. **Separation**: Are concerns properly separated?
8. **Testing**: Is this code testable?
9. **Maintenance**: Will this be easy to change in 6 months?
10. **Deletion**: Can I remove any code and still accomplish the goal?
## When to Apply This Rule
Apply this rule:
- After implementing a feature but before marking it complete
- When you notice your code feels "messy" or complex
- Before requesting code review
- When you see yourself copy-pasting code
- After receiving feedback about code quality
Don't let perfect be the enemy of good, but always strive for:
**Simple, readable, maintainable code that does one thing well.**
@@ -1,216 +0,0 @@
---
description: Migrate deprecated UI components to a unified component
globs:
alwaysApply: false
---
# Component Migration Automation Rule
## Overview
This rule automates the migration of deprecated components to new component systems in React/TypeScript codebases.
## Trigger
When the user requests component migration (e.g., "migrate [DeprecatedComponent] to [NewComponent]" or "component migration").
## Process
### Step 1: Discovery and Planning
1. **Identify migration parameters:**
- Ask user for deprecated component name (e.g., "Modal")
- Ask user for new component name(s) (e.g., "Dialog")
- Ask for any components to exclude (e.g., "ModalWithTabs")
- Ask for specific import paths if needed
2. **Scan codebase** for deprecated components:
- Search for `import.*[DeprecatedComponent]` patterns
- Exclude specified components that should not be migrated
- List all found components with file paths
- Present numbered list to user for confirmation
### Step 2: Component-by-Component Migration
For each component, follow this exact sequence:
#### 2.1 Component Migration
- **Import changes:**
- Ask user to provide the new import structure
- Example transformation pattern:
```typescript
// FROM:
import { [DeprecatedComponent] } from "@/components/ui/[DeprecatedComponent]"
// TO:
import {
[NewComponent],
[NewComponentPart1],
[NewComponentPart2],
// ... other parts
} from "@/components/ui/[NewComponent]"
```
- **Props transformation:**
- Ask user for prop mapping rules (e.g., `open` → `open`, `setOpen` → `onOpenChange`)
- Ask for props to remove (e.g., `noPadding`, `closeOnOutsideClick`, `size`)
- Apply transformations based on user specifications
- **Structure transformation:**
- Ask user for the new component structure pattern
- Apply the transformation maintaining all functionality
- Preserve all existing logic, state management, and event handlers
#### 2.2 Wait for User Approval
- Present the migration changes
- Wait for explicit user approval before proceeding
- If rejected, ask for specific feedback and iterate
#### 2.3 Re-read and Apply Additional Changes
- Re-read the component file to capture any user modifications
- Apply any additional improvements the user made
- Ensure all changes are incorporated
#### 2.4 Test File Updates
- **Find corresponding test file** (same name with `.test.tsx` or `.test.ts`)
- **Update test mocks:**
- Ask user for new component mock structure
- Replace old component mocks with new ones
- Example pattern:
```typescript
// Add to test setup:
jest.mock("@/components/ui/[NewComponent]", () => ({
[NewComponent]: ({ children, [props] }: any) => ([mock implementation]),
[NewComponentPart1]: ({ children }: any) => <div data-testid="[new-component-part1]">{children}</div>,
[NewComponentPart2]: ({ children }: any) => <div data-testid="[new-component-part2]">{children}</div>,
// ... other parts
}));
```
- **Update test expectations:**
- Change test IDs from old component to new component
- Update any component-specific assertions
- Ensure all new component parts used in the component are mocked
#### 2.5 Run Tests and Optimize
- Execute `Node package manager test -- ComponentName.test.tsx`
- Fix any failing tests
- Optimize code quality (imports, formatting, etc.)
- Re-run tests until all pass
- **Maximum 3 iterations** - if still failing, ask user for guidance
#### 2.6 Wait for Final Approval
- Present test results and any optimizations made
- Wait for user approval of the complete migration
- If rejected, iterate based on feedback
#### 2.7 Git Commit
- Run: `git add .`
- Run: `git commit -m "migrate [ComponentName] from [DeprecatedComponent] to [NewComponent]"`
- Confirm commit was successful
### Step 3: Final Report Generation
After all components are migrated, generate a comprehensive GitHub PR report:
#### PR Title
```
feat: migrate [DeprecatedComponent] components to [NewComponent] system
```
#### PR Description Template
```markdown
## 🔄 [DeprecatedComponent] to [NewComponent] Migration
### Overview
Migrated [X] [DeprecatedComponent] components to the new [NewComponent] component system to modernize the UI architecture and improve consistency.
### Components Migrated
[List each component with file path]
### Technical Changes
- **Imports:** Replaced `[DeprecatedComponent]` with `[NewComponent], [NewComponentParts...]`
- **Props:** [List prop transformations]
- **Structure:** Implemented proper [NewComponent] component hierarchy
- **Styling:** [Describe styling changes]
- **Tests:** Updated all test mocks and expectations
### Migration Pattern
```typescript
// Before
<[DeprecatedComponent] [oldProps]>
[oldStructure]
</[DeprecatedComponent]>
// After
<[NewComponent] [newProps]>
[newStructure]
</[NewComponent]>
```
### Testing
- ✅ All existing tests updated and passing
- ✅ Component functionality preserved
- ✅ UI/UX behavior maintained
### How to Test This PR
1. **Functional Testing:**
- Navigate to each migrated component's usage
- Verify [component] opens and closes correctly
- Test all interactive elements within [components]
- Confirm styling and layout are preserved
2. **Automated Testing:**
```bash
Node package manager test
```
3. **Visual Testing:**
- Check that all [components] maintain proper styling
- Verify responsive behavior
- Test keyboard navigation and accessibility
### Breaking Changes
[List any breaking changes or state "None - this is a drop-in replacement maintaining all existing functionality."]
### Notes
- [Any excluded components] were preserved as they already use [NewComponent] internally
- All form validation and complex state management preserved
- Enhanced code quality with better imports and formatting
```
## Special Considerations
### Excluded Components
- **DO NOT MIGRATE** components specified by user as exclusions
- They may already use the new component internally or have other reasons
- Inform user these are skipped and why
### Complex Components
- Preserve all existing functionality (forms, validation, state management)
- Maintain prop interfaces
- Keep all event handlers and callbacks
- Preserve accessibility features
### Test Coverage
- Ensure all new component parts are mocked when used
- Mock all new component parts that appear in the component
- Update test IDs from old component to new component
- Maintain all existing test scenarios
### Error Handling
- If tests fail after 3 iterations, stop and ask user for guidance
- If component is too complex, ask user for specific guidance
- If unsure about functionality preservation, ask for clarification
### Migration Patterns
- Always ask user for specific migration patterns before starting
- Confirm import structures, prop mappings, and component hierarchies
- Adapt to different component architectures (simple replacements, complex restructuring, etc.)
## Success Criteria
- All deprecated components successfully migrated to new components
- All tests passing
- No functionality lost
- Code quality maintained or improved
- User approval on each component
- Successful git commits for each migration
- Comprehensive PR report generated
## Usage Examples
- "migrate Modal to Dialog"
- "migrate Button to NewButton"
- "migrate Card to ModernCard"
- "component migration" (will prompt for details)
@@ -1,177 +0,0 @@
---
description: Create a story in Storybook for a given component
globs:
alwaysApply: false
---
# Formbricks Storybook Stories
## When generating Storybook stories for Formbricks components:
### 1. **File Structure**
- Create `stories.tsx` (not `.stories.tsx`) in component directory
- Use exact import: `import { Meta, StoryObj } from "@storybook/react-vite";`
- Import component from `"./index"`
### 2. **Story Structure Template**
```tsx
import { Meta, StoryObj } from "@storybook/react-vite";
import { ComponentName } from "./index";
// For complex components with configurable options
// consider this as an example the options need to reflect the props types
interface StoryOptions {
showIcon: boolean;
numberOfElements: number;
customLabels: string[];
}
type StoryProps = React.ComponentProps<typeof ComponentName> & StoryOptions;
const meta: Meta<StoryProps> = {
title: "UI/ComponentName",
component: ComponentName,
tags: ["autodocs"],
parameters: {
layout: "centered",
controls: { sort: "alpha", exclude: [] },
docs: {
description: {
component: "The **ComponentName** component provides [description].",
},
},
},
argTypes: {
// Organize in exactly these categories: Behavior, Appearance, Content
},
};
export default meta;
type Story = StoryObj<typeof ComponentName> & { args: StoryOptions };
```
### 3. **ArgTypes Organization**
Organize ALL argTypes into exactly three categories:
- **Behavior**: disabled, variant, onChange, etc.
- **Appearance**: size, color, layout, styling, etc.
- **Content**: text, icons, numberOfElements, etc.
Format:
```tsx
argTypes: {
propName: {
control: "select" | "boolean" | "text" | "number",
options: ["option1", "option2"], // for select
description: "Clear description",
table: {
category: "Behavior" | "Appearance" | "Content",
type: { summary: "string" },
defaultValue: { summary: "default" },
},
order: 1,
},
}
```
### 4. **Required Stories**
Every component must include:
- `Default`: Most common use case
- `Disabled`: If component supports disabled state
- `WithIcon`: If component supports icons
- Variant stories for each variant (Primary, Secondary, Error, etc.)
- Edge case stories (ManyElements, LongText, CustomStyling)
### 5. **Story Format**
```tsx
export const Default: Story = {
args: {
// Props with realistic values
},
};
export const EdgeCase: Story = {
args: { /* ... */ },
parameters: {
docs: {
description: {
story: "Use this when [specific scenario].",
},
},
},
};
```
### 6. **Dynamic Content Pattern**
For components with dynamic content, create render function:
```tsx
const renderComponent = (args: StoryProps) => {
const { numberOfElements, showIcon, customLabels } = args;
// Generate dynamic content
const elements = Array.from({ length: numberOfElements }, (_, i) => ({
id: `element-${i}`,
label: customLabels[i] || `Element ${i + 1}`,
icon: showIcon ? <IconComponent /> : undefined,
}));
return <ComponentName {...args} elements={elements} />;
};
export const Dynamic: Story = {
render: renderComponent,
args: {
numberOfElements: 3,
showIcon: true,
customLabels: ["First", "Second", "Third"],
},
};
```
### 7. **State Management**
For interactive components:
```tsx
import { useState } from "react";
const ComponentWithState = (args: any) => {
const [value, setValue] = useState(args.defaultValue);
return (
<ComponentName
{...args}
value={value}
onChange={(newValue) => {
setValue(newValue);
args.onChange?.(newValue);
}}
/>
);
};
export const Interactive: Story = {
render: ComponentWithState,
args: { defaultValue: "initial" },
};
```
### 8. **Quality Requirements**
- Include component description in parameters.docs
- Add story documentation for non-obvious use cases
- Test edge cases (overflow, empty states, many elements)
- Ensure no TypeScript errors
- Use realistic prop values
- Include at least 3-5 story variants
- Example values need to be in the context of survey application
### 9. **Naming Conventions**
- **Story titles**: "UI/ComponentName"
- **Story exports**: PascalCase (Default, WithIcon, ManyElements)
- **Categories**: "Behavior", "Appearance", "Content" (exact spelling)
- **Props**: camelCase matching component props
### 10. **Special Cases**
- **Generic components**: Remove `component` from meta if type conflicts
- **Form components**: Include Invalid, WithValue stories
- **Navigation**: Include ManyItems stories
- **Modals, Dropdowns and Popups **: Include trigger and content structure
## Generate stories that are comprehensive, well-documented, and reflect all component states and edge cases.
+7 -2
View File
@@ -184,8 +184,13 @@ ENTERPRISE_LICENSE_KEY=
# Ignore Rate Limiting across the Formbricks app
# RATE_LIMITING_DISABLED=1
# OpenTelemetry URL for tracing
# OPENTELEMETRY_LISTENER_URL=http://localhost:4318/v1/traces
# OpenTelemetry OTLP endpoint (base URL, exporters append /v1/traces and /v1/metrics)
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# OTEL_SERVICE_NAME=formbricks
# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=development
# OTEL_TRACES_SAMPLER=parentbased_traceidratio
# OTEL_TRACES_SAMPLER_ARG=1
# Unsplash API Key
UNSPLASH_ACCESS_KEY=
+15 -21
View File
@@ -111,27 +111,21 @@ jobs:
const additions = ${{ steps.check-size.outputs.total_additions }};
const deletions = ${{ steps.check-size.outputs.total_deletions }};
const body = `## 🚨 PR Size Warning
This PR has approximately **${totalChanges} lines** of changes (${additions} additions, ${deletions} deletions across ${countedFiles} files).
Large PRs (>800 lines) are significantly harder to review and increase the chance of merge conflicts. Consider splitting this into smaller, self-contained PRs.
### 💡 Suggestions:
- **Split by feature or module** - Break down into logical, independent pieces
- **Create a sequence of PRs** - Each building on the previous one
- **Branch off PR branches** - Don't wait for reviews to continue dependent work
### 📊 What was counted:
- ✅ Source files, stylesheets, configuration files
- ❌ Excluded ${excludedFiles} files (tests, locales, locks, generated files)
### 📚 Guidelines:
- **Ideal:** 300-500 lines per PR
- **Warning:** 500-800 lines
- **Critical:** 800+ lines ⚠️
If this large PR is unavoidable (e.g., migration, dependency update, major refactor), please explain in the PR description why it couldn't be split.`;
const body = '## 🚨 PR Size Warning\n\n' +
'This PR has approximately **' + totalChanges + ' lines** of changes (' + additions + ' additions, ' + deletions + ' deletions across ' + countedFiles + ' files).\n\n' +
'Large PRs (>800 lines) are significantly harder to review and increase the chance of merge conflicts. Consider splitting this into smaller, self-contained PRs.\n\n' +
'### 💡 Suggestions:\n' +
'- **Split by feature or module** - Break down into logical, independent pieces\n' +
'- **Create a sequence of PRs** - Each building on the previous one\n' +
'- **Branch off PR branches** - Don\'t wait for reviews to continue dependent work\n\n' +
'### 📊 What was counted:\n' +
'- ✅ Source files, stylesheets, configuration files\n' +
'- ❌ Excluded ' + excludedFiles + ' files (tests, locales, locks, generated files)\n\n' +
'### 📚 Guidelines:\n' +
'- **Ideal:** 300-500 lines per PR\n' +
'- **Warning:** 500-800 lines\n' +
'- **Critical:** 800+ lines ⚠️\n\n' +
'If this large PR is unavoidable (e.g., migration, dependency update, major refactor), please explain in the PR description why it couldn\'t be split.';
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
+9 -10
View File
@@ -32,21 +32,20 @@ jobs:
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node.js
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
node-version: 18
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Setup pnpm
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0
- name: Setup Node.js 22.x
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
version: 9.15.9
node-version: 22.x
- name: Install pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Install dependencies
run: pnpm install --frozen-lockfile
run: pnpm install --config.platform=linux --config.architecture=x64
- name: Validate translation keys
run: |
+4
View File
@@ -13,6 +13,7 @@
**/.next/
**/out/
**/build
**/next-env.d.ts
# node
**/dist/
@@ -62,3 +63,6 @@ branch.json
packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.xcworkspace/xcuserdata
.cursorrules
i18n.cache
stats.html
# next-agents-md
.next-docs/
-3
View File
@@ -1,6 +1,3 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Load environment variables from .env files
if [ -f .env ]; then
set -a
+69
View File
File diff suppressed because one or more lines are too long
+16 -16
View File
@@ -11,24 +11,24 @@
"clean": "rimraf .turbo node_modules dist storybook-static"
},
"dependencies": {
"@formbricks/survey-ui": "workspace:*",
"eslint-plugin-react-refresh": "0.4.24"
"@formbricks/survey-ui": "workspace:*"
},
"devDependencies": {
"@chromatic-com/storybook": "^4.1.3",
"@storybook/addon-a11y": "10.0.8",
"@storybook/addon-links": "10.0.8",
"@storybook/addon-onboarding": "10.0.8",
"@storybook/react-vite": "10.0.8",
"@typescript-eslint/eslint-plugin": "8.48.0",
"@tailwindcss/vite": "4.1.17",
"@typescript-eslint/parser": "8.48.0",
"@vitejs/plugin-react": "5.1.1",
"esbuild": "0.27.0",
"eslint-plugin-storybook": "10.0.8",
"@chromatic-com/storybook": "^5.0.0",
"@storybook/addon-a11y": "10.1.11",
"@storybook/addon-links": "10.1.11",
"@storybook/addon-onboarding": "10.1.11",
"@storybook/react-vite": "10.1.11",
"@typescript-eslint/eslint-plugin": "8.53.0",
"@tailwindcss/vite": "4.1.18",
"@typescript-eslint/parser": "8.53.0",
"@vitejs/plugin-react": "5.1.2",
"esbuild": "0.25.12",
"eslint-plugin-react-refresh": "0.4.26",
"eslint-plugin-storybook": "10.1.11",
"prop-types": "15.8.1",
"storybook": "10.0.8",
"vite": "7.2.4",
"@storybook/addon-docs": "10.0.8"
"storybook": "10.1.11",
"vite": "7.3.1",
"@storybook/addon-docs": "10.1.11"
}
}
+7
View File
@@ -0,0 +1,7 @@
node_modules/
.next/
public/
playwright/
dist/
coverage/
vendor/
-16
View File
@@ -1,20 +1,4 @@
module.exports = {
extends: ["@formbricks/eslint-config/legacy-next.js"],
ignorePatterns: ["**/package.json", "**/tsconfig.json"],
overrides: [
{
files: ["locales/*.json"],
plugins: ["i18n-json"],
rules: {
"i18n-json/identical-keys": [
"error",
{
filePath: require("path").join(__dirname, "locales", "en-US.json"),
checkExtraKeys: false,
checkMissingKeys: true,
},
],
},
},
],
};
+16 -18
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine3.22 AS base
FROM node:24-alpine3.23 AS base
#
## step 1: Prune monorepo
@@ -20,7 +20,7 @@ FROM base AS installer
# Enable corepack and prepare pnpm
RUN npm install --ignore-scripts -g corepack@latest
RUN corepack enable
RUN corepack prepare pnpm@9.15.9 --activate
RUN corepack prepare pnpm@10.28.2 --activate
# Install necessary build tools and compilers
RUN apk update && apk add --no-cache cmake g++ gcc jq make openssl-dev python3
@@ -69,20 +69,14 @@ RUN --mount=type=secret,id=database_url \
--mount=type=secret,id=sentry_auth_token \
/tmp/read-secrets.sh pnpm build --filter=@formbricks/web...
# Extract Prisma version
RUN jq -r '.devDependencies.prisma' packages/database/package.json > /prisma_version.txt
#
## step 3: setup production runner
#
FROM base AS runner
RUN npm install --ignore-scripts -g corepack@latest && \
corepack enable
RUN apk add --no-cache curl \
&& apk add --no-cache supercronic \
# && addgroup --system --gid 1001 nodejs \
# Update npm to latest, then create user
# Note: npm's bundled tar has a known vulnerability but npm is only used during build, not at runtime
RUN npm install --ignore-scripts -g npm@latest \
&& addgroup -S nextjs \
&& adduser -S -u 1001 -G nextjs nextjs
@@ -104,31 +98,37 @@ RUN chown -R nextjs:nextjs ./apps/web/.next/static && chmod -R 755 ./apps/web/.n
COPY --from=installer /app/apps/web/public ./apps/web/public
RUN chown -R nextjs:nextjs ./apps/web/public && chmod -R 755 ./apps/web/public
# Create packages/database directory structure with proper ownership for runtime migrations
RUN mkdir -p ./packages/database/migrations && chown -R nextjs:nextjs ./packages/database
COPY --from=installer /app/packages/database/schema.prisma ./packages/database/schema.prisma
RUN chown nextjs:nextjs ./packages/database/schema.prisma && chmod 644 ./packages/database/schema.prisma
COPY --from=installer /app/packages/database/dist ./packages/database/dist
RUN chown -R nextjs:nextjs ./packages/database/dist && chmod -R 755 ./packages/database/dist
# Copy prisma client packages
COPY --from=installer /app/node_modules/@prisma/client ./node_modules/@prisma/client
RUN chown -R nextjs:nextjs ./node_modules/@prisma/client && chmod -R 755 ./node_modules/@prisma/client
COPY --from=installer /app/node_modules/.prisma ./node_modules/.prisma
RUN chown -R nextjs:nextjs ./node_modules/.prisma && chmod -R 755 ./node_modules/.prisma
COPY --from=installer /prisma_version.txt .
RUN chown nextjs:nextjs ./prisma_version.txt && chmod 644 ./prisma_version.txt
COPY --from=installer /app/node_modules/@paralleldrive/cuid2 ./node_modules/@paralleldrive/cuid2
RUN chmod -R 755 ./node_modules/@paralleldrive/cuid2
COPY --from=installer /app/node_modules/uuid ./node_modules/uuid
RUN chmod -R 755 ./node_modules/uuid
COPY --from=installer /app/node_modules/@noble/hashes ./node_modules/@noble/hashes
RUN chmod -R 755 ./node_modules/@noble/hashes
COPY --from=installer /app/node_modules/zod ./node_modules/zod
RUN chmod -R 755 ./node_modules/zod
RUN npm install -g prisma@6
# Install prisma CLI globally for database migrations and fix permissions for nextjs user
RUN npm install --ignore-scripts -g prisma@6 \
&& chown -R nextjs:nextjs /usr/local/lib/node_modules/prisma
# Create a startup script to handle the conditional logic
COPY --from=installer /app/apps/web/scripts/docker/next-start.sh /home/nextjs/start.sh
@@ -138,10 +138,8 @@ EXPOSE 3000
ENV HOSTNAME="0.0.0.0"
USER nextjs
# Prepare pnpm as the nextjs user to ensure it's available at runtime
# Prepare volumes for uploads and SAML connections
RUN corepack prepare pnpm@9.15.9 --activate && \
mkdir -p /home/nextjs/apps/web/uploads/ && \
RUN mkdir -p /home/nextjs/apps/web/uploads/ && \
mkdir -p /home/nextjs/apps/web/saml-connection
VOLUME /home/nextjs/apps/web/uploads/
@@ -25,7 +25,7 @@ const mockProject: TProject = {
},
placement: "bottomRight",
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
languages: [],
logo: null,
@@ -3,7 +3,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
@@ -17,6 +17,7 @@ import {
import { createProjectAction } from "@/app/(app)/environments/[environmentId]/actions";
import { previewSurvey } from "@/app/lib/templates";
import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@/lib/localStorage";
import { buildStylingFromBrandColor } from "@/lib/styling/constants";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { TOrganizationTeam } from "@/modules/ee/teams/project-teams/types/team";
import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal";
@@ -64,10 +65,17 @@ export const ProjectSettings = ({
const { t } = useTranslation();
const addProject = async (data: TProjectUpdateInput) => {
try {
// Build the full styling from the chosen brand color so all derived
// colours (question, button, input, option, progress, etc.) are persisted.
// Without this, only brandColor is saved and the look-and-feel page falls
// back to STYLE_DEFAULTS computed from the default brand (#64748b).
const fullStyling = buildStylingFromBrandColor(data.styling?.brandColor?.light);
const createProjectResponse = await createProjectAction({
organizationId,
data: {
...data,
styling: fullStyling,
config: { channel, industry },
teamIds: data.teamIds,
},
@@ -112,6 +120,7 @@ export const ProjectSettings = ({
const projectName = form.watch("name");
const logoUrl = form.watch("logo.url");
const brandColor = form.watch("styling.brandColor.light") ?? defaultBrandColor;
const previewStyling = useMemo(() => buildStylingFromBrandColor(brandColor), [brandColor]);
const { isSubmitting } = form.formState;
const organizationTeamsOptions = organizationTeams.map((team) => ({
@@ -226,7 +235,7 @@ export const ProjectSettings = ({
alt="Logo"
width={256}
height={56}
className="absolute top-2 left-2 -mb-6 h-20 w-auto max-w-64 rounded-lg border object-contain p-1"
className="absolute left-2 top-2 -mb-6 h-20 w-auto max-w-64 rounded-lg border object-contain p-1"
/>
)}
<p className="text-sm text-slate-400">{t("common.preview")}</p>
@@ -235,7 +244,7 @@ export const ProjectSettings = ({
appUrl={publicDomain}
isPreviewMode={true}
survey={previewSurvey(projectName || "my Product", t)}
styling={{ brandColor: { light: brandColor } }}
styling={previewStyling}
isBrandingEnabled={false}
languageCode="default"
onFileUpload={async (file) => file.name}
@@ -2,7 +2,7 @@
import { z } from "zod";
import { ZId } from "@formbricks/types/common";
import { OperationNotAllowedError } from "@formbricks/types/errors";
import { AuthorizationError, OperationNotAllowedError } from "@formbricks/types/errors";
import { ZProjectUpdateInput } from "@formbricks/types/project";
import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
import { getOrganization } from "@/lib/organization/service";
@@ -138,7 +138,7 @@ export const getProjectsForSwitcherAction = authenticatedActionClient
// Need membership for getProjectsByUserId (1 DB query)
const membership = await getMembershipByUserIdOrganizationId(ctx.user.id, parsedInput.organizationId);
if (!membership) {
throw new Error("Membership not found");
throw new AuthorizationError("Membership not found");
}
return await getProjectsByUserId(ctx.user.id, membership);
@@ -36,7 +36,7 @@ export const EnvironmentLayout = async ({ layoutData, children }: EnvironmentLay
// Calculate derived values (no queries)
const { isMember, isOwner, isManager } = getAccessFlags(membership.role);
const { features, lastChecked, isPendingDowngrade, active } = license;
const { features, lastChecked, isPendingDowngrade, active, status } = license;
const isMultiOrgEnabled = features?.isMultiOrgEnabled ?? false;
const organizationProjectsLimit = await getOrganizationProjectsLimit(organization.billing.limits);
const isOwnerOrManager = isOwner || isManager;
@@ -63,6 +63,7 @@ export const EnvironmentLayout = async ({ layoutData, children }: EnvironmentLay
active={active}
environmentId={environment.id}
locale={user.locale}
status={status}
/>
<div className="flex h-full">
@@ -209,7 +209,7 @@ export const OrganizationBreadcrumb = ({
)}
{!isLoadingOrganizations && !loadError && (
<>
<DropdownMenuGroup>
<DropdownMenuGroup className="max-h-[300px] overflow-y-auto">
{organizations.map((org) => (
<DropdownMenuCheckboxItem
key={org.id}
@@ -234,7 +234,7 @@ export const ProjectBreadcrumb = ({
)}
{!isLoadingProjects && !loadError && (
<>
<DropdownMenuGroup>
<DropdownMenuGroup className="max-h-[300px] overflow-y-auto">
{projects.map((proj) => (
<DropdownMenuCheckboxItem
key={proj.id}
@@ -58,7 +58,7 @@ async function handleEmailUpdate({
payload.email = inputEmail;
await updateBrevoCustomer({ id: ctx.user.id, email: inputEmail });
} else {
await sendVerificationNewEmail(ctx.user.id, inputEmail);
await sendVerificationNewEmail(ctx.user.id, inputEmail, ctx.user.locale);
}
return payload;
}
@@ -0,0 +1,138 @@
"use client";
import { TFunction } from "i18next";
import { RotateCcwIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { recheckLicenseAction } from "@/modules/ee/license-check/actions";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { Badge } from "@/modules/ui/components/badge";
import { Button } from "@/modules/ui/components/button";
import { SettingsCard } from "../../../components/SettingsCard";
type LicenseStatus = "active" | "expired" | "unreachable" | "invalid_license";
interface EnterpriseLicenseStatusProps {
status: LicenseStatus;
gracePeriodEnd?: Date;
environmentId: string;
}
const getBadgeConfig = (
status: LicenseStatus,
t: TFunction
): { type: "success" | "error" | "warning" | "gray"; label: string } => {
switch (status) {
case "active":
return { type: "success", label: t("environments.settings.enterprise.license_status_active") };
case "expired":
return { type: "error", label: t("environments.settings.enterprise.license_status_expired") };
case "unreachable":
return { type: "warning", label: t("environments.settings.enterprise.license_status_unreachable") };
case "invalid_license":
return { type: "error", label: t("environments.settings.enterprise.license_status_invalid") };
default:
return { type: "gray", label: t("environments.settings.enterprise.license_status") };
}
};
export const EnterpriseLicenseStatus = ({ status, gracePeriodEnd, environmentId }: EnterpriseLicenseStatusProps) => {
const { t } = useTranslation();
const router = useRouter();
const [isRechecking, setIsRechecking] = useState(false);
const handleRecheck = async () => {
setIsRechecking(true);
try {
const result = await recheckLicenseAction({ environmentId });
if (result?.serverError) {
toast.error(result.serverError || t("environments.settings.enterprise.recheck_license_failed"));
return;
}
if (result?.data) {
if (result.data.status === "unreachable") {
toast.error(t("environments.settings.enterprise.recheck_license_unreachable"));
} else if (result.data.status === "invalid_license") {
toast.error(t("environments.settings.enterprise.recheck_license_invalid"));
} else {
toast.success(t("environments.settings.enterprise.recheck_license_success"));
}
router.refresh();
} else {
toast.error(t("environments.settings.enterprise.recheck_license_failed"));
}
} catch (error) {
toast.error(
error instanceof Error ? error.message : t("environments.settings.enterprise.recheck_license_failed")
);
} finally {
setIsRechecking(false);
}
};
const badgeConfig = getBadgeConfig(status, t);
return (
<SettingsCard
title={t("environments.settings.enterprise.license_status")}
description={t("environments.settings.enterprise.license_status_description")}>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-3">
<div className="flex flex-col gap-1.5">
<Badge type={badgeConfig.type} text={badgeConfig.label} size="normal" className="w-fit" />
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRecheck}
disabled={isRechecking}
className="shrink-0">
{isRechecking ? (
<>
<RotateCcwIcon className="mr-2 h-4 w-4 animate-spin" />
{t("environments.settings.enterprise.rechecking")}
</>
) : (
<>
<RotateCcwIcon className="mr-2 h-4 w-4" />
{t("environments.settings.enterprise.recheck_license")}
</>
)}
</Button>
</div>
{status === "unreachable" && gracePeriodEnd && (
<Alert variant="warning" size="small">
<AlertDescription className="overflow-visible whitespace-normal">
{t("environments.settings.enterprise.license_unreachable_grace_period", {
gracePeriodEnd: new Date(gracePeriodEnd).toLocaleDateString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
}),
})}
</AlertDescription>
</Alert>
)}
{status === "invalid_license" && (
<Alert variant="error" size="small">
<AlertDescription className="overflow-visible whitespace-normal">
{t("environments.settings.enterprise.license_invalid_description")}
</AlertDescription>
</Alert>
)}
<p className="border-t border-slate-100 pt-4 text-sm text-slate-500">
{t("environments.settings.enterprise.questions_please_reach_out_to")}{" "}
<a
className="font-medium text-slate-700 underline hover:text-slate-900"
href="mailto:hola@formbricks.com">
hola@formbricks.com
</a>
</p>
</div>
</SettingsCard>
);
};
@@ -2,9 +2,10 @@ import { CheckIcon } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { EnterpriseLicenseStatus } from "@/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
import { GRACE_PERIOD_MS, getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -25,7 +26,8 @@ const Page = async (props) => {
return notFound();
}
const { active: isEnterpriseEdition } = await getEnterpriseLicense();
const licenseState = await getEnterpriseLicense();
const hasLicense = licenseState.status !== "no-license";
const paidFeatures = [
{
@@ -90,35 +92,22 @@ const Page = async (props) => {
activeId="enterprise"
/>
</PageHeader>
{isEnterpriseEdition ? (
<div>
<div className="mt-8 max-w-4xl rounded-lg border border-slate-300 bg-slate-100 shadow-sm">
<div className="space-y-4 p-8">
<div className="flex items-center gap-x-2">
<div className="rounded-full border border-green-300 bg-green-100 p-0.5 dark:bg-green-800">
<CheckIcon className="h-5 w-5 p-0.5 text-green-500 dark:text-green-400" />
</div>
<p className="text-slate-800">
{t(
"environments.settings.enterprise.your_enterprise_license_is_active_all_features_unlocked"
)}
</p>
</div>
<p className="text-sm text-slate-500">
{t("environments.settings.enterprise.questions_please_reach_out_to")}{" "}
<a className="font-semibold underline" href="mailto:hola@formbricks.com">
hola@formbricks.com
</a>
</p>
</div>
</div>
</div>
{hasLicense ? (
<EnterpriseLicenseStatus
status={licenseState.status as "active" | "expired" | "unreachable" | "invalid_license"}
gracePeriodEnd={
licenseState.status === "unreachable"
? new Date(licenseState.lastChecked.getTime() + GRACE_PERIOD_MS)
: undefined
}
environmentId={params.environmentId}
/>
) : (
<div>
<div className="relative isolate mt-8 overflow-hidden rounded-lg bg-slate-900 px-3 pt-8 shadow-2xl sm:px-8 md:pt-12 lg:flex lg:gap-x-10 lg:px-12 lg:pt-0">
<svg
viewBox="0 0 1024 1024"
className="absolute top-1/2 left-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0"
className="absolute left-1/2 top-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0"
aria-hidden="true">
<circle
cx={512}
@@ -153,8 +142,8 @@ const Page = async (props) => {
{t("environments.settings.enterprise.enterprise_features")}
</h2>
<ul className="my-4 space-y-4">
{paidFeatures.map((feature, index) => (
<li key={index} className="flex items-center">
{paidFeatures.map((feature) => (
<li key={feature.title} className="flex items-center">
<div className="rounded-full border border-green-300 bg-green-100 p-0.5 dark:bg-green-800">
<CheckIcon className="h-5 w-5 p-0.5 text-green-500 dark:text-green-400" />
</div>
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { TOrganization } from "@formbricks/types/organizations";
import { deleteOrganizationAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions";
import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
@@ -32,7 +33,12 @@ export const DeleteOrganization = ({
setIsDeleting(true);
try {
await deleteOrganizationAction({ organizationId: organization.id });
const result = await deleteOrganizationAction({ organizationId: organization.id });
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
setIsDeleting(false);
return;
}
toast.success(t("environments.settings.general.organization_deleted_successfully"));
if (typeof localStorage !== "undefined") {
localStorage.removeItem(FORMBRICKS_ENVIRONMENT_ID_LS);
@@ -0,0 +1,26 @@
"use client";
import { ShieldCheckIcon } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
export const SecurityListTip = () => {
const { t } = useTranslation();
return (
<div className="max-w-4xl">
<div className="flex items-center space-x-3 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm">
<ShieldCheckIcon className="h-5 w-5 flex-shrink-0 text-blue-400" />
<p className="text-sm">
{t("environments.settings.general.security_list_tip")}{" "}
<Link
href="https://formbricks.com/security#stay-informed-with-formbricks-security-updates"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-blue-700">
{t("environments.settings.general.security_list_tip_link")}
</Link>
</p>
</div>
</div>
);
};
@@ -12,6 +12,7 @@ import { PageHeader } from "@/modules/ui/components/page-header";
import { SettingsCard } from "../../components/SettingsCard";
import { DeleteOrganization } from "./components/DeleteOrganization";
import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm";
import { SecurityListTip } from "./components/SecurityListTip";
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
const params = await props.params;
@@ -48,6 +49,7 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
</Alert>
</div>
)}
{!IS_FORMBRICKS_CLOUD && <SecurityListTip />}
<SettingsCard
title={t("environments.settings.general.organization_name")}
description={t("environments.settings.general.organization_name_description")}>
@@ -316,6 +316,14 @@ export const generateResponseTableColumns = (
},
};
const responseIdColumn: ColumnDef<TResponseTableData> = {
accessorKey: "responseId",
header: () => <div className="gap-x-1.5">{t("common.response_id")}</div>,
cell: ({ row }) => {
return <IdBadge id={row.original.responseId} />;
},
};
const quotasColumn: ColumnDef<TResponseTableData> = {
accessorKey: "quota",
header: t("common.quota"),
@@ -414,6 +422,7 @@ export const generateResponseTableColumns = (
const baseColumns = [
personColumn,
singleUseIdColumn,
responseIdColumn,
dateColumn,
...(showQuotasColumn ? [quotasColumn] : []),
statusColumn,
@@ -58,6 +58,7 @@ export const sendEmbedSurveyPreviewEmailAction = authenticatedActionClient
ctx.user.email,
emailHtml,
survey.environmentId,
ctx.user.locale,
organizationLogoUrl || ""
);
});
@@ -8,7 +8,7 @@ import { TSurvey, TSurveyElementSummaryFileUpload } from "@formbricks/types/surv
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
import { getContactIdentifier } from "@/lib/utils/contact";
import { getOriginalFileNameFromUrl } from "@/modules/storage/utils";
import { getOriginalFileNameFromUrl } from "@/modules/storage/url-helpers";
import { PersonAvatar } from "@/modules/ui/components/avatars";
import { Button } from "@/modules/ui/components/button";
import { EmptyState } from "@/modules/ui/components/empty-state";
@@ -21,6 +21,7 @@ import {
ListOrderedIcon,
MessageSquareTextIcon,
MousePointerClickIcon,
NetworkIcon,
PieChartIcon,
Rows3Icon,
SmartphoneIcon,
@@ -99,6 +100,7 @@ const elementIcons = {
action: MousePointerClickIcon,
country: FlagIcon,
url: LinkIcon,
ipAddress: NetworkIcon,
// others
Language: LanguagesIcon,
@@ -190,7 +192,7 @@ export const ElementsComboBox = ({ options, selected, onChangeValue }: ElementCo
value={inputValue}
onValueChange={setInputValue}
placeholder={open ? `${t("common.search")}...` : t("common.select_filter")}
className="max-w-full grow border-none p-0 pl-2 text-sm shadow-none outline-none ring-offset-transparent focus:border-none focus:shadow-none focus:outline-none focus:ring-offset-0"
className="max-w-full grow border-none p-0 pl-2 text-sm shadow-none ring-offset-transparent outline-none focus:border-none focus:shadow-none focus:ring-offset-0 focus:outline-none"
/>
)}
<Button
@@ -21,6 +21,7 @@ import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[envir
import { BaseSelectDropdown } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/BaseSelectDropdown";
import { fetchTables } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/lib/airtable";
import AirtableLogo from "@/images/airtableLogo.svg";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { recallToHeadline } from "@/lib/utils/recall";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
@@ -268,7 +269,14 @@ export const AddIntegrationModal = ({
airtableIntegrationData.config?.data.push(integrationData);
}
await createOrUpdateIntegrationAction({ environmentId, integrationData: airtableIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: airtableIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
if (isEditMode) {
toast.success(t("environments.integrations.integration_updated_successfully"));
} else {
@@ -304,7 +312,11 @@ export const AddIntegrationModal = ({
const integrationData = structuredClone(airtableIntegrationData);
integrationData.config.data.splice(index, 1);
await createOrUpdateIntegrationAction({ environmentId, integrationData });
const result = await createOrUpdateIntegrationAction({ environmentId, integrationData });
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
handleClose();
router.refresh();
@@ -165,7 +165,14 @@ export const AddIntegrationModal = ({
// create action
googleSheetIntegrationData.config.data.push(integrationData);
}
await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: googleSheetIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
if (selectedIntegration) {
toast.success(t("environments.integrations.integration_updated_successfully"));
} else {
@@ -205,7 +212,14 @@ export const AddIntegrationModal = ({
googleSheetIntegrationData.config.data.splice(selectedIntegration!.index, 1);
try {
setIsDeleting(true);
await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: googleSheetIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
toast.success(t("environments.integrations.integration_removed_successfully"));
setOpen(false);
} catch (error) {
@@ -266,7 +280,7 @@ export const AddIntegrationModal = ({
<div className="space-y-4">
<div>
<Label htmlFor="Surveys">{t("common.questions")}</Label>
<div className="mt-1 max-h-[15vh] overflow-x-hidden overflow-y-auto rounded-lg border border-slate-200">
<div className="mt-1 max-h-[15vh] overflow-y-auto overflow-x-hidden rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
{surveyElements.map((question) => (
<div key={question.id} className="my-1 flex items-center space-x-2">
@@ -22,6 +22,7 @@ import {
createEmptyMapping,
} from "@/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/MappingRow";
import NotionLogo from "@/images/notion.png";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { recallToHeadline } from "@/lib/utils/recall";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
import { Button } from "@/modules/ui/components/button";
@@ -217,7 +218,14 @@ export const AddIntegrationModal = ({
notionIntegrationData.config.data.push(integrationData);
}
await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: notionIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
if (selectedIntegration) {
toast.success(t("environments.integrations.integration_updated_successfully"));
} else {
@@ -236,7 +244,14 @@ export const AddIntegrationModal = ({
notionIntegrationData.config.data.splice(selectedIntegration!.index, 1);
try {
setIsDeleting(true);
await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: notionIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
toast.success(t("environments.integrations.integration_removed_successfully"));
setOpen(false);
} catch (error) {
@@ -17,6 +17,7 @@ import { TSurvey } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/actions";
import SlackLogo from "@/images/slacklogo.png";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { recallToHeadline } from "@/lib/utils/recall";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
@@ -144,7 +145,14 @@ export const AddChannelMappingModal = ({
// create action
slackIntegrationData.config.data.push(integrationData);
}
await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: slackIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
if (selectedIntegration) {
toast.success(t("environments.integrations.integration_updated_successfully"));
} else {
@@ -181,7 +189,14 @@ export const AddChannelMappingModal = ({
slackIntegrationData.config.data.splice(selectedIntegration!.index, 1);
try {
setIsDeleting(true);
await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData });
const result = await createOrUpdateIntegrationAction({
environmentId,
integrationData: slackIntegrationData,
});
if (result?.serverError) {
toast.error(getFormattedErrorMessage(result));
return;
}
toast.success(t("environments.integrations.integration_removed_successfully"));
setOpen(false);
} catch (error) {
@@ -82,6 +82,7 @@ const mockPipelineInput = {
},
country: "USA",
action: "Action Name",
ipAddress: "203.0.113.7",
} as TResponseMeta,
personAttributes: {},
singleUseId: null,
@@ -346,7 +347,7 @@ describe("handleIntegrations", () => {
expect(airtableWriteData).toHaveBeenCalledTimes(1);
// Adjust expectations for metadata and recalled question
const expectedMetadataString =
"Source: web\nURL: http://example.com\nBrowser: Chrome\nOS: Mac OS\nDevice: Desktop\nCountry: USA\nAction: Action Name";
"Source: web\nURL: http://example.com\nBrowser: Chrome\nOS: Mac OS\nDevice: Desktop\nCountry: USA\nAction: Action Name\nIP Address: 203.0.113.7";
expect(airtableWriteData).toHaveBeenCalledWith(
mockAirtableIntegration.config.key,
mockAirtableIntegration.config.data[0],
@@ -31,6 +31,7 @@ const convertMetaObjectToString = (metadata: TResponseMeta): string => {
if (metadata.userAgent?.device) result.push(`Device: ${metadata.userAgent.device}`);
if (metadata.country) result.push(`Country: ${metadata.country}`);
if (metadata.action) result.push(`Action: ${metadata.action}`);
if (metadata.ipAddress) result.push(`IP Address: ${metadata.ipAddress}`);
// Join all the elements in the result array with a newline for formatting
return result.join("\n");
+51 -20
View File
@@ -1,5 +1,6 @@
import { PipelineTriggers, Webhook } from "@prisma/client";
import { headers } from "next/headers";
import { v7 as uuidv7 } from "uuid";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { ResourceNotFoundError } from "@formbricks/types/errors";
@@ -8,6 +9,7 @@ import { ZPipelineInput } from "@/app/api/(internal)/pipeline/types/pipelines";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { CRON_SECRET } from "@/lib/constants";
import { generateStandardWebhookSignature } from "@/lib/crypto";
import { getIntegrations } from "@/lib/integration/service";
import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { getResponseCountBySurveyId } from "@/lib/response/service";
@@ -90,28 +92,50 @@ export const POST = async (request: Request) => {
]);
};
const webhookPromises = webhooks.map((webhook) =>
fetchWithTimeout(webhook.url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
webhookId: webhook.id,
event,
data: {
...response,
survey: {
title: survey.name,
type: survey.type,
status: survey.status,
createdAt: survey.createdAt,
updatedAt: survey.updatedAt,
},
const webhookPromises = webhooks.map((webhook) => {
const body = JSON.stringify({
webhookId: webhook.id,
event,
data: {
...response,
survey: {
title: survey.name,
type: survey.type,
status: survey.status,
createdAt: survey.createdAt,
updatedAt: survey.updatedAt,
},
}),
},
});
// Generate Standard Webhooks headers
const webhookMessageId = uuidv7();
const webhookTimestamp = Math.floor(Date.now() / 1000);
const requestHeaders: Record<string, string> = {
"content-type": "application/json",
"webhook-id": webhookMessageId,
"webhook-timestamp": webhookTimestamp.toString(),
};
// Add signature if webhook has a secret configured
if (webhook.secret) {
requestHeaders["webhook-signature"] = generateStandardWebhookSignature(
webhookMessageId,
webhookTimestamp,
body,
webhook.secret
);
}
return fetchWithTimeout(webhook.url, {
method: "POST",
headers: requestHeaders,
body,
}).catch((error) => {
logger.error({ error, url: request.url }, `Webhook call to ${webhook.url} failed`);
})
);
});
});
if (event === "responseFinished") {
// Fetch integrations and responseCount in parallel
@@ -191,7 +215,14 @@ export const POST = async (request: Request) => {
}
const emailPromises = usersWithNotifications.map((user) =>
sendResponseFinishedEmail(user.email, environmentId, survey, response, responseCount).catch((error) => {
sendResponseFinishedEmail(
user.email,
user.locale,
environmentId,
survey,
response,
responseCount
).catch((error) => {
logger.error(
{ error, url: request.url, userEmail: user.email },
`Failed to send email to ${user.email}`
@@ -64,7 +64,7 @@ const mockProject = {
linkSurveyBranding: true,
placement: "bottomRight",
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
languages: [],
} as unknown as TProject;
@@ -0,0 +1,314 @@
import { Prisma } from "@prisma/client";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { getEnvironmentStateData } from "./data";
// Mock dependencies
vi.mock("@formbricks/database", () => ({
prisma: {
environment: {
findUnique: vi.fn(),
},
},
}));
vi.mock("@formbricks/logger", () => ({
logger: {
error: vi.fn(),
},
}));
vi.mock("@/modules/survey/lib/utils", () => ({
transformPrismaSurvey: vi.fn((survey) => survey),
}));
const environmentId = "cjld2cjxh0000qzrmn831i7rn";
const mockEnvironmentData = {
id: environmentId,
type: "production",
appSetupCompleted: true,
project: {
id: "project-123",
recontactDays: 30,
clickOutsideClose: true,
overlay: "none",
placement: "bottomRight",
inAppSurveyBranding: true,
styling: { allowStyleOverwrite: false },
organization: {
id: "org-123",
billing: {
plan: "free",
limits: { monthly: { responses: 100 } },
},
},
},
actionClasses: [
{
id: "action-1",
type: "code",
name: "Test Action",
key: "test-action",
noCodeConfig: null,
},
],
surveys: [
{
id: "survey-1",
name: "Test Survey",
type: "app",
status: "inProgress",
welcomeCard: { enabled: false },
questions: [],
blocks: null,
variables: [],
showLanguageSwitch: false,
languages: [],
endings: [],
autoClose: null,
styling: null,
recaptcha: { enabled: false },
segment: null,
recontactDays: null,
displayLimit: null,
displayOption: "displayOnce",
hiddenFields: { enabled: false },
isBackButtonHidden: false,
triggers: [],
displayPercentage: null,
delay: 0,
projectOverwrites: null,
},
],
};
describe("getEnvironmentStateData", () => {
beforeEach(() => {
vi.resetAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
test("should return environment state data when environment exists", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue(mockEnvironmentData as never);
const result = await getEnvironmentStateData(environmentId);
expect(result).toEqual({
environment: {
id: environmentId,
type: "production",
appSetupCompleted: true,
project: {
id: "project-123",
recontactDays: 30,
clickOutsideClose: true,
overlay: "none",
placement: "bottomRight",
inAppSurveyBranding: true,
styling: { allowStyleOverwrite: false },
},
},
organization: {
id: "org-123",
billing: {
plan: "free",
limits: { monthly: { responses: 100 } },
},
},
surveys: mockEnvironmentData.surveys,
actionClasses: mockEnvironmentData.actionClasses,
});
expect(prisma.environment.findUnique).toHaveBeenCalledWith({
where: { id: environmentId },
select: expect.objectContaining({
id: true,
type: true,
appSetupCompleted: true,
project: expect.any(Object),
actionClasses: expect.any(Object),
surveys: expect.any(Object),
}),
});
});
test("should throw ResourceNotFoundError when environment is not found", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue(null);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow("environment");
});
test("should throw ResourceNotFoundError when project is not found", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
project: null,
} as never);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError);
});
test("should throw ResourceNotFoundError when organization is not found", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
project: {
...mockEnvironmentData.project,
organization: null,
},
} as never);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(ResourceNotFoundError);
});
test("should throw DatabaseError on Prisma database errors", async () => {
const prismaError = new Prisma.PrismaClientKnownRequestError("Connection failed", {
code: "P2024",
clientVersion: "5.0.0",
});
vi.mocked(prisma.environment.findUnique).mockRejectedValue(prismaError);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow(DatabaseError);
expect(logger.error).toHaveBeenCalled();
});
test("should rethrow unexpected errors", async () => {
const unexpectedError = new Error("Unexpected error");
vi.mocked(prisma.environment.findUnique).mockRejectedValue(unexpectedError);
await expect(getEnvironmentStateData(environmentId)).rejects.toThrow("Unexpected error");
expect(logger.error).toHaveBeenCalled();
});
test("should handle empty surveys array", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
surveys: [],
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.surveys).toEqual([]);
});
test("should handle empty actionClasses array", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
actionClasses: [],
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.actionClasses).toEqual([]);
});
test("should transform surveys using transformPrismaSurvey", async () => {
const multipleSurveys = [
...mockEnvironmentData.surveys,
{
...mockEnvironmentData.surveys[0],
id: "survey-2",
name: "Second Survey",
},
];
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
surveys: multipleSurveys,
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.surveys).toHaveLength(2);
});
test("should correctly map project properties to environment.project", async () => {
const customProject = {
...mockEnvironmentData.project,
recontactDays: 14,
clickOutsideClose: false,
overlay: "dark",
placement: "center",
inAppSurveyBranding: false,
styling: { allowStyleOverwrite: true, brandColor: "#ff0000" },
};
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
project: customProject,
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.environment.project).toEqual({
id: "project-123",
recontactDays: 14,
clickOutsideClose: false,
overlay: "dark",
placement: "center",
inAppSurveyBranding: false,
styling: { allowStyleOverwrite: true, brandColor: "#ff0000" },
});
});
test("should validate environmentId input", async () => {
// Invalid CUID should throw validation error
await expect(getEnvironmentStateData("invalid-id")).rejects.toThrow();
});
test("should handle different environment types", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
type: "development",
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.environment.type).toBe("development");
});
test("should handle appSetupCompleted false", async () => {
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
appSetupCompleted: false,
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.environment.appSetupCompleted).toBe(false);
});
test("should correctly extract organization billing data", async () => {
const customBilling = {
plan: "enterprise",
stripeCustomerId: "cus_123",
limits: {
monthly: { responses: 10000, miu: 50000 },
projects: 100,
},
};
vi.mocked(prisma.environment.findUnique).mockResolvedValue({
...mockEnvironmentData,
project: {
...mockEnvironmentData.project,
organization: {
id: "org-enterprise",
billing: customBilling,
},
},
} as never);
const result = await getEnvironmentStateData(environmentId);
expect(result.organization).toEqual({
id: "org-enterprise",
billing: customBilling,
});
});
});
@@ -54,7 +54,7 @@ export const getEnvironmentStateData = async (environmentId: string): Promise<En
id: true,
recontactDays: true,
clickOutsideClose: true,
darkOverlay: true,
overlay: true,
placement: true,
inAppSurveyBranding: true,
styling: true,
@@ -174,7 +174,7 @@ export const getEnvironmentStateData = async (environmentId: string): Promise<En
id: environmentData.project.id,
recontactDays: environmentData.project.recontactDays,
clickOutsideClose: environmentData.project.clickOutsideClose,
darkOverlay: environmentData.project.darkOverlay,
overlay: environmentData.project.overlay,
placement: environmentData.project.placement,
inAppSurveyBranding: environmentData.project.inAppSurveyBranding,
styling: environmentData.project.styling,
@@ -58,7 +58,7 @@ const mockProject: TJsEnvironmentStateProject = {
inAppSurveyBranding: true,
placement: "bottomRight",
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
styling: {
allowStyleOverwrite: false,
},
@@ -1,13 +1,15 @@
import { NextRequest } from "next/server";
import { logger } from "@formbricks/logger";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
import { ZResponseUpdateInput } from "@formbricks/types/responses";
import { TResponse, TResponseUpdateInput, ZResponseUpdateInput } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
import { sendToPipeline } from "@/app/lib/pipelines";
import { getResponse } from "@/lib/response/service";
import { getSurvey } from "@/lib/survey/service";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element";
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
import { validateFileUploads } from "@/modules/storage/utils";
@@ -31,6 +33,38 @@ const handleDatabaseError = (error: Error, url: string, endpoint: string, respon
return responses.internalServerErrorResponse("Unknown error occurred", true);
};
const validateResponse = (
response: TResponse,
survey: TSurvey,
responseUpdateInput: TResponseUpdateInput
) => {
// Validate response data against validation rules
const mergedData = {
...response.data,
...responseUpdateInput.data,
};
const isFinished = responseUpdateInput.finished ?? false;
const validationErrors = validateResponseData(
survey.blocks,
mergedData,
responseUpdateInput.language ?? response.language ?? "en",
isFinished,
survey.questions
);
if (validationErrors) {
return {
response: responses.badRequestResponse(
"Validation failed",
formatValidationErrorsForV1Api(validationErrors),
true
),
};
}
};
export const PUT = withV1ApiWrapper({
handler: async ({
req,
@@ -113,6 +147,11 @@ export const PUT = withV1ApiWrapper({
};
}
const validationResult = validateResponse(response, survey, inputValidation.data);
if (validationResult) {
return validationResult;
}
// update response with quota evaluation
let updatedResponse;
try {
@@ -6,11 +6,14 @@ import { ZEnvironmentId } from "@formbricks/types/environment";
import { InvalidInputError } from "@formbricks/types/errors";
import { TResponseWithQuotaFull } from "@formbricks/types/quota";
import { TResponseInput, ZResponseInput } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
import { sendToPipeline } from "@/app/lib/pipelines";
import { getSurvey } from "@/lib/survey/service";
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
import { validateFileUploads } from "@/modules/storage/utils";
@@ -32,6 +35,27 @@ export const OPTIONS = async (): Promise<Response> => {
);
};
const validateResponse = (responseInputData: TResponseInput, survey: TSurvey) => {
// Validate response data against validation rules
const validationErrors = validateResponseData(
survey.blocks,
responseInputData.data,
responseInputData.language ?? "en",
responseInputData.finished,
survey.questions
);
if (validationErrors) {
return {
response: responses.badRequestResponse(
"Validation failed",
formatValidationErrorsForV1Api(validationErrors),
true
),
};
}
};
export const POST = withV1ApiWrapper({
handler: async ({ req, props }: { req: NextRequest; props: Context }) => {
const params = await props.params;
@@ -122,6 +146,11 @@ export const POST = withV1ApiWrapper({
};
}
const validationResult = validateResponse(responseInputData, survey);
if (validationResult) {
return validationResult;
}
let response: TResponseWithQuotaFull;
try {
const meta: TResponseInput["meta"] = {
@@ -136,6 +165,13 @@ export const POST = withV1ApiWrapper({
action: responseInputData?.meta?.action,
};
// Capture IP address if the survey has IP capture enabled
// Server-derived IP always overwrites any client-provided value
if (survey.isCaptureIpEnabled) {
const ipAddress = await getClientIpFromHeaders();
meta.ipAddress = ipAddress;
}
response = await createResponseWithQuotaEvaluation({
...responseInputData,
meta,
+1 -1
View File
@@ -1,4 +1,4 @@
import { ImageResponse } from "@vercel/og";
import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";
export const GET = async (req: NextRequest) => {
@@ -8,6 +8,7 @@ import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib
import { sendToPipeline } from "@/app/lib/pipelines";
import { deleteResponse, getResponse } from "@/lib/response/service";
import { getSurvey } from "@/lib/survey/service";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import { updateResponseWithQuotaEvaluation } from "./lib/response";
@@ -140,6 +141,25 @@ export const PUT = withV1ApiWrapper({
};
}
// Validate response data against validation rules
const validationErrors = validateResponseData(
result.survey.blocks,
responseUpdate.data,
responseUpdate.language ?? "en",
responseUpdate.finished,
result.survey.questions
);
if (validationErrors) {
return {
response: responses.badRequestResponse(
"Validation failed",
formatValidationErrorsForV1Api(validationErrors),
true
),
};
}
const inputValidation = ZResponseUpdateInput.safeParse(responseUpdate);
if (!inputValidation.success) {
return {
@@ -7,6 +7,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
import { sendToPipeline } from "@/app/lib/pipelines";
import { getSurvey } from "@/lib/survey/service";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { validateFileUploads } from "@/modules/storage/utils";
import {
@@ -149,6 +150,25 @@ export const POST = withV1ApiWrapper({
};
}
// Validate response data against validation rules
const validationErrors = validateResponseData(
surveyResult.survey.blocks,
responseInput.data,
responseInput.language ?? "en",
responseInput.finished,
surveyResult.survey.questions
);
if (validationErrors) {
return {
response: responses.badRequestResponse(
"Validation failed",
formatValidationErrorsForV1Api(validationErrors),
true
),
};
}
if (responseInput.createdAt && !responseInput.updatedAt) {
responseInput.updatedAt = responseInput.createdAt;
}
@@ -19,6 +19,10 @@ vi.mock("@/lib/utils/validate", () => ({
validateInputs: vi.fn(),
}));
vi.mock("@/lib/crypto", () => ({
generateWebhookSecret: vi.fn(() => "whsec_test_secret_1234567890"),
}));
describe("createWebhook", () => {
afterEach(() => {
cleanup();
@@ -59,6 +63,7 @@ describe("createWebhook", () => {
source: webhookInput.source,
surveyIds: webhookInput.surveyIds,
triggers: webhookInput.triggers,
secret: "whsec_test_secret_1234567890",
environment: {
connect: {
id: webhookInput.environmentId,
@@ -144,6 +149,7 @@ describe("createWebhook", () => {
source: webhookInput.source,
surveyIds: webhookInput.surveyIds,
triggers: webhookInput.triggers,
secret: "whsec_test_secret_1234567890",
environment: {
connect: {
id: webhookInput.environmentId,
@@ -4,12 +4,15 @@ import { ZId, ZOptionalNumber } from "@formbricks/types/common";
import { DatabaseError, InvalidInputError } from "@formbricks/types/errors";
import { TWebhookInput, ZWebhookInput } from "@/app/api/v1/webhooks/types/webhooks";
import { ITEMS_PER_PAGE } from "@/lib/constants";
import { generateWebhookSecret } from "@/lib/crypto";
import { validateInputs } from "@/lib/utils/validate";
export const createWebhook = async (webhookInput: TWebhookInput): Promise<Webhook> => {
validateInputs([webhookInput, ZWebhookInput]);
try {
const secret = generateWebhookSecret();
const createdWebhook = await prisma.webhook.create({
data: {
url: webhookInput.url,
@@ -17,6 +20,7 @@ export const createWebhook = async (webhookInput: TWebhookInput): Promise<Webhoo
source: webhookInput.source,
surveyIds: webhookInput.surveyIds || [],
triggers: webhookInput.triggers || [],
secret,
environment: {
connect: {
id: webhookInput.environmentId,
@@ -10,6 +10,8 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
import { sendToPipeline } from "@/app/lib/pipelines";
import { getSurvey } from "@/lib/survey/service";
import { getElementsFromBlocks } from "@/lib/survey/utils";
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
import { formatValidationErrorsForV1Api, validateResponseData } from "@/modules/api/lib/validation";
import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element";
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
@@ -105,6 +107,23 @@ export const POST = async (request: Request, context: Context): Promise<Response
);
}
// Validate response data against validation rules
const validationErrors = validateResponseData(
survey.blocks,
responseInputData.data,
responseInputData.language ?? "en",
responseInputData.finished,
survey.questions
);
if (validationErrors) {
return responses.badRequestResponse(
"Validation failed",
formatValidationErrorsForV1Api(validationErrors),
true
);
}
let response: TResponseWithQuotaFull;
try {
const meta: TResponseInputV2["meta"] = {
@@ -119,6 +138,13 @@ export const POST = async (request: Request, context: Context): Promise<Response
action: responseInputData?.meta?.action,
};
// Capture IP address if the survey has IP capture enabled
// Server-derived IP always overwrites any client-provided value
if (survey.isCaptureIpEnabled) {
const ipAddress = await getClientIpFromHeaders();
meta.ipAddress = ipAddress;
}
response = await createResponseWithQuotaEvaluation({
...responseInputData,
meta,
+9 -6
View File
@@ -3,8 +3,9 @@
// Error components must be Client components
import * as Sentry from "@sentry/nextjs";
import { TFunction } from "i18next";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { type ClientErrorType, getClientErrorData } from "@formbricks/types/errors";
import { type ClientErrorType, getClientErrorData, isExpectedError } from "@formbricks/types/errors";
import { Button } from "@/modules/ui/components/button";
import { ErrorComponent } from "@/modules/ui/components/error-component";
@@ -30,11 +31,13 @@ const ErrorBoundary = ({ error, reset }: { error: Error; reset: () => void }) =>
const errorData = getClientErrorData(error);
const { title, description } = getErrorMessages(errorData.type, t);
if (process.env.NODE_ENV === "development") {
console.error(error.message);
} else {
Sentry.captureException(error);
}
useEffect(() => {
if (process.env.NODE_ENV === "development") {
console.error(error.message);
} else if (!isExpectedError(error)) {
Sentry.captureException(error);
}
}, [error]);
return (
<div className="flex h-full w-full flex-col items-center justify-center">
@@ -0,0 +1,80 @@
import type { TFunction } from "i18next";
import { describe, expect, test, vi } from "vitest";
import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { createI18nString } from "@/lib/i18n/utils";
import { buildBlock } from "./survey-block-builder";
const mockT = vi.fn((key: string) => {
const translations: Record<string, string> = {
"common.next": "Next",
"common.back": "Back",
"": "",
};
return translations[key] || key;
}) as unknown as TFunction;
describe("survey-block-builder", () => {
describe("buildBlock", () => {
const mockElements = [
{
id: "element-1",
type: TSurveyElementTypeEnum.OpenText,
headline: createI18nString("Test Question", []),
required: false,
inputType: "text",
longAnswer: false,
charLimit: { enabled: false },
},
];
test("should use getDefaultButtonLabel when buttonLabel is provided", () => {
const result = buildBlock({
name: "Test Block",
elements: mockElements,
buttonLabel: "Custom Next",
t: mockT,
});
expect(result.buttonLabel).toEqual({
default: "Custom Next",
});
});
test("should use createI18nString with empty translation when buttonLabel is not provided", () => {
const result = buildBlock({
name: "Test Block",
elements: mockElements,
t: mockT,
});
expect(result.buttonLabel).toEqual({
default: "",
});
});
test("should use getDefaultBackButtonLabel when backButtonLabel is provided", () => {
const result = buildBlock({
name: "Test Block",
elements: mockElements,
backButtonLabel: "Custom Back",
t: mockT,
});
expect(result.backButtonLabel).toEqual({
default: "Custom Back",
});
});
test("should use createI18nString with empty translation when backButtonLabel is not provided", () => {
const result = buildBlock({
name: "Test Block",
elements: mockElements,
t: mockT,
});
expect(result.backButtonLabel).toEqual({
default: "",
});
});
});
});
+4 -2
View File
@@ -302,7 +302,9 @@ export const buildBlock = ({
elements,
logic,
logicFallback,
buttonLabel: buttonLabel ? getDefaultButtonLabel(buttonLabel, t) : undefined,
backButtonLabel: backButtonLabel ? getDefaultBackButtonLabel(backButtonLabel, t) : undefined,
buttonLabel: buttonLabel ? getDefaultButtonLabel(buttonLabel, t) : createI18nString(t(""), []),
backButtonLabel: backButtonLabel
? getDefaultBackButtonLabel(backButtonLabel, t)
: createI18nString(t(""), []),
};
};
+3
View File
@@ -4848,12 +4848,14 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => {
t("templates.preview_survey_question_2_choice_2_label"),
],
headline: t("templates.preview_survey_question_2_headline"),
subheader: t("templates.preview_survey_question_2_subheader"),
required: true,
shuffleOption: "none",
}),
isDraft: true,
},
],
buttonLabel: createI18nString(t("templates.next"), []),
backButtonLabel: createI18nString(t("templates.preview_survey_question_2_back_button_label"), []),
},
{
@@ -4913,6 +4915,7 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => {
showLanguageSwitch: false,
followUps: [],
isBackButtonHidden: false,
isCaptureIpEnabled: false,
metadata: {},
questions: [], // Required for build-time type checking (Zod defaults to [] at runtime)
slug: null,
@@ -8,7 +8,7 @@ import { authorizePrivateDownload } from "@/app/storage/[environmentId]/[accessT
import { authOptions } from "@/modules/auth/lib/authOptions";
import { applyRateLimit } from "@/modules/core/rate-limit/helpers";
import { rateLimitConfigs } from "@/modules/core/rate-limit/rate-limit-configs";
import { deleteFile, getSignedUrlForDownload } from "@/modules/storage/service";
import { deleteFile, getFileStreamForDownload } from "@/modules/storage/service";
import { getErrorResponseFromStorageError } from "@/modules/storage/utils";
import { logFileDeletion } from "./lib/audit-logs";
@@ -39,21 +39,25 @@ export const GET = async (
}
}
const signedUrlResult = await getSignedUrlForDownload(fileName, environmentId, accessType);
// Stream the file directly
const streamResult = await getFileStreamForDownload(fileName, environmentId, accessType);
if (!signedUrlResult.ok) {
const errorResponse = getErrorResponseFromStorageError(signedUrlResult.error, { fileName });
if (!streamResult.ok) {
const errorResponse = getErrorResponseFromStorageError(streamResult.error, { fileName });
return errorResponse;
}
return new Response(null, {
status: 302,
const { body, contentType, contentLength } = streamResult.data;
return new Response(body, {
status: 200,
headers: {
Location: signedUrlResult.data,
"Content-Type": contentType,
...(contentLength > 0 && { "Content-Length": String(contentLength) }),
"Cache-Control":
accessType === "private"
? "no-store, no-cache, must-revalidate"
: "public, max-age=300, s-maxage=300, stale-while-revalidate=300",
: "public, max-age=31536000, immutable",
},
});
};
+6 -5
View File
@@ -9,17 +9,18 @@
"source": "en-US",
"targets": [
"de-DE",
"es-ES",
"fr-FR",
"hu-HU",
"ja-JP",
"nl-NL",
"pt-BR",
"pt-PT",
"ro-RO",
"zh-Hans-CN",
"zh-Hant-TW",
"nl-NL",
"es-ES",
"ru-RU",
"sv-SE",
"ru-RU"
"zh-Hans-CN",
"zh-Hant-TW"
]
},
"version": 1.8
+380 -247
View File
File diff suppressed because it is too large Load Diff
+195 -49
View File
@@ -1,59 +1,205 @@
// instrumentation-node.ts
// OpenTelemetry instrumentation for Next.js - loaded via instrumentation.ts hook
// Pattern based on: ee/src/opentelemetry.ts (license server)
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
import { HostMetrics } from "@opentelemetry/host-metrics";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node";
import {
detectResources,
envDetector,
hostDetector,
processDetector,
resourceFromAttributes,
} from "@opentelemetry/resources";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
AlwaysOffSampler,
AlwaysOnSampler,
BatchSpanProcessor,
ParentBasedSampler,
type Sampler,
TraceIdRatioBasedSampler,
} from "@opentelemetry/sdk-trace-base";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { logger } from "@formbricks/logger";
import { env } from "@/lib/env";
const exporter = new PrometheusExporter({
port: env.PROMETHEUS_EXPORTER_PORT ? parseInt(env.PROMETHEUS_EXPORTER_PORT) : 9464,
endpoint: "/metrics",
host: "0.0.0.0", // Listen on all network interfaces
});
// --- Configuration from environment ---
const serviceName = process.env.OTEL_SERVICE_NAME || "formbricks";
const serviceVersion = process.env.npm_package_version || "0.0.0";
const environment = process.env.ENVIRONMENT || process.env.NODE_ENV || "development";
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
const prometheusEnabled = process.env.PROMETHEUS_ENABLED === "1";
const prometheusPort = process.env.PROMETHEUS_EXPORTER_PORT
? Number.parseInt(process.env.PROMETHEUS_EXPORTER_PORT)
: 9464;
const detectedResources = detectResources({
detectors: [envDetector, processDetector, hostDetector],
});
// --- Configure OTLP exporters (conditional on endpoint being set) ---
let traceExporter: OTLPTraceExporter | undefined;
let otlpMetricExporter: OTLPMetricExporter | undefined;
const customResources = resourceFromAttributes({});
const resources = detectedResources.merge(customResources);
const meterProvider = new MeterProvider({
readers: [exporter],
resource: resources,
});
const hostMetrics = new HostMetrics({
name: `otel-metrics`,
meterProvider,
});
registerInstrumentations({
meterProvider,
instrumentations: [new HttpInstrumentation(), new RuntimeNodeInstrumentation()],
});
hostMetrics.start();
process.on("SIGTERM", async () => {
if (otlpEndpoint) {
try {
// Stop collecting metrics or flush them if needed
await meterProvider.shutdown();
// Possibly close other instrumentation resources
// OTLPTraceExporter reads OTEL_EXPORTER_OTLP_ENDPOINT from env
// and appends /v1/traces for HTTP transport
// Uses OTEL_EXPORTER_OTLP_HEADERS from env natively (W3C OTel format: key=value,key2=value2)
traceExporter = new OTLPTraceExporter();
// OTLPMetricExporter reads OTEL_EXPORTER_OTLP_ENDPOINT from env
// and appends /v1/metrics for HTTP transport
// Uses OTEL_EXPORTER_OTLP_HEADERS from env natively
otlpMetricExporter = new OTLPMetricExporter();
} catch (error) {
logger.error(error, "Failed to create OTLP exporters. Telemetry will not be exported.");
}
}
// --- Configure Prometheus exporter (pull-based metrics for ServiceMonitor) ---
let prometheusExporter: PrometheusExporter | undefined;
if (prometheusEnabled) {
prometheusExporter = new PrometheusExporter({
port: prometheusPort,
endpoint: "/metrics",
host: "0.0.0.0",
});
}
// --- Build metric readers array ---
const metricReaders: (PeriodicExportingMetricReader | PrometheusExporter)[] = [];
if (otlpMetricExporter) {
metricReaders.push(
new PeriodicExportingMetricReader({
exporter: otlpMetricExporter,
exportIntervalMillis: 60000, // Export every 60 seconds
})
);
}
if (prometheusExporter) {
metricReaders.push(prometheusExporter);
}
// --- Resource attributes ---
const resourceAttributes: Record<string, string> = {
[ATTR_SERVICE_NAME]: serviceName,
[ATTR_SERVICE_VERSION]: serviceVersion,
"deployment.environment": environment,
};
// --- Configure sampler ---
const samplerType = process.env.OTEL_TRACES_SAMPLER || "always_on";
const parsedSamplerArg = process.env.OTEL_TRACES_SAMPLER_ARG
? Number.parseFloat(process.env.OTEL_TRACES_SAMPLER_ARG)
: undefined;
const samplerArg =
parsedSamplerArg !== undefined && !Number.isNaN(parsedSamplerArg) ? parsedSamplerArg : undefined;
let sampler: Sampler;
switch (samplerType) {
case "always_on":
sampler = new AlwaysOnSampler();
break;
case "always_off":
sampler = new AlwaysOffSampler();
break;
case "traceidratio":
sampler = new TraceIdRatioBasedSampler(samplerArg ?? 1);
break;
case "parentbased_traceidratio":
sampler = new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(samplerArg ?? 1),
});
break;
case "parentbased_always_on":
sampler = new ParentBasedSampler({
root: new AlwaysOnSampler(),
});
break;
case "parentbased_always_off":
sampler = new ParentBasedSampler({
root: new AlwaysOffSampler(),
});
break;
default:
logger.warn(`Unknown sampler type: ${samplerType}. Using always_on.`);
sampler = new AlwaysOnSampler();
}
// --- Initialize NodeSDK ---
const sdk = new NodeSDK({
sampler,
resource: resourceFromAttributes(resourceAttributes),
// When no OTLP endpoint is configured (e.g. Prometheus-only setups), pass an empty
// spanProcessors array to prevent the SDK from falling back to its default OTLP exporter
// which would attempt connections to localhost:4318 and cause noisy errors.
spanProcessors: traceExporter
? [
new BatchSpanProcessor(traceExporter, {
maxQueueSize: 2048,
maxExportBatchSize: 512,
scheduledDelayMillis: 5000,
exportTimeoutMillis: 30000,
}),
]
: [],
metricReaders: metricReaders.length > 0 ? metricReaders : undefined,
instrumentations: [
getNodeAutoInstrumentations({
// Disable noisy/unnecessary instrumentations
"@opentelemetry/instrumentation-fs": {
enabled: false,
},
"@opentelemetry/instrumentation-dns": {
enabled: false,
},
"@opentelemetry/instrumentation-net": {
enabled: false,
},
// Disable pg instrumentation - PrismaInstrumentation handles DB tracing
"@opentelemetry/instrumentation-pg": {
enabled: false,
},
"@opentelemetry/instrumentation-http": {
// Ignore health/metrics endpoints to reduce noise
ignoreIncomingRequestHook: (req) => {
const url = req.url || "";
return url === "/health" || url.startsWith("/metrics") || url === "/api/v2/health";
},
},
// Enable runtime metrics for Node.js process monitoring
"@opentelemetry/instrumentation-runtime-node": {
enabled: true,
},
}),
// Prisma instrumentation for database query tracing
new PrismaInstrumentation(),
],
});
// Start the SDK
sdk.start();
// --- Log initialization status ---
const enabledFeatures: string[] = [];
if (traceExporter) enabledFeatures.push("traces");
if (otlpMetricExporter) enabledFeatures.push("otlp-metrics");
if (prometheusExporter) enabledFeatures.push("prometheus-metrics");
const samplerArgStr = process.env.OTEL_TRACES_SAMPLER_ARG || "";
const samplerArgMsg = samplerArgStr ? `, samplerArg=${samplerArgStr}` : "";
if (enabledFeatures.length > 0) {
logger.info(
`OpenTelemetry initialized: service=${serviceName}, version=${serviceVersion}, environment=${environment}, exporters=${enabledFeatures.join("+")}, sampler=${samplerType}${samplerArgMsg}`
);
} else {
logger.info(
`OpenTelemetry initialized (no exporters): service=${serviceName}, version=${serviceVersion}, environment=${environment}`
);
}
// --- Graceful shutdown ---
// Run before other SIGTERM listeners (logger flush, etc.) so spans are drained first.
process.prependListener("SIGTERM", async () => {
try {
await sdk.shutdown();
} catch (e) {
logger.error(e, "Error during graceful shutdown");
} finally {
process.exit(0);
logger.error(e, "Error during OpenTelemetry shutdown");
}
});
+4 -1
View File
@@ -5,10 +5,13 @@ export const onRequestError = Sentry.captureRequestError;
export const register = async () => {
if (process.env.NEXT_RUNTIME === "nodejs") {
if (PROMETHEUS_ENABLED) {
// Load OpenTelemetry instrumentation when Prometheus metrics or OTLP export is enabled
if (PROMETHEUS_ENABLED || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
await import("./instrumentation-node");
}
}
// Sentry init loads after OTEL to avoid TracerProvider conflicts
// Sentry tracing is disabled (tracesSampleRate: 0) -- SigNoz handles distributed tracing
if (process.env.NEXT_RUNTIME === "nodejs" && IS_PRODUCTION && SENTRY_DSN) {
await import("./sentry.server.config");
}
+8 -7
View File
@@ -165,19 +165,20 @@ export const MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT = 150;
export const DEFAULT_LOCALE = "en-US";
export const AVAILABLE_LOCALES: TUserLocale[] = [
"en-US",
"de-DE",
"pt-BR",
"en-US",
"es-ES",
"fr-FR",
"hu-HU",
"ja-JP",
"nl-NL",
"zh-Hant-TW",
"pt-BR",
"pt-PT",
"ro-RO",
"ja-JP",
"zh-Hans-CN",
"es-ES",
"sv-SE",
"ru-RU",
"sv-SE",
"zh-Hans-CN",
"zh-Hant-TW",
];
// Billing constants
+143 -13
View File
@@ -1,8 +1,11 @@
import * as crypto from "crypto";
import * as crypto from "node:crypto";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { logger } from "@formbricks/logger";
// Import after unmocking
import {
generateStandardWebhookSignature,
generateWebhookSecret,
getWebhookSecretBytes,
hashSecret,
hashSha256,
parseApiKeyV2,
@@ -283,6 +286,133 @@ describe("Crypto Utils", () => {
});
});
describe("Webhook Signature Functions", () => {
describe("generateWebhookSecret", () => {
test("should generate a secret with whsec_ prefix", () => {
const secret = generateWebhookSecret();
expect(secret.startsWith("whsec_")).toBe(true);
});
test("should generate base64-encoded content after prefix", () => {
const secret = generateWebhookSecret();
const base64Part = secret.slice(6); // Remove "whsec_"
// Should be valid base64
expect(() => Buffer.from(base64Part, "base64")).not.toThrow();
// Should decode to 32 bytes (256 bits)
const decoded = Buffer.from(base64Part, "base64");
expect(decoded.length).toBe(32);
});
test("should generate unique secrets each time", () => {
const secret1 = generateWebhookSecret();
const secret2 = generateWebhookSecret();
expect(secret1).not.toBe(secret2);
});
});
describe("getWebhookSecretBytes", () => {
test("should decode whsec_ prefixed secret to bytes", () => {
const secret = generateWebhookSecret();
const bytes = getWebhookSecretBytes(secret);
expect(Buffer.isBuffer(bytes)).toBe(true);
expect(bytes.length).toBe(32);
});
test("should handle secret without whsec_ prefix", () => {
const base64Secret = Buffer.from("test-secret-bytes-32-characters!").toString("base64");
const bytes = getWebhookSecretBytes(base64Secret);
expect(Buffer.isBuffer(bytes)).toBe(true);
expect(bytes.toString()).toBe("test-secret-bytes-32-characters!");
});
test("should correctly decode a known secret", () => {
// Create a known secret
const knownBytes = Buffer.from("known-test-secret-for-testing!!");
const secret = `whsec_${knownBytes.toString("base64")}`;
const decoded = getWebhookSecretBytes(secret);
expect(decoded.toString()).toBe("known-test-secret-for-testing!!");
});
});
describe("generateStandardWebhookSignature", () => {
test("should generate signature in v1,{base64} format", () => {
const secret = generateWebhookSecret();
const signature = generateStandardWebhookSignature("msg_123", 1704547200, '{"test":"data"}', secret);
expect(signature.startsWith("v1,")).toBe(true);
const base64Part = signature.slice(3);
expect(() => Buffer.from(base64Part, "base64")).not.toThrow();
});
test("should generate deterministic signatures for same inputs", () => {
const secret = "whsec_" + Buffer.from("test-secret-32-bytes-exactly!!!").toString("base64");
const webhookId = "msg_test123";
const timestamp = 1704547200;
const payload = '{"event":"test"}';
const sig1 = generateStandardWebhookSignature(webhookId, timestamp, payload, secret);
const sig2 = generateStandardWebhookSignature(webhookId, timestamp, payload, secret);
expect(sig1).toBe(sig2);
});
test("should generate different signatures for different payloads", () => {
const secret = "whsec_" + Buffer.from("test-secret-32-bytes-exactly!!!").toString("base64");
const webhookId = "msg_test123";
const timestamp = 1704547200;
const sig1 = generateStandardWebhookSignature(webhookId, timestamp, '{"event":"a"}', secret);
const sig2 = generateStandardWebhookSignature(webhookId, timestamp, '{"event":"b"}', secret);
expect(sig1).not.toBe(sig2);
});
test("should generate different signatures for different timestamps", () => {
const secret = "whsec_" + Buffer.from("test-secret-32-bytes-exactly!!!").toString("base64");
const webhookId = "msg_test123";
const payload = '{"event":"test"}';
const sig1 = generateStandardWebhookSignature(webhookId, 1704547200, payload, secret);
const sig2 = generateStandardWebhookSignature(webhookId, 1704547201, payload, secret);
expect(sig1).not.toBe(sig2);
});
test("should generate different signatures for different webhook IDs", () => {
const secret = "whsec_" + Buffer.from("test-secret-32-bytes-exactly!!!").toString("base64");
const timestamp = 1704547200;
const payload = '{"event":"test"}';
const sig1 = generateStandardWebhookSignature("msg_1", timestamp, payload, secret);
const sig2 = generateStandardWebhookSignature("msg_2", timestamp, payload, secret);
expect(sig1).not.toBe(sig2);
});
test("should produce verifiable signatures", () => {
// This test verifies the signature can be verified using the same algorithm
const secretBytes = Buffer.from("test-secret-32-bytes-exactly!!!");
const secret = `whsec_${secretBytes.toString("base64")}`;
const webhookId = "msg_verify";
const timestamp = 1704547200;
const payload = '{"event":"verify"}';
const signature = generateStandardWebhookSignature(webhookId, timestamp, payload, secret);
// Manually compute the expected signature
const signedContent = `${webhookId}.${timestamp}.${payload}`;
const expectedSig = crypto.createHmac("sha256", secretBytes).update(signedContent).digest("base64");
expect(signature).toBe(`v1,${expectedSig}`);
});
});
});
describe("GCM decryption failure logging", () => {
// Test key - 32 bytes for AES-256
const testKey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
@@ -314,11 +444,11 @@ describe("Crypto Utils", () => {
expect(() => symmetricDecrypt(corruptedPayload, testKey)).toThrow();
// Verify logger.warn was called with the correct format (object first, message second)
expect(logger.warn).toHaveBeenCalledWith(
{ err: expect.any(Error) },
"AES-GCM decryption failed; refusing to fall back to insecure CBC"
);
expect(logger.warn).toHaveBeenCalledTimes(1);
const [firstArg, secondArg] = vi.mocked(logger.warn).mock.calls[0];
expect(firstArg).toHaveProperty("err");
expect(firstArg.err).toHaveProperty("message");
expect(secondArg).toBe("AES-GCM decryption failed; refusing to fall back to insecure CBC");
});
test("logs warning and throws when GCM decryption fails with corrupted encrypted data", () => {
@@ -342,11 +472,11 @@ describe("Crypto Utils", () => {
expect(() => symmetricDecrypt(corruptedPayload, testKey)).toThrow();
// Verify logger.warn was called
expect(logger.warn).toHaveBeenCalledWith(
{ err: expect.any(Error) },
"AES-GCM decryption failed; refusing to fall back to insecure CBC"
);
expect(logger.warn).toHaveBeenCalledTimes(1);
const [firstArg, secondArg] = vi.mocked(logger.warn).mock.calls[0];
expect(firstArg).toHaveProperty("err");
expect(firstArg.err).toHaveProperty("message");
expect(secondArg).toBe("AES-GCM decryption failed; refusing to fall back to insecure CBC");
});
test("logs warning and throws when GCM decryption fails with wrong key", () => {
@@ -366,11 +496,11 @@ describe("Crypto Utils", () => {
expect(() => symmetricDecrypt(payload, wrongKey)).toThrow();
// Verify logger.warn was called
expect(logger.warn).toHaveBeenCalledWith(
{ err: expect.any(Error) },
"AES-GCM decryption failed; refusing to fall back to insecure CBC"
);
expect(logger.warn).toHaveBeenCalledTimes(1);
const [firstArg, secondArg] = vi.mocked(logger.warn).mock.calls[0];
expect(firstArg).toHaveProperty("err");
expect(firstArg.err).toHaveProperty("message");
expect(secondArg).toBe("AES-GCM decryption failed; refusing to fall back to insecure CBC");
});
});
});
+52 -1
View File
@@ -1,5 +1,5 @@
import { compare, hash } from "bcryptjs";
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from "node:crypto";
import { logger } from "@formbricks/logger";
import { ENCRYPTION_KEY } from "@/lib/constants";
@@ -141,3 +141,54 @@ export const parseApiKeyV2 = (key: string): { secret: string } | null => {
return { secret };
};
// Standard Webhooks secret prefix
const WEBHOOK_SECRET_PREFIX = "whsec_";
/**
* Generate a Standard Webhooks compliant secret
* Following: https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md
*
* Format: whsec_ + base64(32 random bytes)
* @returns A webhook secret in format "whsec_{base64_encoded_random_bytes}"
*/
export const generateWebhookSecret = (): string => {
const secretBytes = randomBytes(32); // 256 bits of entropy
return `${WEBHOOK_SECRET_PREFIX}${secretBytes.toString("base64")}`;
};
/**
* Decode a Standard Webhooks secret to get the raw bytes
* Strips the whsec_ prefix and base64 decodes the rest
*
* @param secret The webhook secret (with or without whsec_ prefix)
* @returns Buffer containing the raw secret bytes
*/
export const getWebhookSecretBytes = (secret: string): Buffer => {
const base64Part = secret.startsWith(WEBHOOK_SECRET_PREFIX)
? secret.slice(WEBHOOK_SECRET_PREFIX.length)
: secret;
return Buffer.from(base64Part, "base64");
};
/**
* Generate Standard Webhooks compliant signature
* Following: https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md
*
* @param webhookId Unique message identifier
* @param timestamp Unix timestamp in seconds
* @param payload The request body as a string
* @param secret The shared secret (whsec_ prefixed)
* @returns The signature in format "v1,{base64_signature}"
*/
export const generateStandardWebhookSignature = (
webhookId: string,
timestamp: number,
payload: string,
secret: string
): string => {
const signedContent = `${webhookId}.${timestamp}.${payload}`;
const secretBytes = getWebhookSecretBytes(secret);
const signature = createHmac("sha256", secretBytes).update(signedContent).digest("base64");
return `v1,${signature}`;
};
-2
View File
@@ -55,7 +55,6 @@ export const env = createEnv({
OIDC_DISPLAY_NAME: z.string().optional(),
OIDC_ISSUER: z.string().optional(),
OIDC_SIGNING_ALGORITHM: z.string().optional(),
OPENTELEMETRY_LISTENER_URL: z.string().optional(),
REDIS_URL:
process.env.NODE_ENV === "test"
? z.string().optional()
@@ -174,7 +173,6 @@ export const env = createEnv({
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
SENTRY_DSN: process.env.SENTRY_DSN,
OPENTELEMETRY_LISTENER_URL: process.env.OPENTELEMETRY_LISTENER_URL,
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
+63 -196
View File
@@ -88,7 +88,7 @@ export const getLanguageCode = (surveyLanguages: TSurveyLanguage[], languageCode
return language?.default ? "default" : language?.language.code || "default";
};
export const iso639Identifiers = iso639Languages.map((language) => language.alpha2);
export const iso639Identifiers = iso639Languages.map((language) => language.code);
// Helper function to add language keys to a multi-language object (e.g. survey or question)
// Iterates over the object recursively and adds empty strings for new language keys
@@ -126,221 +126,88 @@ export const addMultiLanguageLabels = (object: unknown, languageSymbols: string[
};
export const appLanguages = [
{
code: "en-US",
label: {
"en-US": "English (US)",
"de-DE": "Englisch (US)",
"pt-BR": "Inglês (EUA)",
"fr-FR": "Anglais (États-Unis)",
"zh-Hant-TW": "英文 (美國)",
"pt-PT": "Inglês (EUA)",
"ro-RO": "Engleză (SUA)",
"ja-JP": "英語(米国)",
"zh-Hans-CN": "英语(美国)",
"nl-NL": "Engels (VS)",
"es-ES": "Inglés (EE.UU.)",
"sv-SE": "Engelska (USA)",
"ru-RU": "Английский (США)",
},
},
{
code: "de-DE",
label: {
"en-US": "German",
"de-DE": "Deutsch",
"pt-BR": "Alemão",
"fr-FR": "Allemand",
"zh-Hant-TW": "德語",
"pt-PT": "Alemão",
"ro-RO": "Germană",
"ja-JP": "ドイツ語",
"zh-Hans-CN": "德语",
"nl-NL": "Duits",
"es-ES": "Alemán",
"sv-SE": "Tyska",
"ru-RU": "Немецкий",
},
},
{
code: "pt-BR",
code: "en-US",
label: {
"en-US": "Portuguese (Brazil)",
"de-DE": "Portugiesisch (Brasilien)",
"pt-BR": "Português (Brasil)",
"fr-FR": "Portugais (Brésil)",
"zh-Hant-TW": "葡萄牙語 (巴西)",
"pt-PT": "Português (Brasil)",
"ro-RO": "Portugheză (Brazilia)",
"ja-JP": "ポルトガル語(ブラジル)",
"zh-Hans-CN": "葡萄牙语(巴西)",
"nl-NL": "Portugees (Brazilië)",
"es-ES": "Portugués (Brasil)",
"sv-SE": "Portugisiska (Brasilien)",
"ru-RU": "Португальский (Бразилия)",
},
},
{
code: "fr-FR",
label: {
"en-US": "French",
"de-DE": "Französisch",
"pt-BR": "Francês",
"fr-FR": "Français",
"zh-Hant-TW": "法語",
"pt-PT": "Francês",
"ro-RO": "Franceză",
"ja-JP": "フランス語",
"zh-Hans-CN": "法语",
"nl-NL": "Frans",
"es-ES": "Francés",
"sv-SE": "Franska",
"ru-RU": "Французский",
},
},
{
code: "zh-Hant-TW",
label: {
"en-US": "Chinese (Traditional)",
"de-DE": "Chinesisch (Traditionell)",
"pt-BR": "Chinês (Tradicional)",
"fr-FR": "Chinois (Traditionnel)",
"zh-Hant-TW": "繁體中文",
"pt-PT": "Chinês (Tradicional)",
"ro-RO": "Chineza (Tradițională)",
"ja-JP": "中国語(繁体字)",
"zh-Hans-CN": "繁体中文",
"nl-NL": "Chinees (Traditioneel)",
"es-ES": "Chino (Tradicional)",
"sv-SE": "Kinesiska (traditionell)",
"ru-RU": "Китайский (традиционный)",
},
},
{
code: "pt-PT",
label: {
"en-US": "Portuguese (Portugal)",
"de-DE": "Portugiesisch (Portugal)",
"pt-BR": "Português (Portugal)",
"fr-FR": "Portugais (Portugal)",
"zh-Hant-TW": "葡萄牙語 (葡萄牙)",
"pt-PT": "Português (Portugal)",
"ro-RO": "Portugheză (Portugalia)",
"ja-JP": "ポルトガル語(ポルトガル)",
"zh-Hans-CN": "葡萄牙语(葡萄牙)",
"nl-NL": "Portugees (Portugal)",
"es-ES": "Portugués (Portugal)",
"sv-SE": "Portugisiska (Portugal)",
"ru-RU": "Португальский (Португалия)",
},
},
{
code: "ro-RO",
label: {
"en-US": "Romanian",
"de-DE": "Rumänisch",
"pt-BR": "Romeno",
"fr-FR": "Roumain",
"zh-Hant-TW": "羅馬尼亞語",
"pt-PT": "Romeno",
"ro-RO": "Română",
"ja-JP": "ルーマニア語",
"zh-Hans-CN": "罗马尼亚语",
"nl-NL": "Roemeens",
"es-ES": "Rumano",
"sv-SE": "Rumänska",
"ru-RU": "Румынский",
},
},
{
code: "ja-JP",
label: {
"en-US": "Japanese",
"de-DE": "Japanisch",
"pt-BR": "Japonês",
"fr-FR": "Japonais",
"zh-Hant-TW": "日語",
"pt-PT": "Japonês",
"ro-RO": "Japoneză",
"ja-JP": "日本語",
"zh-Hans-CN": "日语",
"nl-NL": "Japans",
"es-ES": "Japonés",
"sv-SE": "Japanska",
"ru-RU": "Японский",
},
},
{
code: "zh-Hans-CN",
label: {
"en-US": "Chinese (Simplified)",
"de-DE": "Chinesisch (Vereinfacht)",
"pt-BR": "Chinês (Simplificado)",
"fr-FR": "Chinois (Simplifié)",
"zh-Hant-TW": "簡體中文",
"pt-PT": "Chinês (Simplificado)",
"ro-RO": "Chineza (Simplificată)",
"ja-JP": "中国語(簡体字)",
"zh-Hans-CN": "简体中文",
"nl-NL": "Chinees (Vereenvoudigd)",
"es-ES": "Chino (Simplificado)",
"sv-SE": "Kinesiska (förenklad)",
"ru-RU": "Китайский (упрощенный)",
},
},
{
code: "nl-NL",
label: {
"en-US": "Dutch",
"de-DE": "Niederländisch",
"pt-BR": "Holandês",
"fr-FR": "Néerlandais",
"zh-Hant-TW": "荷蘭語",
"pt-PT": "Holandês",
"ro-RO": "Olandeza",
"ja-JP": "オランダ語",
"zh-Hans-CN": "荷兰语",
"nl-NL": "Nederlands",
"es-ES": "Neerlandés",
"sv-SE": "Nederländska",
"ru-RU": "Голландский",
"en-US": "English (US)",
},
},
{
code: "es-ES",
label: {
"en-US": "Spanish",
"de-DE": "Spanisch",
"pt-BR": "Espanhol",
"fr-FR": "Espagnol",
"zh-Hant-TW": "西班牙語",
"pt-PT": "Espanhol",
"ro-RO": "Spaniol",
"ja-JP": "スペイン語",
"zh-Hans-CN": "西班牙语",
"nl-NL": "Spaans",
"es-ES": "Español",
"sv-SE": "Spanska",
"ru-RU": "Испанский",
},
},
{
code: "fr-FR",
label: {
"en-US": "French",
},
},
{
code: "hu-HU",
label: {
"en-US": "Hungarian",
},
},
{
code: "ja-JP",
label: {
"en-US": "Japanese",
},
},
{
code: "nl-NL",
label: {
"en-US": "Dutch",
},
},
{
code: "pt-BR",
label: {
"en-US": "Portuguese (Brazil)",
},
},
{
code: "pt-PT",
label: {
"en-US": "Portuguese (Portugal)",
},
},
{
code: "ro-RO",
label: {
"en-US": "Romanian",
},
},
{
code: "ru-RU",
label: {
"en-US": "Russian",
},
},
{
code: "sv-SE",
label: {
"en-US": "Swedish",
"de-DE": "Schwedisch",
"pt-BR": "Sueco",
"fr-FR": "Suédois",
"zh-Hant-TW": "瑞典語",
"pt-PT": "Sueco",
"ro-RO": "Suedeză",
"ja-JP": "スウェーデン語",
"zh-Hans-CN": "瑞典语",
"nl-NL": "Zweeds",
"es-ES": "Sueco",
"sv-SE": "Svenska",
"ru-RU": "Шведский",
},
},
{
code: "zh-Hans-CN",
label: {
"en-US": "Chinese (Simplified)",
},
},
{
code: "zh-Hant-TW",
label: {
"en-US": "Chinese (Traditional)",
},
},
];
export { iso639Languages };
+9 -9
View File
@@ -48,7 +48,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -106,7 +106,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -171,7 +171,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -196,7 +196,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -250,7 +250,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -324,7 +324,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -378,7 +378,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -403,7 +403,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
@@ -448,7 +448,7 @@ describe("Project Service", () => {
},
placement: WidgetPlacement.bottomRight,
clickOutsideClose: true,
darkOverlay: false,
overlay: "none",
environments: [],
styling: {
allowStyleOverwrite: true,
+1 -1
View File
@@ -22,7 +22,7 @@ const selectProject = {
config: true,
placement: true,
clickOutsideClose: true,
darkOverlay: true,
overlay: true,
environments: true,
styling: true,
logo: true,
+167 -24
View File
@@ -1,5 +1,6 @@
// https://github.com/airbnb/javascript/#naming--uppercase
import { TProjectStyling } from "@formbricks/types/project";
import { isLight, mixColor } from "@/lib/utils/colors";
export const COLOR_DEFAULTS = {
brandColor: "#64748b",
@@ -11,32 +12,174 @@ export const COLOR_DEFAULTS = {
highlightBorderColor: "#64748b",
} as const;
export const defaultStyling: TProjectStyling = {
const DEFAULT_BRAND_COLOR = "#64748b";
/**
* Derives a complete set of suggested color values from a single brand color.
*
* Used by the project-level "Suggest Colors" button **and** to build
* `STYLE_DEFAULTS` so that a fresh install always has colours that are
* visually cohesive with the default brand.
*
* The returned object is a flat map of form-field paths to values so it
* can be spread directly into form defaults or applied via `form.setValue`.
*/
export const getSuggestedColors = (brandColor: string = DEFAULT_BRAND_COLOR) => {
// Question / dark text: brand darkened with black (visible brand tint)
const questionColor = mixColor(brandColor, "#000000", 0.35);
// Input / option background: white with noticeable brand tint
const inputBg = mixColor(brandColor, "#ffffff", 0.92);
// Input border: visible brand-tinted border
const inputBorder = mixColor(brandColor, "#ffffff", 0.6);
// Card tones
const cardBg = mixColor(brandColor, "#ffffff", 0.97);
const cardBorder = mixColor(brandColor, "#ffffff", 0.8);
// Page background
const pageBg = mixColor(brandColor, "#ffffff", 0.855);
return {
// General
"brandColor.light": brandColor,
"questionColor.light": questionColor,
// Headlines & Descriptions — use questionColor to match the legacy behaviour
// where all text elements derived their color from questionColor.
"elementHeadlineColor.light": questionColor,
"elementDescriptionColor.light": questionColor,
"elementUpperLabelColor.light": questionColor,
// Buttons — use the brand color so the button matches the user's intent.
"buttonBgColor.light": brandColor,
"buttonTextColor.light": isLight(brandColor) ? "#0f172a" : "#ffffff",
// Inputs
"inputColor.light": inputBg,
"inputBorderColor.light": inputBorder,
"inputTextColor.light": questionColor,
// Options (Radio / Checkbox)
"optionBgColor.light": inputBg,
"optionLabelColor.light": questionColor,
// Card
"cardBackgroundColor.light": cardBg,
"cardBorderColor.light": cardBorder,
// Highlight / Focus
"highlightBorderColor.light": mixColor(brandColor, "#ffffff", 0.25),
// Progress Bar — indicator uses the brand color; track is a lighter tint.
"progressIndicatorBgColor.light": brandColor,
"progressTrackBgColor.light": mixColor(brandColor, "#ffffff", 0.8),
// Background
background: { bg: pageBg, bgType: "color" as const, brightness: 100 },
};
};
// Pre-compute colors derived from the default brand color.
const _colors = getSuggestedColors(DEFAULT_BRAND_COLOR);
/**
* Single source of truth for every styling default.
*
* Color values are derived from the default brand color (#64748b) via
* `getSuggestedColors()`. Non-color values (dimensions, weights, sizes)
* are hardcoded here and must be kept in sync with globals.css.
*
* Used everywhere: form defaults, preview rendering, email templates,
* and as the reset target for "Restore defaults".
*/
export const STYLE_DEFAULTS: TProjectStyling = {
allowStyleOverwrite: true,
brandColor: {
light: COLOR_DEFAULTS.brandColor,
},
questionColor: {
light: COLOR_DEFAULTS.questionColor,
},
inputColor: {
light: COLOR_DEFAULTS.inputColor,
},
inputBorderColor: {
light: COLOR_DEFAULTS.inputBorderColor,
},
cardBackgroundColor: {
light: COLOR_DEFAULTS.cardBackgroundColor,
},
cardBorderColor: {
light: COLOR_DEFAULTS.cardBorderColor,
},
brandColor: { light: _colors["brandColor.light"] },
questionColor: { light: _colors["questionColor.light"] },
inputColor: { light: _colors["inputColor.light"] },
inputBorderColor: { light: _colors["inputBorderColor.light"] },
cardBackgroundColor: { light: _colors["cardBackgroundColor.light"] },
cardBorderColor: { light: _colors["cardBorderColor.light"] },
isLogoHidden: false,
highlightBorderColor: undefined,
highlightBorderColor: { light: _colors["highlightBorderColor.light"] },
isDarkModeEnabled: false,
roundness: 8,
cardArrangement: {
linkSurveys: "straight",
appSurveys: "straight",
},
cardArrangement: { linkSurveys: "simple", appSurveys: "simple" },
// Headlines & Descriptions
elementHeadlineColor: { light: _colors["elementHeadlineColor.light"] },
elementHeadlineFontSize: 16,
elementHeadlineFontWeight: 600,
elementDescriptionColor: { light: _colors["elementDescriptionColor.light"] },
elementDescriptionFontSize: 14,
elementDescriptionFontWeight: 400,
elementUpperLabelColor: { light: _colors["elementUpperLabelColor.light"] },
elementUpperLabelFontSize: 12,
elementUpperLabelFontWeight: 400,
// Inputs
inputTextColor: { light: _colors["inputTextColor.light"] },
inputBorderRadius: 8,
inputHeight: 40,
inputFontSize: 14,
inputPaddingX: 16,
inputPaddingY: 16,
inputPlaceholderOpacity: 0.5,
inputShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
// Buttons
buttonBgColor: { light: _colors["buttonBgColor.light"] },
buttonTextColor: { light: _colors["buttonTextColor.light"] },
buttonBorderRadius: 8,
buttonHeight: "auto",
buttonFontSize: 16,
buttonFontWeight: 500,
buttonPaddingX: 12,
buttonPaddingY: 12,
// Options
optionBgColor: { light: _colors["optionBgColor.light"] },
optionLabelColor: { light: _colors["optionLabelColor.light"] },
optionBorderRadius: 8,
optionPaddingX: 16,
optionPaddingY: 16,
optionFontSize: 14,
// Progress Bar
progressTrackHeight: 8,
progressTrackBgColor: { light: _colors["progressTrackBgColor.light"] },
progressIndicatorBgColor: { light: _colors["progressIndicatorBgColor.light"] },
};
/**
* Builds a complete TProjectStyling object from a single brand color.
*
* Uses STYLE_DEFAULTS for all non-color properties (dimensions, weights, etc.)
* and derives every color from the given brand color via getSuggestedColors().
*
* Useful when only a brand color is known (e.g. onboarding) and a fully
* coherent styling object is needed for both preview rendering and persistence.
*/
export const buildStylingFromBrandColor = (brandColor: string = DEFAULT_BRAND_COLOR): TProjectStyling => {
const colors = getSuggestedColors(brandColor);
return {
...STYLE_DEFAULTS,
brandColor: { light: colors["brandColor.light"] },
questionColor: { light: colors["questionColor.light"] },
elementHeadlineColor: { light: colors["elementHeadlineColor.light"] },
elementDescriptionColor: { light: colors["elementDescriptionColor.light"] },
elementUpperLabelColor: { light: colors["elementUpperLabelColor.light"] },
buttonBgColor: { light: colors["buttonBgColor.light"] },
buttonTextColor: { light: colors["buttonTextColor.light"] },
inputColor: { light: colors["inputColor.light"] },
inputBorderColor: { light: colors["inputBorderColor.light"] },
inputTextColor: { light: colors["inputTextColor.light"] },
optionBgColor: { light: colors["optionBgColor.light"] },
optionLabelColor: { light: colors["optionLabelColor.light"] },
cardBackgroundColor: { light: colors["cardBackgroundColor.light"] },
cardBorderColor: { light: colors["cardBorderColor.light"] },
highlightBorderColor: { light: colors["highlightBorderColor.light"] },
progressIndicatorBgColor: { light: colors["progressIndicatorBgColor.light"] },
progressTrackBgColor: { light: colors["progressTrackBgColor.light"] },
background: colors.background,
};
};
+2 -1
View File
@@ -85,7 +85,7 @@ export const mockProject: TProject = {
inAppSurveyBranding: false,
placement: "bottomRight",
clickOutsideClose: false,
darkOverlay: false,
overlay: "none",
environments: [],
languages: [],
config: {
@@ -208,6 +208,7 @@ const baseSurveyProperties = {
},
],
isBackButtonHidden: false,
isCaptureIpEnabled: false,
endings: [
{
id: "umyknohldc7w26ocjdhaa62c",
+4
View File
@@ -308,6 +308,10 @@ describe("Tests for updateSurvey", () => {
const updatedSurvey = await updateSurvey(updateSurveyInput);
expect(updatedSurvey).toEqual(mockTransformedSurveyOutput);
});
// Note: Language handling tests (for languages.length > 0 fix) are covered in
// apps/web/modules/survey/editor/lib/survey.test.ts where we have better control
// over the test mocks. The key fix ensures languages.length > 0 (not > 1) is used.
});
describe("Sad Path", () => {
+2 -1
View File
@@ -56,6 +56,7 @@ export const selectSurvey = {
isVerifyEmailEnabled: true,
isSingleResponsePerEmailEnabled: true,
isBackButtonHidden: true,
isCaptureIpEnabled: true,
redirectUrl: true,
projectOverwrites: true,
styling: true,
@@ -328,7 +329,7 @@ export const updateSurveyInternal = async (
? currentSurvey.languages.map((l) => l.language.id)
: [];
const updatedLanguageIds =
languages.length > 1 ? updatedSurvey.languages.map((l) => l.language.id) : [];
languages.length > 0 ? updatedSurvey.languages.map((l) => l.language.id) : [];
const enabledLanguageIds = languages.map((language) => {
if (language.enabled) return language.language.id;
});
+47
View File
@@ -141,5 +141,52 @@ describe("Time Utilities", () => {
expect(convertDatesInObject("string")).toBe("string");
expect(convertDatesInObject(123)).toBe(123);
});
test("should not convert dates in contactAttributes", () => {
const input = {
createdAt: "2024-03-20T15:30:00",
contactAttributes: {
createdAt: "2024-03-20T16:30:00",
email: "test@example.com",
},
};
const result = convertDatesInObject(input);
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.contactAttributes.createdAt).toBe("2024-03-20T16:30:00");
expect(result.contactAttributes.email).toBe("test@example.com");
});
test("should not convert dates in variables", () => {
const input = {
updatedAt: "2024-03-20T15:30:00",
variables: {
createdAt: "2024-03-20T16:30:00",
userId: "123",
},
};
const result = convertDatesInObject(input);
expect(result.updatedAt).toBeInstanceOf(Date);
expect(result.variables.createdAt).toBe("2024-03-20T16:30:00");
expect(result.variables.userId).toBe("123");
});
test("should not convert dates in data or meta", () => {
const input = {
createdAt: "2024-03-20T15:30:00",
data: {
createdAt: "2024-03-20T16:30:00",
},
meta: {
updatedAt: "2024-03-20T17:30:00",
},
};
const result = convertDatesInObject(input);
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.data.createdAt).toBe("2024-03-20T16:30:00");
expect(result.meta.updatedAt).toBe("2024-03-20T17:30:00");
});
});
});
+20 -13
View File
@@ -1,5 +1,5 @@
import { formatDistance, intlFormat } from "date-fns";
import { de, enUS, es, fr, ja, nl, pt, ptBR, ro, ru, sv, zhCN, zhTW } from "date-fns/locale";
import { de, enUS, es, fr, hu, ja, nl, pt, ptBR, ro, ru, sv, zhCN, zhTW } from "date-fns/locale";
import { TUserLocale } from "@formbricks/types/user";
export const convertDateString = (dateString: string | null) => {
@@ -87,28 +87,30 @@ const getLocaleForTimeSince = (locale: TUserLocale) => {
return de;
case "en-US":
return enUS;
case "pt-BR":
return ptBR;
case "es-ES":
return es;
case "fr-FR":
return fr;
case "hu-HU":
return hu;
case "ja-JP":
return ja;
case "nl-NL":
return nl;
case "sv-SE":
return sv;
case "zh-Hant-TW":
return zhTW;
case "pt-BR":
return ptBR;
case "pt-PT":
return pt;
case "ro-RO":
return ro;
case "ja-JP":
return ja;
case "zh-Hans-CN":
return zhCN;
case "es-ES":
return es;
case "ru-RU":
return ru;
case "sv-SE":
return sv;
case "zh-Hans-CN":
return zhCN;
case "zh-Hant-TW":
return zhTW;
}
};
@@ -158,7 +160,12 @@ export const convertDatesInObject = <T>(obj: T): T => {
return obj.map((item) => convertDatesInObject(item)) as unknown as T;
}
const newObj: any = {};
const keysToIgnore = new Set(["contactAttributes", "variables", "data", "meta"]);
for (const key in obj) {
if (keysToIgnore.has(key)) {
newObj[key] = obj[key];
continue;
}
if (
(key === "createdAt" || key === "updatedAt") &&
typeof obj[key] === "string" &&
@@ -0,0 +1,261 @@
import * as Sentry from "@sentry/nextjs";
import { getServerSession } from "next-auth";
import { DEFAULT_SERVER_ERROR_MESSAGE } from "next-safe-action";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import {
AuthenticationError,
AuthorizationError,
EXPECTED_ERROR_NAMES,
InvalidInputError,
OperationNotAllowedError,
ResourceNotFoundError,
TooManyRequestsError,
UnknownError,
ValidationError,
isExpectedError,
} from "@formbricks/types/errors";
// Mock Sentry
vi.mock("@sentry/nextjs", () => ({
captureException: vi.fn(),
}));
// Mock logger — use plain functions for chained calls so vi.resetAllMocks() doesn't break them
vi.mock("@formbricks/logger", () => ({
logger: {
withContext: () => ({ error: vi.fn() }),
warn: vi.fn(),
},
}));
// Mock next-auth
vi.mock("next-auth", () => ({
getServerSession: vi.fn(),
}));
// Mock authOptions
vi.mock("@/modules/auth/lib/authOptions", () => ({
authOptions: {},
}));
// Mock user service
vi.mock("@/lib/user/service", () => ({
getUser: vi.fn(),
}));
// Mock client IP
vi.mock("@/lib/utils/client-ip", () => ({
getClientIpFromHeaders: vi.fn(),
}));
// Mock constants
vi.mock("@/lib/constants", () => ({
AUDIT_LOG_ENABLED: false,
AUDIT_LOG_GET_USER_IP: false,
}));
// Mock audit log types
vi.mock("@/modules/ee/audit-logs/types/audit-log", () => ({
UNKNOWN_DATA: "unknown",
}));
// ── shared helper tests (pure logic, no action client needed) ──────────
describe("isExpectedError (shared helper)", () => {
test("EXPECTED_ERROR_NAMES contains exactly the right error names", () => {
const expected = [
"ResourceNotFoundError",
"AuthorizationError",
"InvalidInputError",
"ValidationError",
"AuthenticationError",
"OperationNotAllowedError",
"TooManyRequestsError",
];
expect(EXPECTED_ERROR_NAMES.size).toBe(expected.length);
for (const name of expected) {
expect(EXPECTED_ERROR_NAMES.has(name)).toBe(true);
}
});
test.each([
{ ErrorClass: AuthorizationError, args: ["Not authorized"] },
{ ErrorClass: AuthenticationError, args: ["Not authenticated"] },
{ ErrorClass: TooManyRequestsError, args: ["Rate limit exceeded"] },
{ ErrorClass: ResourceNotFoundError, args: ["Survey", "123"] },
{ ErrorClass: InvalidInputError, args: ["Invalid input"] },
{ ErrorClass: ValidationError, args: ["Invalid data"] },
{ ErrorClass: OperationNotAllowedError, args: ["Not allowed"] },
])("returns true for $ErrorClass.name", ({ ErrorClass, args }) => {
const error = new (ErrorClass as any)(...args);
expect(isExpectedError(error)).toBe(true);
});
test("returns true for serialised errors that only have a matching name", () => {
const serialisedError = new Error("Auth failed");
serialisedError.name = "AuthorizationError";
expect(isExpectedError(serialisedError)).toBe(true);
});
test.each([
{ error: new Error("Something broke"), label: "Error" },
{ error: new TypeError("Cannot read properties"), label: "TypeError" },
{ error: new RangeError("Maximum call stack"), label: "RangeError" },
{ error: new UnknownError("Unknown"), label: "UnknownError" },
])("returns false for $label", ({ error }) => {
expect(isExpectedError(error)).toBe(false);
});
});
// ── integration tests against the real actionClient / authenticatedActionClient ──
describe("actionClient handleServerError", () => {
// Lazily import so mocks are in place first
let actionClient: (typeof import("./index"))["actionClient"];
beforeEach(async () => {
vi.clearAllMocks();
const mod = await import("./index");
actionClient = mod.actionClient;
});
afterEach(() => {
vi.clearAllMocks();
});
// Helper: create and execute an action that throws the given error
const executeThrowingAction = async (error: Error) => {
const action = actionClient.action(async () => {
throw error;
});
return action();
};
describe("expected errors should NOT be reported to Sentry", () => {
test("AuthorizationError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new AuthorizationError("Not authorized"));
expect(result?.serverError).toBe("Not authorized");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("AuthenticationError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new AuthenticationError("Not authenticated"));
expect(result?.serverError).toBe("Not authenticated");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("TooManyRequestsError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new TooManyRequestsError("Rate limit exceeded"));
expect(result?.serverError).toBe("Rate limit exceeded");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("ResourceNotFoundError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new ResourceNotFoundError("Survey", "123"));
expect(result?.serverError).toContain("Survey");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("InvalidInputError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new InvalidInputError("Invalid input"));
expect(result?.serverError).toBe("Invalid input");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("ValidationError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new ValidationError("Invalid data"));
expect(result?.serverError).toBe("Invalid data");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("OperationNotAllowedError returns its message and is not sent to Sentry", async () => {
const result = await executeThrowingAction(new OperationNotAllowedError("Not allowed"));
expect(result?.serverError).toBe("Not allowed");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
});
describe("unexpected errors SHOULD be reported to Sentry", () => {
test("generic Error is sent to Sentry and returns default message", async () => {
const error = new Error("Something broke");
const result = await executeThrowingAction(error);
expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE);
expect(Sentry.captureException).toHaveBeenCalledWith(
error,
expect.objectContaining({ extra: expect.any(Object) })
);
});
test("TypeError is sent to Sentry and returns default message", async () => {
const error = new TypeError("Cannot read properties of undefined");
const result = await executeThrowingAction(error);
expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE);
expect(Sentry.captureException).toHaveBeenCalledWith(
error,
expect.objectContaining({ extra: expect.any(Object) })
);
});
test("UnknownError is sent to Sentry (not an expected business-logic error)", async () => {
const error = new UnknownError("Unknown error");
const result = await executeThrowingAction(error);
expect(result?.serverError).toBe(DEFAULT_SERVER_ERROR_MESSAGE);
expect(Sentry.captureException).toHaveBeenCalledWith(
error,
expect.objectContaining({ extra: expect.any(Object) })
);
});
});
});
describe("authenticatedActionClient", () => {
let authenticatedActionClient: (typeof import("./index"))["authenticatedActionClient"];
let getUser: (typeof import("@/lib/user/service"))["getUser"];
beforeEach(async () => {
vi.clearAllMocks();
const mod = await import("./index");
authenticatedActionClient = mod.authenticatedActionClient;
const userService = await import("@/lib/user/service");
getUser = userService.getUser;
});
afterEach(() => {
vi.clearAllMocks();
});
test("throws AuthenticationError when there is no session", async () => {
vi.mocked(getServerSession).mockResolvedValue(null);
const action = authenticatedActionClient.action(async () => "ok");
const result = await action();
// handleServerError catches AuthenticationError and returns its message
expect(result?.serverError).toBe("Not authenticated");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("throws AuthorizationError when user is not found", async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { id: "user-1" } });
vi.mocked(getUser).mockResolvedValue(null as any);
const action = authenticatedActionClient.action(async () => "ok");
const result = await action();
expect(result?.serverError).toBe("User not found");
expect(Sentry.captureException).not.toHaveBeenCalled();
});
test("executes action successfully when session and user exist", async () => {
vi.mocked(getServerSession).mockResolvedValue({ user: { id: "user-1" } });
vi.mocked(getUser).mockResolvedValue({ id: "user-1", name: "Test" } as any);
const action = authenticatedActionClient.action(async () => "success");
const result = await action();
expect(result?.data).toBe("success");
expect(result?.serverError).toBeUndefined();
expect(Sentry.captureException).not.toHaveBeenCalled();
});
});
+7 -21
View File
@@ -3,15 +3,7 @@ import { getServerSession } from "next-auth";
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
import { v4 as uuidv4 } from "uuid";
import { logger } from "@formbricks/logger";
import {
AuthenticationError,
AuthorizationError,
InvalidInputError,
OperationNotAllowedError,
ResourceNotFoundError,
TooManyRequestsError,
UnknownError,
} from "@formbricks/types/errors";
import { AuthenticationError, AuthorizationError, isExpectedError } from "@formbricks/types/errors";
import { AUDIT_LOG_ENABLED, AUDIT_LOG_GET_USER_IP } from "@/lib/constants";
import { getUser } from "@/lib/user/service";
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
@@ -22,24 +14,18 @@ import { ActionClientCtx } from "./types/context";
export const actionClient = createSafeActionClient({
handleServerError(e, utils) {
const eventId = (utils.ctx as Record<string, any>)?.auditLoggingCtx?.eventId ?? undefined; // keep explicit fallback
if (isExpectedError(e)) {
return e.message;
}
// Only capture unexpected errors to Sentry
Sentry.captureException(e, {
extra: {
eventId,
},
});
if (
e instanceof ResourceNotFoundError ||
e instanceof AuthorizationError ||
e instanceof InvalidInputError ||
e instanceof UnknownError ||
e instanceof AuthenticationError ||
e instanceof OperationNotAllowedError ||
e instanceof TooManyRequestsError
) {
return e.message;
}
// eslint-disable-next-line no-console -- This error needs to be logged for debugging server-side errors
logger.withContext({ eventId }).error(e, "SERVER ERROR");
return DEFAULT_SERVER_ERROR_MESSAGE;
+1 -2
View File
@@ -90,11 +90,10 @@ describe("locale", () => {
// Verify sv-SE is in AVAILABLE_LOCALES
expect(AVAILABLE_LOCALES).toContain("sv-SE");
// Verify Swedish has a language entry with proper labels
// Verify Swedish has a language entry with proper label
const swedishLanguage = appLanguages.find((lang) => lang.code === "sv-SE");
expect(swedishLanguage).toBeDefined();
expect(swedishLanguage?.label["en-US"]).toBe("Swedish");
expect(swedishLanguage?.label["sv-SE"]).toBe("Svenska");
// Verify the locale can be matched from Accept-Language header
vi.mocked(nextHeaders.headers).mockReturnValue({
+17 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, test, vi } from "vitest";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { getLocale } from "@/lingodotdev/language";
import { getTranslate } from "./server";
@@ -11,6 +11,10 @@ vi.mock("@/lingodotdev/shared", () => ({
}));
describe("lingodotdev server", () => {
beforeEach(() => {
vi.clearAllMocks();
});
test("should get translate", async () => {
vi.mocked(getLocale).mockResolvedValue("en-US");
const translate = await getTranslate();
@@ -22,4 +26,16 @@ describe("lingodotdev server", () => {
const translate = await getTranslate();
expect(translate).toBeDefined();
});
test("should use provided locale instead of calling getLocale", async () => {
const translate = await getTranslate("de-DE");
expect(getLocale).not.toHaveBeenCalled();
expect(translate).toBeDefined();
});
test("should call getLocale when locale is not provided", async () => {
vi.mocked(getLocale).mockResolvedValue("fr-FR");
await getTranslate();
expect(getLocale).toHaveBeenCalled();
});
});
+5 -4
View File
@@ -2,6 +2,7 @@ import { createInstance } from "i18next";
import ICU from "i18next-icu";
import resourcesToBackend from "i18next-resources-to-backend";
import { initReactI18next } from "react-i18next/initReactI18next";
import { TUserLocale } from "@formbricks/types/user";
import { DEFAULT_LOCALE } from "@/lib/constants";
import { getLocale } from "@/lingodotdev/language";
@@ -21,9 +22,9 @@ const initI18next = async (lng: string) => {
return i18nInstance;
};
export async function getTranslate() {
const locale = await getLocale();
export async function getTranslate(locale?: TUserLocale) {
const resolvedLocale = locale ?? (await getLocale());
const i18nextInstance = await initI18next(locale);
return i18nextInstance.getFixedT(locale);
const i18nextInstance = await initI18next(resolvedLocale);
return i18nextInstance.getFixedT(resolvedLocale);
}
+174 -39
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Mix aus Groß- und Kleinbuchstaben",
"please_verify_captcha": "Bitte bestätige reCAPTCHA",
"privacy_policy": "Datenschutzerklärung",
"product_updates_description": "Monatliche Produktneuigkeiten und Feature-Updates, es gilt die Datenschutzerklärung.",
"product_updates_title": "Produkt-Updates",
"security_updates_description": "Nur sicherheitsrelevante Informationen, es gilt die Datenschutzerklärung.",
"security_updates_title": "Sicherheits-Updates",
"terms_of_service": "Nutzungsbedingungen",
"title": "Erstelle dein Formbricks-Konto"
},
@@ -197,6 +201,7 @@
"docs": "Dokumentation",
"documentation": "Dokumentation",
"domain": "Domain",
"done": "Fertig",
"download": "Herunterladen",
"draft": "Entwurf",
"duplicate": "Duplikat",
@@ -238,7 +243,6 @@
"imprint": "Impressum",
"in_progress": "Im Gange",
"inactive_surveys": "Inaktive Umfragen",
"input_type": "Eingabetyp",
"integration": "Integration",
"integrations": "Integrationen",
"invalid_date": "Ungültiges Datum",
@@ -250,6 +254,7 @@
"label": "Bezeichnung",
"language": "Sprache",
"learn_more": "Mehr erfahren",
"license_expired": "License Expired",
"light_overlay": "Helle Überlagerung",
"limits_reached": "Limits erreicht",
"link": "Link",
@@ -262,13 +267,11 @@
"look_and_feel": "Darstellung",
"manage": "Verwalten",
"marketing": "Marketing",
"maximum": "Maximal",
"member": "Mitglied",
"members": "Mitglieder",
"members_and_teams": "Mitglieder & Teams",
"membership_not_found": "Mitgliedschaft nicht gefunden",
"metadata": "Metadaten",
"minimum": "Minimum",
"mobile_overlay_app_works_best_on_desktop": "Formbricks funktioniert am besten auf einem größeren Bildschirm. Um Umfragen zu verwalten oder zu erstellen, wechsle zu einem anderen Gerät.",
"mobile_overlay_surveys_look_good": "Keine Sorge deine Umfragen sehen auf jedem Gerät und jeder Bildschirmgröße großartig aus!",
"mobile_overlay_title": "Oops, Bildschirm zu klein erkannt!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Kein Hintergrundbild gefunden.",
"no_code": "No Code",
"no_files_uploaded": "Keine Dateien hochgeladen",
"no_overlay": "Kein Overlay",
"no_quotas_found": "Keine Kontingente gefunden",
"no_result_found": "Kein Ergebnis gefunden",
"no_results": "Keine Ergebnisse",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Organisations-Teams nicht gefunden",
"other": "Andere",
"others": "Andere",
"overlay_color": "Overlay-Farbe",
"overview": "Überblick",
"password": "Passwort",
"paused": "Pausiert",
@@ -321,7 +326,7 @@
"placeholder": "Platzhalter",
"please_select_at_least_one_survey": "Bitte wähle mindestens eine Umfrage aus",
"please_select_at_least_one_trigger": "Bitte wähle mindestens einen Auslöser aus",
"please_upgrade_your_plan": "Bitte upgrade deinen Plan.",
"please_upgrade_your_plan": "Bitte aktualisieren Sie Ihren Plan",
"preview": "Vorschau",
"preview_survey": "Umfragevorschau",
"privacy": "Datenschutz",
@@ -347,6 +352,7 @@
"request_trial_license": "Testlizenz anfordern",
"reset_to_default": "Auf Standard zurücksetzen",
"response": "Antwort",
"response_id": "Antwort-ID",
"responses": "Antworten",
"restart": "Neustart",
"role": "Rolle",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Sie haben Ihr Limit von {projectLimit} Workspaces erreicht.",
"you_have_reached_your_monthly_miu_limit_of": "Du hast dein monatliches MIU-Limit erreicht",
"you_have_reached_your_monthly_response_limit_of": "Du hast dein monatliches Antwortlimit erreicht",
"you_will_be_downgraded_to_the_community_edition_on_date": "Du wirst am {date} auf die Community Edition herabgestuft."
"you_will_be_downgraded_to_the_community_edition_on_date": "Du wirst am {date} auf die Community Edition herabgestuft.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Annehmen",
@@ -783,20 +790,26 @@
"add_webhook": "Webhook hinzufügen",
"add_webhook_description": "Sende Umfragedaten an einen benutzerdefinierten Endpunkt",
"all_current_and_new_surveys": "Alle aktuellen und neuen Umfragen",
"copy_secret_now": "Signierungsschlüssel kopieren",
"created_by_third_party": "Erstellt von einer dritten Partei",
"discord_webhook_not_supported": "Discord-Webhooks werden derzeit nicht unterstützt.",
"empty_webhook_message": "Deine Webhooks werden hier angezeigt, sobald Du sie hinzufügst ⏲️",
"endpoint_pinged": "Juhu! Wir können den Webhook anpingen!",
"endpoint_pinged_error": "Kann den Webhook nicht anpingen!",
"learn_to_verify": "Erfahren Sie, wie Sie Webhook-Signaturen verifizieren",
"please_check_console": "Bitte überprüfe die Konsole für weitere Details",
"please_enter_a_url": "Bitte gib eine URL ein",
"response_created": "Antwort erstellt",
"response_finished": "Antwort abgeschlossen",
"response_updated": "Antwort aktualisiert",
"secret_copy_warning": "Bewahren Sie diesen Schlüssel sicher auf. Sie können ihn erneut in den Webhook-Einstellungen einsehen.",
"secret_description": "Verwenden Sie diesen Schlüssel, um Webhook-Anfragen zu verifizieren. Siehe Dokumentation zur Signaturverifizierung.",
"signing_secret": "Signierungsschlüssel",
"source": "Quelle",
"test_endpoint": "Test-Endpunkt",
"triggers": "Auslöser",
"webhook_added_successfully": "Webhook wurde erfolgreich hinzugefügt",
"webhook_created": "Webhook erstellt",
"webhook_delete_confirmation": "Bist Du sicher, dass Du diesen Webhook löschen möchtest? Dadurch werden dir keine weiteren Benachrichtigungen mehr gesendet.",
"webhook_deleted_successfully": "Webhook erfolgreich gelöscht",
"webhook_name_placeholder": "Optional: Benenne deinen Webhook zur einfachen Identifizierung",
@@ -943,19 +956,32 @@
"enterprise_features": "Unternehmensfunktionen",
"get_an_enterprise_license_to_get_access_to_all_features": "Hol dir eine Enterprise-Lizenz, um Zugriff auf alle Funktionen zu erhalten.",
"keep_full_control_over_your_data_privacy_and_security": "Behalte die volle Kontrolle über deine Daten, Privatsphäre und Sicherheit.",
"license_invalid_description": "Der Lizenzschlüssel in deiner ENTERPRISE_LICENSE_KEY-Umgebungsvariable ist nicht gültig. Bitte überprüfe auf Tippfehler oder fordere einen neuen Schlüssel an.",
"license_status": "Lizenzstatus",
"license_status_active": "Aktiv",
"license_status_description": "Status deiner Enterprise-Lizenz.",
"license_status_expired": "Abgelaufen",
"license_status_invalid": "Ungültige Lizenz",
"license_status_unreachable": "Nicht erreichbar",
"license_unreachable_grace_period": "Der Lizenzserver ist nicht erreichbar. Deine Enterprise-Funktionen bleiben während einer 3-tägigen Kulanzfrist bis zum {gracePeriodEnd} aktiv.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Ganz unkompliziert: Fordere eine kostenlose 30-Tage-Testlizenz an, um alle Funktionen zu testen, indem Du dieses Formular ausfüllst:",
"no_credit_card_no_sales_call_just_test_it": "Keine Kreditkarte. Kein Verkaufsgespräch. Einfach testen :)",
"on_request": "Auf Anfrage",
"organization_roles": "Organisationsrollen (Admin, Editor, Entwickler, etc.)",
"questions_please_reach_out_to": "Fragen? Bitte melde Dich bei",
"recheck_license": "Lizenz erneut prüfen",
"recheck_license_failed": "Lizenzprüfung fehlgeschlagen. Der Lizenzserver ist möglicherweise nicht erreichbar.",
"recheck_license_invalid": "Der Lizenzschlüssel ist ungültig. Bitte überprüfe deinen ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Lizenzprüfung erfolgreich",
"recheck_license_unreachable": "Lizenzserver ist nicht erreichbar. Bitte versuche es später erneut.",
"rechecking": "Wird erneut geprüft...",
"request_30_day_trial_license": "30-Tage-Testlizenz anfordern",
"saml_sso": "SAML-SSO",
"service_level_agreement": "Service-Level-Vereinbarung",
"soc2_hipaa_iso_27001_compliance_check": "SOC2-, HIPAA- und ISO 27001-Konformitätsprüfung",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Teams & Zugriffskontrolle (Lesen, Lesen & Schreiben, Verwalten)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos.",
"your_enterprise_license_is_active_all_features_unlocked": "Deine Unternehmenslizenz ist aktiv. Alle Funktionen freigeschaltet."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos."
},
"general": {
"bulk_invite_warning_description": "Bitte beachte, dass im Free-Plan alle Organisationsmitglieder automatisch die Rolle \"Owner\" zugewiesen bekommen, unabhängig von der im CSV-File angegebenen Rolle.",
@@ -979,7 +1005,7 @@
"from_your_organization": "von deiner Organisation",
"invitation_sent_once_more": "Einladung nochmal gesendet.",
"invite_deleted_successfully": "Einladung erfolgreich gelöscht",
"invited_on": "Eingeladen am {date}",
"invite_expires_on": "Einladung läuft ab am {date}",
"invites_failed": "Einladungen fehlgeschlagen",
"leave_organization": "Organisation verlassen",
"leave_organization_description": "Du wirst diese Organisation verlassen und den Zugriff auf alle Umfragen und Antworten verlieren. Du kannst nur wieder beitreten, wenn Du erneut eingeladen wirst.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Logo entfernen",
"replace_logo": "Logo ersetzen",
"resend_invitation_email": "Einladungsemail erneut senden",
"security_list_tip": "Haben Sie sich für unsere Sicherheitsliste angemeldet? Bleiben Sie informiert, um Ihre Instanz sicher zu halten!",
"security_list_tip_link": "Hier registrieren.",
"share_invite_link": "Einladungslink teilen",
"share_this_link_to_let_your_organization_member_join_your_organization": "Teile diesen Link, damit dein Organisationsmitglied deiner Organisation beitreten kann:",
"test_email_sent_successfully": "Test-E-Mail erfolgreich gesendet",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Bitte füllen Sie alle Felder aus, um einen neuen Workspace hinzuzufügen.",
"read": "Lesen",
"read_write": "Lesen & Schreiben",
"select_member": "Mitglied auswählen",
"select_workspace": "Workspace auswählen",
"team_admin": "Team-Admin",
"team_created_successfully": "Team erfolgreich erstellt.",
"team_deleted_successfully": "Team erfolgreich gelöscht.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Platzhalter hinzufügen, falls kein Wert zur Verfügung steht.",
"add_hidden_field_id": "Verstecktes Feld ID hinzufügen",
"add_highlight_border": "Rahmen hinzufügen",
"add_highlight_border_description": "Füge deiner Umfragekarte einen äußeren Rahmen hinzu.",
"add_logic": "Logik hinzufügen",
"add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu",
"add_option": "Option hinzufügen",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Ändere die Nachricht, die Besucher sehen, wenn die Umfrage geschlossen ist.",
"adjust_the_theme_in_the": "Passe das Thema an in den",
"all_other_answers_will_continue_to": "Alle anderen Antworten werden weiterhin",
"allow_file_type": "Dateityp begrenzen",
"allow_multi_select": "Mehrfachauswahl erlauben",
"allow_multiple_files": "Mehrere Dateien zulassen",
"allow_users_to_select_more_than_one_image": "Erlaube Nutzern, mehr als ein Bild auszuwählen",
@@ -1169,6 +1193,9 @@
"assign": "Zuweisen =",
"audience": "Publikum",
"auto_close_on_inactivity": "Automatisches Schließen bei Inaktivität",
"auto_save_disabled": "Automatisches Speichern deaktiviert",
"auto_save_disabled_tooltip": "Ihre Umfrage wird nur im Entwurfsmodus automatisch gespeichert. So wird sichergestellt, dass öffentliche Umfragen nicht unbeabsichtigt aktualisiert werden.",
"auto_save_on": "Automatisches Speichern an",
"automatically_close_survey_after": "Umfrage automatisch schließen nach",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Schließe die Umfrage automatisch nach einer bestimmten Anzahl von Antworten.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Schließe die Umfrage automatisch, wenn der Benutzer nach einer bestimmten Anzahl von Sekunden nicht antwortet.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Block dupliziert.",
"bold": "Fett",
"brand_color": "Markenfarbe",
"brand_color_description": "Wird auf Buttons, Links und Hervorhebungen angewendet.",
"brightness": "Helligkeit",
"bulk_edit": "Massenbearbeitung",
"bulk_edit_description": "Bearbeiten Sie alle Optionen unten, eine pro Zeile. Leere Zeilen werden übersprungen und Duplikate entfernt.",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.com Benutzername oder Benutzername/Ereignis",
"calculate": "Berechnen",
"capture_a_new_action_to_trigger_a_survey_on": "Erfasse eine neue Aktion, um eine Umfrage auszulösen.",
"capture_ip_address": "IP-Adresse erfassen",
"capture_ip_address_description": "Speichern Sie die IP-Adresse des Befragten in den Antwort-Metadaten zur Duplikaterkennung und für Sicherheitszwecke",
"capture_new_action": "Neue Aktion erfassen",
"card_arrangement_for_survey_type_derived": "Kartenanordnung für {surveyTypeDerived} Umfragen",
"card_background_color": "Hintergrundfarbe der Karte",
"card_background_color_description": "Füllt den Bereich der Umfragekarte.",
"card_border_color": "Farbe des Kartenrandes",
"card_border_color_description": "Umrandet die Umfragekarte.",
"card_styling": "Kartengestaltung",
"casual": "Lässig",
"caution_edit_duplicate": "Duplizieren & bearbeiten",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Ältere und neuere Antworten vermischen sich, was zu irreführenden Datensummen führen kann.",
"caution_recommendation": "Dies kann im Umfrageübersicht zu Dateninkonsistenzen führen. Wir empfehlen stattdessen, die Umfrage zu duplizieren.",
"caution_text": "Änderungen werden zu Inkonsistenzen führen",
"centered_modal_overlay_color": "Zentrierte modale Überlagerungsfarbe",
"change_anyway": "Trotzdem ändern",
"change_background": "Hintergrund ändern",
"change_question_type": "Fragetyp ändern",
"change_survey_type": "Die Änderung des Umfragetypen kann vorhandenen Zugriff beeinträchtigen",
"change_the_background_color_of_the_card": "Hintergrundfarbe der Karte ändern.",
"change_the_background_color_of_the_input_fields": "Hintergrundfarbe der Eingabefelder ändern.",
"change_the_background_to_a_color_image_or_animation": "Hintergrund zu einer Farbe, einem Bild oder einer Animation ändern.",
"change_the_border_color_of_the_card": "Randfarbe der Karte ändern.",
"change_the_border_color_of_the_input_fields": "Randfarbe der Eingabefelder ändern.",
"change_the_border_radius_of_the_card_and_the_inputs": "Radius der Ränder der Karte und der Eingabefelder ändern.",
"change_the_brand_color_of_the_survey": "Markenfarbe der Umfrage ändern.",
"change_the_placement_of_this_survey": "Platzierung dieser Umfrage ändern.",
"change_the_question_color_of_the_survey": "Fragefarbe der Umfrage ändern.",
"changes_saved": "Änderungen gespeichert.",
"changing_survey_type_will_remove_existing_distribution_channels": "\"Das Ändern des Umfragetypen beeinflusst, wie er geteilt werden kann. Wenn Teilnehmer bereits Zugriffslinks für den aktuellen Typ haben, könnten sie das Zugriffsrecht nach dem Wechsel verlieren.\"",
"character_limit_toggle_description": "Begrenzen Sie, wie kurz oder lang eine Antwort sein kann.",
"character_limit_toggle_title": "Fügen Sie Zeichenbeschränkungen hinzu",
"checkbox_label": "Checkbox-Beschriftung",
"choose_the_actions_which_trigger_the_survey": "Aktionen auswählen, die die Umfrage auslösen.",
"choose_the_first_question_on_your_block": "Wählen sie die erste frage in ihrem block",
@@ -1241,7 +1263,6 @@
"contact_fields": "Kontaktfelder",
"contains": "enthält",
"continue_to_settings": "Weiter zu den Einstellungen",
"control_which_file_types_can_be_uploaded": "Steuere, welche Dateitypen hochgeladen werden können.",
"convert_to_multiple_choice": "In Multiple-Choice umwandeln",
"convert_to_single_choice": "In Einzelauswahl umwandeln",
"country": "Land",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Hintergrund deiner Wahl abdunkeln oder aufhellen.",
"date_format": "Datumsformat",
"days_before_showing_this_survey_again": "oder mehr Tage müssen zwischen der zuletzt angezeigten Umfrage und der Anzeige dieser Umfrage vergehen.",
"delete_anyways": "Trotzdem löschen",
"delete_block": "Block löschen",
"delete_choice": "Auswahl löschen",
"disable_the_visibility_of_survey_progress": "Deaktiviere die Sichtbarkeit des Umfragefortschritts.",
"display_an_estimate_of_completion_time_for_survey": "Zeige eine Schätzung der Fertigstellungszeit für die Umfrage an",
"display_number_of_responses_for_survey": "Anzahl der Antworten für Umfrage anzeigen",
"display_type": "Anzeigetyp",
"divide": "Teilen /",
"does_not_contain": "Enthält nicht",
"does_not_end_with": "Endet nicht mit",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Enthält nicht alle von",
"does_not_include_one_of": "Enthält nicht eines von",
"does_not_start_with": "Fängt nicht an mit",
"dropdown": "Dropdown",
"duplicate_block": "Block duplizieren",
"duplicate_question": "Frage duplizieren",
"edit_link": "Bearbeitungslink",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Fortschrittsbalken ausblenden",
"hide_question_settings": "Frageeinstellungen ausblenden",
"hostname": "Hostname",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Wie funky sollen deine Karten in {surveyTypeDerived} Umfragen sein",
"if_you_need_more_please": "Wenn Du mehr brauchst, bitte",
"if_you_need_more_please": "Wenn Sie mehr benötigen, bitte",
"if_you_really_want_that_answer_ask_until_you_get_it": "Weiterhin anzeigen, wenn ausgelöst, bis eine Antwort abgegeben wird.",
"ignore_global_waiting_time": "Abkühlphase ignorieren",
"ignore_global_waiting_time_description": "Diese Umfrage kann angezeigt werden, wenn ihre Bedingungen erfüllt sind, auch wenn kürzlich eine andere Umfrage angezeigt wurde.",
@@ -1368,7 +1391,9 @@
"initial_value": "Anfangswert",
"inner_text": "Innerer Text",
"input_border_color": "Randfarbe des Eingabefelds",
"input_border_color_description": "Umrandet Texteingaben und Textbereiche.",
"input_color": "Farbe des Eingabefelds",
"input_color_description": "Füllt das Innere von Texteingaben.",
"insert_link": "Link einfügen",
"invalid_targeting": "Ungültiges Targeting: Bitte überprüfe deine Zielgruppenfilter",
"invalid_video_url_warning": "Bitte gib eine gültige YouTube-, Vimeo- oder Loom-URL ein. Andere Video-Plattformen werden derzeit nicht unterstützt.",
@@ -1395,10 +1420,10 @@
"key": "Schlüssel",
"last_name": "Nachname",
"let_people_upload_up_to_25_files_at_the_same_time": "Erlaube bis zu 25 Dateien gleichzeitig hochzuladen.",
"limit_file_types": "Dateitypen einschränken",
"limit_the_maximum_file_size": "Maximale Dateigröße begrenzen",
"limit_upload_file_size_to": "Maximale Dateigröße für Uploads",
"limit_the_maximum_file_size": "Begrenzen Sie die maximale Dateigröße für Uploads.",
"limit_upload_file_size_to": "Upload-Dateigröße begrenzen auf",
"link_survey_description": "Teile einen Link zu einer Umfrageseite oder bette ihn in eine Webseite oder E-Mail ein.",
"list": "Liste",
"load_segment": "Segment laden",
"logic_error_warning": "Änderungen werden zu Logikfehlern führen",
"logic_error_warning_text": "Das Ändern des Fragetypen entfernt die Logikbedingungen von dieser Frage",
@@ -1409,8 +1434,8 @@
"manage_languages": "Sprachen verwalten",
"matrix_all_fields": "Alle Felder",
"matrix_rows": "Zeilen",
"max_file_size": "Max. Dateigröße",
"max_file_size_limit_is": "Max. Dateigröße ist",
"max_file_size": "Maximale Dateigröße",
"max_file_size_limit_is": "Die maximale Dateigrößenbeschränkung beträgt",
"move_question_to_block": "Frage in Block verschieben",
"multiply": "Multiplizieren *",
"needed_for_self_hosted_cal_com_instance": "Benötigt für eine selbstgehostete Cal.com-Instanz",
@@ -1442,22 +1467,22 @@
"picture_idx": "Bild {idx}",
"pin_can_only_contain_numbers": "PIN darf nur Zahlen enthalten.",
"pin_must_be_a_four_digit_number": "Die PIN muss eine vierstellige Zahl sein.",
"please_enter_a_file_extension": "Bitte gib eine Dateierweiterung ein.",
"please_enter_a_valid_url": "Bitte geben Sie eine gültige URL ein (z. B. https://beispiel.de)",
"please_set_a_survey_trigger": "Bitte richte einen Umfrage-Trigger ein",
"please_specify": "Bitte angeben",
"prevent_double_submission": "Doppeltes Anbschicken verhindern",
"prevent_double_submission_description": "Nur eine Antwort pro E-Mail-Adresse zulassen (beta)",
"progress_saved": "Fortschritt gespeichert",
"protect_survey_with_pin": "Umfrage mit einer PIN schützen",
"protect_survey_with_pin_description": "Nur Benutzer, die die PIN haben, können auf die Umfrage zugreifen.",
"publish": "Veröffentlichen",
"question": "Frage",
"question_color": "Fragefarbe",
"question_deleted": "Frage gelöscht.",
"question_duplicated": "Frage dupliziert.",
"question_id_updated": "Frage-ID aktualisiert",
"question_used_in_logic": "Diese Frage wird in der Logik der Frage {questionIndex} verwendet.",
"question_used_in_quota": "Diese Frage wird in der \"{quotaName}\" Quote verwendet",
"question_used_in_logic_warning_text": "Elemente aus diesem Block werden in einer Logikregel verwendet. Möchten Sie ihn wirklich löschen?",
"question_used_in_logic_warning_title": "Logikinkonsistenz",
"question_used_in_quota": "Diese Frage wird in der “{quotaName}” Quote verwendet",
"question_used_in_recall": "Diese Frage wird in Frage {questionIndex} abgerufen.",
"question_used_in_recall_ending_card": "Diese Frage wird in der Abschlusskarte abgerufen.",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Antwort Limits, Weiterleitungen und mehr.",
"response_options": "Antwortoptionen",
"roundness": "Rundheit",
"roundness_description": "Steuert, wie abgerundet die Kartenecken sind.",
"row_used_in_logic_error": "Diese Zeile wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.",
"rows": "Zeilen",
"save_and_close": "Speichern & Schließen",
@@ -1520,6 +1546,7 @@
"search_for_images": "Nach Bildern suchen",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "Sekunden nach dem Auslösen wird die Umfrage geschlossen, wenn keine Antwort erfolgt.",
"seconds_before_showing_the_survey": "Sekunden, bevor die Umfrage angezeigt wird.",
"select_field": "Feld auswählen",
"select_or_type_value": "Auswählen oder Wert eingeben",
"select_ordering": "Anordnung auswählen",
"select_saved_action": "Gespeicherte Aktion auswählen",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Styling auf Themenstile eingestellt",
"subheading": "Zwischenüberschrift",
"subtract": "Subtrahieren -",
"suggest_colors": "Farben vorschlagen",
"survey_completed_heading": "Umfrage abgeschlossen",
"survey_completed_subheading": "Diese kostenlose und quelloffene Umfrage wurde geschlossen",
"survey_display_settings": "Einstellungen zur Anzeige der Umfrage",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Einmal anzeigen, auch wenn sie nicht antworten.",
"then": "dann",
"this_action_will_remove_all_the_translations_from_this_survey": "Diese Aktion entfernt alle Übersetzungen aus dieser Umfrage.",
"this_extension_is_already_added": "Diese Erweiterung ist bereits hinzugefügt.",
"this_file_type_is_not_supported": "Dieser Dateityp wird nicht unterstützt.",
"three_points": "3 Punkte",
"times": "Zeiten",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Um die Platzierung über alle Umfragen hinweg konsistent zu halten, kannst du",
@@ -1589,8 +1613,51 @@
"upper_label": "Oberes Label",
"url_filters": "URL-Filter",
"url_not_supported": "URL nicht unterstützt",
"validation": {
"add_validation_rule": "Validierungsregel hinzufügen",
"answer_all_rows": "Alle Zeilen beantworten",
"characters": "Zeichen",
"contains": "enthält",
"delete_validation_rule": "Validierungsregel löschen",
"does_not_contain": "enthält nicht",
"email": "Ist gültige E-Mail",
"end_date": "Enddatum",
"file_extension_is": "Dateierweiterung ist",
"file_extension_is_not": "Dateierweiterung ist nicht",
"is": "ist",
"is_between": "ist zwischen",
"is_earlier_than": "ist früher als",
"is_greater_than": "ist größer als",
"is_later_than": "ist später als",
"is_less_than": "ist weniger als",
"is_not": "ist nicht",
"is_not_between": "ist nicht zwischen",
"kb": "KB",
"max_length": "Höchstens",
"max_selections": "Höchstens",
"max_value": "Höchstens",
"mb": "MB",
"min_length": "Mindestens",
"min_selections": "Mindestens",
"min_value": "Mindestens",
"minimum_options_ranked": "Mindestanzahl bewerteter Optionen",
"minimum_rows_answered": "Mindestanzahl beantworteter Zeilen",
"options_selected": "Optionen ausgewählt",
"pattern": "Entspricht Regex-Muster",
"phone": "Ist gültige Telefonnummer",
"rank_all_options": "Alle Optionen bewerten",
"select_file_extensions": "Dateierweiterungen auswählen...",
"select_option": "Option auswählen",
"start_date": "Startdatum",
"url": "Ist gültige URL"
},
"validation_logic_and": "Alle sind wahr",
"validation_logic_or": "mindestens eine ist wahr",
"validation_rules": "Validierungsregeln",
"validation_rules_description": "Nur Antworten akzeptieren, die die folgenden Kriterien erfüllen",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne es zuerst aus der Logik.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable \"{variableName}\" wird in der \"{quotaName}\" Quote verwendet",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variable {variableName} wird in der {quotaName} Quote verwendet",
"variable_name_conflicts_with_hidden_field": "Der Variablenname steht im Konflikt mit einer vorhandenen Hidden-Field-ID.",
"variable_name_is_already_taken_please_choose_another": "Variablenname ist bereits vergeben, bitte wähle einen anderen.",
"variable_name_must_start_with_a_letter": "Variablenname muss mit einem Buchstaben beginnen.",
"variable_used_in_recall": "Variable \"{variable}\" wird in Frage {questionIndex} abgerufen.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Beim Herunterladen der Antworten ist ein Fehler aufgetreten",
"first_name": "Vorname",
"how_to_identify_users": "Wie man Benutzer identifiziert",
"ip_address": "IP-Adresse",
"last_name": "Nachname",
"not_completed": "Nicht abgeschlossen ⏳",
"os": "Betriebssystem",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Hintergrundfarbe hinzufügen",
"add_background_color_description": "Füge dem Logo-Container eine Hintergrundfarbe hinzu.",
"advanced_styling_field_border_radius": "Rahmenradius",
"advanced_styling_field_button_bg": "Button-Hintergrund",
"advanced_styling_field_button_bg_description": "Füllt den Weiter-/Absenden-Button.",
"advanced_styling_field_button_border_radius_description": "Rundet die Button-Ecken ab.",
"advanced_styling_field_button_font_size_description": "Skaliert den Text der Button-Beschriftung.",
"advanced_styling_field_button_font_weight_description": "Macht den Button-Text heller oder fetter.",
"advanced_styling_field_button_height_description": "Steuert die Button-Höhe.",
"advanced_styling_field_button_padding_x_description": "Fügt links und rechts Abstand hinzu.",
"advanced_styling_field_button_padding_y_description": "Fügt oben und unten Abstand hinzu.",
"advanced_styling_field_button_text": "Button-Text",
"advanced_styling_field_button_text_description": "Färbt die Beschriftung innerhalb von Buttons.",
"advanced_styling_field_description_color": "Beschreibungsfarbe",
"advanced_styling_field_description_color_description": "Färbt den Text unterhalb jeder Überschrift.",
"advanced_styling_field_description_size": "Schriftgröße der Beschreibung",
"advanced_styling_field_description_size_description": "Skaliert den Beschreibungstext.",
"advanced_styling_field_description_weight": "Schriftstärke der Beschreibung",
"advanced_styling_field_description_weight_description": "Macht den Beschreibungstext heller oder fetter.",
"advanced_styling_field_font_size": "Schriftgröße",
"advanced_styling_field_font_weight": "Schriftstärke",
"advanced_styling_field_headline_color": "Überschriftsfarbe",
"advanced_styling_field_headline_color_description": "Färbt den Hauptfragetext.",
"advanced_styling_field_headline_size": "Schriftgröße der Überschrift",
"advanced_styling_field_headline_size_description": "Skaliert den Überschriftentext.",
"advanced_styling_field_headline_weight": "Schriftstärke der Überschrift",
"advanced_styling_field_headline_weight_description": "Macht den Überschriftentext heller oder fetter.",
"advanced_styling_field_height": "Höhe",
"advanced_styling_field_indicator_bg": "Indikator-Hintergrund",
"advanced_styling_field_indicator_bg_description": "Färbt den gefüllten Teil des Balkens.",
"advanced_styling_field_input_border_radius_description": "Rundet die Eingabeecken ab.",
"advanced_styling_field_input_font_size_description": "Skaliert den eingegebenen Text in Eingabefeldern.",
"advanced_styling_field_input_height_description": "Steuert die Höhe des Eingabefelds.",
"advanced_styling_field_input_padding_x_description": "Fügt links und rechts Abstand hinzu.",
"advanced_styling_field_input_padding_y_description": "Fügt oben und unten Abstand hinzu.",
"advanced_styling_field_input_placeholder_opacity_description": "Blendet den Platzhaltertext aus.",
"advanced_styling_field_input_shadow_description": "Fügt einen Schlagschatten um Eingabefelder hinzu.",
"advanced_styling_field_input_text": "Eingabetext",
"advanced_styling_field_input_text_description": "Färbt den eingegebenen Text in Eingabefeldern.",
"advanced_styling_field_option_bg": "Hintergrund",
"advanced_styling_field_option_bg_description": "Füllt die Optionselemente.",
"advanced_styling_field_option_border_radius_description": "Rundet die Ecken der Optionen ab.",
"advanced_styling_field_option_font_size_description": "Skaliert den Text der Optionsbeschriftung.",
"advanced_styling_field_option_label": "Label-Farbe",
"advanced_styling_field_option_label_description": "Färbt den Text der Optionsbeschriftung.",
"advanced_styling_field_option_padding_x_description": "Fügt links und rechts Abstand hinzu.",
"advanced_styling_field_option_padding_y_description": "Fügt oben und unten Abstand hinzu.",
"advanced_styling_field_padding_x": "Innenabstand X",
"advanced_styling_field_padding_y": "Innenabstand Y",
"advanced_styling_field_placeholder_opacity": "Platzhalter-Deckkraft",
"advanced_styling_field_shadow": "Schatten",
"advanced_styling_field_track_bg": "Track-Hintergrund",
"advanced_styling_field_track_bg_description": "Färbt den nicht ausgefüllten Teil des Balkens.",
"advanced_styling_field_track_height": "Track-Höhe",
"advanced_styling_field_track_height_description": "Steuert die Dicke des Fortschrittsbalkens.",
"advanced_styling_field_upper_label_color": "Farbe des oberen Labels",
"advanced_styling_field_upper_label_color_description": "Färbt die kleine Beschriftung über Eingabefeldern.",
"advanced_styling_field_upper_label_size": "Schriftgröße des oberen Labels",
"advanced_styling_field_upper_label_size_description": "Skaliert die kleine Beschriftung über Eingabefeldern.",
"advanced_styling_field_upper_label_weight": "Schriftstärke des oberen Labels",
"advanced_styling_field_upper_label_weight_description": "Macht die Beschriftung leichter oder fetter.",
"advanced_styling_section_buttons": "Buttons",
"advanced_styling_section_headlines": "Überschriften & Beschreibungen",
"advanced_styling_section_inputs": "Eingabefelder",
"advanced_styling_section_options": "Optionen (Radio/Checkbox)",
"app_survey_placement": "Platzierung der App-Umfrage",
"app_survey_placement_settings_description": "Ändere, wo Umfragen in deiner Web-App oder Website angezeigt werden.",
"centered_modal_overlay_color": "Zentrierte modale Überlagerungsfarbe",
"email_customization": "E-Mail-Anpassung",
"email_customization_description": "Ändere das Aussehen und die Gestaltung von E-Mails, die Formbricks in deinem Namen versendet.",
"enable_custom_styling": "Benutzerdefiniertes Styling aktivieren",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricks-Branding ist ausgeblendet.",
"formbricks_branding_settings_description": "Wir freuen uns über deine Unterstützung, haben aber Verständnis, wenn du es ausschaltest.",
"formbricks_branding_shown": "Formbricks-Branding wird angezeigt.",
"generate_theme_btn": "Generieren",
"generate_theme_confirmation": "Möchtest du ein passendes Farbschema basierend auf deiner Markenfarbe generieren? Dies überschreibt deine aktuellen Farbeinstellungen.",
"generate_theme_header": "Farbschema generieren?",
"logo_removed_successfully": "Logo erfolgreich entfernt",
"logo_settings_description": "Lade dein Firmenlogo hoch, um Umfragen und Link-Vorschauen zu branden.",
"logo_updated_successfully": "Logo erfolgreich aktualisiert",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Formbricks-Branding in {type}-Umfragen anzeigen",
"show_powered_by_formbricks": "\"Powered by Formbricks\"-Signatur anzeigen",
"styling_updated_successfully": "Styling erfolgreich aktualisiert",
"suggest_colors": "Farben vorschlagen",
"theme": "Theme",
"theme_settings_description": "Erstelle ein Style-Theme für alle Umfragen. Du kannst für jede Umfrage individuelles Styling aktivieren."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Ja, halte mich auf dem Laufenden.",
"preview_survey_question_2_choice_2_label": "Nein, danke!",
"preview_survey_question_2_headline": "Möchtest Du auf dem Laufenden bleiben?",
"preview_survey_question_2_subheader": "Dies ist eine Beispielbeschreibung.",
"preview_survey_welcome_card_headline": "Willkommen!",
"prioritize_features_description": "Identifiziere die Funktionen, die deine Nutzer am meisten und am wenigsten brauchen.",
"prioritize_features_name": "Funktionen priorisieren",
File diff suppressed because it is too large Load Diff
+171 -36
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Mezcla de mayúsculas y minúsculas",
"please_verify_captcha": "Por favor, verifica el reCAPTCHA",
"privacy_policy": "Política de privacidad",
"product_updates_description": "Noticias mensuales del producto y actualizaciones de funciones, se aplica la política de privacidad.",
"product_updates_title": "Actualizaciones del producto",
"security_updates_description": "Solo información relevante sobre seguridad, se aplica la política de privacidad.",
"security_updates_title": "Actualizaciones de seguridad",
"terms_of_service": "Términos de servicio",
"title": "Crea tu cuenta de Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Documentación",
"documentation": "Documentación",
"domain": "Dominio",
"done": "Hecho",
"download": "Descargar",
"draft": "Borrador",
"duplicate": "Duplicar",
@@ -238,7 +243,6 @@
"imprint": "Aviso legal",
"in_progress": "En progreso",
"inactive_surveys": "Encuestas inactivas",
"input_type": "Tipo de entrada",
"integration": "integración",
"integrations": "Integraciones",
"invalid_date": "Fecha no válida",
@@ -250,6 +254,7 @@
"label": "Etiqueta",
"language": "Idioma",
"learn_more": "Saber más",
"license_expired": "License Expired",
"light_overlay": "Superposición clara",
"limits_reached": "Límites alcanzados",
"link": "Enlace",
@@ -262,13 +267,11 @@
"look_and_feel": "Apariencia",
"manage": "Gestionar",
"marketing": "Marketing",
"maximum": "Máximo",
"member": "Miembro",
"members": "Miembros",
"members_and_teams": "Miembros y equipos",
"membership_not_found": "Membresía no encontrada",
"metadata": "Metadatos",
"minimum": "Mínimo",
"mobile_overlay_app_works_best_on_desktop": "Formbricks funciona mejor en una pantalla más grande. Para gestionar o crear encuestas, cambia a otro dispositivo.",
"mobile_overlay_surveys_look_good": "No te preocupes ¡tus encuestas se ven geniales en todos los dispositivos y tamaños de pantalla!",
"mobile_overlay_title": "¡Ups, pantalla pequeña detectada!",
@@ -282,6 +285,7 @@
"no_background_image_found": "No se encontró imagen de fondo.",
"no_code": "Sin código",
"no_files_uploaded": "No se subieron archivos",
"no_overlay": "Sin superposición",
"no_quotas_found": "No se encontraron cuotas",
"no_result_found": "No se encontró resultado",
"no_results": "Sin resultados",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Equipos de la organización no encontrados",
"other": "Otro",
"others": "Otros",
"overlay_color": "Color de superposición",
"overview": "Resumen",
"password": "Contraseña",
"paused": "Pausado",
@@ -321,7 +326,7 @@
"placeholder": "Marcador de posición",
"please_select_at_least_one_survey": "Por favor, selecciona al menos una encuesta",
"please_select_at_least_one_trigger": "Por favor, selecciona al menos un disparador",
"please_upgrade_your_plan": "Por favor, actualiza tu plan.",
"please_upgrade_your_plan": "Por favor, actualiza tu plan",
"preview": "Vista previa",
"preview_survey": "Vista previa de la encuesta",
"privacy": "Política de privacidad",
@@ -347,6 +352,7 @@
"request_trial_license": "Solicitar licencia de prueba",
"reset_to_default": "Restablecer a valores predeterminados",
"response": "Respuesta",
"response_id": "ID de respuesta",
"responses": "Respuestas",
"restart": "Reiniciar",
"role": "Rol",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Has alcanzado tu límite de {projectLimit} espacios de trabajo.",
"you_have_reached_your_monthly_miu_limit_of": "Has alcanzado tu límite mensual de MIU de",
"you_have_reached_your_monthly_response_limit_of": "Has alcanzado tu límite mensual de respuestas de",
"you_will_be_downgraded_to_the_community_edition_on_date": "Serás degradado a la edición Community el {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Serás degradado a la edición Community el {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Aceptar",
@@ -783,20 +790,26 @@
"add_webhook": "Añadir webhook",
"add_webhook_description": "Envía datos de respuestas de encuestas a un endpoint personalizado",
"all_current_and_new_surveys": "Todas las encuestas actuales y nuevas",
"copy_secret_now": "Copia tu secreto de firma",
"created_by_third_party": "Creado por un tercero",
"discord_webhook_not_supported": "Los webhooks de Discord no son compatibles actualmente.",
"empty_webhook_message": "Tus webhooks aparecerán aquí tan pronto como los añadas. ⏲️",
"endpoint_pinged": "¡Genial! ¡Podemos hacer ping al webhook!",
"endpoint_pinged_error": "¡No se puede hacer ping al webhook!",
"learn_to_verify": "Aprende a verificar las firmas de webhook",
"please_check_console": "Por favor, consulta la consola para más detalles",
"please_enter_a_url": "Por favor, introduce una URL",
"response_created": "Respuesta creada",
"response_finished": "Respuesta finalizada",
"response_updated": "Respuesta actualizada",
"secret_copy_warning": "Almacena este secreto de forma segura. Puedes verlo de nuevo en la configuración del webhook.",
"secret_description": "Usa este secreto para verificar las solicitudes del webhook. Consulta la documentación para la verificación de firma.",
"signing_secret": "Secreto de firma",
"source": "Origen",
"test_endpoint": "Probar endpoint",
"triggers": "Disparadores",
"webhook_added_successfully": "Webhook añadido correctamente",
"webhook_created": "Webhook creado",
"webhook_delete_confirmation": "¿Estás seguro de que quieres eliminar este webhook? Esto detendrá el envío de futuras notificaciones.",
"webhook_deleted_successfully": "Webhook eliminado correctamente",
"webhook_name_placeholder": "Opcional: Etiqueta tu webhook para identificarlo fácilmente",
@@ -943,19 +956,32 @@
"enterprise_features": "Características empresariales",
"get_an_enterprise_license_to_get_access_to_all_features": "Obtén una licencia empresarial para acceder a todas las características.",
"keep_full_control_over_your_data_privacy_and_security": "Mantén el control total sobre la privacidad y seguridad de tus datos.",
"license_invalid_description": "La clave de licencia en tu variable de entorno ENTERPRISE_LICENSE_KEY no es válida. Por favor, comprueba si hay errores tipográficos o solicita una clave nueva.",
"license_status": "Estado de la licencia",
"license_status_active": "Activa",
"license_status_description": "Estado de tu licencia enterprise.",
"license_status_expired": "Caducada",
"license_status_invalid": "Licencia no válida",
"license_status_unreachable": "Inaccesible",
"license_unreachable_grace_period": "No se puede acceder al servidor de licencias. Tus funciones empresariales permanecen activas durante un período de gracia de 3 días que finaliza el {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sin necesidad de llamadas, sin compromisos: solicita una licencia de prueba gratuita de 30 días para probar todas las características rellenando este formulario:",
"no_credit_card_no_sales_call_just_test_it": "Sin tarjeta de crédito. Sin llamada de ventas. Solo pruébalo :)",
"on_request": "Bajo petición",
"organization_roles": "Roles de organización (administrador, editor, desarrollador, etc.)",
"questions_please_reach_out_to": "¿Preguntas? Por favor, contacta con",
"recheck_license": "Volver a comprobar licencia",
"recheck_license_failed": "Error al comprobar la licencia. Es posible que el servidor de licencias no esté disponible.",
"recheck_license_invalid": "La clave de licencia no es válida. Por favor, verifica tu ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Comprobación de licencia correcta",
"recheck_license_unreachable": "El servidor de licencias no está disponible. Inténtalo de nuevo más tarde.",
"rechecking": "Comprobando...",
"request_30_day_trial_license": "Solicitar licencia de prueba de 30 días",
"saml_sso": "SAML SSO",
"service_level_agreement": "Acuerdo de nivel de servicio",
"soc2_hipaa_iso_27001_compliance_check": "Verificación de cumplimiento SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Equipos y roles de acceso (lectura, lectura y escritura, gestión)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días.",
"your_enterprise_license_is_active_all_features_unlocked": "Tu licencia empresarial está activa. Todas las características desbloqueadas."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días."
},
"general": {
"bulk_invite_warning_description": "En el plan gratuito, a todos los miembros de la organización se les asigna siempre el rol de \"Propietario\".",
@@ -979,7 +1005,7 @@
"from_your_organization": "de tu organización",
"invitation_sent_once_more": "Invitación enviada una vez más.",
"invite_deleted_successfully": "Invitación eliminada correctamente",
"invited_on": "Invitado el {date}",
"invite_expires_on": "La invitación expira el {date}",
"invites_failed": "Las invitaciones fallaron",
"leave_organization": "Abandonar organización",
"leave_organization_description": "Abandonarás esta organización y perderás acceso a todas las encuestas y respuestas. Solo podrás volver a unirte si te invitan de nuevo.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Eliminar logotipo",
"replace_logo": "Reemplazar logotipo",
"resend_invitation_email": "Reenviar correo electrónico de invitación",
"security_list_tip": "¿Estás suscrito a nuestra lista de seguridad? ¡Mantente informado para mantener tu instancia segura!",
"security_list_tip_link": "Regístrate aquí.",
"share_invite_link": "Compartir enlace de invitación",
"share_this_link_to_let_your_organization_member_join_your_organization": "Comparte este enlace para permitir que los miembros de tu organización se unan a tu organización:",
"test_email_sent_successfully": "Correo electrónico de prueba enviado correctamente",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Por favor, rellena todos los campos para añadir un proyecto nuevo.",
"read": "Lectura",
"read_write": "Lectura y escritura",
"select_member": "Seleccionar miembro",
"select_workspace": "Seleccionar proyecto",
"team_admin": "Administrador de equipo",
"team_created_successfully": "Equipo creado con éxito.",
"team_deleted_successfully": "Equipo eliminado correctamente.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Añadir un marcador de posición para mostrar si no hay valor que recuperar.",
"add_hidden_field_id": "Añadir ID de campo oculto",
"add_highlight_border": "Añadir borde destacado",
"add_highlight_border_description": "Añadir un borde exterior a tu tarjeta de encuesta.",
"add_logic": "Añadir lógica",
"add_none_of_the_above": "Añadir \"Ninguna de las anteriores\"",
"add_option": "Añadir opción",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Cambiar el mensaje que ven los visitantes cuando la encuesta está cerrada.",
"adjust_the_theme_in_the": "Ajustar el tema en el",
"all_other_answers_will_continue_to": "Todas las demás respuestas continuarán",
"allow_file_type": "Permitir tipo de archivo",
"allow_multi_select": "Permitir selección múltiple",
"allow_multiple_files": "Permitir múltiples archivos",
"allow_users_to_select_more_than_one_image": "Permitir a los usuarios seleccionar más de una imagen",
@@ -1169,6 +1193,9 @@
"assign": "Asignar =",
"audience": "Audiencia",
"auto_close_on_inactivity": "Cierre automático por inactividad",
"auto_save_disabled": "Guardado automático desactivado",
"auto_save_disabled_tooltip": "Su encuesta solo se guarda automáticamente cuando está en borrador. Esto asegura que las encuestas públicas no se actualicen involuntariamente.",
"auto_save_on": "Guardado automático activado",
"automatically_close_survey_after": "Cerrar automáticamente la encuesta después de",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Cerrar automáticamente la encuesta después de un cierto número de respuestas.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Cerrar automáticamente la encuesta si el usuario no responde después de cierto número de segundos.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Bloque duplicado.",
"bold": "Negrita",
"brand_color": "Color de marca",
"brand_color_description": "Se aplica a botones, enlaces y resaltados.",
"brightness": "Brillo",
"bulk_edit": "Edición masiva",
"bulk_edit_description": "Edita todas las opciones a continuación, una por línea. Las líneas vacías se omitirán y los duplicados se eliminarán.",
@@ -1190,10 +1218,14 @@
"cal_username": "Nombre de usuario de Cal.com o nombre de usuario/evento",
"calculate": "Calcular",
"capture_a_new_action_to_trigger_a_survey_on": "Captura una nueva acción para activar una encuesta.",
"capture_ip_address": "Capturar dirección IP",
"capture_ip_address_description": "Almacenar la dirección IP del encuestado en los metadatos de respuesta para la detección de duplicados y fines de seguridad",
"capture_new_action": "Capturar nueva acción",
"card_arrangement_for_survey_type_derived": "Disposición de tarjetas para encuestas de tipo {surveyTypeDerived}",
"card_background_color": "Color de fondo de la tarjeta",
"card_background_color_description": "Rellena el área de la tarjeta de encuesta.",
"card_border_color": "Color del borde de la tarjeta",
"card_border_color_description": "Delinea la tarjeta de encuesta.",
"card_styling": "Estilo de la tarjeta",
"casual": "Informal",
"caution_edit_duplicate": "Duplicar y editar",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Las respuestas antiguas y nuevas se mezclan, lo que puede llevar a resúmenes de datos engañosos.",
"caution_recommendation": "Esto puede causar inconsistencias de datos en el resumen de la encuesta. Recomendamos duplicar la encuesta en su lugar.",
"caution_text": "Los cambios provocarán inconsistencias",
"centered_modal_overlay_color": "Color de superposición del modal centrado",
"change_anyway": "Cambiar de todos modos",
"change_background": "Cambiar fondo",
"change_question_type": "Cambiar tipo de pregunta",
"change_survey_type": "Cambiar el tipo de encuesta afecta al acceso existente",
"change_the_background_color_of_the_card": "Cambiar el color de fondo de la tarjeta.",
"change_the_background_color_of_the_input_fields": "Cambiar el color de fondo de los campos de entrada.",
"change_the_background_to_a_color_image_or_animation": "Cambiar el fondo a un color, imagen o animación.",
"change_the_border_color_of_the_card": "Cambiar el color del borde de la tarjeta.",
"change_the_border_color_of_the_input_fields": "Cambiar el color del borde de los campos de entrada.",
"change_the_border_radius_of_the_card_and_the_inputs": "Cambiar el radio del borde de la tarjeta y las entradas.",
"change_the_brand_color_of_the_survey": "Cambiar el color de marca de la encuesta.",
"change_the_placement_of_this_survey": "Cambiar la ubicación de esta encuesta.",
"change_the_question_color_of_the_survey": "Cambiar el color de las preguntas de la encuesta.",
"changes_saved": "Cambios guardados.",
"changing_survey_type_will_remove_existing_distribution_channels": "Cambiar el tipo de encuesta afectará a cómo se puede compartir. Si los encuestados ya tienen enlaces de acceso para el tipo actual, podrían perder el acceso después del cambio.",
"character_limit_toggle_description": "Limitar lo corta o larga que puede ser una respuesta.",
"character_limit_toggle_title": "Añadir límites de caracteres",
"checkbox_label": "Etiqueta de casilla de verificación",
"choose_the_actions_which_trigger_the_survey": "Elige las acciones que activan la encuesta.",
"choose_the_first_question_on_your_block": "Elige la primera pregunta en tu bloque",
@@ -1241,7 +1263,6 @@
"contact_fields": "Campos de contacto",
"contains": "Contiene",
"continue_to_settings": "Continuar a ajustes",
"control_which_file_types_can_be_uploaded": "Controla qué tipos de archivos se pueden subir.",
"convert_to_multiple_choice": "Convertir a selección múltiple",
"convert_to_single_choice": "Convertir a selección única",
"country": "País",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Oscurece o aclara el fondo de tu elección.",
"date_format": "Formato de fecha",
"days_before_showing_this_survey_again": "o más días deben transcurrir entre la última encuesta mostrada y la visualización de esta encuesta.",
"delete_anyways": "Eliminar de todos modos",
"delete_block": "Eliminar bloque",
"delete_choice": "Eliminar opción",
"disable_the_visibility_of_survey_progress": "Desactivar la visibilidad del progreso de la encuesta.",
"display_an_estimate_of_completion_time_for_survey": "Mostrar una estimación del tiempo de finalización de la encuesta",
"display_number_of_responses_for_survey": "Mostrar número de respuestas para la encuesta",
"display_type": "Tipo de visualización",
"divide": "Dividir /",
"does_not_contain": "No contiene",
"does_not_end_with": "No termina con",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "No incluye todos los",
"does_not_include_one_of": "No incluye uno de",
"does_not_start_with": "No comienza con",
"dropdown": "Desplegable",
"duplicate_block": "Duplicar bloque",
"duplicate_question": "Duplicar pregunta",
"edit_link": "Editar enlace",
@@ -1357,7 +1381,6 @@
"hide_progress_bar": "Ocultar barra de progreso",
"hide_question_settings": "Ocultar ajustes de la pregunta",
"hostname": "Nombre de host",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "¿Cuánto estilo quieres darle a tus tarjetas en las encuestas de tipo {surveyTypeDerived}?",
"if_you_need_more_please": "Si necesitas más, por favor",
"if_you_really_want_that_answer_ask_until_you_get_it": "Seguir mostrando cuando se active hasta que se envíe una respuesta.",
"ignore_global_waiting_time": "Ignorar periodo de espera",
@@ -1368,7 +1391,9 @@
"initial_value": "Valor inicial",
"inner_text": "Texto interior",
"input_border_color": "Color del borde de entrada",
"input_border_color_description": "Delinea los campos de texto y áreas de texto.",
"input_color": "Color de entrada",
"input_color_description": "Rellena el interior de los campos de texto.",
"insert_link": "Insertar enlace",
"invalid_targeting": "Segmentación no válida: por favor, comprueba tus filtros de audiencia",
"invalid_video_url_warning": "Por favor, introduce una URL válida de YouTube, Vimeo o Loom. Actualmente no admitimos otros proveedores de alojamiento de vídeos.",
@@ -1395,10 +1420,10 @@
"key": "Clave",
"last_name": "Apellido",
"let_people_upload_up_to_25_files_at_the_same_time": "Permitir que las personas suban hasta 25 archivos al mismo tiempo.",
"limit_file_types": "Limitar tipos de archivo",
"limit_the_maximum_file_size": "Limitar el tamaño máximo de archivo",
"limit_upload_file_size_to": "Limitar tamaño de subida de archivos a",
"limit_the_maximum_file_size": "Limita el tamaño máximo de archivo para las subidas.",
"limit_upload_file_size_to": "Limitar el tamaño de archivo de subida a",
"link_survey_description": "Comparte un enlace a una página de encuesta o incrústala en una página web o correo electrónico.",
"list": "Lista",
"load_segment": "Cargar segmento",
"logic_error_warning": "El cambio causará errores lógicos",
"logic_error_warning_text": "Cambiar el tipo de pregunta eliminará las condiciones lógicas de esta pregunta",
@@ -1442,22 +1467,22 @@
"picture_idx": "Imagen {idx}",
"pin_can_only_contain_numbers": "El PIN solo puede contener números.",
"pin_must_be_a_four_digit_number": "El PIN debe ser un número de cuatro dígitos.",
"please_enter_a_file_extension": "Por favor, introduce una extensión de archivo.",
"please_enter_a_valid_url": "Por favor, introduce una URL válida (p. ej., https://example.com)",
"please_set_a_survey_trigger": "Establece un disparador de encuesta",
"please_specify": "Por favor, especifica",
"prevent_double_submission": "Evitar envío duplicado",
"prevent_double_submission_description": "Permitir solo 1 respuesta por dirección de correo electrónico",
"progress_saved": "Progreso guardado",
"protect_survey_with_pin": "Proteger encuesta con un PIN",
"protect_survey_with_pin_description": "Solo los usuarios que tengan el PIN pueden acceder a la encuesta.",
"publish": "Publicar",
"question": "Pregunta",
"question_color": "Color de la pregunta",
"question_deleted": "Pregunta eliminada.",
"question_duplicated": "Pregunta duplicada.",
"question_id_updated": "ID de pregunta actualizado",
"question_used_in_logic": "Esta pregunta se utiliza en la lógica de la pregunta {questionIndex}.",
"question_used_in_quota": "Esta pregunta se está utilizando en la cuota \"{quotaName}\"",
"question_used_in_logic_warning_text": "Los elementos de este bloque se usan en una regla de lógica, ¿estás seguro de que quieres eliminarlo?",
"question_used_in_logic_warning_title": "Inconsistencia de lógica",
"question_used_in_quota": "Esta pregunta se está utilizando en la cuota “{quotaName}”",
"question_used_in_recall": "Esta pregunta se está recordando en la pregunta {questionIndex}.",
"question_used_in_recall_ending_card": "Esta pregunta se está recordando en la Tarjeta Final",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Límites de respuestas, redirecciones y más.",
"response_options": "Opciones de respuesta",
"roundness": "Redondez",
"roundness_description": "Controla qué tan redondeadas están las esquinas de la tarjeta.",
"row_used_in_logic_error": "Esta fila se utiliza en la lógica de la pregunta {questionIndex}. Por favor, elimínala de la lógica primero.",
"rows": "Filas",
"save_and_close": "Guardar y cerrar",
@@ -1520,6 +1546,7 @@
"search_for_images": "Buscar imágenes",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "segundos después de activarse, la encuesta se cerrará si no hay respuesta",
"seconds_before_showing_the_survey": "segundos antes de mostrar la encuesta.",
"select_field": "Seleccionar campo",
"select_or_type_value": "Selecciona o escribe un valor",
"select_ordering": "Seleccionar ordenación",
"select_saved_action": "Seleccionar acción guardada",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Estilo configurado según los estilos del tema",
"subheading": "Subtítulo",
"subtract": "Restar -",
"suggest_colors": "Sugerir colores",
"survey_completed_heading": "Encuesta completada",
"survey_completed_subheading": "Esta encuesta gratuita y de código abierto ha sido cerrada",
"survey_display_settings": "Ajustes de visualización de la encuesta",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar una sola vez, incluso si no responden.",
"then": "Entonces",
"this_action_will_remove_all_the_translations_from_this_survey": "Esta acción eliminará todas las traducciones de esta encuesta.",
"this_extension_is_already_added": "Esta extensión ya está añadida.",
"this_file_type_is_not_supported": "Este tipo de archivo no es compatible.",
"three_points": "3 puntos",
"times": "veces",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para mantener la ubicación coherente en todas las encuestas, puedes",
@@ -1589,8 +1613,51 @@
"upper_label": "Etiqueta superior",
"url_filters": "Filtros de URL",
"url_not_supported": "URL no compatible",
"validation": {
"add_validation_rule": "Añadir regla de validación",
"answer_all_rows": "Responde todas las filas",
"characters": "Caracteres",
"contains": "Contiene",
"delete_validation_rule": "Eliminar regla de validación",
"does_not_contain": "No contiene",
"email": "Es un correo electrónico válido",
"end_date": "Fecha de finalización",
"file_extension_is": "La extensión del archivo es",
"file_extension_is_not": "La extensión del archivo no es",
"is": "Es",
"is_between": "Está entre",
"is_earlier_than": "Es anterior a",
"is_greater_than": "Es mayor que",
"is_later_than": "Es posterior a",
"is_less_than": "Es menor que",
"is_not": "No es",
"is_not_between": "No está entre",
"kb": "KB",
"max_length": "Como máximo",
"max_selections": "Como máximo",
"max_value": "Como máximo",
"mb": "MB",
"min_length": "Al menos",
"min_selections": "Al menos",
"min_value": "Al menos",
"minimum_options_ranked": "Opciones mínimas clasificadas",
"minimum_rows_answered": "Filas mínimas respondidas",
"options_selected": "Opciones seleccionadas",
"pattern": "Coincide con el patrón regex",
"phone": "Es un teléfono válido",
"rank_all_options": "Clasificar todas las opciones",
"select_file_extensions": "Selecciona extensiones de archivo...",
"select_option": "Seleccionar opción",
"start_date": "Fecha de inicio",
"url": "Es una URL válida"
},
"validation_logic_and": "Todas son verdaderas",
"validation_logic_or": "alguna es verdadera",
"validation_rules": "Reglas de validación",
"validation_rules_description": "Solo aceptar respuestas que cumplan los siguientes criterios",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} se usa en la lógica de la pregunta {questionIndex}. Por favor, elimínala primero de la lógica.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable \"{variableName}\" se está utilizando en la cuota \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable {variableName} se está utilizando en la cuota {quotaName}",
"variable_name_conflicts_with_hidden_field": "El nombre de la variable entra en conflicto con un ID de campo oculto existente.",
"variable_name_is_already_taken_please_choose_another": "El nombre de la variable ya está en uso, por favor elige otro.",
"variable_name_must_start_with_a_letter": "El nombre de la variable debe comenzar con una letra.",
"variable_used_in_recall": "La variable \"{variable}\" se está recuperando en la pregunta {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Se produjo un error al descargar las respuestas",
"first_name": "Nombre",
"how_to_identify_users": "Cómo identificar a los usuarios",
"ip_address": "Dirección IP",
"last_name": "Apellido",
"not_completed": "No completado ⏳",
"os": "Sistema operativo",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Añadir color de fondo",
"add_background_color_description": "Añade un color de fondo al contenedor del logotipo.",
"advanced_styling_field_border_radius": "Radio del borde",
"advanced_styling_field_button_bg": "Fondo del botón",
"advanced_styling_field_button_bg_description": "Rellena el botón siguiente / enviar.",
"advanced_styling_field_button_border_radius_description": "Redondea las esquinas del botón.",
"advanced_styling_field_button_font_size_description": "Escala el texto de la etiqueta del botón.",
"advanced_styling_field_button_font_weight_description": "Hace el texto del botón más ligero o más grueso.",
"advanced_styling_field_button_height_description": "Controla la altura del botón.",
"advanced_styling_field_button_padding_x_description": "Añade espacio a la izquierda y a la derecha.",
"advanced_styling_field_button_padding_y_description": "Añade espacio arriba y abajo.",
"advanced_styling_field_button_text": "Texto del botón",
"advanced_styling_field_button_text_description": "Colorea la etiqueta dentro de los botones.",
"advanced_styling_field_description_color": "Color de la descripción",
"advanced_styling_field_description_color_description": "Colorea el texto debajo de cada titular.",
"advanced_styling_field_description_size": "Tamaño de fuente de la descripción",
"advanced_styling_field_description_size_description": "Escala el texto de la descripción.",
"advanced_styling_field_description_weight": "Grosor de fuente de la descripción",
"advanced_styling_field_description_weight_description": "Hace el texto de la descripción más ligero o más grueso.",
"advanced_styling_field_font_size": "Tamaño de fuente",
"advanced_styling_field_font_weight": "Grosor de fuente",
"advanced_styling_field_headline_color": "Color del titular",
"advanced_styling_field_headline_color_description": "Colorea el texto principal de la pregunta.",
"advanced_styling_field_headline_size": "Tamaño de fuente del titular",
"advanced_styling_field_headline_size_description": "Escala el texto del titular.",
"advanced_styling_field_headline_weight": "Grosor de fuente del titular",
"advanced_styling_field_headline_weight_description": "Hace el texto del titular más ligero o más grueso.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_indicator_bg": "Fondo del indicador",
"advanced_styling_field_indicator_bg_description": "Colorea la porción rellena de la barra.",
"advanced_styling_field_input_border_radius_description": "Redondea las esquinas del campo.",
"advanced_styling_field_input_font_size_description": "Escala el texto escrito en los campos.",
"advanced_styling_field_input_height_description": "Controla la altura del campo de entrada.",
"advanced_styling_field_input_padding_x_description": "Añade espacio a la izquierda y a la derecha.",
"advanced_styling_field_input_padding_y_description": "Añade espacio en la parte superior e inferior.",
"advanced_styling_field_input_placeholder_opacity_description": "Atenúa el texto de sugerencia del marcador de posición.",
"advanced_styling_field_input_shadow_description": "Añade una sombra alrededor de los campos de entrada.",
"advanced_styling_field_input_text": "Texto de entrada",
"advanced_styling_field_input_text_description": "Colorea el texto escrito en los campos de entrada.",
"advanced_styling_field_option_bg": "Fondo",
"advanced_styling_field_option_bg_description": "Rellena los elementos de opción.",
"advanced_styling_field_option_border_radius_description": "Redondea las esquinas de las opciones.",
"advanced_styling_field_option_font_size_description": "Escala el texto de la etiqueta de opción.",
"advanced_styling_field_option_label": "Color de la etiqueta",
"advanced_styling_field_option_label_description": "Colorea el texto de la etiqueta de opción.",
"advanced_styling_field_option_padding_x_description": "Añade espacio a la izquierda y a la derecha.",
"advanced_styling_field_option_padding_y_description": "Añade espacio en la parte superior e inferior.",
"advanced_styling_field_padding_x": "Relleno X",
"advanced_styling_field_padding_y": "Relleno Y",
"advanced_styling_field_placeholder_opacity": "Opacidad del marcador de posición",
"advanced_styling_field_shadow": "Sombra",
"advanced_styling_field_track_bg": "Fondo de la pista",
"advanced_styling_field_track_bg_description": "Colorea la parte no rellenada de la barra.",
"advanced_styling_field_track_height": "Altura de la pista",
"advanced_styling_field_track_height_description": "Controla el grosor de la barra de progreso.",
"advanced_styling_field_upper_label_color": "Color de la etiqueta del titular",
"advanced_styling_field_upper_label_color_description": "Colorea la etiqueta pequeña sobre los campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamaño de fuente de la etiqueta del titular",
"advanced_styling_field_upper_label_size_description": "Escala la etiqueta pequeña sobre los campos de entrada.",
"advanced_styling_field_upper_label_weight": "Grosor de fuente de la etiqueta del titular",
"advanced_styling_field_upper_label_weight_description": "Hace que la etiqueta sea más ligera o más gruesa.",
"advanced_styling_section_buttons": "Botones",
"advanced_styling_section_headlines": "Títulos y descripciones",
"advanced_styling_section_inputs": "Campos de entrada",
"advanced_styling_section_options": "Opciones (radio/casilla de verificación)",
"app_survey_placement": "Ubicación de encuesta de aplicación",
"app_survey_placement_settings_description": "Cambia dónde se mostrarán las encuestas en tu aplicación web o sitio web.",
"centered_modal_overlay_color": "Color de superposición del modal centrado",
"email_customization": "Personalización de correo electrónico",
"email_customization_description": "Cambia el aspecto de los correos electrónicos que Formbricks envía en tu nombre.",
"enable_custom_styling": "Habilitar estilo personalizado",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "La marca de Formbricks está oculta.",
"formbricks_branding_settings_description": "Nos encanta tu apoyo, pero lo entendemos si lo desactivas.",
"formbricks_branding_shown": "La marca de Formbricks se muestra.",
"generate_theme_btn": "Generar",
"generate_theme_confirmation": "¿Te gustaría generar un tema de colores que combine con el color de tu marca? Esto sobrescribirá tu configuración de colores actual.",
"generate_theme_header": "¿Generar tema de colores?",
"logo_removed_successfully": "Logotipo eliminado correctamente",
"logo_settings_description": "Sube el logotipo de tu empresa para personalizar las encuestas y las vistas previas de enlaces.",
"logo_updated_successfully": "Logotipo actualizado correctamente",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Mostrar marca de Formbricks en encuestas de {type}",
"show_powered_by_formbricks": "Mostrar firma 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo actualizado correctamente",
"suggest_colors": "Sugerir colores",
"theme": "Tema",
"theme_settings_description": "Crea un tema de estilo para todas las encuestas. Puedes activar el estilo personalizado para cada encuesta."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Sí, mantenme informado.",
"preview_survey_question_2_choice_2_label": "¡No, gracias!",
"preview_survey_question_2_headline": "¿Quieres estar al tanto?",
"preview_survey_question_2_subheader": "Esta es una descripción de ejemplo.",
"preview_survey_welcome_card_headline": "¡Bienvenido!",
"prioritize_features_description": "Identifica las funciones que tus usuarios necesitan más y menos.",
"prioritize_features_name": "Priorizar funciones",
+173 -38
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Mélange de majuscules et de minuscules",
"please_verify_captcha": "Veuillez vérifier reCAPTCHA",
"privacy_policy": "Politique de confidentialité",
"product_updates_description": "Actualités mensuelles du produit et mises à jour des fonctionnalités, la politique de confidentialité s'applique.",
"product_updates_title": "Mises à jour du produit",
"security_updates_description": "Informations relatives à la sécurité uniquement, la politique de confidentialité s'applique.",
"security_updates_title": "Mises à jour de sécurité",
"terms_of_service": "Conditions d'utilisation",
"title": "Créez votre compte Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Documentation",
"documentation": "Documentation",
"domain": "Domaine",
"done": "Terminé",
"download": "Télécharger",
"draft": "Brouillon",
"duplicate": "Dupliquer",
@@ -238,7 +243,6 @@
"imprint": "Empreinte",
"in_progress": "En cours",
"inactive_surveys": "Sondages inactifs",
"input_type": "Type d'entrée",
"integration": "intégration",
"integrations": "Intégrations",
"invalid_date": "Date invalide",
@@ -250,6 +254,7 @@
"label": "Étiquette",
"language": "Langue",
"learn_more": "En savoir plus",
"license_expired": "License Expired",
"light_overlay": "Claire",
"limits_reached": "Limites atteints",
"link": "Lien",
@@ -262,13 +267,11 @@
"look_and_feel": "Apparence",
"manage": "Gérer",
"marketing": "Marketing",
"maximum": "Max",
"member": "Membre",
"members": "Membres",
"members_and_teams": "Membres & Équipes",
"membership_not_found": "Abonnement non trouvé",
"metadata": "Métadonnées",
"minimum": "Min",
"mobile_overlay_app_works_best_on_desktop": "Formbricks fonctionne mieux sur un écran plus grand. Pour gérer ou créer des sondages, passez à un autre appareil.",
"mobile_overlay_surveys_look_good": "Ne t'inquiète pas tes enquêtes sont superbes sur tous les appareils et tailles d'écran!",
"mobile_overlay_title": "Oups, écran minuscule détecté!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Aucune image de fond trouvée.",
"no_code": "Sans code",
"no_files_uploaded": "Aucun fichier n'a été téléchargé.",
"no_overlay": "Aucune superposition",
"no_quotas_found": "Aucun quota trouvé",
"no_result_found": "Aucun résultat trouvé",
"no_results": "Aucun résultat",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Équipes d'organisation non trouvées",
"other": "Autre",
"others": "Autres",
"overlay_color": "Couleur de superposition",
"overview": "Aperçu",
"password": "Mot de passe",
"paused": "En pause",
@@ -321,7 +326,7 @@
"placeholder": "Remplaçant",
"please_select_at_least_one_survey": "Veuillez sélectionner au moins une enquête.",
"please_select_at_least_one_trigger": "Veuillez sélectionner au moins un déclencheur.",
"please_upgrade_your_plan": "Veuillez mettre à niveau votre plan.",
"please_upgrade_your_plan": "Veuillez mettre à niveau votre plan",
"preview": "Aperçu",
"preview_survey": "Aperçu de l'enquête",
"privacy": "Politique de confidentialité",
@@ -347,6 +352,7 @@
"request_trial_license": "Demander une licence d'essai",
"reset_to_default": "Réinitialiser par défaut",
"response": "Réponse",
"response_id": "ID de réponse",
"responses": "Réponses",
"restart": "Recommencer",
"role": "Rôle",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Vous avez atteint votre limite de {projectLimit} espaces de travail.",
"you_have_reached_your_monthly_miu_limit_of": "Vous avez atteint votre limite mensuelle de MIU de",
"you_have_reached_your_monthly_response_limit_of": "Vous avez atteint votre limite de réponses mensuelle de",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vous serez rétrogradé à l'édition communautaire le {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Vous serez rétrogradé à l'édition communautaire le {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Accepter",
@@ -783,20 +790,26 @@
"add_webhook": "Ajouter un Webhook",
"add_webhook_description": "Envoyer les données de réponse à l'enquête à un point de terminaison personnalisé",
"all_current_and_new_surveys": "Tous les sondages actuels et nouveaux",
"copy_secret_now": "Copiez votre secret de signature",
"created_by_third_party": "Créé par un tiers",
"discord_webhook_not_supported": "Les webhooks Discord ne sont actuellement pas pris en charge.",
"empty_webhook_message": "Vos webhooks apparaîtront ici dès que vous les ajouterez. ⏲️",
"endpoint_pinged": "Yay ! Nous pouvons pinger le webhook !",
"endpoint_pinged_error": "Impossible de pinger le webhook !",
"learn_to_verify": "Découvrez comment vérifier les signatures de webhook",
"please_check_console": "Veuillez vérifier la console pour plus de détails.",
"please_enter_a_url": "Veuillez entrer une URL.",
"response_created": "Réponse créée",
"response_finished": "Réponse terminée",
"response_updated": "Réponse mise à jour",
"secret_copy_warning": "Conservez ce secret en lieu sûr. Vous pourrez le consulter à nouveau dans les paramètres du webhook.",
"secret_description": "Utilisez ce secret pour vérifier les requêtes webhook. Consultez la documentation pour la vérification de signature.",
"signing_secret": "Secret de signature",
"source": "Source",
"test_endpoint": "Point de test",
"triggers": "Déclencheurs",
"webhook_added_successfully": "Webhook ajouté avec succès",
"webhook_created": "Webhook créé",
"webhook_delete_confirmation": "Êtes-vous sûr de vouloir supprimer ce Webhook ? Cela arrêtera l'envoi de toute notification future.",
"webhook_deleted_successfully": "Webhook supprimé avec succès",
"webhook_name_placeholder": "Optionnel : Étiquetez votre webhook pour une identification facile",
@@ -943,19 +956,32 @@
"enterprise_features": "Fonctionnalités d'entreprise",
"get_an_enterprise_license_to_get_access_to_all_features": "Obtenez une licence Entreprise pour accéder à toutes les fonctionnalités.",
"keep_full_control_over_your_data_privacy_and_security": "Gardez un contrôle total sur la confidentialité et la sécurité de vos données.",
"license_invalid_description": "La clé de licence dans votre variable d'environnement ENTERPRISE_LICENSE_KEY n'est pas valide. Veuillez vérifier les fautes de frappe ou demander une nouvelle clé.",
"license_status": "Statut de la licence",
"license_status_active": "Active",
"license_status_description": "Statut de votre licence entreprise.",
"license_status_expired": "Expirée",
"license_status_invalid": "Licence invalide",
"license_status_unreachable": "Inaccessible",
"license_unreachable_grace_period": "Le serveur de licence est injoignable. Vos fonctionnalités entreprise restent actives pendant une période de grâce de 3 jours se terminant le {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Aucun appel nécessaire, aucune obligation : Demandez une licence d'essai gratuite de 30 jours pour tester toutes les fonctionnalités en remplissant ce formulaire :",
"no_credit_card_no_sales_call_just_test_it": "Aucune carte de crédit. Aucun appel de vente. Testez-le simplement :)",
"on_request": "Sur demande",
"organization_roles": "Rôles d'organisation (Administrateur, Éditeur, Développeur, etc.)",
"questions_please_reach_out_to": "Des questions ? Veuillez contacter",
"recheck_license": "Revérifier la licence",
"recheck_license_failed": "La vérification de la licence a échoué. Le serveur de licences est peut-être inaccessible.",
"recheck_license_invalid": "La clé de licence est invalide. Veuillez vérifier votre ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Vérification de la licence réussie",
"recheck_license_unreachable": "Le serveur de licences est inaccessible. Veuillez réessayer plus tard.",
"rechecking": "Revérification en cours...",
"request_30_day_trial_license": "Demander une licence d'essai de 30 jours",
"saml_sso": "SAML SSO",
"service_level_agreement": "Accord de niveau de service",
"soc2_hipaa_iso_27001_compliance_check": "Vérification de conformité SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Équipes et Rôles d'Accès (Lire, Lire et Écrire, Gérer)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours.",
"your_enterprise_license_is_active_all_features_unlocked": "Votre licence d'entreprise est active. Toutes les fonctionnalités sont déverrouillées."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours."
},
"general": {
"bulk_invite_warning_description": "Dans le plan gratuit, tous les membres de l'organisation se voient toujours attribuer le rôle \"Owner\".",
@@ -979,7 +1005,7 @@
"from_your_organization": "de votre organisation",
"invitation_sent_once_more": "Invitation envoyée une fois de plus.",
"invite_deleted_successfully": "Invitation supprimée avec succès",
"invited_on": "Invité le {date}",
"invite_expires_on": "L'invitation expire le {date}",
"invites_failed": "Invitations échouées",
"leave_organization": "Quitter l'organisation",
"leave_organization_description": "Vous quitterez cette organisation et perdrez l'accès à toutes les enquêtes et réponses. Vous ne pourrez revenir que si vous êtes de nouveau invité.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Supprimer le logo",
"replace_logo": "Remplacer le logo",
"resend_invitation_email": "Renvoyer l'e-mail d'invitation",
"security_list_tip": "Êtes-vous inscrit à notre liste de sécurité ? Restez informé pour maintenir votre instance sécurisée!",
"security_list_tip_link": "Inscrivez-vous ici.",
"share_invite_link": "Partager le lien d'invitation",
"share_this_link_to_let_your_organization_member_join_your_organization": "Partagez ce lien pour permettre à un membre de votre organisation de rejoindre votre organisation :",
"test_email_sent_successfully": "E-mail de test envoyé avec succès",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Veuillez remplir tous les champs pour ajouter un nouvel espace de travail.",
"read": "Lire",
"read_write": "Lire et Écrire",
"select_member": "Sélectionner membre",
"select_workspace": "Sélectionner un espace de travail",
"team_admin": "Administrateur d'équipe",
"team_created_successfully": "Équipe créée avec succès.",
"team_deleted_successfully": "Équipe supprimée avec succès.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Ajouter un espace réservé à afficher s'il n'y a pas de valeur à rappeler.",
"add_hidden_field_id": "Ajouter un champ caché ID",
"add_highlight_border": "Ajouter une bordure de surlignage",
"add_highlight_border_description": "Ajoutez une bordure extérieure à votre carte d'enquête.",
"add_logic": "Ajouter de la logique",
"add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"",
"add_option": "Ajouter une option",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Modifiez le message que les visiteurs voient lorsque l'enquête est fermée.",
"adjust_the_theme_in_the": "Ajustez le thème dans le",
"all_other_answers_will_continue_to": "Toutes les autres réponses continueront à",
"allow_file_type": "Autoriser le type de fichier",
"allow_multi_select": "Autoriser la sélection multiple",
"allow_multiple_files": "Autoriser plusieurs fichiers",
"allow_users_to_select_more_than_one_image": "Permettre aux utilisateurs de sélectionner plusieurs images",
@@ -1169,6 +1193,9 @@
"assign": "Attribuer =",
"audience": "Public",
"auto_close_on_inactivity": "Fermeture automatique en cas d'inactivité",
"auto_save_disabled": "Sauvegarde automatique désactivée",
"auto_save_disabled_tooltip": "Votre sondage n'est sauvegardé automatiquement que lorsqu'il est en brouillon. Cela garantit que les sondages publics ne sont pas mis à jour involontairement.",
"auto_save_on": "Sauvegarde automatique activée",
"automatically_close_survey_after": "Fermer automatiquement l'enquête après",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fermer automatiquement l'enquête après un certain nombre de réponses.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Fermer automatiquement l'enquête si l'utilisateur ne répond pas après un certain nombre de secondes.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Bloc dupliqué.",
"bold": "Gras",
"brand_color": "Couleur de marque",
"brand_color_description": "Appliqué aux boutons, liens et éléments mis en évidence.",
"brightness": "Luminosité",
"bulk_edit": "Modification en masse",
"bulk_edit_description": "Modifiez toutes les options ci-dessous, une par ligne. Les lignes vides seront ignorées et les doublons supprimés.",
@@ -1190,10 +1218,14 @@
"cal_username": "Nom d'utilisateur Cal.com ou nom d'utilisateur/événement",
"calculate": "Calculer",
"capture_a_new_action_to_trigger_a_survey_on": "Capturez une nouvelle action pour déclencher une enquête.",
"capture_ip_address": "Capturer l'adresse IP",
"capture_ip_address_description": "Stocker l'adresse IP du répondant dans les métadonnées de réponse à des fins de détection des doublons et de sécurité",
"capture_new_action": "Capturer une nouvelle action",
"card_arrangement_for_survey_type_derived": "Disposition des cartes pour les enquêtes {surveyTypeDerived}",
"card_background_color": "Couleur de fond de la carte",
"card_background_color_description": "Remplit la zone de la carte d'enquête.",
"card_border_color": "Couleur de la bordure de la carte",
"card_border_color_description": "Délimite la carte d'enquête.",
"card_styling": "Style de carte",
"casual": "Décontracté",
"caution_edit_duplicate": "Dupliquer et modifier",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Les réponses anciennes et nouvelles se mélangent, ce qui peut entraîner des résumés de données trompeurs.",
"caution_recommendation": "Cela peut entraîner des incohérences de données dans le résumé du sondage. Nous recommandons de dupliquer le sondage à la place.",
"caution_text": "Les changements entraîneront des incohérences.",
"centered_modal_overlay_color": "Couleur de superposition modale centrée",
"change_anyway": "Changer de toute façon",
"change_background": "Changer l'arrière-plan",
"change_question_type": "Changer le type de question",
"change_survey_type": "Le changement de type de sondage affecte l'accès existant",
"change_the_background_color_of_the_card": "Changez la couleur de fond de la carte.",
"change_the_background_color_of_the_input_fields": "Vous pouvez modifier la couleur d'arrière-plan des champs de saisie.",
"change_the_background_to_a_color_image_or_animation": "Changez l'arrière-plan en une couleur, une image ou une animation.",
"change_the_border_color_of_the_card": "Changez la couleur de la bordure de la carte.",
"change_the_border_color_of_the_input_fields": "Vous pouvez modifier la couleur de la bordure des champs de saisie.",
"change_the_border_radius_of_the_card_and_the_inputs": "Vous pouvez arrondir la bordure des encadrés et des champs de saisie.",
"change_the_brand_color_of_the_survey": "Vous pouvez modifier la couleur dominante d'une enquête.",
"change_the_placement_of_this_survey": "Changez le placement de cette enquête.",
"change_the_question_color_of_the_survey": "Vous pouvez modifier la couleur des questions d'une enquête.",
"changes_saved": "Modifications enregistrées.",
"changing_survey_type_will_remove_existing_distribution_channels": "Le changement du type de sondage affectera la façon dont il peut être partagé. Si les répondants ont déjà des liens d'accès pour le type actuel, ils peuvent perdre l'accès après le changement.",
"character_limit_toggle_description": "Limitez la longueur des réponses.",
"character_limit_toggle_title": "Ajouter des limites de caractères",
"checkbox_label": "Étiquette de case à cocher",
"choose_the_actions_which_trigger_the_survey": "Choisissez les actions qui déclenchent l'enquête.",
"choose_the_first_question_on_your_block": "Choisissez la première question de votre bloc",
@@ -1241,7 +1263,6 @@
"contact_fields": "Champs de contact",
"contains": "Contient",
"continue_to_settings": "Continuer vers les paramètres",
"control_which_file_types_can_be_uploaded": "Contrôlez quels types de fichiers peuvent être téléchargés.",
"convert_to_multiple_choice": "Convertir en choix multiples",
"convert_to_single_choice": "Convertir en choix unique",
"country": "Pays",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Assombrir ou éclaircir l'arrière-plan de votre choix.",
"date_format": "Format de date",
"days_before_showing_this_survey_again": "ou plus de jours doivent s'écouler entre le dernier sondage affiché et l'affichage de ce sondage.",
"delete_anyways": "Supprimer quand même",
"delete_block": "Supprimer le bloc",
"delete_choice": "Supprimer l'option",
"disable_the_visibility_of_survey_progress": "Désactiver la visibilité de la progression du sondage.",
"display_an_estimate_of_completion_time_for_survey": "Afficher une estimation du temps de complétion pour l'enquête.",
"display_number_of_responses_for_survey": "Afficher le nombre de réponses pour l'enquête",
"display_type": "Type d'affichage",
"divide": "Diviser /",
"does_not_contain": "Ne contient pas",
"does_not_end_with": "Ne se termine pas par",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "n'inclut pas tout",
"does_not_include_one_of": "n'inclut pas un de",
"does_not_start_with": "Ne commence pas par",
"dropdown": "Menu déroulant",
"duplicate_block": "Dupliquer le bloc",
"duplicate_question": "Dupliquer la question",
"edit_link": "Modifier le lien",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Cacher la barre de progression",
"hide_question_settings": "Masquer les paramètres de la question",
"hostname": "Nom d'hôte",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "À quel point voulez-vous que vos cartes soient funky dans les enquêtes {surveyTypeDerived}",
"if_you_need_more_please": "Si vous en avez besoin de plus, s'il vous plaît",
"if_you_need_more_please": "Si vous avez besoin de plus, veuillez",
"if_you_really_want_that_answer_ask_until_you_get_it": "Continuer à afficher à chaque déclenchement jusqu'à ce qu'une réponse soit soumise.",
"ignore_global_waiting_time": "Ignorer la période de refroidissement",
"ignore_global_waiting_time_description": "Cette enquête peut s'afficher chaque fois que ses conditions sont remplies, même si une autre enquête a été affichée récemment.",
@@ -1368,7 +1391,9 @@
"initial_value": "Valeur initiale",
"inner_text": "Texte interne",
"input_border_color": "Couleur de la bordure des champs de saisie",
"input_border_color_description": "Délimite les champs de texte et les zones de texte.",
"input_color": "Couleur d'arrière-plan des champs de saisie",
"input_color_description": "Remplit l'intérieur des champs de texte.",
"insert_link": "Insérer un lien",
"invalid_targeting": "Ciblage invalide : Veuillez vérifier vos filtres d'audience",
"invalid_video_url_warning": "Merci d'entrer une URL YouTube, Vimeo ou Loom valide. Les autres plateformes vidéo ne sont pas encore supportées.",
@@ -1395,10 +1420,10 @@
"key": "Clé",
"last_name": "Nom de famille",
"let_people_upload_up_to_25_files_at_the_same_time": "Permettre aux utilisateurs de télécharger jusqu'à 25 fichiers en même temps.",
"limit_file_types": "Limiter les types de fichiers",
"limit_the_maximum_file_size": "Limiter la taille maximale du fichier",
"limit_upload_file_size_to": "Limiter la taille des fichiers téléchargés à",
"limit_the_maximum_file_size": "Limiter la taille maximale des fichiers pour les téléversements.",
"limit_upload_file_size_to": "Limiter la taille de téléversement des fichiers à",
"link_survey_description": "Partagez un lien vers une page d'enquête ou intégrez-le dans une page web ou un e-mail.",
"list": "Liste",
"load_segment": "Segment de chargement",
"logic_error_warning": "Changer causera des erreurs logiques",
"logic_error_warning_text": "Changer le type de question supprimera les conditions logiques de cette question.",
@@ -1410,7 +1435,7 @@
"matrix_all_fields": "Tous les champs",
"matrix_rows": "Lignes",
"max_file_size": "Taille maximale du fichier",
"max_file_size_limit_is": "La taille maximale du fichier est",
"max_file_size_limit_is": "La limite de taille maximale du fichier est",
"move_question_to_block": "Déplacer la question vers le bloc",
"multiply": "Multiplier *",
"needed_for_self_hosted_cal_com_instance": "Nécessaire pour une instance Cal.com auto-hébergée",
@@ -1442,22 +1467,22 @@
"picture_idx": "Image {idx}",
"pin_can_only_contain_numbers": "Le code PIN ne peut contenir que des chiffres.",
"pin_must_be_a_four_digit_number": "Le code PIN doit être un numéro à quatre chiffres.",
"please_enter_a_file_extension": "Veuillez entrer une extension de fichier.",
"please_enter_a_valid_url": "Veuillez entrer une URL valide (par exemple, https://example.com)",
"please_set_a_survey_trigger": "Veuillez définir un déclencheur d'enquête.",
"please_specify": "Veuillez préciser",
"prevent_double_submission": "Empêcher la double soumission",
"prevent_double_submission_description": "Autoriser uniquement 1 réponse par adresse e-mail",
"progress_saved": "Progression enregistrée",
"protect_survey_with_pin": "Protéger l'enquête par un code PIN",
"protect_survey_with_pin_description": "Seules les personnes ayant le code PIN peuvent accéder à l'enquête.",
"publish": "Publier",
"question": "Question",
"question_color": "Couleur des questions",
"question_deleted": "Question supprimée.",
"question_duplicated": "Question dupliquée.",
"question_id_updated": "ID de la question mis à jour",
"question_used_in_logic": "Cette question est utilisée dans la logique de la question '{'questionIndex'}'.",
"question_used_in_quota": "Cette question est utilisée dans le quota \"{quotaName}\"",
"question_used_in_logic_warning_text": "Des éléments de ce bloc sont utilisés dans une règle logique, êtes-vous sûr de vouloir le supprimer?",
"question_used_in_logic_warning_title": "Incohérence de logique",
"question_used_in_quota": "Cette question est utilisée dans le quota “{quotaName}”",
"question_used_in_recall": "Cette question est rappelée dans la question {questionIndex}.",
"question_used_in_recall_ending_card": "Cette question est rappelée dans la carte de fin.",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Limites de réponse, redirections et plus.",
"response_options": "Options de réponse",
"roundness": "Rondeur",
"roundness_description": "Contrôle l'arrondi des coins de la carte.",
"row_used_in_logic_error": "Cette ligne est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.",
"rows": "Lignes",
"save_and_close": "Enregistrer et fermer",
@@ -1520,6 +1546,7 @@
"search_for_images": "Rechercher des images",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "Les secondes après le déclenchement, l'enquête sera fermée si aucune réponse n'est donnée.",
"seconds_before_showing_the_survey": "secondes avant de montrer l'enquête.",
"select_field": "Sélectionner un champ",
"select_or_type_value": "Sélectionnez ou saisissez une valeur",
"select_ordering": "Choisir l'ordre",
"select_saved_action": "Sélectionner une action enregistrée",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Style défini sur les styles du thème",
"subheading": "Sous-titre",
"subtract": "Soustraire -",
"suggest_colors": "Suggérer des couleurs",
"survey_completed_heading": "Enquête terminée",
"survey_completed_subheading": "Cette enquête gratuite et open-source a été fermée",
"survey_display_settings": "Paramètres d'affichage de l'enquête",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Afficher une seule fois, même si la personne ne répond pas.",
"then": "Alors",
"this_action_will_remove_all_the_translations_from_this_survey": "Cette action supprimera toutes les traductions de cette enquête.",
"this_extension_is_already_added": "Cette extension est déjà ajoutée.",
"this_file_type_is_not_supported": "Ce type de fichier n'est pas pris en charge.",
"three_points": "3 points",
"times": "fois",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Pour maintenir la cohérence du placement sur tous les sondages, vous pouvez",
@@ -1589,8 +1613,51 @@
"upper_label": "Étiquette supérieure",
"url_filters": "Filtres d'URL",
"url_not_supported": "URL non supportée",
"validation": {
"add_validation_rule": "Ajouter une règle de validation",
"answer_all_rows": "Répondre à toutes les lignes",
"characters": "Caractères",
"contains": "Contient",
"delete_validation_rule": "Supprimer la règle de validation",
"does_not_contain": "Ne contient pas",
"email": "Est un e-mail valide",
"end_date": "Date de fin",
"file_extension_is": "L'extension de fichier est",
"file_extension_is_not": "L'extension de fichier n'est pas",
"is": "Est",
"is_between": "Est entre",
"is_earlier_than": "Est antérieur à",
"is_greater_than": "Est supérieur à",
"is_later_than": "Est postérieur à",
"is_less_than": "Est inférieur à",
"is_not": "N'est pas",
"is_not_between": "N'est pas entre",
"kb": "Ko",
"max_length": "Au maximum",
"max_selections": "Au maximum",
"max_value": "Au maximum",
"mb": "Mo",
"min_length": "Au moins",
"min_selections": "Au moins",
"min_value": "Au moins",
"minimum_options_ranked": "Nombre minimum d'options classées",
"minimum_rows_answered": "Nombre minimum de lignes répondues",
"options_selected": "Options sélectionnées",
"pattern": "Correspond au modèle d'expression régulière",
"phone": "Est un numéro de téléphone valide",
"rank_all_options": "Classer toutes les options",
"select_file_extensions": "Sélectionner les extensions de fichier...",
"select_option": "Sélectionner une option",
"start_date": "Date de début",
"url": "Est une URL valide"
},
"validation_logic_and": "Toutes sont vraies",
"validation_logic_or": "au moins une est vraie",
"validation_rules": "Règles de validation",
"validation_rules_description": "Accepter uniquement les réponses qui répondent aux critères suivants",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} est utilisé dans la logique de la question {questionIndex}. Veuillez d'abord le supprimer de la logique.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable \"{variableName}\" est utilisée dans le quota \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "La variable {variableName} est utilisée dans le quota {quotaName}",
"variable_name_conflicts_with_hidden_field": "Le nom de la variable est en conflit avec un ID de champ masqué existant.",
"variable_name_is_already_taken_please_choose_another": "Le nom de la variable est déjà pris, veuillez en choisir un autre.",
"variable_name_must_start_with_a_letter": "Le nom de la variable doit commencer par une lettre.",
"variable_used_in_recall": "La variable \"{variable}\" est rappelée dans la question {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Une erreur s'est produite lors du téléchargement des réponses",
"first_name": "Prénom",
"how_to_identify_users": "Comment identifier les utilisateurs",
"ip_address": "Adresse IP",
"last_name": "Nom de famille",
"not_completed": "Non terminé ⏳",
"os": "Système d'exploitation",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Ajouter une couleur d'arrière-plan",
"add_background_color_description": "Ajoutez une couleur d'arrière-plan au conteneur du logo.",
"advanced_styling_field_border_radius": "Rayon de bordure",
"advanced_styling_field_button_bg": "Arrière-plan du bouton",
"advanced_styling_field_button_bg_description": "Remplit le bouton Suivant / Envoyer.",
"advanced_styling_field_button_border_radius_description": "Arrondit les coins du bouton.",
"advanced_styling_field_button_font_size_description": "Ajuste la taille du texte du libellé du bouton.",
"advanced_styling_field_button_font_weight_description": "Rend le texte du bouton plus léger ou plus gras.",
"advanced_styling_field_button_height_description": "Contrôle la hauteur du bouton.",
"advanced_styling_field_button_padding_x_description": "Ajoute de l'espace à gauche et à droite.",
"advanced_styling_field_button_padding_y_description": "Ajoute de l'espace en haut et en bas.",
"advanced_styling_field_button_text": "Texte du bouton",
"advanced_styling_field_button_text_description": "Colore le libellé à l'intérieur des boutons.",
"advanced_styling_field_description_color": "Couleur de la description",
"advanced_styling_field_description_color_description": "Colore le texte sous chaque titre.",
"advanced_styling_field_description_size": "Taille de police de la description",
"advanced_styling_field_description_size_description": "Ajuste la taille du texte de description.",
"advanced_styling_field_description_weight": "Graisse de police de la description",
"advanced_styling_field_description_weight_description": "Rend le texte de description plus léger ou plus gras.",
"advanced_styling_field_font_size": "Taille de police",
"advanced_styling_field_font_weight": "Graisse de police",
"advanced_styling_field_headline_color": "Couleur du titre",
"advanced_styling_field_headline_color_description": "Colore le texte principal de la question.",
"advanced_styling_field_headline_size": "Taille de police du titre",
"advanced_styling_field_headline_size_description": "Ajuste la taille du texte du titre.",
"advanced_styling_field_headline_weight": "Graisse de police du titre",
"advanced_styling_field_headline_weight_description": "Rend le texte du titre plus léger ou plus gras.",
"advanced_styling_field_height": "Hauteur",
"advanced_styling_field_indicator_bg": "Arrière-plan de l'indicateur",
"advanced_styling_field_indicator_bg_description": "Colore la partie remplie de la barre.",
"advanced_styling_field_input_border_radius_description": "Arrondit les coins du champ de saisie.",
"advanced_styling_field_input_font_size_description": "Ajuste la taille du texte saisi dans les champs.",
"advanced_styling_field_input_height_description": "Contrôle la hauteur du champ de saisie.",
"advanced_styling_field_input_padding_x_description": "Ajoute de l'espace à gauche et à droite.",
"advanced_styling_field_input_padding_y_description": "Ajoute de l'espace en haut et en bas.",
"advanced_styling_field_input_placeholder_opacity_description": "Atténue le texte d'indication du placeholder.",
"advanced_styling_field_input_shadow_description": "Ajoute une ombre portée autour des champs de saisie.",
"advanced_styling_field_input_text": "Texte de saisie",
"advanced_styling_field_input_text_description": "Colore le texte saisi dans les champs.",
"advanced_styling_field_option_bg": "Arrière-plan",
"advanced_styling_field_option_bg_description": "Remplit les éléments d'option.",
"advanced_styling_field_option_border_radius_description": "Arrondit les coins des options.",
"advanced_styling_field_option_font_size_description": "Ajuste la taille du texte des libellés d'option.",
"advanced_styling_field_option_label": "Couleur de l'étiquette",
"advanced_styling_field_option_label_description": "Colore le texte des libellés d'option.",
"advanced_styling_field_option_padding_x_description": "Ajoute de l'espace à gauche et à droite.",
"advanced_styling_field_option_padding_y_description": "Ajoute de l'espace en haut et en bas.",
"advanced_styling_field_padding_x": "Marge intérieure X",
"advanced_styling_field_padding_y": "Marge intérieure Y",
"advanced_styling_field_placeholder_opacity": "Opacité du placeholder",
"advanced_styling_field_shadow": "Ombre",
"advanced_styling_field_track_bg": "Arrière-plan de la piste",
"advanced_styling_field_track_bg_description": "Colore la partie non remplie de la barre.",
"advanced_styling_field_track_height": "Hauteur de la piste",
"advanced_styling_field_track_height_description": "Contrôle l'épaisseur de la barre de progression.",
"advanced_styling_field_upper_label_color": "Couleur de l'étiquette du titre",
"advanced_styling_field_upper_label_color_description": "Colore le petit libellé au-dessus des champs de saisie.",
"advanced_styling_field_upper_label_size": "Taille de police de l'étiquette du titre",
"advanced_styling_field_upper_label_size_description": "Ajuste la taille du petit libellé au-dessus des champs de saisie.",
"advanced_styling_field_upper_label_weight": "Graisse de police de l'étiquette du titre",
"advanced_styling_field_upper_label_weight_description": "Rend le libellé plus léger ou plus gras.",
"advanced_styling_section_buttons": "Boutons",
"advanced_styling_section_headlines": "Titres et descriptions",
"advanced_styling_section_inputs": "Champs de saisie",
"advanced_styling_section_options": "Options (boutons radio/cases à cocher)",
"app_survey_placement": "Placement du sondage d'application",
"app_survey_placement_settings_description": "Modifiez l'emplacement où les sondages seront affichés dans votre application web ou site web.",
"centered_modal_overlay_color": "Couleur de superposition modale centrée",
"email_customization": "Personnalisation des e-mails",
"email_customization_description": "Modifiez l'apparence des e-mails que Formbricks envoie en votre nom.",
"enable_custom_styling": "Activer le style personnalisé",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Le logo Formbricks est masqué.",
"formbricks_branding_settings_description": "Nous apprécions votre soutien mais comprenons si vous choisissez de le désactiver.",
"formbricks_branding_shown": "Le logo Formbricks est affiché.",
"generate_theme_btn": "Générer",
"generate_theme_confirmation": "Souhaitez-vous générer un thème de couleurs assorti basé sur votre couleur de marque? Cela écrasera vos paramètres de couleur actuels.",
"generate_theme_header": "Générer un thème de couleurs?",
"logo_removed_successfully": "Logo supprimé avec succès",
"logo_settings_description": "Téléchargez le logo de votre entreprise pour personnaliser les enquêtes et les aperçus de liens.",
"logo_updated_successfully": "Logo mis à jour avec succès",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Afficher le logo Formbricks dans les enquêtes {type}",
"show_powered_by_formbricks": "Afficher la signature « Propulsé par Formbricks »",
"styling_updated_successfully": "Style mis à jour avec succès",
"suggest_colors": "Suggérer des couleurs",
"theme": "Thème",
"theme_settings_description": "Créez un thème de style pour toutes les enquêtes. Vous pouvez activer un style personnalisé pour chaque enquête."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Oui, tenez-moi au courant.",
"preview_survey_question_2_choice_2_label": "Non, merci !",
"preview_survey_question_2_headline": "Souhaitez-vous être informé ?",
"preview_survey_question_2_subheader": "Ceci est un exemple de description.",
"preview_survey_welcome_card_headline": "Bienvenue !",
"prioritize_features_description": "Identifiez les fonctionnalités dont vos utilisateurs ont le plus et le moins besoin.",
"prioritize_features_name": "Prioriser les fonctionnalités",
File diff suppressed because it is too large Load Diff
+171 -36
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "大文字と小文字を混ぜる",
"please_verify_captcha": "reCAPTCHAを認証してください",
"privacy_policy": "プライバシーポリシー",
"product_updates_description": "毎月の製品ニュースと機能アップデート、プライバシーポリシーが適用されます。",
"product_updates_title": "製品アップデート",
"security_updates_description": "セキュリティ関連情報のみ、プライバシーポリシーが適用されます。",
"security_updates_title": "セキュリティアップデート",
"terms_of_service": "利用規約",
"title": "Formbricksアカウントを作成"
},
@@ -197,6 +201,7 @@
"docs": "ドキュメント",
"documentation": "ドキュメント",
"domain": "ドメイン",
"done": "完了",
"download": "ダウンロード",
"draft": "下書き",
"duplicate": "複製",
@@ -238,7 +243,6 @@
"imprint": "企業情報",
"in_progress": "進行中",
"inactive_surveys": "非アクティブなフォーム",
"input_type": "入力タイプ",
"integration": "連携",
"integrations": "連携",
"invalid_date": "無効な日付です",
@@ -250,6 +254,7 @@
"label": "ラベル",
"language": "言語",
"learn_more": "詳細を見る",
"license_expired": "License Expired",
"light_overlay": "明るいオーバーレイ",
"limits_reached": "上限に達しました",
"link": "リンク",
@@ -262,13 +267,11 @@
"look_and_feel": "デザイン",
"manage": "管理",
"marketing": "マーケティング",
"maximum": "最大",
"member": "メンバー",
"members": "メンバー",
"members_and_teams": "メンバー&チーム",
"membership_not_found": "メンバーシップが見つかりません",
"metadata": "メタデータ",
"minimum": "最小",
"mobile_overlay_app_works_best_on_desktop": "Formbricks は より 大きな 画面 で最適に 作動します。 フォーム を 管理または 構築する には、 別の デバイス に 切り替える 必要が あります。",
"mobile_overlay_surveys_look_good": "ご安心ください - お使い の デバイス や 画面 サイズ に 関係なく、 フォーム は 素晴らしく 見えます!",
"mobile_overlay_title": "おっと、 小さな 画面 が 検出されました!",
@@ -282,6 +285,7 @@
"no_background_image_found": "背景画像が見つかりません。",
"no_code": "ノーコード",
"no_files_uploaded": "ファイルがアップロードされていません",
"no_overlay": "オーバーレイなし",
"no_quotas_found": "クォータが見つかりません",
"no_result_found": "結果が見つかりません",
"no_results": "結果なし",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "組織のチームが見つかりません",
"other": "その他",
"others": "その他",
"overlay_color": "オーバーレイの色",
"overview": "概要",
"password": "パスワード",
"paused": "一時停止",
@@ -321,7 +326,7 @@
"placeholder": "プレースホルダー",
"please_select_at_least_one_survey": "少なくとも1つのフォームを選択してください",
"please_select_at_least_one_trigger": "少なくとも1つのトリガーを選択してください",
"please_upgrade_your_plan": "プランをアップグレードしてください",
"please_upgrade_your_plan": "プランをアップグレードしてください",
"preview": "プレビュー",
"preview_survey": "フォームをプレビュー",
"privacy": "プライバシーポリシー",
@@ -347,6 +352,7 @@
"request_trial_license": "トライアルライセンスをリクエスト",
"reset_to_default": "デフォルトにリセット",
"response": "回答",
"response_id": "回答ID",
"responses": "回答",
"restart": "再開",
"role": "役割",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "ワークスペースの上限である{projectLimit}件に達しました。",
"you_have_reached_your_monthly_miu_limit_of": "月間MIU(月間アクティブユーザー)の上限に達しました",
"you_have_reached_your_monthly_response_limit_of": "月間回答数の上限に達しました",
"you_will_be_downgraded_to_the_community_edition_on_date": "コミュニティ版へのダウングレードは {date} に行われます。"
"you_will_be_downgraded_to_the_community_edition_on_date": "コミュニティ版へのダウングレードは {date} に行われます。",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "承認",
@@ -783,20 +790,26 @@
"add_webhook": "Webhook を追加",
"add_webhook_description": "フォーム回答データを任意のエンドポイントへ送信",
"all_current_and_new_surveys": "現在および新規のすべてのフォーム",
"copy_secret_now": "署名シークレットをコピー",
"created_by_third_party": "サードパーティによって作成",
"discord_webhook_not_supported": "現在、Discord Webhook はサポートしていません。",
"empty_webhook_message": "Webhook は追加するとここに表示されます。⏲️",
"endpoint_pinged": "成功!Webhook に ping できました。",
"endpoint_pinged_error": "Webhook への ping に失敗しました。",
"learn_to_verify": "Webhook署名の検証方法を学ぶ",
"please_check_console": "詳細はコンソールを確認してください",
"please_enter_a_url": "URL を入力してください",
"response_created": "回答作成",
"response_finished": "回答完了",
"response_updated": "回答更新",
"secret_copy_warning": "このシークレットを安全に保管してください。Webhook 設定で再度確認できます。",
"secret_description": "このシークレットを使用して Webhook リクエストを検証します。署名検証についてはドキュメントを参照してください。",
"signing_secret": "署名シークレット",
"source": "ソース",
"test_endpoint": "エンドポイントをテスト",
"triggers": "トリガー",
"webhook_added_successfully": "Webhook を追加しました",
"webhook_created": "Webhook を作成しました",
"webhook_delete_confirmation": "このWebhookを削除してもよろしいですか?以後の通知は送信されません。",
"webhook_deleted_successfully": "Webhook を削除しました",
"webhook_name_placeholder": "任意: 識別しやすいようWebhookにラベルを付ける",
@@ -943,19 +956,32 @@
"enterprise_features": "エンタープライズ機能",
"get_an_enterprise_license_to_get_access_to_all_features": "すべての機能にアクセスするには、エンタープライズライセンスを取得してください。",
"keep_full_control_over_your_data_privacy_and_security": "データのプライバシーとセキュリティを完全に制御できます。",
"license_invalid_description": "ENTERPRISE_LICENSE_KEY環境変数のライセンスキーが無効です。入力ミスがないか確認するか、新しいキーをリクエストしてください。",
"license_status": "ライセンスステータス",
"license_status_active": "有効",
"license_status_description": "エンタープライズライセンスのステータス。",
"license_status_expired": "期限切れ",
"license_status_invalid": "無効なライセンス",
"license_status_unreachable": "接続不可",
"license_unreachable_grace_period": "ライセンスサーバーに接続できません。エンタープライズ機能は{gracePeriodEnd}までの3日間の猶予期間中は引き続き利用できます。",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "電話不要、制約なし: このフォームに記入して、すべての機能をテストするための無料の30日間トライアルライセンスをリクエストしてください:",
"no_credit_card_no_sales_call_just_test_it": "クレジットカード不要。営業電話もありません。ただテストしてください :)",
"on_request": "リクエストに応じて",
"organization_roles": "組織ロール(管理者、編集者、開発者など)",
"questions_please_reach_out_to": "質問はありますか?こちらまでお問い合わせください",
"recheck_license": "ライセンスを再確認",
"recheck_license_failed": "ライセンスの確認に失敗しました。ライセンスサーバーに接続できない可能性があります。",
"recheck_license_invalid": "ライセンスキーが無効です。ENTERPRISE_LICENSE_KEYを確認してください。",
"recheck_license_success": "ライセンスの確認に成功しました",
"recheck_license_unreachable": "ライセンスサーバーに接続できません。後ほど再度お試しください。",
"rechecking": "再確認中...",
"request_30_day_trial_license": "30日間トライアルライセンスをリクエスト",
"saml_sso": "SAML SSO",
"service_level_agreement": "サービスレベル契約",
"soc2_hipaa_iso_27001_compliance_check": "SOC2、HIPAA、ISO 27001準拠チェック",
"sso": "SSOGoogle、Microsoft、OpenID Connect",
"teams": "チーム&アクセスロール(読み取り、読み書き、管理)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。",
"your_enterprise_license_is_active_all_features_unlocked": "あなたのエンタープライズライセンスは有効です。すべての機能がアンロックされました。"
"unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。"
},
"general": {
"bulk_invite_warning_description": "無料プランでは、すべての組織メンバーに常に「オーナー」ロールが割り当てられます。",
@@ -979,7 +1005,7 @@
"from_your_organization": "あなたの組織から",
"invitation_sent_once_more": "招待状を再度送信しました。",
"invite_deleted_successfully": "招待を正常に削除しました",
"invited_on": "{date}に招待",
"invite_expires_on": "招待は{date}に期限切れ",
"invites_failed": "招待に失敗しました",
"leave_organization": "組織を離れる",
"leave_organization_description": "この組織を離れ、すべてのフォームと回答へのアクセス権を失います。再度招待された場合にのみ再参加できます。",
@@ -1008,6 +1034,8 @@
"remove_logo": "ロゴを削除",
"replace_logo": "ロゴを交換",
"resend_invitation_email": "招待メールを再送信",
"security_list_tip": "セキュリティリストに登録していますか?インスタンスを安全に保つために最新情報を入手しましょう!",
"security_list_tip_link": "こちらからサインアップしてください。",
"share_invite_link": "招待リンクを共有",
"share_this_link_to_let_your_organization_member_join_your_organization": "このリンクを共有して、組織メンバーを招待できます:",
"test_email_sent_successfully": "テストメールを正常に送信しました",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "新しいワークスペースを追加するには、すべてのフィールドを入力してください。",
"read": "読み取り",
"read_write": "読み書き",
"select_member": "メンバーを選択",
"select_workspace": "ワークスペースを選択",
"team_admin": "チーム管理者",
"team_created_successfully": "チームを正常に作成しました。",
"team_deleted_successfully": "チームを正常に削除しました。",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "質問がスキップされた場合に表示するプレースホルダーを追加:",
"add_hidden_field_id": "非表示フィールドIDを追加",
"add_highlight_border": "ハイライトボーダーを追加",
"add_highlight_border_description": "フォームカードに外側のボーダーを追加します。",
"add_logic": "ロジックを追加",
"add_none_of_the_above": "\"いずれも該当しません\" を追加",
"add_option": "オプションを追加",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "フォームがクローズしたときに訪問者が見るメッセージを変更します。",
"adjust_the_theme_in_the": "テーマを",
"all_other_answers_will_continue_to": "他のすべての回答は引き続き",
"allow_file_type": "ファイルタイプを許可",
"allow_multi_select": "複数選択を許可",
"allow_multiple_files": "複数のファイルを許可",
"allow_users_to_select_more_than_one_image": "ユーザーが複数の画像を選択できるようにする",
@@ -1169,6 +1193,9 @@
"assign": "割り当て =",
"audience": "オーディエンス",
"auto_close_on_inactivity": "非アクティブ時に自動閉鎖",
"auto_save_disabled": "自動保存が無効",
"auto_save_disabled_tooltip": "アンケートは下書き状態の時のみ自動保存されます。これにより、公開中のアンケートが意図せず更新されることを防ぎます。",
"auto_save_on": "自動保存オン",
"automatically_close_survey_after": "フォームを自動的に閉じる",
"automatically_close_the_survey_after_a_certain_number_of_responses": "一定の回答数に達した後にフォームを自動的に閉じます。",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "ユーザーが一定秒数応答しない場合、フォームを自動的に閉じます。",
@@ -1178,6 +1205,7 @@
"block_duplicated": "ブロックが複製されました。",
"bold": "太字",
"brand_color": "ブランドカラー",
"brand_color_description": "ボタン、リンク、ハイライトに適用されます。",
"brightness": "明るさ",
"bulk_edit": "一括編集",
"bulk_edit_description": "以下のオプションを1行ずつ編集してください。空の行はスキップされ、重複は削除されます。",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.comのユーザー名またはユーザー名/イベント",
"calculate": "計算",
"capture_a_new_action_to_trigger_a_survey_on": "フォームをトリガーする新しいアクションをキャプチャします。",
"capture_ip_address": "IPアドレスを記録",
"capture_ip_address_description": "重複検出とセキュリティ目的で、回答者のIPアドレスを回答メタデータに保存します",
"capture_new_action": "新しいアクションをキャプチャ",
"card_arrangement_for_survey_type_derived": "{surveyTypeDerived} フォームのカード配置",
"card_background_color": "カードの背景色",
"card_background_color_description": "フォームカードエリアを塗りつぶします。",
"card_border_color": "カードの枠線の色",
"card_border_color_description": "フォームカードの輪郭を描きます。",
"card_styling": "カードのスタイル設定",
"casual": "カジュアル",
"caution_edit_duplicate": "複製して編集",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "古い回答と新しい回答が混ざり、データの概要が誤解を招く可能性があります。",
"caution_recommendation": "これにより、フォームの概要にデータの不整合が生じる可能性があります。代わりにフォームを複製することをお勧めします。",
"caution_text": "変更は不整合を引き起こします",
"centered_modal_overlay_color": "中央モーダルのオーバーレイ色",
"change_anyway": "とにかく変更",
"change_background": "背景を変更",
"change_question_type": "質問の種類を変更",
"change_survey_type": "フォームの種類を変更すると、既存のアクセスに影響します",
"change_the_background_color_of_the_card": "カードの背景色を変更します。",
"change_the_background_color_of_the_input_fields": "入力フィールドの背景色を変更します。",
"change_the_background_to_a_color_image_or_animation": "背景を色、画像、またはアニメーションに変更します。",
"change_the_border_color_of_the_card": "カードの枠線の色を変更します。",
"change_the_border_color_of_the_input_fields": "入力フィールドの枠線の色を変更します。",
"change_the_border_radius_of_the_card_and_the_inputs": "カードと入力の角丸を変更します。",
"change_the_brand_color_of_the_survey": "フォームのブランドカラーを変更します。",
"change_the_placement_of_this_survey": "このフォームの配置を変更します。",
"change_the_question_color_of_the_survey": "フォームの質問の色を変更します。",
"changes_saved": "変更を保存しました。",
"changing_survey_type_will_remove_existing_distribution_channels": "フォームの種類を変更すると、共有方法に影響します。回答者が現在のタイプのアクセスリンクをすでに持っている場合、切り替え後にアクセスを失う可能性があります。",
"character_limit_toggle_description": "回答の長さの上限・下限を設定します。",
"character_limit_toggle_title": "文字数制限を追加",
"checkbox_label": "チェックボックスのラベル",
"choose_the_actions_which_trigger_the_survey": "フォームをトリガーするアクションを選択してください。",
"choose_the_first_question_on_your_block": "ブロックの最初の質問を選択してください",
@@ -1241,7 +1263,6 @@
"contact_fields": "連絡先フィールド",
"contains": "を含む",
"continue_to_settings": "設定に進む",
"control_which_file_types_can_be_uploaded": "アップロードできるファイルの種類を制御します。",
"convert_to_multiple_choice": "複数選択に変換",
"convert_to_single_choice": "単一選択に変換",
"country": "国",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "お好みの背景を暗くしたり明るくしたりします。",
"date_format": "日付形式",
"days_before_showing_this_survey_again": "最後に表示されたアンケートとこのアンケートを表示するまでに、この日数以上の期間を空ける必要があります。",
"delete_anyways": "削除する",
"delete_block": "ブロックを削除",
"delete_choice": "選択肢を削除",
"disable_the_visibility_of_survey_progress": "フォームの進捗状況の表示を無効にする。",
"display_an_estimate_of_completion_time_for_survey": "フォームの完了時間の目安を表示",
"display_number_of_responses_for_survey": "フォームの回答数を表示",
"display_type": "表示タイプ",
"divide": "除算 /",
"does_not_contain": "を含まない",
"does_not_end_with": "で終わらない",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "のすべてを含まない",
"does_not_include_one_of": "のいずれも含まない",
"does_not_start_with": "で始まらない",
"dropdown": "ドロップダウン",
"duplicate_block": "ブロックを複製",
"duplicate_question": "質問を複製",
"edit_link": "編集 リンク",
@@ -1357,7 +1381,6 @@
"hide_progress_bar": "プログレスバーを非表示",
"hide_question_settings": "質問設定を非表示",
"hostname": "ホスト名",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "{surveyTypeDerived} フォームのカードをどれくらいユニークにしますか",
"if_you_need_more_please": "さらに必要な場合は、",
"if_you_really_want_that_answer_ask_until_you_get_it": "回答が提出されるまで、トリガーされるたびに表示し続けます。",
"ignore_global_waiting_time": "クールダウン期間を無視",
@@ -1368,7 +1391,9 @@
"initial_value": "初期値",
"inner_text": "内部テキスト",
"input_border_color": "入力の枠線の色",
"input_border_color_description": "テキスト入力とテキストエリアの輪郭を描きます。",
"input_color": "入力の色",
"input_color_description": "テキスト入力の内側を塗りつぶします。",
"insert_link": "リンク を 挿入",
"invalid_targeting": "無効なターゲティング: オーディエンスフィルターを確認してください",
"invalid_video_url_warning": "有効なYouTube、Vimeo、またはLoomのURLを入力してください。現在、他の動画ホスティングプロバイダーはサポートしていません。",
@@ -1395,10 +1420,10 @@
"key": "キー",
"last_name": "姓",
"let_people_upload_up_to_25_files_at_the_same_time": "一度に最大25個のファイルをアップロードできるようにする。",
"limit_file_types": "ファイルタイプを制限",
"limit_the_maximum_file_size": "最大ファイルサイズを制限",
"limit_upload_file_size_to": "アップロードファイルサイズを以下に制限",
"limit_the_maximum_file_size": "アップロードの最大ファイルサイズを制限します。",
"limit_upload_file_size_to": "アップロードファイルサイズの上限",
"link_survey_description": "フォームページへのリンクを共有するか、ウェブページやメールに埋め込みます。",
"list": "リスト",
"load_segment": "セグメントを読み込み",
"logic_error_warning": "変更するとロジックエラーが発生します",
"logic_error_warning_text": "質問の種類を変更すると、この質問のロジック条件が削除されます",
@@ -1442,22 +1467,22 @@
"picture_idx": "写真 {idx}",
"pin_can_only_contain_numbers": "PINは数字のみでなければなりません。",
"pin_must_be_a_four_digit_number": "PINは4桁の数字でなければなりません。",
"please_enter_a_file_extension": "ファイル拡張子を入力してください。",
"please_enter_a_valid_url": "有効な URL を入力してください (例:https://example.com)",
"please_set_a_survey_trigger": "フォームのトリガーを設定してください",
"please_specify": "具体的に指定してください",
"prevent_double_submission": "二重送信を防ぐ",
"prevent_double_submission_description": "メールアドレスごとに1つの回答のみを許可する",
"progress_saved": "進捗を保存しました",
"protect_survey_with_pin": "PINでフォームを保護",
"protect_survey_with_pin_description": "PINを持つユーザーのみがフォームにアクセスできます。",
"publish": "公開",
"question": "質問",
"question_color": "質問の色",
"question_deleted": "質問を削除しました。",
"question_duplicated": "質問を複製しました。",
"question_id_updated": "質問IDを更新しました",
"question_used_in_logic": "この質問は質問 {questionIndex} のロジックで使用されています",
"question_used_in_quota": "この 質問 は \"{quotaName}\" の クオータ に使用されています",
"question_used_in_logic_warning_text": "このブロックの要素はロジックルールで使用されていますが、本当に削除しますか?",
"question_used_in_logic_warning_title": "ロジックの不整合",
"question_used_in_quota": "この質問は“{quotaName}”クォータで使用されています",
"question_used_in_recall": "この 質問 は 質問 {questionIndex} で 呼び出され て います 。",
"question_used_in_recall_ending_card": "この 質問 は エンディング カード で 呼び出され て います。",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "回答数の上限、リダイレクトなど。",
"response_options": "回答オプション",
"roundness": "丸み",
"roundness_description": "カードの角の丸みを調整します。",
"row_used_in_logic_error": "この行は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
"rows": "行",
"save_and_close": "保存して閉じる",
@@ -1520,6 +1546,7 @@
"search_for_images": "画像を検索",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "トリガーから数秒後に回答がない場合、フォームは閉じられます",
"seconds_before_showing_the_survey": "秒後にフォームを表示します。",
"select_field": "フィールドを選択",
"select_or_type_value": "値を選択または入力",
"select_ordering": "順序を選択",
"select_saved_action": "保存済みのアクションを選択",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "スタイルをテーマのスタイルに設定しました",
"subheading": "サブ見出し",
"subtract": "減算 -",
"suggest_colors": "色を提案",
"survey_completed_heading": "フォームが完了しました",
"survey_completed_subheading": "この無料のオープンソースフォームは閉鎖されました",
"survey_display_settings": "フォーム表示設定",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "回答がなくても1回だけ表示します。",
"then": "その後",
"this_action_will_remove_all_the_translations_from_this_survey": "このアクションは、このフォームからすべての翻訳を削除します。",
"this_extension_is_already_added": "この拡張機能はすでに追加されています。",
"this_file_type_is_not_supported": "このファイルタイプはサポートされていません。",
"three_points": "3点",
"times": "回",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "すべてのフォームの配置を一貫させるために、",
@@ -1589,8 +1613,51 @@
"upper_label": "上限ラベル",
"url_filters": "URLフィルター",
"url_not_supported": "URLはサポートされていません",
"validation": {
"add_validation_rule": "検証ルールを追加",
"answer_all_rows": "すべての行に回答してください",
"characters": "文字数",
"contains": "を含む",
"delete_validation_rule": "検証ルールを削除",
"does_not_contain": "を含まない",
"email": "有効なメールアドレスである",
"end_date": "終了日",
"file_extension_is": "ファイル拡張子が次と一致",
"file_extension_is_not": "ファイル拡張子が次と一致しない",
"is": "である",
"is_between": "の間である",
"is_earlier_than": "より前である",
"is_greater_than": "より大きい",
"is_later_than": "より後である",
"is_less_than": "より小さい",
"is_not": "ではない",
"is_not_between": "の間ではない",
"kb": "KB",
"max_length": "最大",
"max_selections": "最大",
"max_value": "最大",
"mb": "MB",
"min_length": "最小",
"min_selections": "最小",
"min_value": "最小",
"minimum_options_ranked": "ランク付けされた最小オプション数",
"minimum_rows_answered": "回答された最小行数",
"options_selected": "選択されたオプション",
"pattern": "正規表現パターンに一致する",
"phone": "有効な電話番号である",
"rank_all_options": "すべてのオプションをランク付け",
"select_file_extensions": "ファイル拡張子を選択...",
"select_option": "オプションを選択",
"start_date": "開始日",
"url": "有効なURLである"
},
"validation_logic_and": "すべてが真である",
"validation_logic_or": "いずれかが真",
"validation_rules": "検証ルール",
"validation_rules_description": "次の条件を満たす回答のみを受け付ける",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "変数 \"{variableName}\" は \"{quotaName}\" クォータ で使用されています",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "変数{variableName}”は“{quotaName}クォータで使用されています",
"variable_name_conflicts_with_hidden_field": "変数名が既存の非表示フィールドIDと競合しています。",
"variable_name_is_already_taken_please_choose_another": "変数名はすでに使用されています。別の名前を選択してください。",
"variable_name_must_start_with_a_letter": "変数名はアルファベットで始まらなければなりません。",
"variable_used_in_recall": "変数 \"{variable}\" が 質問 {questionIndex} で 呼び出され て います 。",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "回答のダウンロード中にエラーが発生しました",
"first_name": "名",
"how_to_identify_users": "ユーザーを識別する方法",
"ip_address": "IPアドレス",
"last_name": "姓",
"not_completed": "未完了 ⏳",
"os": "OS",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "背景色を追加",
"add_background_color_description": "ロゴコンテナに背景色を追加します。",
"advanced_styling_field_border_radius": "境界線の丸み",
"advanced_styling_field_button_bg": "ボタンの背景",
"advanced_styling_field_button_bg_description": "次へ/送信ボタンを塗りつぶします。",
"advanced_styling_field_button_border_radius_description": "ボタンの角を丸めます。",
"advanced_styling_field_button_font_size_description": "ボタンラベルのテキストサイズを調整します。",
"advanced_styling_field_button_font_weight_description": "ボタンテキストを細くまたは太くします。",
"advanced_styling_field_button_height_description": "ボタンの高さを調整します。",
"advanced_styling_field_button_padding_x_description": "左右にスペースを追加します。",
"advanced_styling_field_button_padding_y_description": "上下にスペースを追加します。",
"advanced_styling_field_button_text": "ボタンのテキスト",
"advanced_styling_field_button_text_description": "ボタン内のラベルに色を付けます。",
"advanced_styling_field_description_color": "説明文の色",
"advanced_styling_field_description_color_description": "各見出しの下のテキストに色を付けます。",
"advanced_styling_field_description_size": "説明文のフォントサイズ",
"advanced_styling_field_description_size_description": "説明テキストのサイズを調整します。",
"advanced_styling_field_description_weight": "説明文のフォントの太さ",
"advanced_styling_field_description_weight_description": "説明テキストを細くまたは太くします。",
"advanced_styling_field_font_size": "フォントサイズ",
"advanced_styling_field_font_weight": "フォントの太さ",
"advanced_styling_field_headline_color": "見出しの色",
"advanced_styling_field_headline_color_description": "メインの質問テキストに色を付けます。",
"advanced_styling_field_headline_size": "見出しのフォントサイズ",
"advanced_styling_field_headline_size_description": "見出しテキストのサイズを調整します。",
"advanced_styling_field_headline_weight": "見出しのフォントの太さ",
"advanced_styling_field_headline_weight_description": "見出しテキストを細くまたは太くします。",
"advanced_styling_field_height": "高さ",
"advanced_styling_field_indicator_bg": "インジケーターの背景",
"advanced_styling_field_indicator_bg_description": "バーの塗りつぶし部分に色を付けます。",
"advanced_styling_field_input_border_radius_description": "入力フィールドの角を丸めます。",
"advanced_styling_field_input_font_size_description": "入力フィールド内の入力テキストのサイズを調整します。",
"advanced_styling_field_input_height_description": "入力フィールドの高さを調整します。",
"advanced_styling_field_input_padding_x_description": "左右にスペースを追加します。",
"advanced_styling_field_input_padding_y_description": "上下にスペースを追加します。",
"advanced_styling_field_input_placeholder_opacity_description": "プレースホルダーのヒントテキストを薄くします。",
"advanced_styling_field_input_shadow_description": "入力フィールドの周囲にドロップシャドウを追加します。",
"advanced_styling_field_input_text": "入力テキスト",
"advanced_styling_field_input_text_description": "入力フィールドに入力されたテキストの色を設定します。",
"advanced_styling_field_option_bg": "背景",
"advanced_styling_field_option_bg_description": "オプション項目を塗りつぶします。",
"advanced_styling_field_option_border_radius_description": "オプションの角を丸くします。",
"advanced_styling_field_option_font_size_description": "オプションラベルのテキストサイズを調整します。",
"advanced_styling_field_option_label": "ラベルの色",
"advanced_styling_field_option_label_description": "オプションラベルのテキストの色を設定します。",
"advanced_styling_field_option_padding_x_description": "左右にスペースを追加します。",
"advanced_styling_field_option_padding_y_description": "上下にスペースを追加します。",
"advanced_styling_field_padding_x": "パディングX",
"advanced_styling_field_padding_y": "パディングY",
"advanced_styling_field_placeholder_opacity": "プレースホルダーの不透明度",
"advanced_styling_field_shadow": "影",
"advanced_styling_field_track_bg": "トラックの背景",
"advanced_styling_field_track_bg_description": "バーの未入力部分の色を設定します。",
"advanced_styling_field_track_height": "トラックの高さ",
"advanced_styling_field_track_height_description": "プログレスバーの太さを調整します。",
"advanced_styling_field_upper_label_color": "見出しラベルの色",
"advanced_styling_field_upper_label_color_description": "入力フィールド上部の小さなラベルの色を設定します。",
"advanced_styling_field_upper_label_size": "見出しラベルのフォントサイズ",
"advanced_styling_field_upper_label_size_description": "入力フィールド上部の小さなラベルのサイズを調整します。",
"advanced_styling_field_upper_label_weight": "見出しラベルのフォントの太さ",
"advanced_styling_field_upper_label_weight_description": "ラベルを細くまたは太くします。",
"advanced_styling_section_buttons": "ボタン",
"advanced_styling_section_headlines": "見出しと説明",
"advanced_styling_section_inputs": "入力フィールド",
"advanced_styling_section_options": "選択肢(ラジオボタン/チェックボックス)",
"app_survey_placement": "アプリ内フォームの配置",
"app_survey_placement_settings_description": "Webアプリまたはウェブサイトでフォームを表示する場所を変更します。",
"centered_modal_overlay_color": "中央モーダルのオーバーレイ色",
"email_customization": "メールのカスタマイズ",
"email_customization_description": "Formbricksがあなたに代わって送信するメールの外観を変更します。",
"enable_custom_styling": "カスタムスタイルを有効化",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricksブランディングは非表示です。",
"formbricks_branding_settings_description": "あなたのサポートに感謝していますが、オフにすることもご理解いただけます。",
"formbricks_branding_shown": "Formbricksブランディングは表示されています。",
"generate_theme_btn": "生成",
"generate_theme_confirmation": "ブランドカラーに基づいて、マッチするカラーテーマを生成しますか?現在のカラー設定は上書きされます。",
"generate_theme_header": "カラーテーマを生成しますか?",
"logo_removed_successfully": "ロゴを正常に削除しました",
"logo_settings_description": "会社のロゴをアップロードして、アンケートとリンクプレビューにブランディングを適用します。",
"logo_updated_successfully": "ロゴを正常に更新しました",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "{type}アンケートにFormbricksブランディングを表示",
"show_powered_by_formbricks": "「Powered by Formbricks」署名を表示",
"styling_updated_successfully": "スタイルを正常に更新しました",
"suggest_colors": "カラーを提案",
"theme": "テーマ",
"theme_settings_description": "すべてのアンケート用のスタイルテーマを作成します。各アンケートでカスタムスタイルを有効にできます。"
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "はい、最新情報を知りたいです。",
"preview_survey_question_2_choice_2_label": "いいえ、結構です!",
"preview_survey_question_2_headline": "最新情報を知りたいですか?",
"preview_survey_question_2_subheader": "これは説明の例です。",
"preview_survey_welcome_card_headline": "ようこそ!",
"prioritize_features_description": "ユーザーが最も必要とする機能と最も必要としない機能を特定する。",
"prioritize_features_name": "機能の優先順位付け",
+173 -38
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Mix van hoofdletters en kleine letters",
"please_verify_captcha": "Controleer reCAPTCHA",
"privacy_policy": "Privacybeleid",
"product_updates_description": "Maandelijks productnieuws en feature-updates, privacybeleid is van toepassing.",
"product_updates_title": "Product-updates",
"security_updates_description": "Alleen beveiligingsrelevante informatie, privacybeleid is van toepassing.",
"security_updates_title": "Beveiligingsupdates",
"terms_of_service": "Servicevoorwaarden",
"title": "Maak uw Formbricks-account aan"
},
@@ -197,6 +201,7 @@
"docs": "Documentatie",
"documentation": "Documentatie",
"domain": "Domein",
"done": "Klaar",
"download": "Downloaden",
"draft": "Voorlopige versie",
"duplicate": "Duplicaat",
@@ -238,7 +243,6 @@
"imprint": "Afdruk",
"in_progress": "In uitvoering",
"inactive_surveys": "Inactieve enquêtes",
"input_type": "Invoertype",
"integration": "integratie",
"integrations": "Integraties",
"invalid_date": "Ongeldige datum",
@@ -250,6 +254,7 @@
"label": "Label",
"language": "Taal",
"learn_more": "Meer informatie",
"license_expired": "License Expired",
"light_overlay": "Lichte overlay",
"limits_reached": "Grenzen bereikt",
"link": "Link",
@@ -262,13 +267,11 @@
"look_and_feel": "Kijk & voel",
"manage": "Beheren",
"marketing": "Marketing",
"maximum": "Maximaal",
"member": "Lid",
"members": "Leden",
"members_and_teams": "Leden & teams",
"membership_not_found": "Lidmaatschap niet gevonden",
"metadata": "Metagegevens",
"minimum": "Minimum",
"mobile_overlay_app_works_best_on_desktop": "Formbricks werkt het beste op een groter scherm. Schakel over naar een ander apparaat om enquêtes te beheren of samen te stellen.",
"mobile_overlay_surveys_look_good": "Maakt u zich geen zorgen: uw enquêtes zien er geweldig uit op elk apparaat en schermformaat!",
"mobile_overlay_title": "Oeps, klein scherm gedetecteerd!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Geen achtergrondafbeelding gevonden.",
"no_code": "Geen code",
"no_files_uploaded": "Er zijn geen bestanden geüpload",
"no_overlay": "Geen overlay",
"no_quotas_found": "Geen quota gevonden",
"no_result_found": "Geen resultaat gevonden",
"no_results": "Geen resultaten",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Organisatieteams niet gevonden",
"other": "Ander",
"others": "Anderen",
"overlay_color": "Overlaykleur",
"overview": "Overzicht",
"password": "Wachtwoord",
"paused": "Gepauzeerd",
@@ -321,7 +326,7 @@
"placeholder": "Tijdelijke aanduiding",
"please_select_at_least_one_survey": "Selecteer ten minste één enquête",
"please_select_at_least_one_trigger": "Selecteer ten minste één trigger",
"please_upgrade_your_plan": "Upgrade uw abonnement.",
"please_upgrade_your_plan": "Upgrade je abonnement",
"preview": "Voorbeeld",
"preview_survey": "Voorbeeld van enquête",
"privacy": "Privacybeleid",
@@ -347,6 +352,7 @@
"request_trial_license": "Proeflicentie aanvragen",
"reset_to_default": "Resetten naar standaard",
"response": "Antwoord",
"response_id": "Antwoord-ID",
"responses": "Reacties",
"restart": "Opnieuw opstarten",
"role": "Rol",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Je hebt je limiet van {projectLimit} werkruimtes bereikt.",
"you_have_reached_your_monthly_miu_limit_of": "U heeft uw maandelijkse MIU-limiet van bereikt",
"you_have_reached_your_monthly_response_limit_of": "U heeft uw maandelijkse responslimiet bereikt van",
"you_will_be_downgraded_to_the_community_edition_on_date": "Je wordt gedowngraded naar de Community-editie op {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Je wordt gedowngraded naar de Community-editie op {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Accepteren",
@@ -783,20 +790,26 @@
"add_webhook": "Webhook toevoegen",
"add_webhook_description": "Stuur enquêtereactiegegevens naar een aangepast eindpunt",
"all_current_and_new_surveys": "Alle huidige en nieuwe onderzoeken",
"copy_secret_now": "Kopieer je ondertekeningsgeheim",
"created_by_third_party": "Gemaakt door een derde partij",
"discord_webhook_not_supported": "Discord-webhooks worden momenteel niet ondersteund.",
"empty_webhook_message": "Uw webhooks verschijnen hier zodra u ze toevoegt. ⏲️",
"endpoint_pinged": "Jawel! We kunnen de webhook pingen!",
"endpoint_pinged_error": "Kan de webhook niet pingen!",
"learn_to_verify": "Leer hoe je webhook-handtekeningen kunt verifiëren",
"please_check_console": "Controleer de console voor meer details",
"please_enter_a_url": "Voer een URL in",
"response_created": "Reactie gemaakt",
"response_finished": "Reactie voltooid",
"response_updated": "Reactie bijgewerkt",
"secret_copy_warning": "Bewaar dit geheim veilig. Je kunt het opnieuw bekijken in de webhook-instellingen.",
"secret_description": "Gebruik dit geheim om webhook-verzoeken te verifiëren. Zie de documentatie voor handtekeningverificatie.",
"signing_secret": "Ondertekeningsgeheim",
"source": "Bron",
"test_endpoint": "Eindpunt testen",
"triggers": "Triggers",
"webhook_added_successfully": "Webhook succesvol toegevoegd",
"webhook_created": "Webhook aangemaakt",
"webhook_delete_confirmation": "Weet u zeker dat u deze webhook wilt verwijderen? Hierdoor worden er geen verdere meldingen meer verzonden.",
"webhook_deleted_successfully": "Webhook is succesvol verwijderd",
"webhook_name_placeholder": "Optioneel: Label uw webhook voor gemakkelijke identificatie",
@@ -943,19 +956,32 @@
"enterprise_features": "Enterprise-functies",
"get_an_enterprise_license_to_get_access_to_all_features": "Ontvang een Enterprise-licentie om toegang te krijgen tot alle functies.",
"keep_full_control_over_your_data_privacy_and_security": "Houd de volledige controle over de privacy en beveiliging van uw gegevens.",
"license_invalid_description": "De licentiesleutel in je ENTERPRISE_LICENSE_KEY omgevingsvariabele is niet geldig. Controleer op typefouten of vraag een nieuwe sleutel aan.",
"license_status": "Licentiestatus",
"license_status_active": "Actief",
"license_status_description": "Status van je enterprise-licentie.",
"license_status_expired": "Verlopen",
"license_status_invalid": "Ongeldige licentie",
"license_status_unreachable": "Niet bereikbaar",
"license_unreachable_grace_period": "Licentieserver is niet bereikbaar. Je enterprise functies blijven actief tijdens een respijtperiode van 3 dagen die eindigt op {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Geen telefoontje nodig, geen verplichtingen: vraag een gratis proeflicentie van 30 dagen aan om alle functies te testen door dit formulier in te vullen:",
"no_credit_card_no_sales_call_just_test_it": "Geen creditcard. Geen verkoopgesprek. Gewoon testen :)",
"on_request": "Op aanvraag",
"organization_roles": "Organisatierollen (beheerder, redacteur, ontwikkelaar, etc.)",
"questions_please_reach_out_to": "Vragen? Neem contact op met",
"recheck_license": "Licentie opnieuw controleren",
"recheck_license_failed": "Licentiecontrole mislukt. De licentieserver is mogelijk niet bereikbaar.",
"recheck_license_invalid": "De licentiesleutel is ongeldig. Controleer je ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Licentiecontrole geslaagd",
"recheck_license_unreachable": "Licentieserver is niet bereikbaar. Probeer het later opnieuw.",
"rechecking": "Opnieuw controleren...",
"request_30_day_trial_license": "Vraag een proeflicentie van 30 dagen aan",
"saml_sso": "SAML-SSO",
"service_level_agreement": "Service Level Overeenkomst",
"soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 Conformiteitscontrole",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Teams en toegangsrollen (lezen, lezen en schrijven, beheren)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis.",
"your_enterprise_license_is_active_all_features_unlocked": "Uw Enterprise-licentie is actief. Alle functies ontgrendeld."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis."
},
"general": {
"bulk_invite_warning_description": "Bij het gratis abonnement krijgen alle organisatieleden altijd de rol 'Eigenaar' toegewezen.",
@@ -979,7 +1005,7 @@
"from_your_organization": "vanuit uw organisatie",
"invitation_sent_once_more": "Uitnodiging nogmaals verzonden.",
"invite_deleted_successfully": "Uitnodiging succesvol verwijderd",
"invited_on": "Uitgenodigd op {date}",
"invite_expires_on": "Uitnodiging verloopt op {date}",
"invites_failed": "Uitnodigingen zijn mislukt",
"leave_organization": "Verlaat de organisatie",
"leave_organization_description": "U verlaat deze organisatie en verliest de toegang tot alle enquêtes en reacties. Je kunt alleen weer meedoen als je opnieuw wordt uitgenodigd.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Logo verwijderen",
"replace_logo": "Logo vervangen",
"resend_invitation_email": "Uitnodigings-e-mail opnieuw verzenden",
"security_list_tip": "Ben je aangemeld voor onze beveiligingslijst? Blijf op de hoogte om je instantie veilig te houden!",
"security_list_tip_link": "Meld je hier aan.",
"share_invite_link": "Deel de uitnodigingslink",
"share_this_link_to_let_your_organization_member_join_your_organization": "Deel deze link om uw organisatielid lid te laten worden van uw organisatie:",
"test_email_sent_successfully": "Test-e-mail succesvol verzonden",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Vul alle velden in om een nieuwe werkruimte toe te voegen.",
"read": "Lezen",
"read_write": "Lezen en schrijven",
"select_member": "Selecteer lid",
"select_workspace": "Selecteer werkruimte",
"team_admin": "Teambeheerder",
"team_created_successfully": "Team succesvol aangemaakt.",
"team_deleted_successfully": "Team succesvol verwijderd.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Voeg een tijdelijke aanduiding toe om aan te geven of er geen waarde is om te onthouden.",
"add_hidden_field_id": "Voeg een verborgen veld-ID toe",
"add_highlight_border": "Markeerrand toevoegen",
"add_highlight_border_description": "Voeg een buitenrand toe aan uw enquêtekaart.",
"add_logic": "Voeg logica toe",
"add_none_of_the_above": "Voeg 'Geen van bovenstaande' toe",
"add_option": "Optie toevoegen",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Wijzig het bericht dat bezoekers zien wanneer de enquête wordt gesloten.",
"adjust_the_theme_in_the": "Pas het thema aan in de",
"all_other_answers_will_continue_to": "Alle andere antwoorden blijven hetzelfde",
"allow_file_type": "Bestandstype toestaan",
"allow_multi_select": "Multi-select toestaan",
"allow_multiple_files": "Meerdere bestanden toestaan",
"allow_users_to_select_more_than_one_image": "Sta gebruikers toe meer dan één afbeelding te selecteren",
@@ -1169,6 +1193,9 @@
"assign": "Toewijzen =",
"audience": "Publiek",
"auto_close_on_inactivity": "Automatisch sluiten bij inactiviteit",
"auto_save_disabled": "Automatisch opslaan uitgeschakeld",
"auto_save_disabled_tooltip": "Uw enquête wordt alleen automatisch opgeslagen wanneer deze een concept is. Dit zorgt ervoor dat openbare enquêtes niet onbedoeld worden bijgewerkt.",
"auto_save_on": "Automatisch opslaan aan",
"automatically_close_survey_after": "Sluit de enquête daarna automatisch af",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Sluit de enquête automatisch af na een bepaald aantal reacties.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Sluit de enquête automatisch af als de gebruiker na een bepaald aantal seconden niet reageert.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Blok gedupliceerd.",
"bold": "Vetgedrukt",
"brand_color": "Merk kleur",
"brand_color_description": "Toegepast op knoppen, links en highlights.",
"brightness": "Helderheid",
"bulk_edit": "Bulkbewerking",
"bulk_edit_description": "Bewerk alle opties hieronder, één per regel. Lege regels worden overgeslagen en duplicaten verwijderd.",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.com-gebruikersnaam of gebruikersnaam/evenement",
"calculate": "Berekenen",
"capture_a_new_action_to_trigger_a_survey_on": "Leg een nieuwe actie vast om een enquête over te activeren.",
"capture_ip_address": "IP-adres vastleggen",
"capture_ip_address_description": "Sla het IP-adres van de respondent op in de metadata van het antwoord voor detectie van duplicaten en beveiligingsdoeleinden",
"capture_new_action": "Leg nieuwe actie vast",
"card_arrangement_for_survey_type_derived": "Kaartarrangement voor {surveyTypeDerived} enquêtes",
"card_background_color": "Achtergrondkleur van de kaart",
"card_background_color_description": "Vult het enquêtekaartgebied.",
"card_border_color": "Randkleur kaart",
"card_border_color_description": "Omlijnt de enquêtekaart.",
"card_styling": "Kaartstijl",
"casual": "Casual",
"caution_edit_duplicate": "Dupliceren en bewerken",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Oudere en nieuwere antwoorden lopen door elkaar heen, wat kan leiden tot misleidende gegevenssamenvattingen.",
"caution_recommendation": "Dit kan inconsistenties in de gegevens in de onderzoekssamenvatting veroorzaken. Wij raden u aan de enquête te dupliceren.",
"caution_text": "Veranderingen zullen tot inconsistenties leiden",
"centered_modal_overlay_color": "Gecentreerde modale overlaykleur",
"change_anyway": "Hoe dan ook veranderen",
"change_background": "Achtergrond wijzigen",
"change_question_type": "Vraagtype wijzigen",
"change_survey_type": "Als u van enquêtetype verandert, heeft dit invloed op de bestaande toegang",
"change_the_background_color_of_the_card": "Verander de achtergrondkleur van de kaart.",
"change_the_background_color_of_the_input_fields": "Verander de achtergrondkleur van de invoervelden.",
"change_the_background_to_a_color_image_or_animation": "Verander de achtergrond in een kleur, afbeelding of animatie.",
"change_the_border_color_of_the_card": "Verander de randkleur van de kaart.",
"change_the_border_color_of_the_input_fields": "Wijzig de randkleur van de invoervelden.",
"change_the_border_radius_of_the_card_and_the_inputs": "Wijzig de randradius van de kaart en de ingangen.",
"change_the_brand_color_of_the_survey": "Wijzig de merkkleur van de enquête.",
"change_the_placement_of_this_survey": "Wijzig de plaatsing van deze enquête.",
"change_the_question_color_of_the_survey": "Verander de vraagkleur van de enquête.",
"changes_saved": "Wijzigingen opgeslagen.",
"changing_survey_type_will_remove_existing_distribution_channels": "Het wijzigen van het enquêtetype heeft invloed op de manier waarop deze kan worden gedeeld. Als respondenten al toegangslinks hebben voor het huidige type, verliezen ze mogelijk de toegang na de overstap.",
"character_limit_toggle_description": "Beperk hoe kort of lang een antwoord mag zijn.",
"character_limit_toggle_title": "Tekenlimieten toevoegen",
"checkbox_label": "Selectievakje-label",
"choose_the_actions_which_trigger_the_survey": "Kies de acties die de enquête activeren.",
"choose_the_first_question_on_your_block": "Kies de eerste vraag in je blok",
@@ -1241,7 +1263,6 @@
"contact_fields": "Contactvelden",
"contains": "Bevat",
"continue_to_settings": "Ga verder naar Instellingen",
"control_which_file_types_can_be_uploaded": "Bepaal welke bestandstypen kunnen worden geüpload.",
"convert_to_multiple_choice": "Converteren naar Multi-select",
"convert_to_single_choice": "Converteren naar Enkele selectie",
"country": "Land",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Maak de achtergrond naar keuze donkerder of lichter.",
"date_format": "Datumformaat",
"days_before_showing_this_survey_again": "of meer dagen moeten verstrijken tussen de laatst getoonde enquête en het tonen van deze enquête.",
"delete_anyways": "Toch verwijderen",
"delete_block": "Blok verwijderen",
"delete_choice": "Keuze verwijderen",
"disable_the_visibility_of_survey_progress": "Schakel de zichtbaarheid van de voortgang van het onderzoek uit.",
"display_an_estimate_of_completion_time_for_survey": "Geef een schatting weer van de voltooiingstijd voor het onderzoek",
"display_number_of_responses_for_survey": "Weergave aantal reacties voor enquête",
"display_type": "Weergavetype",
"divide": "Verdeling /",
"does_not_contain": "Bevat niet",
"does_not_end_with": "Eindigt niet met",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Omvat niet alles",
"does_not_include_one_of": "Bevat niet een van",
"does_not_start_with": "Begint niet met",
"dropdown": "Dropdown",
"duplicate_block": "Blok dupliceren",
"duplicate_question": "Vraag dupliceren",
"edit_link": "Link bewerken",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Voortgangsbalk verbergen",
"hide_question_settings": "Vraaginstellingen verbergen",
"hostname": "Hostnaam",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Hoe funky wil je je kaarten hebben in {surveyTypeDerived} Enquêtes",
"if_you_need_more_please": "Als u meer nodig heeft, alstublieft",
"if_you_need_more_please": "Als je meer nodig hebt,",
"if_you_really_want_that_answer_ask_until_you_get_it": "Blijf tonen wanneer geactiveerd totdat een reactie is ingediend.",
"ignore_global_waiting_time": "Afkoelperiode negeren",
"ignore_global_waiting_time_description": "Deze enquête kan worden getoond wanneer aan de voorwaarden wordt voldaan, zelfs als er onlangs een andere enquête is getoond.",
@@ -1368,7 +1391,9 @@
"initial_value": "Initiële waarde",
"inner_text": "Innerlijke tekst",
"input_border_color": "Randkleur invoeren",
"input_border_color_description": "Omlijnt tekstvelden en tekstgebieden.",
"input_color": "Kleur invoeren",
"input_color_description": "Vult de binnenkant van tekstvelden.",
"insert_link": "Link invoegen",
"invalid_targeting": "Ongeldige targeting: controleer uw doelgroepfilters",
"invalid_video_url_warning": "Voer een geldige YouTube-, Vimeo- of Loom-URL in. We ondersteunen momenteel geen andere videohostingproviders.",
@@ -1395,10 +1420,10 @@
"key": "Sleutel",
"last_name": "Achternaam",
"let_people_upload_up_to_25_files_at_the_same_time": "Laat mensen maximaal 25 bestanden tegelijk uploaden.",
"limit_file_types": "Beperk bestandstypen",
"limit_the_maximum_file_size": "Beperk de maximale bestandsgrootte",
"limit_upload_file_size_to": "Beperk de uploadbestandsgrootte tot",
"limit_the_maximum_file_size": "Beperk de maximale bestandsgrootte voor uploads.",
"limit_upload_file_size_to": "Beperk uploadbestandsgrootte tot",
"link_survey_description": "Deel een link naar een enquêtepagina of sluit deze in op een webpagina of e-mail.",
"list": "Lijst",
"load_segment": "Laadsegment",
"logic_error_warning": "Wijzigen zal logische fouten veroorzaken",
"logic_error_warning_text": "Als u het vraagtype wijzigt, worden de logische voorwaarden van deze vraag verwijderd",
@@ -1410,7 +1435,7 @@
"matrix_all_fields": "Alle velden",
"matrix_rows": "Rijen",
"max_file_size": "Maximale bestandsgrootte",
"max_file_size_limit_is": "De maximale bestandsgrootte is",
"max_file_size_limit_is": "Maximale bestandsgroottelimiet is",
"move_question_to_block": "Vraag naar blok verplaatsen",
"multiply": "Vermenigvuldig *",
"needed_for_self_hosted_cal_com_instance": "Nodig voor een zelf-gehoste Cal.com-instantie",
@@ -1442,22 +1467,22 @@
"picture_idx": "Afbeelding {idx}",
"pin_can_only_contain_numbers": "De pincode kan alleen cijfers bevatten.",
"pin_must_be_a_four_digit_number": "De pincode moet uit vier cijfers bestaan.",
"please_enter_a_file_extension": "Voer een bestandsextensie in.",
"please_enter_a_valid_url": "Voer een geldige URL in (bijvoorbeeld https://example.com)",
"please_set_a_survey_trigger": "Stel een enquêtetrigger in",
"please_specify": "Gelieve te specificeren",
"prevent_double_submission": "Voorkom dubbele indiening",
"prevent_double_submission_description": "Er is slechts 1 reactie per e-mailadres toegestaan",
"progress_saved": "Voortgang opgeslagen",
"protect_survey_with_pin": "Beveilig onderzoek met een pincode",
"protect_survey_with_pin_description": "Alleen gebruikers die de pincode hebben, hebben toegang tot de enquête.",
"publish": "Publiceren",
"question": "Vraag",
"question_color": "Vraag kleur",
"question_deleted": "Vraag verwijderd.",
"question_duplicated": "Vraag dubbel gesteld.",
"question_id_updated": "Vraag-ID bijgewerkt",
"question_used_in_logic": "Deze vraag wordt gebruikt in de logica van vraag {questionIndex}.",
"question_used_in_quota": "Deze vraag wordt gebruikt in het quotum '{quotaName}'",
"question_used_in_logic_warning_text": "Elementen uit dit blok worden gebruikt in een logische regel, weet je zeker dat je het wilt verwijderen?",
"question_used_in_logic_warning_title": "Logica-inconsistentie",
"question_used_in_quota": "Deze vraag wordt gebruikt in het quotum “{quotaName}”",
"question_used_in_recall": "Deze vraag wordt teruggehaald in vraag {questionIndex}.",
"question_used_in_recall_ending_card": "Deze vraag wordt teruggeroepen in de Eindkaart",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Reactielimieten, omleidingen en meer.",
"response_options": "Reactieopties",
"roundness": "Rondheid",
"roundness_description": "Bepaalt hoe afgerond de kaarthoeken zijn.",
"row_used_in_logic_error": "Deze rij wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.",
"rows": "Rijen",
"save_and_close": "Opslaan en sluiten",
@@ -1520,6 +1546,7 @@
"search_for_images": "Zoek naar afbeeldingen",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "seconden na trigger wordt de enquête gesloten als er geen reactie is",
"seconds_before_showing_the_survey": "seconden voordat de enquête wordt weergegeven.",
"select_field": "Selecteer veld",
"select_or_type_value": "Selecteer of typ een waarde",
"select_ordering": "Selecteer bestellen",
"select_saved_action": "Selecteer opgeslagen actie",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Styling ingesteld op themastijlen",
"subheading": "Ondertitel",
"subtract": "Aftrekken -",
"suggest_colors": "Stel kleuren voor",
"survey_completed_heading": "Enquête voltooid",
"survey_completed_subheading": "Deze gratis en open source-enquête is gesloten",
"survey_display_settings": "Enquêteweergave-instellingen",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Toon één keer, zelfs als ze niet reageren.",
"then": "Dan",
"this_action_will_remove_all_the_translations_from_this_survey": "Met deze actie worden alle vertalingen uit deze enquête verwijderd.",
"this_extension_is_already_added": "Deze extensie is al toegevoegd.",
"this_file_type_is_not_supported": "Dit bestandstype wordt niet ondersteund.",
"three_points": "3 punten",
"times": "keer",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Om de plaatsing over alle enquêtes consistent te houden, kunt u dat doen",
@@ -1589,8 +1613,51 @@
"upper_label": "Bovenste etiket",
"url_filters": "URL-filters",
"url_not_supported": "URL niet ondersteund",
"validation": {
"add_validation_rule": "Validatieregel toevoegen",
"answer_all_rows": "Beantwoord alle rijen",
"characters": "Tekens",
"contains": "Bevat",
"delete_validation_rule": "Validatieregel verwijderen",
"does_not_contain": "Bevat niet",
"email": "Is geldig e-mailadres",
"end_date": "Einddatum",
"file_extension_is": "Bestandsextensie is",
"file_extension_is_not": "Bestandsextensie is niet",
"is": "Is",
"is_between": "Is tussen",
"is_earlier_than": "Is eerder dan",
"is_greater_than": "Is groter dan",
"is_later_than": "Is later dan",
"is_less_than": "Is minder dan",
"is_not": "Is niet",
"is_not_between": "Is niet tussen",
"kb": "KB",
"max_length": "Maximaal",
"max_selections": "Maximaal",
"max_value": "Maximaal",
"mb": "MB",
"min_length": "Minimaal",
"min_selections": "Minimaal",
"min_value": "Minimaal",
"minimum_options_ranked": "Minimaal aantal gerangschikte opties",
"minimum_rows_answered": "Minimaal aantal beantwoorde rijen",
"options_selected": "Opties geselecteerd",
"pattern": "Komt overeen met regex-patroon",
"phone": "Is geldig telefoonnummer",
"rank_all_options": "Rangschik alle opties",
"select_file_extensions": "Selecteer bestandsextensies...",
"select_option": "Optie selecteren",
"start_date": "Startdatum",
"url": "Is geldige URL"
},
"validation_logic_and": "Alle zijn waar",
"validation_logic_or": "een is waar",
"validation_rules": "Validatieregels",
"validation_rules_description": "Accepteer alleen antwoorden die voldoen aan de volgende criteria",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} wordt gebruikt in de logica van vraag {questionIndex}. Verwijder het eerst uit de logica.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabele \"{variableName}\" wordt gebruikt in het \"{quotaName}\" quotum",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabele {variableName} wordt gebruikt in het quotum “{quotaName}”",
"variable_name_conflicts_with_hidden_field": "Variabelenaam conflicteert met een bestaande verborgen veld-ID.",
"variable_name_is_already_taken_please_choose_another": "Variabelenaam is al in gebruik, kies een andere.",
"variable_name_must_start_with_a_letter": "Variabelenaam moet beginnen met een letter.",
"variable_used_in_recall": "Variabele \"{variable}\" wordt opgeroepen in vraag {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Er is een fout opgetreden bij het downloaden van de antwoorden",
"first_name": "Voornaam",
"how_to_identify_users": "Hoe gebruikers te identificeren",
"ip_address": "IP-adres",
"last_name": "Achternaam",
"not_completed": "Niet voltooid ⏳",
"os": "Besturingssysteem",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Achtergrondkleur toevoegen",
"add_background_color_description": "Voeg een achtergrondkleur toe aan de logocontainer.",
"advanced_styling_field_border_radius": "Hoekradius",
"advanced_styling_field_button_bg": "Knopachtergrond",
"advanced_styling_field_button_bg_description": "Vult de volgende/verzend-knop.",
"advanced_styling_field_button_border_radius_description": "Rondt de knophoeken af.",
"advanced_styling_field_button_font_size_description": "Schaalt de tekst van het knoplabel.",
"advanced_styling_field_button_font_weight_description": "Maakt knoptekst lichter of vetter.",
"advanced_styling_field_button_height_description": "Bepaalt de knophoogte.",
"advanced_styling_field_button_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.",
"advanced_styling_field_button_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.",
"advanced_styling_field_button_text": "Knoptekst",
"advanced_styling_field_button_text_description": "Kleurt het label binnen knoppen.",
"advanced_styling_field_description_color": "Beschrijvingskleur",
"advanced_styling_field_description_color_description": "Kleurt de tekst onder elke kop.",
"advanced_styling_field_description_size": "Lettergrootte beschrijving",
"advanced_styling_field_description_size_description": "Schaalt de beschrijvingstekst.",
"advanced_styling_field_description_weight": "Letterdikte beschrijving",
"advanced_styling_field_description_weight_description": "Maakt beschrijvingstekst lichter of vetter.",
"advanced_styling_field_font_size": "Lettergrootte",
"advanced_styling_field_font_weight": "Letterdikte",
"advanced_styling_field_headline_color": "Kopkleur",
"advanced_styling_field_headline_color_description": "Kleurt de hoofdvraagtekst.",
"advanced_styling_field_headline_size": "Lettergrootte kop",
"advanced_styling_field_headline_size_description": "Schaalt de koptekst.",
"advanced_styling_field_headline_weight": "Letterdikte kop",
"advanced_styling_field_headline_weight_description": "Maakt koptekst lichter of vetter.",
"advanced_styling_field_height": "Hoogte",
"advanced_styling_field_indicator_bg": "Indicatorachtergrond",
"advanced_styling_field_indicator_bg_description": "Kleurt het gevulde deel van de balk.",
"advanced_styling_field_input_border_radius_description": "Rondt de invoerhoeken af.",
"advanced_styling_field_input_font_size_description": "Schaalt de getypte tekst in invoervelden.",
"advanced_styling_field_input_height_description": "Bepaalt de hoogte van het invoerveld.",
"advanced_styling_field_input_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.",
"advanced_styling_field_input_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.",
"advanced_styling_field_input_placeholder_opacity_description": "Vervaagt de tijdelijke aanwijzingstekst.",
"advanced_styling_field_input_shadow_description": "Voegt een slagschaduw toe rond invoervelden.",
"advanced_styling_field_input_text": "Invoertekst",
"advanced_styling_field_input_text_description": "Kleurt de getypte tekst in invoervelden.",
"advanced_styling_field_option_bg": "Achtergrond",
"advanced_styling_field_option_bg_description": "Vult de optie-items.",
"advanced_styling_field_option_border_radius_description": "Rondt de hoeken van opties af.",
"advanced_styling_field_option_font_size_description": "Schaalt de tekst van optielabels.",
"advanced_styling_field_option_label": "Labelkleur",
"advanced_styling_field_option_label_description": "Kleurt de tekst van optielabels.",
"advanced_styling_field_option_padding_x_description": "Voegt ruimte toe aan de linker- en rechterkant.",
"advanced_styling_field_option_padding_y_description": "Voegt ruimte toe aan de boven- en onderkant.",
"advanced_styling_field_padding_x": "Opvulling X",
"advanced_styling_field_padding_y": "Opvulling Y",
"advanced_styling_field_placeholder_opacity": "Plaatshouderopaciteit",
"advanced_styling_field_shadow": "Schaduw",
"advanced_styling_field_track_bg": "Sporachtergrond",
"advanced_styling_field_track_bg_description": "Kleurt het ongevulde gedeelte van de balk.",
"advanced_styling_field_track_height": "Spoorhoogte",
"advanced_styling_field_track_height_description": "Regelt de dikte van de voortgangsbalk.",
"advanced_styling_field_upper_label_color": "Koplabelkleur",
"advanced_styling_field_upper_label_color_description": "Kleurt het kleine label boven invoervelden.",
"advanced_styling_field_upper_label_size": "Lettergrootte koplabel",
"advanced_styling_field_upper_label_size_description": "Schaalt het kleine label boven invoervelden.",
"advanced_styling_field_upper_label_weight": "Letterdikte koplabel",
"advanced_styling_field_upper_label_weight_description": "Maakt het label lichter of vetter.",
"advanced_styling_section_buttons": "Knoppen",
"advanced_styling_section_headlines": "Koppen & beschrijvingen",
"advanced_styling_section_inputs": "Invoervelden",
"advanced_styling_section_options": "Opties (radio/checkbox)",
"app_survey_placement": "App-enquête plaatsing",
"app_survey_placement_settings_description": "Wijzig waar enquêtes worden weergegeven in uw web-app of website.",
"centered_modal_overlay_color": "Gecentreerde modale overlaykleur",
"email_customization": "E-mail aanpassing",
"email_customization_description": "Wijzig het uiterlijk van e-mails die Formbricks namens u verstuurt.",
"enable_custom_styling": "Aangepaste styling inschakelen",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricks-branding is verborgen.",
"formbricks_branding_settings_description": "We waarderen uw steun, maar begrijpen het als u dit uitschakelt.",
"formbricks_branding_shown": "Formbricks-branding wordt weergegeven.",
"generate_theme_btn": "Genereren",
"generate_theme_confirmation": "Wil je een passend kleurthema genereren op basis van je merkkleur? Dit overschrijft je huidige kleurinstellingen.",
"generate_theme_header": "Kleurthema genereren?",
"logo_removed_successfully": "Logo succesvol verwijderd",
"logo_settings_description": "Upload uw bedrijfslogo om enquêtes en linkvoorbeelden te voorzien van uw huisstijl.",
"logo_updated_successfully": "Logo succesvol bijgewerkt",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Toon Formbricks-branding in {type} enquêtes",
"show_powered_by_formbricks": "Toon 'Powered by Formbricks' handtekening",
"styling_updated_successfully": "Styling succesvol bijgewerkt",
"suggest_colors": "Kleuren voorstellen",
"theme": "Thema",
"theme_settings_description": "Maak een stijlthema voor alle enquêtes. Je kunt aangepaste styling inschakelen voor elke enquête."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Ja, houd mij op de hoogte.",
"preview_survey_question_2_choice_2_label": "Nee, dank je!",
"preview_survey_question_2_headline": "Wil je op de hoogte blijven?",
"preview_survey_question_2_subheader": "Dit is een voorbeeldbeschrijving.",
"preview_survey_welcome_card_headline": "Welkom!",
"prioritize_features_description": "Identificeer functies die uw gebruikers het meest en het minst nodig hebben.",
"prioritize_features_name": "Geef prioriteit aan functies",
+172 -37
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "mistura de maiúsculas e minúsculas",
"please_verify_captcha": "Por favor, verifique o reCAPTCHA",
"privacy_policy": "Política de Privacidade",
"product_updates_description": "Novidades mensais do produto e atualizações de recursos, a Política de Privacidade se aplica.",
"product_updates_title": "Atualizações do produto",
"security_updates_description": "Apenas informações relevantes sobre segurança, a Política de Privacidade se aplica.",
"security_updates_title": "Atualizações de segurança",
"terms_of_service": "Termos de Serviço",
"title": "Crie sua conta no Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Documentação",
"documentation": "Documentação",
"domain": "Domínio",
"done": "Concluído",
"download": "baixar",
"draft": "Rascunho",
"duplicate": "Duplicar",
@@ -238,7 +243,6 @@
"imprint": "impressão",
"in_progress": "Em andamento",
"inactive_surveys": "Pesquisas inativas",
"input_type": "Tipo de entrada",
"integration": "integração",
"integrations": "Integrações",
"invalid_date": "Data inválida",
@@ -250,6 +254,7 @@
"label": "Etiqueta",
"language": "Língua",
"learn_more": "Saiba mais",
"license_expired": "License Expired",
"light_overlay": "sobreposição leve",
"limits_reached": "Limites Atingidos",
"link": "link",
@@ -262,13 +267,11 @@
"look_and_feel": "Aparência e Experiência",
"manage": "gerenciar",
"marketing": "marketing",
"maximum": "Máximo",
"member": "Membros",
"members": "Membros",
"members_and_teams": "Membros e equipes",
"membership_not_found": "Assinatura não encontrada",
"metadata": "metadados",
"minimum": "Mínimo",
"mobile_overlay_app_works_best_on_desktop": "Formbricks funciona melhor em uma tela maior. Para gerenciar ou criar pesquisas, mude para outro dispositivo.",
"mobile_overlay_surveys_look_good": "Não se preocupe suas pesquisas ficam ótimas em qualquer dispositivo e tamanho de tela!",
"mobile_overlay_title": "Eita, tela pequena detectada!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Imagem de fundo não encontrada.",
"no_code": "Sem código",
"no_files_uploaded": "Nenhum arquivo foi enviado",
"no_overlay": "Sem sobreposição",
"no_quotas_found": "Nenhuma cota encontrada",
"no_result_found": "Nenhum resultado encontrado",
"no_results": "Nenhum resultado",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Equipes da organização não encontradas",
"other": "outro",
"others": "Outros",
"overlay_color": "Cor da sobreposição",
"overview": "Visão Geral",
"password": "Senha",
"paused": "Pausado",
@@ -321,7 +326,7 @@
"placeholder": "Espaço reservado",
"please_select_at_least_one_survey": "Por favor, selecione pelo menos uma pesquisa",
"please_select_at_least_one_trigger": "Por favor, selecione pelo menos um gatilho",
"please_upgrade_your_plan": "Por favor, atualize seu plano.",
"please_upgrade_your_plan": "Por favor, atualize seu plano",
"preview": "Prévia",
"preview_survey": "Prévia da Pesquisa",
"privacy": "Política de Privacidade",
@@ -347,6 +352,7 @@
"request_trial_license": "Pedir licença de teste",
"reset_to_default": "Restaurar para o padrão",
"response": "Resposta",
"response_id": "ID da resposta",
"responses": "Respostas",
"restart": "Reiniciar",
"role": "Rolê",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Você atingiu seu limite de {projectLimit} espaços de trabalho.",
"you_have_reached_your_monthly_miu_limit_of": "Você atingiu o seu limite mensal de MIU de",
"you_have_reached_your_monthly_response_limit_of": "Você atingiu o limite mensal de respostas de",
"you_will_be_downgraded_to_the_community_edition_on_date": "Você será rebaixado para a Edição Comunitária em {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Você será rebaixado para a Edição Comunitária em {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Aceitar",
@@ -783,20 +790,26 @@
"add_webhook": "Adicionar Webhook",
"add_webhook_description": "Enviar dados das respostas da pesquisa para um endpoint personalizado",
"all_current_and_new_surveys": "Todas as pesquisas atuais e novas",
"copy_secret_now": "Copie seu segredo de assinatura",
"created_by_third_party": "Criado por um Terceiro",
"discord_webhook_not_supported": "Webhooks do Discord não são suportados no momento.",
"empty_webhook_message": "Seus webhooks vão aparecer aqui assim que você adicioná-los. ⏲️",
"endpoint_pinged": "Uhul! Conseguimos pingar o webhook!",
"endpoint_pinged_error": "Não consegui pingar o webhook!",
"learn_to_verify": "Aprenda como verificar assinaturas de webhook",
"please_check_console": "Por favor, verifica o console para mais detalhes",
"please_enter_a_url": "Por favor, insira uma URL",
"response_created": "Resposta Criada",
"response_finished": "Resposta Finalizada",
"response_updated": "Resposta Atualizada",
"secret_copy_warning": "Armazene este segredo com segurança. Você pode visualizá-lo novamente nas configurações do webhook.",
"secret_description": "Use este segredo para verificar requisições de webhook. Consulte a documentação para verificação de assinatura.",
"signing_secret": "Segredo de assinatura",
"source": "fonte",
"test_endpoint": "Testar Ponto de Extremidade",
"triggers": "gatilhos",
"webhook_added_successfully": "Webhook adicionado com sucesso",
"webhook_created": "Webhook criado",
"webhook_delete_confirmation": "Tem certeza de que quer deletar esse Webhook? Isso vai parar de te enviar qualquer notificação.",
"webhook_deleted_successfully": "Webhook deletado com sucesso",
"webhook_name_placeholder": "Opcional: Dê um nome ao seu webhook para facilitar a identificação",
@@ -943,19 +956,32 @@
"enterprise_features": "Recursos Empresariais",
"get_an_enterprise_license_to_get_access_to_all_features": "Adquira uma licença Enterprise para ter acesso a todos os recursos.",
"keep_full_control_over_your_data_privacy_and_security": "Mantenha controle total sobre a privacidade e segurança dos seus dados.",
"license_invalid_description": "A chave de licença na sua variável de ambiente ENTERPRISE_LICENSE_KEY não é válida. Verifique se há erros de digitação ou solicite uma nova chave.",
"license_status": "Status da licença",
"license_status_active": "Ativa",
"license_status_description": "Status da sua licença enterprise.",
"license_status_expired": "Expirada",
"license_status_invalid": "Licença inválida",
"license_status_unreachable": "Inacessível",
"license_unreachable_grace_period": "O servidor de licenças não pode ser alcançado. Seus recursos empresariais permanecem ativos durante um período de carência de 3 dias que termina em {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sem necessidade de ligação, sem compromisso: Solicite uma licença de teste gratuita de 30 dias para testar todas as funcionalidades preenchendo este formulário:",
"no_credit_card_no_sales_call_just_test_it": "Sem cartão de crédito. Sem ligação de vendas. Só teste :)",
"on_request": "Quando solicitado",
"organization_roles": "Funções na Organização (Admin, Editor, Desenvolvedor, etc.)",
"questions_please_reach_out_to": "Perguntas? Entre em contato com",
"recheck_license": "Verificar licença novamente",
"recheck_license_failed": "Falha na verificação da licença. O servidor de licenças pode estar inacessível.",
"recheck_license_invalid": "A chave de licença é inválida. Verifique sua ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Verificação da licença bem-sucedida",
"recheck_license_unreachable": "Servidor de licenças inacessível. Por favor, tente novamente mais tarde.",
"rechecking": "Verificando novamente...",
"request_30_day_trial_license": "Pedir Licença de Teste de 30 Dias",
"saml_sso": "SSO SAML",
"service_level_agreement": "Acordo de Nível de Serviço",
"soc2_hipaa_iso_27001_compliance_check": "Verificação de conformidade SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Equipes e Funções de Acesso (Ler, Ler e Escrever, Gerenciar)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias.",
"your_enterprise_license_is_active_all_features_unlocked": "Sua licença empresarial está ativa. Todos os recursos estão desbloqueados."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"bulk_invite_warning_description": "Por favor, note que no Plano Gratuito, todos os membros da organização são automaticamente atribuídos ao papel de 'Owner', independentemente do papel especificado no arquivo CSV.",
@@ -979,7 +1005,7 @@
"from_your_organization": "da sua organização",
"invitation_sent_once_more": "Convite enviado de novo.",
"invite_deleted_successfully": "Convite deletado com sucesso",
"invited_on": "Convidado em {date}",
"invite_expires_on": "O convite expira em {date}",
"invites_failed": "Convites falharam",
"leave_organization": "Sair da organização",
"leave_organization_description": "Você vai sair dessa organização e perder acesso a todas as pesquisas e respostas. Você só pode voltar se for convidado de novo.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Remover logo",
"replace_logo": "Substituir logo",
"resend_invitation_email": "Reenviar E-mail de Convite",
"security_list_tip": "Você está inscrito na nossa Lista de Segurança? Mantenha-se informado para manter sua instância segura!",
"security_list_tip_link": "Cadastre-se aqui.",
"share_invite_link": "Compartilhar Link de Convite",
"share_this_link_to_let_your_organization_member_join_your_organization": "Compartilhe esse link para que o membro da sua organização possa entrar na sua organização:",
"test_email_sent_successfully": "E-mail de teste enviado com sucesso",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Preencha todos os campos para adicionar um novo espaço de trabalho.",
"read": "Leitura",
"read_write": "Leitura & Escrita",
"select_member": "Selecionar membro",
"select_workspace": "Selecionar espaço de trabalho",
"team_admin": "Administrador da equipe",
"team_created_successfully": "Equipe criada com sucesso.",
"team_deleted_successfully": "Equipe excluída com sucesso.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Adicionar um texto padrão para mostrar se a pergunta for ignorada:",
"add_hidden_field_id": "Adicionar campo oculto ID",
"add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Adicione uma borda externa ao seu cartão de pesquisa.",
"add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"",
"add_option": "Adicionar opção",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Mude a mensagem que os visitantes veem quando a pesquisa está fechada.",
"adjust_the_theme_in_the": "Ajuste o tema no",
"all_other_answers_will_continue_to": "Todas as outras respostas continuarão a",
"allow_file_type": "Permitir tipo de arquivo",
"allow_multi_select": "Permitir seleção múltipla",
"allow_multiple_files": "Permitir vários arquivos",
"allow_users_to_select_more_than_one_image": "Permitir que os usuários selecionem mais de uma imagem",
@@ -1169,6 +1193,9 @@
"assign": "atribuir =",
"audience": "Público",
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
"auto_save_disabled": "Salvamento automático desativado",
"auto_save_disabled_tooltip": "Sua pesquisa só é salva automaticamente quando está em rascunho. Isso garante que pesquisas públicas não sejam atualizadas involuntariamente.",
"auto_save_on": "Salvamento automático ativado",
"automatically_close_survey_after": "Fechar pesquisa automaticamente após",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fechar automaticamente a pesquisa depois de um certo número de respostas.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Feche automaticamente a pesquisa se o usuário não responder depois de alguns segundos.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Bloco duplicado.",
"bold": "Negrito",
"brand_color": "Cor da marca",
"brand_color_description": "Aplicado a botões, links e destaques.",
"brightness": "brilho",
"bulk_edit": "Edição em massa",
"bulk_edit_description": "Edite todas as opções abaixo, uma por linha. Linhas vazias serão ignoradas e duplicatas removidas.",
@@ -1190,10 +1218,14 @@
"cal_username": "Nome de usuário do Cal.com ou nome de usuário/evento",
"calculate": "Calcular",
"capture_a_new_action_to_trigger_a_survey_on": "Captura uma nova ação pra disparar uma pesquisa.",
"capture_ip_address": "Capturar endereço IP",
"capture_ip_address_description": "Armazenar o endereço IP do respondente nos metadados da resposta para fins de detecção de duplicatas e segurança",
"capture_new_action": "Capturar nova ação",
"card_arrangement_for_survey_type_derived": "Arranjo de Cartões para Pesquisas {surveyTypeDerived}",
"card_background_color": "Cor de fundo do cartão",
"card_background_color_description": "Preenche a área do cartão da pesquisa.",
"card_border_color": "Cor da borda do cartão",
"card_border_color_description": "Contorna o cartão da pesquisa.",
"card_styling": "Estilo do cartão",
"casual": "Casual",
"caution_edit_duplicate": "Duplicar e editar",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Respostas antigas e novas são misturadas, o que pode levar a resumos de dados enganosos.",
"caution_recommendation": "Isso pode causar inconsistências de dados no resumo da pesquisa. Recomendamos duplicar a pesquisa em vez disso.",
"caution_text": "Mudanças vão levar a inconsistências",
"centered_modal_overlay_color": "cor de sobreposição modal centralizada",
"change_anyway": "Mudar mesmo assim",
"change_background": "Mudar fundo",
"change_question_type": "Mudar tipo de pergunta",
"change_survey_type": "Alterar o tipo de pesquisa afeta o acesso existente",
"change_the_background_color_of_the_card": "Muda a cor de fundo do cartão.",
"change_the_background_color_of_the_input_fields": "Mude a cor de fundo dos campos de entrada.",
"change_the_background_to_a_color_image_or_animation": "Mude o fundo para uma cor, imagem ou animação.",
"change_the_border_color_of_the_card": "Muda a cor da borda do cartão.",
"change_the_border_color_of_the_input_fields": "Mude a cor da borda dos campos de entrada.",
"change_the_border_radius_of_the_card_and_the_inputs": "Muda o raio da borda do card e dos inputs.",
"change_the_brand_color_of_the_survey": "Muda a cor da marca da pesquisa.",
"change_the_placement_of_this_survey": "Muda a posição dessa pesquisa.",
"change_the_question_color_of_the_survey": "Muda a cor da pergunta da pesquisa.",
"changes_saved": "Mudanças salvas.",
"changing_survey_type_will_remove_existing_distribution_channels": "Alterar o tipo de pesquisa afetará a forma como ela pode ser compartilhada. Se os respondentes já tiverem links de acesso para o tipo atual, podem perder o acesso após a mudança.",
"character_limit_toggle_description": "Limite o quão curta ou longa uma resposta pode ser.",
"character_limit_toggle_title": "Adicionar limites de caracteres",
"checkbox_label": "Rótulo da Caixa de Seleção",
"choose_the_actions_which_trigger_the_survey": "Escolha as ações que disparam a pesquisa.",
"choose_the_first_question_on_your_block": "Escolha a primeira pergunta do seu bloco",
@@ -1241,7 +1263,6 @@
"contact_fields": "Campos de Contato",
"contains": "contém",
"continue_to_settings": "Continuar para Configurações",
"control_which_file_types_can_be_uploaded": "Controlar quais tipos de arquivos podem ser enviados.",
"convert_to_multiple_choice": "Converter para Múltipla Escolha",
"convert_to_single_choice": "Converter para Escolha Única",
"country": "país",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Escureça ou clareie o fundo da sua escolha.",
"date_format": "Formato de data",
"days_before_showing_this_survey_again": "ou mais dias devem passar entre a última pesquisa exibida e a exibição desta pesquisa.",
"delete_anyways": "Excluir mesmo assim",
"delete_block": "Excluir bloco",
"delete_choice": "Deletar opção",
"disable_the_visibility_of_survey_progress": "Desativar a visibilidade do progresso da pesquisa.",
"display_an_estimate_of_completion_time_for_survey": "Mostrar uma estimativa de tempo de conclusão da pesquisa",
"display_number_of_responses_for_survey": "Mostrar número de respostas da pesquisa",
"display_type": "Tipo de exibição",
"divide": "Divida /",
"does_not_contain": "não contém",
"does_not_end_with": "Não termina com",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Não inclui todos de",
"does_not_include_one_of": "Não inclui um de",
"does_not_start_with": "Não começa com",
"dropdown": "Menu suspenso",
"duplicate_block": "Duplicar bloco",
"duplicate_question": "Duplicar pergunta",
"edit_link": "Editar link",
@@ -1357,7 +1381,6 @@
"hide_progress_bar": "Esconder barra de progresso",
"hide_question_settings": "Ocultar configurações da pergunta",
"hostname": "nome do host",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Quão descoladas você quer suas cartas em Pesquisas {surveyTypeDerived}",
"if_you_need_more_please": "Se você precisar de mais, por favor",
"if_you_really_want_that_answer_ask_until_you_get_it": "Continuar mostrando sempre que acionada até que uma resposta seja enviada.",
"ignore_global_waiting_time": "Ignorar período de espera",
@@ -1368,7 +1391,9 @@
"initial_value": "Valor inicial",
"inner_text": "Texto Interno",
"input_border_color": "Cor da borda de entrada",
"input_border_color_description": "Contorna campos de texto e áreas de texto.",
"input_color": "Cor de entrada",
"input_color_description": "Preenche o interior dos campos de texto.",
"insert_link": "Inserir link",
"invalid_targeting": "Segmentação inválida: Por favor, verifique os filtros do seu público",
"invalid_video_url_warning": "Por favor, insira uma URL válida do YouTube, Vimeo ou Loom. No momento, não suportamos outros provedores de vídeo.",
@@ -1395,10 +1420,10 @@
"key": "chave",
"last_name": "Sobrenome",
"let_people_upload_up_to_25_files_at_the_same_time": "Deixe as pessoas fazerem upload de até 25 arquivos ao mesmo tempo.",
"limit_file_types": "Limitar tipos de arquivos",
"limit_the_maximum_file_size": "Limitar o tamanho máximo do arquivo",
"limit_upload_file_size_to": "Limitar tamanho do arquivo de upload para",
"limit_the_maximum_file_size": "Limitar o tamanho máximo de arquivo para uploads.",
"limit_upload_file_size_to": "Limitar tamanho de arquivo de upload para",
"link_survey_description": "Compartilhe um link para a página da pesquisa ou incorpore-a em uma página da web ou e-mail.",
"list": "Lista",
"load_segment": "segmento de carga",
"logic_error_warning": "Mudar vai causar erros de lógica",
"logic_error_warning_text": "Mudar o tipo de pergunta vai remover as condições lógicas dessa pergunta",
@@ -1410,7 +1435,7 @@
"matrix_all_fields": "Todos os campos",
"matrix_rows": "Linhas",
"max_file_size": "Tamanho máximo do arquivo",
"max_file_size_limit_is": "Tamanho máximo do arquivo é",
"max_file_size_limit_is": "O limite de tamanho máximo do arquivo é",
"move_question_to_block": "Mover pergunta para o bloco",
"multiply": "Multiplicar *",
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
@@ -1442,22 +1467,22 @@
"picture_idx": "Imagem {idx}",
"pin_can_only_contain_numbers": "O PIN só pode conter números.",
"pin_must_be_a_four_digit_number": "O PIN deve ser um número de quatro dígitos.",
"please_enter_a_file_extension": "Por favor, insira uma extensão de arquivo.",
"please_enter_a_valid_url": "Por favor, insira uma URL válida (ex.: https://example.com)",
"please_set_a_survey_trigger": "Por favor, configure um gatilho para a pesquisa",
"please_specify": "Por favor, especifique",
"prevent_double_submission": "Evitar envio duplicado",
"prevent_double_submission_description": "Permitir apenas 1 resposta por endereço de email",
"progress_saved": "Progresso salvo",
"protect_survey_with_pin": "Proteger pesquisa com um PIN",
"protect_survey_with_pin_description": "Somente usuários que têm o PIN podem acessar a pesquisa.",
"publish": "Publicar",
"question": "Pergunta",
"question_color": "Cor da pergunta",
"question_deleted": "Pergunta deletada.",
"question_duplicated": "Pergunta duplicada.",
"question_id_updated": "ID da pergunta atualizado",
"question_used_in_logic": "Essa pergunta é usada na lógica da pergunta {questionIndex}.",
"question_used_in_quota": "Esta questão está sendo usada na cota \"{quotaName}\"",
"question_used_in_logic_warning_text": "Elementos deste bloco são usados em uma regra de lógica, tem certeza de que deseja excluí-lo?",
"question_used_in_logic_warning_title": "Inconsistência de lógica",
"question_used_in_quota": "Esta pergunta está sendo usada na cota \"{quotaName}\"",
"question_used_in_recall": "Esta pergunta está sendo recordada na pergunta {questionIndex}.",
"question_used_in_recall_ending_card": "Esta pergunta está sendo recordada no card de Encerramento",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta",
"roundness": "Circularidade",
"roundness_description": "Controla o arredondamento dos cantos do cartão.",
"row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"rows": "linhas",
"save_and_close": "Salvar e Fechar",
@@ -1520,6 +1546,7 @@
"search_for_images": "Buscar imagens",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "segundos após acionar, a pesquisa será encerrada se não houver resposta",
"seconds_before_showing_the_survey": "segundos antes de mostrar a pesquisa.",
"select_field": "Selecionar campo",
"select_or_type_value": "Selecionar ou digitar valor",
"select_ordering": "Selecionar pedido",
"select_saved_action": "Selecionar ação salva",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Estilo definido para os estilos do tema",
"subheading": "Subtítulo",
"subtract": "Subtrair -",
"suggest_colors": "Sugerir cores",
"survey_completed_heading": "Pesquisa Concluída",
"survey_completed_subheading": "Essa pesquisa gratuita e de código aberto foi encerrada",
"survey_display_settings": "Configurações de Exibição da Pesquisa",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar uma única vez, mesmo que não respondam.",
"then": "Então",
"this_action_will_remove_all_the_translations_from_this_survey": "Essa ação vai remover todas as traduções dessa pesquisa.",
"this_extension_is_already_added": "Essa extensão já foi adicionada.",
"this_file_type_is_not_supported": "Esse tipo de arquivo não é suportado.",
"three_points": "3 pontos",
"times": "times",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para manter a colocação consistente em todas as pesquisas, você pode",
@@ -1589,8 +1613,51 @@
"upper_label": "Etiqueta Superior",
"url_filters": "Filtros de URL",
"url_not_supported": "URL não suportada",
"validation": {
"add_validation_rule": "Adicionar regra de validação",
"answer_all_rows": "Responda todas as linhas",
"characters": "Caracteres",
"contains": "Contém",
"delete_validation_rule": "Excluir regra de validação",
"does_not_contain": "Não contém",
"email": "É um e-mail válido",
"end_date": "Data final",
"file_extension_is": "A extensão do arquivo é",
"file_extension_is_not": "A extensão do arquivo não é",
"is": "É",
"is_between": "Está entre",
"is_earlier_than": "É anterior a",
"is_greater_than": "É maior que",
"is_later_than": "É posterior a",
"is_less_than": "É menor que",
"is_not": "Não é",
"is_not_between": "Não está entre",
"kb": "KB",
"max_length": "No máximo",
"max_selections": "No máximo",
"max_value": "No máximo",
"mb": "MB",
"min_length": "No mínimo",
"min_selections": "No mínimo",
"min_value": "No mínimo",
"minimum_options_ranked": "Mínimo de opções classificadas",
"minimum_rows_answered": "Mínimo de linhas respondidas",
"options_selected": "Opções selecionadas",
"pattern": "Corresponde ao padrão regex",
"phone": "É um telefone válido",
"rank_all_options": "Classificar todas as opções",
"select_file_extensions": "Selecionar extensões de arquivo...",
"select_option": "Selecionar opção",
"start_date": "Data inicial",
"url": "É uma URL válida"
},
"validation_logic_and": "Todas são verdadeiras",
"validation_logic_or": "qualquer uma é verdadeira",
"validation_rules": "Regras de validação",
"validation_rules_description": "Aceitar apenas respostas que atendam aos seguintes critérios",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} está sendo usado na lógica da pergunta {questionIndex}. Por favor, remova-o da lógica primeiro.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variável \"{variableName}\" está sendo usada na cota \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "A variável \"{variableName}\" está sendo usada na cota \"{quotaName}\"",
"variable_name_conflicts_with_hidden_field": "O nome da variável está em conflito com um ID de campo oculto existente.",
"variable_name_is_already_taken_please_choose_another": "O nome da variável já está em uso, por favor escolha outro.",
"variable_name_must_start_with_a_letter": "O nome da variável deve começar com uma letra.",
"variable_used_in_recall": "Variável \"{variable}\" está sendo recordada na pergunta {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Ocorreu um erro ao baixar as respostas",
"first_name": "Primeiro Nome",
"how_to_identify_users": "Como identificar usuários",
"ip_address": "Endereço IP",
"last_name": "Sobrenome",
"not_completed": "Não Concluído ⏳",
"os": "sistema operacional",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Adicionar cor de fundo",
"add_background_color_description": "Adicione uma cor de fundo ao container do logo.",
"advanced_styling_field_border_radius": "Raio da borda",
"advanced_styling_field_button_bg": "Fundo do botão",
"advanced_styling_field_button_bg_description": "Preenche o botão Próximo / Enviar.",
"advanced_styling_field_button_border_radius_description": "Arredonda os cantos do botão.",
"advanced_styling_field_button_font_size_description": "Ajusta o tamanho do texto do rótulo do botão.",
"advanced_styling_field_button_font_weight_description": "Torna o texto do botão mais leve ou mais negrito.",
"advanced_styling_field_button_height_description": "Controla a altura do botão.",
"advanced_styling_field_button_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_button_padding_y_description": "Adiciona espaço no topo e na base.",
"advanced_styling_field_button_text": "Texto do botão",
"advanced_styling_field_button_text_description": "Colore o rótulo dentro dos botões.",
"advanced_styling_field_description_color": "Cor da descrição",
"advanced_styling_field_description_color_description": "Colore o texto abaixo de cada título.",
"advanced_styling_field_description_size": "Tamanho da fonte da descrição",
"advanced_styling_field_description_size_description": "Ajusta o tamanho do texto da descrição.",
"advanced_styling_field_description_weight": "Peso da fonte da descrição",
"advanced_styling_field_description_weight_description": "Torna o texto da descrição mais leve ou mais negrito.",
"advanced_styling_field_font_size": "Tamanho da fonte",
"advanced_styling_field_font_weight": "Peso da fonte",
"advanced_styling_field_headline_color": "Cor do título",
"advanced_styling_field_headline_color_description": "Colore o texto principal da pergunta.",
"advanced_styling_field_headline_size": "Tamanho da fonte do título",
"advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.",
"advanced_styling_field_headline_weight": "Peso da fonte do título",
"advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_indicator_bg": "Fundo do indicador",
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.",
"advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.",
"advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.",
"advanced_styling_field_input_height_description": "Controla a altura do campo de entrada.",
"advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_input_padding_y_description": "Adiciona espaço na parte superior e inferior.",
"advanced_styling_field_input_placeholder_opacity_description": "Esmaece o texto de dica do placeholder.",
"advanced_styling_field_input_shadow_description": "Adiciona uma sombra ao redor dos campos de entrada.",
"advanced_styling_field_input_text": "Texto de entrada",
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.",
"advanced_styling_field_option_bg": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.",
"advanced_styling_field_option_font_size_description": "Ajusta o tamanho do texto do rótulo da opção.",
"advanced_styling_field_option_label": "Cor do rótulo",
"advanced_styling_field_option_label_description": "Colore o texto do rótulo da opção.",
"advanced_styling_field_option_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_option_padding_y_description": "Adiciona espaço na parte superior e inferior.",
"advanced_styling_field_padding_x": "Espaçamento X",
"advanced_styling_field_padding_y": "Espaçamento Y",
"advanced_styling_field_placeholder_opacity": "Opacidade do placeholder",
"advanced_styling_field_shadow": "Sombra",
"advanced_styling_field_track_bg": "Fundo da trilha",
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
"advanced_styling_field_track_height": "Altura da trilha",
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
"advanced_styling_field_upper_label_color": "Cor do rótulo do título",
"advanced_styling_field_upper_label_color_description": "Colore o pequeno rótulo acima dos campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte do rótulo do título",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho do pequeno rótulo acima dos campos de entrada.",
"advanced_styling_field_upper_label_weight": "Peso da fonte do rótulo do título",
"advanced_styling_field_upper_label_weight_description": "Torna o rótulo mais leve ou mais negrito.",
"advanced_styling_section_buttons": "Botões",
"advanced_styling_section_headlines": "Títulos e descrições",
"advanced_styling_section_inputs": "Campos de entrada",
"advanced_styling_section_options": "Opções (rádio/caixa de seleção)",
"app_survey_placement": "Posicionamento da pesquisa de app",
"app_survey_placement_settings_description": "Altere onde as pesquisas serão exibidas em seu aplicativo web ou site.",
"centered_modal_overlay_color": "Cor de sobreposição modal centralizada",
"email_customization": "Personalização de e-mail",
"email_customization_description": "Altere a aparência dos e-mails que o Formbricks envia em seu nome.",
"enable_custom_styling": "Habilitar estilização personalizada",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "A marca Formbricks está oculta.",
"formbricks_branding_settings_description": "Adoramos seu apoio, mas entendemos se você desativar.",
"formbricks_branding_shown": "A marca Formbricks está visível.",
"generate_theme_btn": "Gerar",
"generate_theme_confirmation": "Gostaria de gerar um tema de cores correspondente baseado na cor da sua marca? Isso substituirá suas configurações de cores atuais.",
"generate_theme_header": "Gerar tema de cores?",
"logo_removed_successfully": "Logo removido com sucesso",
"logo_settings_description": "Faça upload do logo da sua empresa para personalizar pesquisas e pré-visualizações de links.",
"logo_updated_successfully": "Logo atualizado com sucesso",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Mostrar marca Formbricks em pesquisas {type}",
"show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo atualizado com sucesso",
"suggest_colors": "Sugerir cores",
"theme": "Tema",
"theme_settings_description": "Crie um tema de estilo para todas as pesquisas. Você pode ativar estilo personalizado para cada pesquisa."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Sim, me mantenha informado.",
"preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer ficar por dentro?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_welcome_card_headline": "Bem-vindo!",
"prioritize_features_description": "Identifique os recursos que seus usuários mais e menos precisam.",
"prioritize_features_name": "Priorizar Funcionalidades",
+172 -37
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Mistura de maiúsculas e minúsculas",
"please_verify_captcha": "Por favor, verifique o reCAPTCHA",
"privacy_policy": "Política de Privacidade",
"product_updates_description": "Notícias mensais sobre o produto e atualizações de funcionalidades, aplica-se a Política de Privacidade.",
"product_updates_title": "Atualizações do produto",
"security_updates_description": "Apenas informações relevantes sobre segurança, aplica-se a Política de Privacidade.",
"security_updates_title": "Atualizações de segurança",
"terms_of_service": "Termos de Serviço",
"title": "Crie a sua conta Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Documentação",
"documentation": "Documentação",
"domain": "Domínio",
"done": "Concluído",
"download": "Transferir",
"draft": "Rascunho",
"duplicate": "Duplicar",
@@ -238,7 +243,6 @@
"imprint": "Impressão",
"in_progress": "Em Progresso",
"inactive_surveys": "Inquéritos inativos",
"input_type": "Tipo de entrada",
"integration": "integração",
"integrations": "Integrações",
"invalid_date": "Data inválida",
@@ -250,6 +254,7 @@
"label": "Etiqueta",
"language": "Idioma",
"learn_more": "Saiba mais",
"license_expired": "License Expired",
"light_overlay": "Sobreposição leve",
"limits_reached": "Limites Atingidos",
"link": "Link",
@@ -262,13 +267,11 @@
"look_and_feel": "Aparência e Sensação",
"manage": "Gerir",
"marketing": "Marketing",
"maximum": "Máximo",
"member": "Membro",
"members": "Membros",
"members_and_teams": "Membros e equipas",
"membership_not_found": "Associação não encontrada",
"metadata": "Metadados",
"minimum": "Mínimo",
"mobile_overlay_app_works_best_on_desktop": "Formbricks funciona melhor num ecrã maior. Para gerir ou criar inquéritos, mude de dispositivo.",
"mobile_overlay_surveys_look_good": "Não se preocupe os seus inquéritos têm uma ótima aparência em todos os dispositivos e tamanhos de ecrã!",
"mobile_overlay_title": "Oops, ecrã pequeno detectado!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Nenhuma imagem de fundo encontrada.",
"no_code": "Sem código",
"no_files_uploaded": "Nenhum ficheiro foi carregado",
"no_overlay": "Sem sobreposição",
"no_quotas_found": "Nenhum quota encontrado",
"no_result_found": "Nenhum resultado encontrado",
"no_results": "Nenhum resultado",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Equipas da organização não encontradas",
"other": "Outro",
"others": "Outros",
"overlay_color": "Cor da sobreposição",
"overview": "Visão geral",
"password": "Palavra-passe",
"paused": "Em pausa",
@@ -321,7 +326,7 @@
"placeholder": "Espaço reservado",
"please_select_at_least_one_survey": "Por favor, selecione pelo menos um inquérito",
"please_select_at_least_one_trigger": "Por favor, selecione pelo menos um gatilho",
"please_upgrade_your_plan": "Por favor, atualize o seu plano.",
"please_upgrade_your_plan": "Por favor, atualize o seu plano",
"preview": "Pré-visualização",
"preview_survey": "Pré-visualização do inquérito",
"privacy": "Política de Privacidade",
@@ -347,6 +352,7 @@
"request_trial_license": "Solicitar licença de teste",
"reset_to_default": "Repor para o padrão",
"response": "Resposta",
"response_id": "ID de resposta",
"responses": "Respostas",
"restart": "Reiniciar",
"role": "Função",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Atingiu o seu limite de {projectLimit} áreas de trabalho.",
"you_have_reached_your_monthly_miu_limit_of": "Atingiu o seu limite mensal de MIU de",
"you_have_reached_your_monthly_response_limit_of": "Atingiu o seu limite mensal de respostas de",
"you_will_be_downgraded_to_the_community_edition_on_date": "Será rebaixado para a Edição Comunitária em {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Será rebaixado para a Edição Comunitária em {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Aceitar",
@@ -783,20 +790,26 @@
"add_webhook": "Adicionar Webhook",
"add_webhook_description": "Enviar dados de resposta do inquérito para um endpoint personalizado",
"all_current_and_new_surveys": "Todos os inquéritos atuais e novos",
"copy_secret_now": "Copiar o seu segredo de assinatura",
"created_by_third_party": "Criado por um Terceiro",
"discord_webhook_not_supported": "Os webhooks do Discord não são atualmente suportados.",
"empty_webhook_message": "Os seus webhooks aparecerão aqui assim que os adicionar. ⏲️",
"endpoint_pinged": "Yay! Conseguimos aceder ao webhook!",
"endpoint_pinged_error": "Não foi possível aceder ao webhook!",
"learn_to_verify": "Aprenda a verificar assinaturas de webhook",
"please_check_console": "Por favor, verifique a consola para mais detalhes",
"please_enter_a_url": "Por favor, insira um URL",
"response_created": "Resposta Criada",
"response_finished": "Resposta Concluída",
"response_updated": "Resposta Atualizada",
"secret_copy_warning": "Armazene este segredo de forma segura. Pode visualizá-lo novamente nas definições do webhook.",
"secret_description": "Use este segredo para verificar os pedidos do webhook. Consulte a documentação para verificação de assinatura.",
"signing_secret": "Segredo de assinatura",
"source": "Fonte",
"test_endpoint": "Testar Endpoint",
"triggers": "Disparadores",
"webhook_added_successfully": "Webhook adicionado com sucesso",
"webhook_created": "Webhook criado",
"webhook_delete_confirmation": "Tem a certeza de que deseja eliminar este Webhook? Isto irá parar de lhe enviar quaisquer notificações futuras.",
"webhook_deleted_successfully": "Webhook eliminado com sucesso",
"webhook_name_placeholder": "Opcional: Rotule o seu webhook para fácil identificação",
@@ -943,19 +956,32 @@
"enterprise_features": "Funcionalidades da Empresa",
"get_an_enterprise_license_to_get_access_to_all_features": "Obtenha uma licença Enterprise para ter acesso a todas as funcionalidades.",
"keep_full_control_over_your_data_privacy_and_security": "Mantenha controlo total sobre a privacidade e segurança dos seus dados.",
"license_invalid_description": "A chave de licença na sua variável de ambiente ENTERPRISE_LICENSE_KEY não é válida. Por favor, verifique se existem erros de digitação ou solicite uma nova chave.",
"license_status": "Estado da licença",
"license_status_active": "Ativa",
"license_status_description": "Estado da sua licença empresarial.",
"license_status_expired": "Expirada",
"license_status_invalid": "Licença inválida",
"license_status_unreachable": "Inacessível",
"license_unreachable_grace_period": "Não é possível contactar o servidor de licenças. As suas funcionalidades empresariais permanecem ativas durante um período de tolerância de 3 dias que termina a {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Sem necessidade de chamada, sem compromissos: Solicite uma licença de teste gratuita de 30 dias para testar todas as funcionalidades preenchendo este formulário:",
"no_credit_card_no_sales_call_just_test_it": "Sem cartão de crédito. Sem chamada de vendas. Apenas teste :)",
"on_request": "A pedido",
"organization_roles": "Funções da Organização (Administrador, Editor, Programador, etc.)",
"questions_please_reach_out_to": "Questões? Por favor entre em contacto com",
"recheck_license": "Verificar licença novamente",
"recheck_license_failed": "A verificação da licença falhou. O servidor de licenças pode estar inacessível.",
"recheck_license_invalid": "A chave de licença é inválida. Por favor, verifique a sua ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Verificação da licença bem-sucedida",
"recheck_license_unreachable": "O servidor de licenças está inacessível. Por favor, tenta novamente mais tarde.",
"rechecking": "A verificar novamente...",
"request_30_day_trial_license": "Solicitar Licença de Teste de 30 Dias",
"saml_sso": "SSO SAML",
"service_level_agreement": "Acordo de Nível de Serviço",
"soc2_hipaa_iso_27001_compliance_check": "Verificação de conformidade SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Equipas e Funções de Acesso (Ler, Ler e Escrever, Gerir)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias.",
"your_enterprise_license_is_active_all_features_unlocked": "A sua licença Enterprise está ativa. Todas as funcionalidades desbloqueadas."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"bulk_invite_warning_description": "No plano gratuito, todos os membros da organização são sempre atribuídos ao papel de \"Proprietário\".",
@@ -979,7 +1005,7 @@
"from_your_organization": "da sua organização",
"invitation_sent_once_more": "Convite enviado mais uma vez.",
"invite_deleted_successfully": "Convite eliminado com sucesso",
"invited_on": "Convidado em {date}",
"invite_expires_on": "O convite expira em {date}",
"invites_failed": "Convites falharam",
"leave_organization": "Sair da organização",
"leave_organization_description": "Vai sair desta organização e perder o acesso a todos os inquéritos e respostas. Só pode voltar a juntar-se se for convidado novamente.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Remover logótipo",
"replace_logo": "Substituir logotipo",
"resend_invitation_email": "Reenviar Email de Convite",
"security_list_tip": "Está inscrito na nossa Lista de Segurança? Mantenha-se informado para manter a sua instância segura!",
"security_list_tip_link": "Inscreva-se aqui.",
"share_invite_link": "Partilhar Link de Convite",
"share_this_link_to_let_your_organization_member_join_your_organization": "Partilhe este link para permitir que o membro da sua organização se junte à sua organização:",
"test_email_sent_successfully": "Email de teste enviado com sucesso",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Preencha todos os campos para adicionar um novo espaço de trabalho.",
"read": "Ler",
"read_write": "Ler e Escrever",
"select_member": "Selecionar membro",
"select_workspace": "Selecionar espaço de trabalho",
"team_admin": "Administrador da Equipa",
"team_created_successfully": "Equipa criada com sucesso.",
"team_deleted_successfully": "Equipa eliminada com sucesso.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Adicionar um espaço reservado para mostrar se não houver valor para recordar.",
"add_hidden_field_id": "Adicionar ID do campo oculto",
"add_highlight_border": "Adicionar borda de destaque",
"add_highlight_border_description": "Adicione uma borda externa ao seu cartão de inquérito.",
"add_logic": "Adicionar lógica",
"add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"",
"add_option": "Adicionar opção",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Alterar a mensagem que os visitantes veem quando o inquérito está fechado.",
"adjust_the_theme_in_the": "Ajustar o tema no",
"all_other_answers_will_continue_to": "Todas as outras respostas continuarão a",
"allow_file_type": "Permitir tipo de ficheiro",
"allow_multi_select": "Permitir seleção múltipla",
"allow_multiple_files": "Permitir vários ficheiros",
"allow_users_to_select_more_than_one_image": "Permitir aos utilizadores selecionar mais do que uma imagem",
@@ -1169,6 +1193,9 @@
"assign": "Atribuir =",
"audience": "Público",
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
"auto_save_disabled": "Guardar automático desativado",
"auto_save_disabled_tooltip": "O seu inquérito só é guardado automaticamente quando está em rascunho. Isto garante que os inquéritos públicos não sejam atualizados involuntariamente.",
"auto_save_on": "Guardar automático ativado",
"automatically_close_survey_after": "Fechar automaticamente o inquérito após",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fechar automaticamente o inquérito após um certo número de respostas",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Fechar automaticamente o inquérito se o utilizador não responder após um certo número de segundos.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Bloco duplicado.",
"bold": "Negrito",
"brand_color": "Cor da marca",
"brand_color_description": "Aplicado a botões, links e destaques.",
"brightness": "Brilho",
"bulk_edit": "Edição em massa",
"bulk_edit_description": "Edite todas as opções abaixo, uma por linha. Linhas vazias serão ignoradas e duplicados removidos.",
@@ -1190,10 +1218,14 @@
"cal_username": "Nome de utilizador do Cal.com ou nome de utilizador/evento",
"calculate": "Calcular",
"capture_a_new_action_to_trigger_a_survey_on": "Capturar uma nova ação para desencadear um inquérito.",
"capture_ip_address": "Capturar endereço IP",
"capture_ip_address_description": "Armazenar o endereço IP do inquirido nos metadados da resposta para deteção de duplicados e fins de segurança",
"capture_new_action": "Capturar nova ação",
"card_arrangement_for_survey_type_derived": "Arranjo de Cartões para Inquéritos {surveyTypeDerived}",
"card_background_color": "Cor de fundo do cartão",
"card_background_color_description": "Preenche a área do cartão do inquérito.",
"card_border_color": "Cor da borda do cartão",
"card_border_color_description": "Contorna o cartão do inquérito.",
"card_styling": "Estilo de cartão",
"casual": "Casual",
"caution_edit_duplicate": "Duplicar e editar",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "As respostas mais antigas e mais recentes se misturam, o que pode levar a resumos de dados enganosos.",
"caution_recommendation": "Isso pode causar inconsistências de dados no resumo do inquérito. Recomendamos duplicar o inquérito em vez disso.",
"caution_text": "As alterações levarão a inconsistências",
"centered_modal_overlay_color": "Cor da sobreposição modal centralizada",
"change_anyway": "Alterar mesmo assim",
"change_background": "Alterar fundo",
"change_question_type": "Alterar tipo de pergunta",
"change_survey_type": "Alterar o tipo de inquérito afeta o acesso existente",
"change_the_background_color_of_the_card": "Alterar a cor de fundo do cartão",
"change_the_background_color_of_the_input_fields": "Alterar a cor de fundo dos campos de entrada",
"change_the_background_to_a_color_image_or_animation": "Altere o fundo para uma cor, imagem ou animação",
"change_the_border_color_of_the_card": "Alterar a cor da borda do cartão.",
"change_the_border_color_of_the_input_fields": "Alterar a cor da borda dos campos de entrada",
"change_the_border_radius_of_the_card_and_the_inputs": "Alterar o raio da borda do cartão e dos campos de entrada",
"change_the_brand_color_of_the_survey": "Alterar a cor da marca do inquérito",
"change_the_placement_of_this_survey": "Alterar a colocação deste inquérito.",
"change_the_question_color_of_the_survey": "Alterar a cor da pergunta do inquérito",
"changes_saved": "Alterações guardadas.",
"changing_survey_type_will_remove_existing_distribution_channels": "Alterar o tipo de inquérito afetará como ele pode ser partilhado. Se os respondentes já tiverem links de acesso para o tipo atual, podem perder o acesso após a mudança.",
"character_limit_toggle_description": "Limitar o quão curta ou longa uma resposta pode ser.",
"character_limit_toggle_title": "Adicionar limites de caracteres",
"checkbox_label": "Rótulo da Caixa de Seleção",
"choose_the_actions_which_trigger_the_survey": "Escolha as ações que desencadeiam o inquérito.",
"choose_the_first_question_on_your_block": "Escolha a primeira pergunta no seu bloco",
@@ -1241,7 +1263,6 @@
"contact_fields": "Campos de Contacto",
"contains": "Contém",
"continue_to_settings": "Continuar para Definições",
"control_which_file_types_can_be_uploaded": "Controlar quais tipos de ficheiros podem ser carregados.",
"convert_to_multiple_choice": "Converter para Seleção Múltipla",
"convert_to_single_choice": "Converter para Seleção Única",
"country": "País",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Escurecer ou clarear o fundo da sua escolha.",
"date_format": "Formato da data",
"days_before_showing_this_survey_again": "ou mais dias a decorrer entre o último inquérito apresentado e a apresentação deste inquérito.",
"delete_anyways": "Eliminar mesmo assim",
"delete_block": "Eliminar bloco",
"delete_choice": "Eliminar escolha",
"disable_the_visibility_of_survey_progress": "Desativar a visibilidade do progresso da pesquisa.",
"display_an_estimate_of_completion_time_for_survey": "Mostrar uma estimativa do tempo de conclusão do inquérito",
"display_number_of_responses_for_survey": "Mostrar número de respostas do inquérito",
"display_type": "Tipo de exibição",
"divide": "Dividir /",
"does_not_contain": "Não contém",
"does_not_end_with": "Não termina com",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Não inclui todos de",
"does_not_include_one_of": "Não inclui um de",
"does_not_start_with": "Não começa com",
"dropdown": "Menu suspenso",
"duplicate_block": "Duplicar bloco",
"duplicate_question": "Duplicar pergunta",
"edit_link": "Editar link",
@@ -1357,7 +1381,6 @@
"hide_progress_bar": "Ocultar barra de progresso",
"hide_question_settings": "Ocultar definições da pergunta",
"hostname": "Nome do host",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Quão extravagantes quer os seus cartões em Inquéritos {surveyTypeDerived}",
"if_you_need_more_please": "Se precisar de mais, por favor",
"if_you_really_want_that_answer_ask_until_you_get_it": "Continuar a mostrar sempre que acionado até que uma resposta seja submetida.",
"ignore_global_waiting_time": "Ignorar período de espera",
@@ -1368,7 +1391,9 @@
"initial_value": "Valor inicial",
"inner_text": "Texto Interno",
"input_border_color": "Cor da borda do campo de entrada",
"input_border_color_description": "Contorna campos de texto e áreas de texto.",
"input_color": "Cor do campo de entrada",
"input_color_description": "Preenche o interior dos campos de texto.",
"insert_link": "Inserir ligação",
"invalid_targeting": "Segmentação inválida: Por favor, verifique os seus filtros de audiência",
"invalid_video_url_warning": "Por favor, insira um URL válido do YouTube, Vimeo ou Loom. Atualmente, não suportamos outros fornecedores de hospedagem de vídeo.",
@@ -1395,10 +1420,10 @@
"key": "Chave",
"last_name": "Apelido",
"let_people_upload_up_to_25_files_at_the_same_time": "Permitir que as pessoas carreguem até 25 ficheiros ao mesmo tempo.",
"limit_file_types": "Limitar tipos de ficheiros",
"limit_the_maximum_file_size": "Limitar o tamanho máximo do ficheiro",
"limit_upload_file_size_to": "Limitar tamanho do ficheiro carregado a",
"limit_the_maximum_file_size": "Limitar o tamanho máximo de ficheiro para carregamentos.",
"limit_upload_file_size_to": "Limitar o tamanho de ficheiro de carregamento para",
"link_survey_description": "Partilhe um link para uma página de inquérito ou incorpore-o numa página web ou email.",
"list": "Lista",
"load_segment": "Carregar segmento",
"logic_error_warning": "A alteração causará erros de lógica",
"logic_error_warning_text": "Alterar o tipo de pergunta irá remover as condições lógicas desta pergunta",
@@ -1409,8 +1434,8 @@
"manage_languages": "Gerir Idiomas",
"matrix_all_fields": "Todos os campos",
"matrix_rows": "Linhas",
"max_file_size": "Tamanho máximo do ficheiro",
"max_file_size_limit_is": "O limite do tamanho máximo do ficheiro é",
"max_file_size": "Tamanho máximo de ficheiro",
"max_file_size_limit_is": "O limite de tamanho máximo de ficheiro é",
"move_question_to_block": "Mover pergunta para o bloco",
"multiply": "Multiplicar *",
"needed_for_self_hosted_cal_com_instance": "Necessário para uma instância auto-hospedada do Cal.com",
@@ -1442,21 +1467,21 @@
"picture_idx": "Imagem {idx}",
"pin_can_only_contain_numbers": "O PIN só pode conter números.",
"pin_must_be_a_four_digit_number": "O PIN deve ser um número de quatro dígitos.",
"please_enter_a_file_extension": "Por favor, insira uma extensão de ficheiro.",
"please_enter_a_valid_url": "Por favor, insira um URL válido (por exemplo, https://example.com)",
"please_set_a_survey_trigger": "Por favor, defina um desencadeador de inquérito",
"please_specify": "Por favor, especifique",
"prevent_double_submission": "Impedir submissão dupla",
"prevent_double_submission_description": "Permitir apenas 1 resposta por endereço de email",
"progress_saved": "Progresso guardado",
"protect_survey_with_pin": "Proteger inquérito com um PIN",
"protect_survey_with_pin_description": "Apenas utilizadores com o PIN podem aceder ao inquérito.",
"publish": "Publicar",
"question": "Pergunta",
"question_color": "Cor da pergunta",
"question_deleted": "Pergunta eliminada.",
"question_duplicated": "Pergunta duplicada.",
"question_id_updated": "ID da pergunta atualizado",
"question_used_in_logic": "Esta pergunta é usada na lógica da pergunta {questionIndex}.",
"question_used_in_logic_warning_text": "Os elementos deste bloco são utilizados numa regra de lógica, tem a certeza de que pretende eliminá-lo?",
"question_used_in_logic_warning_title": "Inconsistência de lógica",
"question_used_in_quota": "Esta pergunta está a ser usada na quota \"{quotaName}\"",
"question_used_in_recall": "Esta pergunta está a ser recordada na pergunta {questionIndex}.",
"question_used_in_recall_ending_card": "Esta pergunta está a ser recordada no Cartão de Conclusão",
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Limites de resposta, redirecionamentos e mais.",
"response_options": "Opções de Resposta",
"roundness": "Arredondamento",
"roundness_description": "Controla o arredondamento dos cantos do cartão.",
"row_used_in_logic_error": "Esta linha é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"rows": "Linhas",
"save_and_close": "Guardar e Fechar",
@@ -1520,6 +1546,7 @@
"search_for_images": "Procurar imagens",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "segundos após o acionamento o inquérito será fechado se não houver resposta",
"seconds_before_showing_the_survey": "segundos antes de mostrar o inquérito.",
"select_field": "Selecionar campo",
"select_or_type_value": "Selecionar ou digitar valor",
"select_ordering": "Selecionar ordem",
"select_saved_action": "Selecionar ação guardada",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Estilo definido para estilos do tema",
"subheading": "Subtítulo",
"subtract": "Subtrair -",
"suggest_colors": "Sugerir cores",
"survey_completed_heading": "Inquérito Concluído",
"survey_completed_subheading": "Este inquérito gratuito e de código aberto foi encerrado",
"survey_display_settings": "Configurações de Exibição do Inquérito",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Mostrar uma única vez, mesmo que não respondam.",
"then": "Então",
"this_action_will_remove_all_the_translations_from_this_survey": "Esta ação irá remover todas as traduções deste inquérito.",
"this_extension_is_already_added": "Esta extensão já está adicionada.",
"this_file_type_is_not_supported": "Este tipo de ficheiro não é suportado.",
"three_points": "3 pontos",
"times": "tempos",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Para manter a colocação consistente em todos os questionários, pode",
@@ -1589,8 +1613,51 @@
"upper_label": "Etiqueta Superior",
"url_filters": "Filtros de URL",
"url_not_supported": "URL não suportado",
"validation": {
"add_validation_rule": "Adicionar regra de validação",
"answer_all_rows": "Responda a todas as linhas",
"characters": "Caracteres",
"contains": "Contém",
"delete_validation_rule": "Eliminar regra de validação",
"does_not_contain": "Não contém",
"email": "É um email válido",
"end_date": "Data de fim",
"file_extension_is": "A extensão do ficheiro é",
"file_extension_is_not": "A extensão do ficheiro não é",
"is": "É",
"is_between": "Está entre",
"is_earlier_than": "É anterior a",
"is_greater_than": "É maior que",
"is_later_than": "É posterior a",
"is_less_than": "É menor que",
"is_not": "Não é",
"is_not_between": "Não está entre",
"kb": "KB",
"max_length": "No máximo",
"max_selections": "No máximo",
"max_value": "No máximo",
"mb": "MB",
"min_length": "Pelo menos",
"min_selections": "Pelo menos",
"min_value": "Pelo menos",
"minimum_options_ranked": "Opções mínimas classificadas",
"minimum_rows_answered": "Linhas mínimas respondidas",
"options_selected": "Opções selecionadas",
"pattern": "Coincide com o padrão regex",
"phone": "É um telefone válido",
"rank_all_options": "Classificar todas as opções",
"select_file_extensions": "Selecionar extensões de ficheiro...",
"select_option": "Selecionar opção",
"start_date": "Data de início",
"url": "É um URL válido"
},
"validation_logic_and": "Todas são verdadeiras",
"validation_logic_or": "qualquer uma é verdadeira",
"validation_rules": "Regras de validação",
"validation_rules_description": "Aceitar apenas respostas que cumpram os seguintes critérios",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variável \"{variableName}\" está a ser utilizada na quota \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "A variável \"{variableName}\" está a ser usada na quota \"{quotaName}\"",
"variable_name_conflicts_with_hidden_field": "O nome da variável está em conflito com um ID de campo oculto existente.",
"variable_name_is_already_taken_please_choose_another": "O nome da variável já está em uso, por favor escolha outro.",
"variable_name_must_start_with_a_letter": "O nome da variável deve começar com uma letra.",
"variable_used_in_recall": "Variável \"{variable}\" está a ser recordada na pergunta {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Ocorreu um erro ao transferir as respostas",
"first_name": "Primeiro Nome",
"how_to_identify_users": "Como identificar utilizadores",
"ip_address": "Endereço IP",
"last_name": "Apelido",
"not_completed": "Não Concluído ⏳",
"os": "SO",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Adicionar cor de fundo",
"add_background_color_description": "Adicione uma cor de fundo ao contentor do logótipo.",
"advanced_styling_field_border_radius": "Raio da borda",
"advanced_styling_field_button_bg": "Fundo do botão",
"advanced_styling_field_button_bg_description": "Preenche o botão Seguinte / Submeter.",
"advanced_styling_field_button_border_radius_description": "Arredonda os cantos do botão.",
"advanced_styling_field_button_font_size_description": "Ajusta o tamanho do texto da etiqueta do botão.",
"advanced_styling_field_button_font_weight_description": "Torna o texto do botão mais leve ou mais negrito.",
"advanced_styling_field_button_height_description": "Controla a altura do botão.",
"advanced_styling_field_button_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_button_padding_y_description": "Adiciona espaço no topo e na base.",
"advanced_styling_field_button_text": "Texto do botão",
"advanced_styling_field_button_text_description": "Colore a etiqueta dentro dos botões.",
"advanced_styling_field_description_color": "Cor da descrição",
"advanced_styling_field_description_color_description": "Colore o texto abaixo de cada título.",
"advanced_styling_field_description_size": "Tamanho da fonte da descrição",
"advanced_styling_field_description_size_description": "Ajusta o tamanho do texto da descrição.",
"advanced_styling_field_description_weight": "Peso da fonte da descrição",
"advanced_styling_field_description_weight_description": "Torna o texto da descrição mais leve ou mais negrito.",
"advanced_styling_field_font_size": "Tamanho da fonte",
"advanced_styling_field_font_weight": "Peso da fonte",
"advanced_styling_field_headline_color": "Cor do título",
"advanced_styling_field_headline_color_description": "Colore o texto principal da pergunta.",
"advanced_styling_field_headline_size": "Tamanho da fonte do título",
"advanced_styling_field_headline_size_description": "Ajusta o tamanho do texto do título.",
"advanced_styling_field_headline_weight": "Peso da fonte do título",
"advanced_styling_field_headline_weight_description": "Torna o texto do título mais leve ou mais negrito.",
"advanced_styling_field_height": "Altura",
"advanced_styling_field_indicator_bg": "Fundo do indicador",
"advanced_styling_field_indicator_bg_description": "Colore a porção preenchida da barra.",
"advanced_styling_field_input_border_radius_description": "Arredonda os cantos do campo.",
"advanced_styling_field_input_font_size_description": "Ajusta o tamanho do texto digitado nos campos.",
"advanced_styling_field_input_height_description": "Controla a altura do campo de entrada.",
"advanced_styling_field_input_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_input_padding_y_description": "Adiciona espaço no topo e na base.",
"advanced_styling_field_input_placeholder_opacity_description": "Atenua o texto de sugestão do placeholder.",
"advanced_styling_field_input_shadow_description": "Adiciona uma sombra ao redor dos campos de entrada.",
"advanced_styling_field_input_text": "Texto de entrada",
"advanced_styling_field_input_text_description": "Colore o texto digitado nos campos de entrada.",
"advanced_styling_field_option_bg": "Fundo",
"advanced_styling_field_option_bg_description": "Preenche os itens de opção.",
"advanced_styling_field_option_border_radius_description": "Arredonda os cantos das opções.",
"advanced_styling_field_option_font_size_description": "Ajusta o tamanho do texto da etiqueta da opção.",
"advanced_styling_field_option_label": "Cor da etiqueta",
"advanced_styling_field_option_label_description": "Colore o texto da etiqueta da opção.",
"advanced_styling_field_option_padding_x_description": "Adiciona espaço à esquerda e à direita.",
"advanced_styling_field_option_padding_y_description": "Adiciona espaço no topo e na base.",
"advanced_styling_field_padding_x": "Espaçamento X",
"advanced_styling_field_padding_y": "Espaçamento Y",
"advanced_styling_field_placeholder_opacity": "Opacidade do marcador de posição",
"advanced_styling_field_shadow": "Sombra",
"advanced_styling_field_track_bg": "Fundo da faixa",
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
"advanced_styling_field_track_height": "Altura da faixa",
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
"advanced_styling_field_upper_label_color": "Cor da etiqueta do título",
"advanced_styling_field_upper_label_color_description": "Colore a pequena etiqueta acima dos campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte da etiqueta do título",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho da pequena etiqueta acima dos campos de entrada.",
"advanced_styling_field_upper_label_weight": "Peso da fonte da etiqueta do título",
"advanced_styling_field_upper_label_weight_description": "Torna a etiqueta mais leve ou mais negrito.",
"advanced_styling_section_buttons": "Botões",
"advanced_styling_section_headlines": "Títulos e descrições",
"advanced_styling_section_inputs": "Campos de entrada",
"advanced_styling_section_options": "Opções (rádio/caixa de seleção)",
"app_survey_placement": "Colocação do inquérito (app)",
"app_survey_placement_settings_description": "Altere onde os inquéritos serão apresentados na sua aplicação web ou website.",
"centered_modal_overlay_color": "Cor da sobreposição modal centralizada",
"email_customization": "Personalização de e-mail",
"email_customization_description": "Altere a aparência dos e-mails que a Formbricks envia em seu nome.",
"enable_custom_styling": "Ativar estilização personalizada",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "A marca Formbricks está oculta.",
"formbricks_branding_settings_description": "Adoramos o seu apoio, mas compreendemos se preferir desativar.",
"formbricks_branding_shown": "A marca Formbricks está visível.",
"generate_theme_btn": "Gerar",
"generate_theme_confirmation": "Gostarias de gerar um tema de cores correspondente com base na cor da tua marca? Isto irá substituir as tuas definições de cor atuais.",
"generate_theme_header": "Gerar tema de cores?",
"logo_removed_successfully": "Logótipo removido com sucesso",
"logo_settings_description": "Carregue o logótipo da sua empresa para personalizar inquéritos e pré-visualizações de links.",
"logo_updated_successfully": "Logótipo atualizado com sucesso",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Mostrar marca Formbricks em inquéritos {type}",
"show_powered_by_formbricks": "Mostrar assinatura 'Powered by Formbricks'",
"styling_updated_successfully": "Estilo atualizado com sucesso",
"suggest_colors": "Sugerir cores",
"theme": "Tema",
"theme_settings_description": "Crie um tema de estilo para todos os inquéritos. Pode ativar estilos personalizados para cada inquérito."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Sim, mantenha-me informado.",
"preview_survey_question_2_choice_2_label": "Não, obrigado!",
"preview_survey_question_2_headline": "Quer manter-se atualizado?",
"preview_survey_question_2_subheader": "Este é um exemplo de descrição.",
"preview_survey_welcome_card_headline": "Bem-vindo!",
"prioritize_features_description": "Identifique as funcionalidades que os seus utilizadores precisam mais e menos.",
"prioritize_features_name": "Priorizar Funcionalidades",
+173 -38
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Amestec de majuscule și minuscule",
"please_verify_captcha": "Vă rugăm să verificați CAPTCHA",
"privacy_policy": "Politica de confidențialitate",
"product_updates_description": "Noutăți lunare despre produse și actualizări de funcționalități; se aplică Politica de confidențialitate.",
"product_updates_title": "Actualizări de produs",
"security_updates_description": "Doar informații relevante pentru securitate; se aplică Politica de confidențialitate.",
"security_updates_title": "Actualizări de securitate",
"terms_of_service": "Termeni de utilizare a serviciului",
"title": "Creați-vă contul Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Documentație",
"documentation": "Documentație",
"domain": "Domeniu",
"done": "Gata",
"download": "Descărcare",
"draft": "Schiță",
"duplicate": "Duplicități",
@@ -238,7 +243,6 @@
"imprint": "Amprentă",
"in_progress": "În progres",
"inactive_surveys": "Sondaje inactive",
"input_type": "Tipul de intrare",
"integration": "integrare",
"integrations": "Integrări",
"invalid_date": "Dată invalidă",
@@ -250,6 +254,7 @@
"label": "Etichetă",
"language": "Limba",
"learn_more": "Află mai multe",
"license_expired": "License Expired",
"light_overlay": "Suprapunere ușoară",
"limits_reached": "Limite atinse",
"link": "Legătura",
@@ -262,13 +267,11 @@
"look_and_feel": "Aspect și Comportament",
"manage": "Gestionați",
"marketing": "Marketing",
"maximum": "Maximum",
"member": "Membru",
"members": "Membri",
"members_and_teams": "Membri și echipe",
"membership_not_found": "Apartenența nu a fost găsită",
"metadata": "Metadate",
"minimum": "Minim",
"mobile_overlay_app_works_best_on_desktop": "Formbricks funcționează cel mai bine pe un ecran mai mare. Pentru a gestiona sau crea chestionare, treceți la un alt dispozitiv.",
"mobile_overlay_surveys_look_good": "Nu vă faceți griji chestionarele dumneavoastră arată grozav pe orice dispozitiv și dimensiune a ecranului!",
"mobile_overlay_title": "Ups, ecran mic detectat!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Nu a fost găsită nicio imagine de fundal.",
"no_code": "Fără Cod",
"no_files_uploaded": "Nu au fost încărcate fișiere",
"no_overlay": "Fără overlay",
"no_quotas_found": "Nicio cotă găsită",
"no_result_found": "Niciun rezultat găsit",
"no_results": "Nicio rezultat",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Echipele organizației nu au fost găsite",
"other": "Altele",
"others": "Altele",
"overlay_color": "Culoare overlay",
"overview": "Prezentare generală",
"password": "Parolă",
"paused": "Pauză",
@@ -321,7 +326,7 @@
"placeholder": "Marcaj substituent",
"please_select_at_least_one_survey": "Vă rugăm să selectați cel puțin un sondaj",
"please_select_at_least_one_trigger": "Vă rugăm să selectați cel puțin un declanșator",
"please_upgrade_your_plan": "Vă rugăm să vă actualizați planul.",
"please_upgrade_your_plan": "Vă rugăm să faceți upgrade la planul dumneavoastră",
"preview": "Previzualizare",
"preview_survey": "Previzualizare Chestionar",
"privacy": "Politica de Confidențialitate",
@@ -347,6 +352,7 @@
"request_trial_license": "Solicitați o licență de încercare",
"reset_to_default": "Revino la implicit",
"response": "Răspuns",
"response_id": "ID răspuns",
"responses": "Răspunsuri",
"restart": "Repornește",
"role": "Rolul",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Ați atins limita de {projectLimit} spații de lucru.",
"you_have_reached_your_monthly_miu_limit_of": "Ați atins limita lunară MIU de",
"you_have_reached_your_monthly_response_limit_of": "Ați atins limita lunară de răspunsuri de",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vei fi retrogradat la ediția Community pe {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Vei fi retrogradat la ediția Community pe {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Acceptă",
@@ -783,20 +790,26 @@
"add_webhook": "Adaugă Webhook",
"add_webhook_description": "Trimite datele de răspuns ale chestionarului la un punct final personalizat",
"all_current_and_new_surveys": "Toate chestionarele curente și noi",
"copy_secret_now": "Copiază secretul de semnare",
"created_by_third_party": "Creat de o Parte Terță",
"discord_webhook_not_supported": "Webhook-urile Discord nu sunt în prezent suportate.",
"empty_webhook_message": "Webhook-urile tale vor apărea aici de îndată ce le vei adăuga. ⏲️",
"endpoint_pinged": "Grozav! Am reușit să ping-ui webhooks-ul!",
"endpoint_pinged_error": "Nu pot să ping-ui webhooks-ul!",
"learn_to_verify": "Află cum să verifici semnăturile webhook",
"please_check_console": "Vă rugăm să verificați consola pentru mai multe detalii",
"please_enter_a_url": "Vă rugăm să introduceți un URL",
"response_created": "Răspuns creat",
"response_finished": "Răspuns finalizat",
"response_updated": "Răspuns actualizat",
"secret_copy_warning": "Păstrează acest secret în siguranță. Îl poți vizualiza din nou în setările webhook-ului.",
"secret_description": "Folosește acest secret pentru a verifica cererile webhook. Vezi documentația pentru verificarea semnăturii.",
"signing_secret": "Secret de semnare",
"source": "Sursă",
"test_endpoint": "Punct final de test",
"triggers": "Declanșatori",
"webhook_added_successfully": "Webhook adăugat cu succes",
"webhook_created": "Webhook creat",
"webhook_delete_confirmation": "Sigur doriți să ștergeți acest Webhook? Acest lucru va opri trimiterea oricăror notificări viitoare.",
"webhook_deleted_successfully": "Webhook șters cu succes",
"webhook_name_placeholder": "Opțional: Etichetează webhook-ul pentru identificare ușoară",
@@ -943,19 +956,32 @@
"enterprise_features": "Funcții Enterprise",
"get_an_enterprise_license_to_get_access_to_all_features": "Obțineți o licență Enterprise pentru a avea acces la toate funcționalitățile.",
"keep_full_control_over_your_data_privacy_and_security": "Mențineți controlul complet asupra confidențialității și securității datelor dumneavoastră.",
"license_invalid_description": "Cheia de licență din variabila de mediu ENTERPRISE_LICENSE_KEY nu este validă. Te rugăm să verifici dacă există greșeli de scriere sau să soliciți o cheie nouă.",
"license_status": "Stare licență",
"license_status_active": "Activă",
"license_status_description": "Starea licenței tale enterprise.",
"license_status_expired": "Expirată",
"license_status_invalid": "Licență invalidă",
"license_status_unreachable": "Indisponibilă",
"license_unreachable_grace_period": "Serverul de licențe nu poate fi contactat. Funcționalitățile enterprise rămân active timp de 3 zile, până la data de {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Nicio apel necesar, fără obligații: Solicitați o licență de probă gratuită de 30 de zile pentru a testa toate funcțiile prin completarea acestui formular:",
"no_credit_card_no_sales_call_just_test_it": "Nu este nevoie de card de credit. Fără apeluri de vânzări. Doar testează-l :)",
"on_request": "La cerere",
"organization_roles": "Roluri organizaționale (Administrator, Editor, Dezvoltator, etc.)",
"questions_please_reach_out_to": "Întrebări? Vă rugăm să trimiteți mesaj către",
"recheck_license": "Verifică din nou licența",
"recheck_license_failed": "Verificarea licenței a eșuat. Serverul de licențe poate fi indisponibil.",
"recheck_license_invalid": "Cheia de licență este invalidă. Te rugăm să verifici variabila ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Licența a fost verificată cu succes",
"recheck_license_unreachable": "Serverul de licențe este indisponibil. Te rugăm să încerci din nou mai târziu.",
"rechecking": "Se verifică din nou...",
"request_30_day_trial_license": "Solicitați o licență de încercare de 30 de zile",
"saml_sso": "SAML SSO",
"service_level_agreement": "Acord privind nivelul de servicii",
"soc2_hipaa_iso_27001_compliance_check": "Verificare conformitate SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Echipe & Roluri de Acces (Citiți, Citiți și Scrieți, Gestionați)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile.",
"your_enterprise_license_is_active_all_features_unlocked": "Licența dvs. Enterprise este activă. Toate funcțiile sunt deblocate."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile."
},
"general": {
"bulk_invite_warning_description": "În planul gratuit, toți membrii organizației sunt întotdeauna alocați rolului „Proprietar”.",
@@ -979,7 +1005,7 @@
"from_your_organization": "din organizația ta",
"invitation_sent_once_more": "Invitație trimisă din nou.",
"invite_deleted_successfully": "Invitație ștearsă cu succes",
"invited_on": "Invitat pe {date}",
"invite_expires_on": "Invitația expiră pe {date}",
"invites_failed": "Invitații eșuate",
"leave_organization": "Părăsește organizația",
"leave_organization_description": "Vei părăsi această organizație și vei pierde accesul la toate sondajele și răspunsurile. Poți să te alături din nou doar dacă ești invitat.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Înlătură siglă",
"replace_logo": "Înlocuiește sigla",
"resend_invitation_email": "Retrimite emailul de invitație",
"security_list_tip": "Ești abonat la lista noastră de securitate? Rămâi informat pentru a-ți menține instanța în siguranță!",
"security_list_tip_link": "Înscrie-te aici.",
"share_invite_link": "Distribuie link-ul de invitație",
"share_this_link_to_let_your_organization_member_join_your_organization": "Distribuie acest link pentru a permite membrului organizației să se alăture organizației tale:",
"test_email_sent_successfully": "Email de test trimis cu succes",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Vă rugăm să completați toate câmpurile pentru a adăuga un nou spațiu de lucru.",
"read": "Citește",
"read_write": "Citire & Scriere",
"select_member": "Selectează membrul",
"select_workspace": "Selectați spațiul de lucru",
"team_admin": "Administrator Echipe",
"team_created_successfully": "Echipă creată cu succes",
"team_deleted_successfully": "Echipă ștearsă cu succes.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Adaugă un placeholder pentru a afișa dacă nu există valoare de reamintit",
"add_hidden_field_id": "Adăugați ID câmp ascuns",
"add_highlight_border": "Adaugă bordură evidențiată",
"add_highlight_border_description": "Adaugă o margine exterioară cardului tău de sondaj.",
"add_logic": "Adaugă logică",
"add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"",
"add_option": "Adăugați opțiune",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Schimbați mesajul pe care îl văd vizitatorii atunci când sondajul este închis.",
"adjust_the_theme_in_the": "Ajustați tema în",
"all_other_answers_will_continue_to": "Toate celelalte răspunsuri vor continua să",
"allow_file_type": "Permite tipul de fișier",
"allow_multi_select": "Permite selectare multiplă",
"allow_multiple_files": "Permite fișiere multiple",
"allow_users_to_select_more_than_one_image": "Permite utilizatorilor să selecteze mai mult de o imagine",
@@ -1169,6 +1193,9 @@
"assign": "Atribuire =",
"audience": "Public",
"auto_close_on_inactivity": "Închidere automată la inactivitate",
"auto_save_disabled": "Salvare automată dezactivată",
"auto_save_disabled_tooltip": "Chestionarul dvs. este salvat automat doar când este în ciornă. Acest lucru asigură că sondajele publice nu sunt actualizate neintenționat.",
"auto_save_on": "Salvare automată activată",
"automatically_close_survey_after": "Închideți automat sondajul după",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Închideți automat sondajul după un număr anumit de răspunsuri.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Închideți automat sondajul dacă utilizatorul nu răspunde după un anumit număr de secunde.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Bloc duplicat.",
"bold": "Îngroșat",
"brand_color": "Culoarea brandului",
"brand_color_description": "Se aplică pe butoane, linkuri și evidențieri.",
"brightness": "Luminozitate",
"bulk_edit": "Editare în bloc",
"bulk_edit_description": "Editați toate opțiunile de mai jos, câte una pe linie. Liniile goale vor fi omise, iar duplicatele vor fi eliminate.",
@@ -1190,10 +1218,14 @@
"cal_username": "Utilizator Cal.com sau utilizator/eveniment",
"calculate": "Calculați",
"capture_a_new_action_to_trigger_a_survey_on": "Capturează o acțiune nouă pentru a declanșa un sondaj.",
"capture_ip_address": "Capturare adresă IP",
"capture_ip_address_description": "Stochează adresa IP a respondentului în metadatele răspunsului pentru detectarea duplicatelor și în scopuri de securitate",
"capture_new_action": "Capturați acțiune nouă",
"card_arrangement_for_survey_type_derived": "Aranjament de carduri pentru sondaje de tip {surveyTypeDerived}",
"card_background_color": "Culoarea de fundal a cardului",
"card_background_color_description": "Umple zona cardului de sondaj.",
"card_border_color": "Culoarea bordurii cardului",
"card_border_color_description": "Conturează cardul sondajului.",
"card_styling": "Stilizare card",
"casual": "Casual",
"caution_edit_duplicate": "Duplică & editează",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Răspunsurile mai vechi și mai noi se amestecă, ceea ce poate duce la rezumate de date înșelătoare.",
"caution_recommendation": "Aceasta poate cauza inconsistențe de date în rezultatul sondajului. Vă recomandăm să duplicați sondajul în schimb.",
"caution_text": "Schimbările vor duce la inconsecvențe",
"centered_modal_overlay_color": "Culoare suprapunere modală centralizată",
"change_anyway": "Schimbă oricum",
"change_background": "Schimbați fundalul",
"change_question_type": "Schimbă tipul întrebării",
"change_survey_type": "Schimbarea tipului chestionarului afectează accesul existent",
"change_the_background_color_of_the_card": "Schimbați culoarea de fundal a cardului.",
"change_the_background_color_of_the_input_fields": "Schimbați culoarea de fundal a câmpurilor de introducere.",
"change_the_background_to_a_color_image_or_animation": "Schimbați fundalul cu o culoare, imagine sau animație.",
"change_the_border_color_of_the_card": "Schimbați culoarea bordurii cardului.",
"change_the_border_color_of_the_input_fields": "Schimbați culoarea bordurii câmpurilor de introducere.",
"change_the_border_radius_of_the_card_and_the_inputs": "Schimbați raza de rotunjire a cardului și a câmpurilor de introducere.",
"change_the_brand_color_of_the_survey": "Schimbați culoarea brandului chestionarului",
"change_the_placement_of_this_survey": "Schimbă amplasarea acestui sondaj.",
"change_the_question_color_of_the_survey": "Schimbați culoarea întrebării chestionarului.",
"changes_saved": "Modificările au fost salvate",
"changing_survey_type_will_remove_existing_distribution_channels": "Schimbarea tipului chestionarului va afecta modul în care acesta poate fi distribuit. Dacă respondenții au deja linkuri de acces pentru tipul curent, aceștia ar putea pierde accesul după schimbare.",
"character_limit_toggle_description": "Limitați cât de scurt sau lung poate fi un răspuns.",
"character_limit_toggle_title": "Adăugați limite de caractere",
"checkbox_label": "Etichetă casetă de selectare",
"choose_the_actions_which_trigger_the_survey": "Alegeți acțiunile care declanșează sondajul.",
"choose_the_first_question_on_your_block": "Alege prima întrebare din blocul tău",
@@ -1241,7 +1263,6 @@
"contact_fields": "Câmpuri de contact",
"contains": "Conține",
"continue_to_settings": "Continuă către Setări",
"control_which_file_types_can_be_uploaded": "Controlează ce tipuri de fișiere pot fi încărcate.",
"convert_to_multiple_choice": "Convertiți la selectare multiplă",
"convert_to_single_choice": "Convertiți la selectare unică",
"country": "Țară",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Întunecați sau luminați fundalul după preferințe.",
"date_format": "Format dată",
"days_before_showing_this_survey_again": "sau mai multe zile să treacă între ultima afișare a sondajului și afișarea acestui sondaj.",
"delete_anyways": "Șterge oricum",
"delete_block": "Șterge blocul",
"delete_choice": "Șterge alegerea",
"disable_the_visibility_of_survey_progress": "Dezactivați vizibilitatea progresului sondajului",
"display_an_estimate_of_completion_time_for_survey": "Afișează o estimare a timpului de finalizare pentru sondaj",
"display_number_of_responses_for_survey": "Afișează numărul de răspunsuri pentru sondaj",
"display_type": "Tip de afișare",
"divide": "Împarte /",
"does_not_contain": "Nu conține",
"does_not_end_with": "Nu se termină cu",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Nu include toate",
"does_not_include_one_of": "Nu include una dintre",
"does_not_start_with": "Nu începe cu",
"dropdown": "Dropdown",
"duplicate_block": "Duplicați blocul",
"duplicate_question": "Duplică întrebarea",
"edit_link": "Editare legătură",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Ascunde bara de progres",
"hide_question_settings": "Ascunde setările întrebării",
"hostname": "Nume gazdă",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Cât de funky doriți să fie cardurile dumneavoastră în sondajele de tip {surveyTypeDerived}",
"if_you_need_more_please": "Dacă aveți nevoie de mai multe, vă rugăm să",
"if_you_need_more_please": "Dacă aveți nevoie de mai mult, vă rugăm",
"if_you_really_want_that_answer_ask_until_you_get_it": "Continuă afișarea ori de câte ori este declanșat până când se trimite un răspuns.",
"ignore_global_waiting_time": "Ignoră perioada de răcire",
"ignore_global_waiting_time_description": "Acest sondaj poate fi afișat ori de câte ori condițiile sale sunt îndeplinite, chiar dacă un alt sondaj a fost afișat recent.",
@@ -1368,7 +1391,9 @@
"initial_value": "Valoare inițială",
"inner_text": "Text Interior",
"input_border_color": "Culoarea graniței câmpului de introducere",
"input_border_color_description": "Conturează câmpurile de text și zonele de text.",
"input_color": "Culoarea câmpului de introducere",
"input_color_description": "Umple interiorul câmpurilor de text.",
"insert_link": "Inserează link",
"invalid_targeting": "\"Targetare nevalidă: Vă rugăm să verificați filtrele pentru audiență\"",
"invalid_video_url_warning": "Vă rugăm să introduceți un URL valid de YouTube, Vimeo sau Loom. În prezent nu susținem alți furnizori de găzduire video.",
@@ -1395,10 +1420,10 @@
"key": "Cheie",
"last_name": "Nume de familie",
"let_people_upload_up_to_25_files_at_the_same_time": "Permiteți utilizatorilor să încarce până la 25 de fișiere simultan.",
"limit_file_types": "Limitare tipuri de fișiere",
"limit_the_maximum_file_size": "Limitează dimensiunea maximă a fișierului",
"limit_upload_file_size_to": "Limitați dimensiunea fișierului de încărcare la",
"limit_the_maximum_file_size": "Limitați dimensiunea maximă a fișierului pentru încărcări.",
"limit_upload_file_size_to": "Limitați dimensiunea fișierului încărcat la",
"link_survey_description": "Partajați un link către o pagină de chestionar sau încorporați-l într-o pagină web sau email.",
"list": "Listă",
"load_segment": "Încarcă segment",
"logic_error_warning": "Schimbarea va provoca erori de logică",
"logic_error_warning_text": "Schimbarea tipului de întrebare va elimina condițiile de logică din această întrebare",
@@ -1410,7 +1435,7 @@
"matrix_all_fields": "Toate câmpurile",
"matrix_rows": "Rânduri",
"max_file_size": "Dimensiune maximă fișier",
"max_file_size_limit_is": "Limita dimensiunii maxime a fișierului este",
"max_file_size_limit_is": "Limita maximă pentru dimensiunea fișierului este",
"move_question_to_block": "Mută întrebarea în bloc",
"multiply": "Multiplicare",
"needed_for_self_hosted_cal_com_instance": "Necesar pentru un exemplu autogăzduit Cal.com",
@@ -1442,22 +1467,22 @@
"picture_idx": "Poză {idx}",
"pin_can_only_contain_numbers": "PIN-ul poate conține doar numere.",
"pin_must_be_a_four_digit_number": "PIN-ul trebuie să fie un număr de patru cifre",
"please_enter_a_file_extension": "Vă rugăm să introduceți o extensie de fișier.",
"please_enter_a_valid_url": "Vă rugăm să introduceți un URL valid (de exemplu, https://example.com)",
"please_set_a_survey_trigger": "Vă rugăm să setați un declanșator sondaj",
"please_specify": "Vă rugăm să specificați",
"prevent_double_submission": "Prevenire trimitere dublă",
"prevent_double_submission_description": "Permite doar 1 răspuns per adresă de email.",
"progress_saved": "Progres salvat",
"protect_survey_with_pin": "Protejați sondajul cu un PIN",
"protect_survey_with_pin_description": "Doar utilizatorii care cunosc PIN-ul pot accesa sondajul.",
"publish": "Publică",
"question": "Întrebare",
"question_color": "Culoarea întrebării",
"question_deleted": "Întrebare ștearsă.",
"question_duplicated": "Întrebare duplicată.",
"question_id_updated": "ID întrebare actualizat",
"question_used_in_logic": "Această întrebare este folosită în logica întrebării {questionIndex}.",
"question_used_in_quota": "Întrebarea aceasta este folosită în cota \"{quotaName}\"",
"question_used_in_logic_warning_text": "Elemente din acest bloc sunt folosite într-o regulă de logică. Sigur doriți să îl ștergeți?",
"question_used_in_logic_warning_title": "Inconsistență logică",
"question_used_in_quota": "Întrebarea aceasta este folosită în cota „{quotaName}”",
"question_used_in_recall": "Această întrebare este reamintită în întrebarea {questionIndex}.",
"question_used_in_recall_ending_card": "Această întrebare este reamintită în Cardul de Încheiere.",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.",
"response_options": "Opțiuni răspuns",
"roundness": "Rotunjire",
"roundness_description": "Controlează cât de rotunjite sunt colțurile cardului.",
"row_used_in_logic_error": "Această linie este folosită în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.",
"rows": "Rânduri",
"save_and_close": "Salvează & Închide",
@@ -1520,6 +1546,7 @@
"search_for_images": "Căutare de imagini",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "secunde după declanșare sondajul va fi închis dacă nu există niciun răspuns",
"seconds_before_showing_the_survey": "secunde înainte de afișarea sondajului",
"select_field": "Selectează câmpul",
"select_or_type_value": "Selectați sau introduceți valoarea",
"select_ordering": "Selectează ordonarea",
"select_saved_action": "Selectați acțiunea salvată",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Stilizare setată la stilurile temei",
"subheading": "Subtitlu",
"subtract": "Scade -",
"suggest_colors": "Sugerați culori",
"survey_completed_heading": "Sondaj Completat",
"survey_completed_subheading": "Acest sondaj gratuit și open-source a fost închis",
"survey_display_settings": "Setări de afișare a sondajului",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Afișează o singură dată, chiar dacă persoana nu răspunde.",
"then": "Apoi",
"this_action_will_remove_all_the_translations_from_this_survey": "Această acțiune va elimina toate traducerile din acest sondaj.",
"this_extension_is_already_added": "Această extensie este deja adăugată.",
"this_file_type_is_not_supported": "Acest tip de fișier nu este acceptat.",
"three_points": "3 puncte",
"times": "ori",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Pentru a menține amplasarea consecventă pentru toate sondajele, puteți",
@@ -1589,8 +1613,51 @@
"upper_label": "Etichetă superioară",
"url_filters": "Filtre URL",
"url_not_supported": "URL nesuportat",
"validation": {
"add_validation_rule": "Adaugă regulă de validare",
"answer_all_rows": "Răspunde la toate rândurile",
"characters": "Caractere",
"contains": "Conține",
"delete_validation_rule": "Șterge regula de validare",
"does_not_contain": "Nu conține",
"email": "Este un email valid",
"end_date": "Data de sfârșit",
"file_extension_is": "Extensia fișierului este",
"file_extension_is_not": "Extensia fișierului nu este",
"is": "Este",
"is_between": "Este între",
"is_earlier_than": "Este mai devreme decât",
"is_greater_than": "Este mai mare decât",
"is_later_than": "Este mai târziu decât",
"is_less_than": "Este mai mic decât",
"is_not": "Nu este",
"is_not_between": "Nu este între",
"kb": "KB",
"max_length": "Cel mult",
"max_selections": "Cel mult",
"max_value": "Cel mult",
"mb": "MB",
"min_length": "Cel puțin",
"min_selections": "Cel puțin",
"min_value": "Cel puțin",
"minimum_options_ranked": "Număr minim de opțiuni ordonate",
"minimum_rows_answered": "Număr minim de rânduri completate",
"options_selected": "Opțiuni selectate",
"pattern": "Se potrivește cu un șablon regex",
"phone": "Este un număr de telefon valid",
"rank_all_options": "Ordonați toate opțiunile",
"select_file_extensions": "Selectați extensiile de fișier...",
"select_option": "Selectează opțiunea",
"start_date": "Data de început",
"url": "Este un URL valid"
},
"validation_logic_and": "Toate sunt adevărate",
"validation_logic_or": "oricare este adevărată",
"validation_rules": "Reguli de validare",
"validation_rules_description": "Acceptă doar răspunsurile care îndeplinesc următoarele criterii",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} este folosit în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabila \"{variableName}\" este folosită în cota \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabila {variableName} este folosită în cota {quotaName}”. Vă rugăm să o eliminați mai întâi din cotă",
"variable_name_conflicts_with_hidden_field": "Numele variabilei intră în conflict cu un ID de câmp ascuns existent.",
"variable_name_is_already_taken_please_choose_another": "Numele variabilei este deja utilizat, vă rugăm să alegeți altul.",
"variable_name_must_start_with_a_letter": "Numele variabilei trebuie să înceapă cu o literă.",
"variable_used_in_recall": "Variabila \"{variable}\" este reamintită în întrebarea {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "A apărut o eroare la descărcarea răspunsurilor",
"first_name": "Prenume",
"how_to_identify_users": "Cum să identifici utilizatorii",
"ip_address": "Adresă IP",
"last_name": "Nume de familie",
"not_completed": "Necompletat ⏳",
"os": "SO",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Adăugați culoare de fundal",
"add_background_color_description": "Adăugați o culoare de fundal la containerul siglei.",
"advanced_styling_field_border_radius": "Raza colțurilor",
"advanced_styling_field_button_bg": "Fundal buton",
"advanced_styling_field_button_bg_description": "Umple butonul Următor / Trimite.",
"advanced_styling_field_button_border_radius_description": "Rotunjește colțurile butonului.",
"advanced_styling_field_button_font_size_description": "Scalează textul etichetei butonului.",
"advanced_styling_field_button_font_weight_description": "Face textul butonului mai subțire sau mai îngroșat.",
"advanced_styling_field_button_height_description": "Controlează înălțimea butonului.",
"advanced_styling_field_button_padding_x_description": "Adaugă spațiu la stânga și la dreapta.",
"advanced_styling_field_button_padding_y_description": "Adaugă spațiu sus și jos.",
"advanced_styling_field_button_text": "Text buton",
"advanced_styling_field_button_text_description": "Colorează eticheta din interiorul butoanelor.",
"advanced_styling_field_description_color": "Culoare descriere",
"advanced_styling_field_description_color_description": "Colorează textul de sub fiecare titlu.",
"advanced_styling_field_description_size": "Mărime font descriere",
"advanced_styling_field_description_size_description": "Scalează textul descrierii.",
"advanced_styling_field_description_weight": "Grosime font descriere",
"advanced_styling_field_description_weight_description": "Face textul descrierii mai subțire sau mai îngroșat.",
"advanced_styling_field_font_size": "Mărime font",
"advanced_styling_field_font_weight": "Grosime font",
"advanced_styling_field_headline_color": "Culoare titlu",
"advanced_styling_field_headline_color_description": "Colorează textul principal al întrebării.",
"advanced_styling_field_headline_size": "Mărime font titlu",
"advanced_styling_field_headline_size_description": "Scalează textul titlului.",
"advanced_styling_field_headline_weight": "Grosime font titlu",
"advanced_styling_field_headline_weight_description": "Face textul titlului mai subțire sau mai îngroșat.",
"advanced_styling_field_height": "Înălțime",
"advanced_styling_field_indicator_bg": "Fundal indicator",
"advanced_styling_field_indicator_bg_description": "Colorează partea umplută a barei.",
"advanced_styling_field_input_border_radius_description": "Rotunjește colțurile câmpurilor de introducere.",
"advanced_styling_field_input_font_size_description": "Scalează textul introdus în câmpuri.",
"advanced_styling_field_input_height_description": "Controlează înălțimea câmpului de introducere.",
"advanced_styling_field_input_padding_x_description": "Adaugă spațiu la stânga și la dreapta.",
"advanced_styling_field_input_padding_y_description": "Adaugă spațiu deasupra și dedesubt.",
"advanced_styling_field_input_placeholder_opacity_description": "Estompează textul de sugestie din placeholder.",
"advanced_styling_field_input_shadow_description": "Adaugă o umbră în jurul câmpurilor de introducere.",
"advanced_styling_field_input_text": "Text câmp",
"advanced_styling_field_input_text_description": "Colorează textul introdus în câmpuri.",
"advanced_styling_field_option_bg": "Fundal",
"advanced_styling_field_option_bg_description": "Umple elementele de opțiune.",
"advanced_styling_field_option_border_radius_description": "Rotunjește colțurile opțiunilor.",
"advanced_styling_field_option_font_size_description": "Redimensionează textul etichetei opțiunii.",
"advanced_styling_field_option_label": "Culoare etichetă",
"advanced_styling_field_option_label_description": "Colorează textul etichetei opțiunii.",
"advanced_styling_field_option_padding_x_description": "Adaugă spațiu în stânga și în dreapta.",
"advanced_styling_field_option_padding_y_description": "Adaugă spațiu deasupra și dedesubt.",
"advanced_styling_field_padding_x": "Spațiere X",
"advanced_styling_field_padding_y": "Spațiere Y",
"advanced_styling_field_placeholder_opacity": "Opacitate placeholder",
"advanced_styling_field_shadow": "Umbră",
"advanced_styling_field_track_bg": "Fundal track",
"advanced_styling_field_track_bg_description": "Colorează partea necompletată a barei.",
"advanced_styling_field_track_height": "Înălțime track",
"advanced_styling_field_track_height_description": "Controlează grosimea barei de progres.",
"advanced_styling_field_upper_label_color": "Culoare etichetă titlu",
"advanced_styling_field_upper_label_color_description": "Colorează eticheta mică de deasupra câmpurilor.",
"advanced_styling_field_upper_label_size": "Mărime font etichetă titlu",
"advanced_styling_field_upper_label_size_description": "Redimensionează eticheta mică de deasupra câmpurilor.",
"advanced_styling_field_upper_label_weight": "Grosime font etichetă titlu",
"advanced_styling_field_upper_label_weight_description": "Face eticheta mai subțire sau mai îngroșată.",
"advanced_styling_section_buttons": "Butoane",
"advanced_styling_section_headlines": "Titluri și descrieri",
"advanced_styling_section_inputs": "Inputuri",
"advanced_styling_section_options": "Opțiuni (Radio/Checkbox)",
"app_survey_placement": "Amplasarea sondajului în aplicație",
"app_survey_placement_settings_description": "Schimbați unde vor fi afișate sondajele în aplicația sau site-ul dvs. web.",
"centered_modal_overlay_color": "Culoare suprapunere modală centralizată",
"email_customization": "Personalizare email",
"email_customization_description": "Schimbați aspectul și stilul emailurilor trimise de Formbricks în numele dvs.",
"enable_custom_styling": "Activați stilizarea personalizată",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Brandingul Formbricks este ascuns.",
"formbricks_branding_settings_description": "Ne bucurăm de susținerea ta, dar înțelegem dacă vrei să dezactivezi această opțiune.",
"formbricks_branding_shown": "Brandingul Formbricks este afișat.",
"generate_theme_btn": "Generează",
"generate_theme_confirmation": "Vrei să generezi o temă de culori potrivită pe baza culorii brandului tău? Aceasta va suprascrie setările actuale de culoare.",
"generate_theme_header": "Generezi temă de culori?",
"logo_removed_successfully": "Sigla a fost eliminată cu succes",
"logo_settings_description": "Încarcă sigla companiei pentru a personaliza sondajele și previzualizările de linkuri.",
"logo_updated_successfully": "Sigla a fost actualizată cu succes",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Afișează brandingul Formbricks în sondajele de tip {type}",
"show_powered_by_formbricks": "Afișează semnătura „Powered by Formbricks”",
"styling_updated_successfully": "Stilizarea a fost actualizată cu succes",
"suggest_colors": "Sugerează culori",
"theme": "Temă",
"theme_settings_description": "Creează o temă de stil pentru toate sondajele. Poți activa stilizare personalizată pentru fiecare sondaj."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Da, ține-mă informat.",
"preview_survey_question_2_choice_2_label": "Nu, mulţumesc!",
"preview_survey_question_2_headline": "Vrei să fii în temă?",
"preview_survey_question_2_subheader": "Aceasta este o descriere exemplu.",
"preview_survey_welcome_card_headline": "Bun venit!",
"prioritize_features_description": "Identificați caracteristicile de care utilizatorii dumneavoastră au cel mai mult și cel mai puțin nevoie.",
"prioritize_features_name": "Prioritizați caracteristicile",
+171 -36
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Сочетание заглавных и строчных букв",
"please_verify_captcha": "Пожалуйста, подтвердите reCAPTCHA",
"privacy_policy": "Политика конфиденциальности",
"product_updates_description": "Ежемесячные новости о продукте и обновления функций. Применяется Политика конфиденциальности.",
"product_updates_title": "Обновления продукта",
"security_updates_description": "Только важная информация по безопасности. Применяется Политика конфиденциальности.",
"security_updates_title": "Обновления безопасности",
"terms_of_service": "Условия использования",
"title": "Создайте аккаунт Formbricks"
},
@@ -197,6 +201,7 @@
"docs": "Документация",
"documentation": "Документация",
"domain": "Домен",
"done": "Готово",
"download": "Скачать",
"draft": "Черновик",
"duplicate": "Дублировать",
@@ -238,7 +243,6 @@
"imprint": "Выходные данные",
"in_progress": "В процессе",
"inactive_surveys": "Неактивные опросы",
"input_type": "Тип ввода",
"integration": "интеграция",
"integrations": "Интеграции",
"invalid_date": "Неверная дата",
@@ -250,6 +254,7 @@
"label": "Метка",
"language": "Язык",
"learn_more": "Подробнее",
"license_expired": "License Expired",
"light_overlay": "Светлый оверлей",
"limits_reached": "Достигнуты лимиты",
"link": "Ссылка",
@@ -262,13 +267,11 @@
"look_and_feel": "Внешний вид",
"manage": "Управление",
"marketing": "Маркетинг",
"maximum": "Максимум",
"member": "Участник",
"members": "Участники",
"members_and_teams": "Участники и команды",
"membership_not_found": "Участие не найдено",
"metadata": "Метаданные",
"minimum": "Минимум",
"mobile_overlay_app_works_best_on_desktop": "Formbricks лучше всего работает на большом экране. Для управления или создания опросов перейдите на другое устройство.",
"mobile_overlay_surveys_look_good": "Не волнуйтесь — ваши опросы отлично выглядят на любом устройстве и экране!",
"mobile_overlay_title": "Ой, обнаружен маленький экран!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Фоновое изображение не найдено.",
"no_code": "Нет кода",
"no_files_uploaded": "Файлы не были загружены",
"no_overlay": "Без наложения",
"no_quotas_found": "Квоты не найдены",
"no_result_found": "Результат не найден",
"no_results": "Нет результатов",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Команды организации не найдены",
"other": "Другое",
"others": "Другие",
"overlay_color": "Цвет наложения",
"overview": "Обзор",
"password": "Пароль",
"paused": "Приостановлено",
@@ -321,7 +326,7 @@
"placeholder": "Заполнитель",
"please_select_at_least_one_survey": "Пожалуйста, выберите хотя бы один опрос",
"please_select_at_least_one_trigger": "Пожалуйста, выберите хотя бы один триггер",
"please_upgrade_your_plan": "Пожалуйста, обновите ваш тарифный план.",
"please_upgrade_your_plan": "Пожалуйста, обновите ваш тарифный план",
"preview": "Предпросмотр",
"preview_survey": "Предпросмотр опроса",
"privacy": "Политика конфиденциальности",
@@ -347,6 +352,7 @@
"request_trial_license": "Запросить пробную лицензию",
"reset_to_default": "Сбросить по умолчанию",
"response": "Ответ",
"response_id": "ID ответа",
"responses": "Ответы",
"restart": "Перезапустить",
"role": "Роль",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Вы достигли лимита в {projectLimit} рабочих пространств.",
"you_have_reached_your_monthly_miu_limit_of": "Вы достигли месячного лимита MIU:",
"you_have_reached_your_monthly_response_limit_of": "Вы достигли месячного лимита ответов:",
"you_will_be_downgraded_to_the_community_edition_on_date": "Ваша версия будет понижена до Community Edition {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Ваша версия будет понижена до Community Edition {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Принять",
@@ -783,20 +790,26 @@
"add_webhook": "Добавить webhook",
"add_webhook_description": "Отправляйте данные ответов на опрос на пользовательский endpoint",
"all_current_and_new_surveys": "Все текущие и новые опросы",
"copy_secret_now": "Скопируйте ваш секрет подписи",
"created_by_third_party": "Создано сторонней организацией",
"discord_webhook_not_supported": "В настоящее время webhooks Discord не поддерживаются.",
"empty_webhook_message": "Ваши webhooks появятся здесь, как только вы их добавите. ⏲️",
"endpoint_pinged": "Ура! Нам удалось отправить ping на webhook!",
"endpoint_pinged_error": "Не удалось отправить ping на webhook!",
"learn_to_verify": "Узнайте, как проверить подписи вебхуков",
"please_check_console": "Пожалуйста, проверьте консоль для получения подробностей",
"please_enter_a_url": "Пожалуйста, введите URL",
"response_created": "Ответ создан",
"response_finished": "Ответ завершён",
"response_updated": "Ответ обновлён",
"secret_copy_warning": "Храните этот секрет в надёжном месте. Вы сможете просмотреть его снова в настройках webhook.",
"secret_description": "Используйте этот секрет для проверки запросов webhook. Подробнее о проверке подписи — в документации.",
"signing_secret": "Секрет подписи",
"source": "Источник",
"test_endpoint": "Тестировать endpoint",
"triggers": "Триггеры",
"webhook_added_successfully": "Webhook успешно добавлен",
"webhook_created": "Webhook создан",
"webhook_delete_confirmation": "Вы уверены, что хотите удалить этот webhook? Это прекратит отправку вам любых дальнейших уведомлений.",
"webhook_deleted_successfully": "Webhook успешно удалён",
"webhook_name_placeholder": "Необязательно: дайте метку вашему webhook для удобной идентификации",
@@ -943,19 +956,32 @@
"enterprise_features": "Функции для предприятий",
"get_an_enterprise_license_to_get_access_to_all_features": "Получите корпоративную лицензию для доступа ко всем функциям.",
"keep_full_control_over_your_data_privacy_and_security": "Полный контроль над конфиденциальностью и безопасностью ваших данных.",
"license_invalid_description": "Ключ лицензии в переменной окружения ENTERPRISE_LICENSE_KEY недействителен. Проверь, нет ли опечаток, или запроси новый ключ.",
"license_status": "Статус лицензии",
"license_status_active": "Активна",
"license_status_description": "Статус вашей корпоративной лицензии.",
"license_status_expired": "Срок действия истёк",
"license_status_invalid": "Недействительная лицензия",
"license_status_unreachable": "Недоступна",
"license_unreachable_grace_period": "Не удаётся подключиться к серверу лицензий. Корпоративные функции останутся активными в течение 3-дневного льготного периода, который закончится {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Без звонков и обязательств: запросите бесплатную 30-дневную пробную лицензию для тестирования всех функций, заполнив эту форму:",
"no_credit_card_no_sales_call_just_test_it": "Без кредитной карты. Без звонков от отдела продаж. Просто попробуйте :)",
"on_request": "По запросу",
"organization_roles": "Роли в организации (администратор, редактор, разработчик и др.)",
"questions_please_reach_out_to": "Вопросы? Свяжитесь с",
"recheck_license": "Проверить лицензию ещё раз",
"recheck_license_failed": "Не удалось проверить лицензию. Сервер лицензий может быть недоступен.",
"recheck_license_invalid": "Ключ лицензии недействителен. Пожалуйста, проверь свою переменную ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Проверка лицензии прошла успешно",
"recheck_license_unreachable": "Сервер лицензий недоступен. Пожалуйста, попробуй позже.",
"rechecking": "Проверка...",
"request_30_day_trial_license": "Запросить 30-дневную пробную лицензию",
"saml_sso": "SAML SSO",
"service_level_agreement": "Соглашение об уровне обслуживания (SLA)",
"soc2_hipaa_iso_27001_compliance_check": "Проверка соответствия SOC2, HIPAA, ISO 27001",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Команды и роли доступа (чтение, чтение и запись, управление)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней.",
"your_enterprise_license_is_active_all_features_unlocked": "Ваша корпоративная лицензия активна. Все функции разблокированы."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней."
},
"general": {
"bulk_invite_warning_description": "В бесплатном тарифе всем участникам организации всегда назначается роль \"Владелец\".",
@@ -979,7 +1005,7 @@
"from_your_organization": "из вашей организации",
"invitation_sent_once_more": "Приглашение отправлено ещё раз.",
"invite_deleted_successfully": "Приглашение успешно удалено",
"invited_on": "Приглашён {date}",
"invite_expires_on": "Приглашение истекает {date}",
"invites_failed": "Не удалось отправить приглашения",
"leave_organization": "Покинуть организацию",
"leave_organization_description": "Вы покинете эту организацию и потеряете доступ ко всем опросам и ответам. Вы сможете вернуться только по новому приглашению.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Удалить логотип",
"replace_logo": "Заменить логотип",
"resend_invitation_email": "Отправить приглашение повторно",
"security_list_tip": "Вы подписаны на нашу рассылку по безопасности? Будьте в курсе, чтобы обезопасить свой экземпляр!",
"security_list_tip_link": "Зарегистрируйтесь здесь.",
"share_invite_link": "Поделиться ссылкой-приглашением",
"share_this_link_to_let_your_organization_member_join_your_organization": "Поделитесь этой ссылкой, чтобы участник вашей организации мог присоединиться к ней:",
"test_email_sent_successfully": "Тестовое письмо успешно отправлено",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Пожалуйста, заполните все поля для добавления нового рабочего пространства.",
"read": "Чтение",
"read_write": "Чтение и запись",
"select_member": "Выберите участника",
"select_workspace": "Выберите рабочее пространство",
"team_admin": "Администратор команды",
"team_created_successfully": "Команда успешно создана.",
"team_deleted_successfully": "Команда успешно удалена.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Добавить плейсхолдер, который будет показан, если нет значения для отображения.",
"add_hidden_field_id": "Добавить скрытый ID поля",
"add_highlight_border": "Добавить выделяющую рамку",
"add_highlight_border_description": "Добавьте внешнюю рамку к карточке опроса.",
"add_logic": "Добавить логику",
"add_none_of_the_above": "Добавить вариант «Ничего из вышеперечисленного»",
"add_option": "Добавить вариант",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Измените сообщение, которое видят посетители, когда опрос закрыт.",
"adjust_the_theme_in_the": "Настройте тему в",
"all_other_answers_will_continue_to": "Все остальные ответы будут продолжать",
"allow_file_type": "Разрешить тип файла",
"allow_multi_select": "Разрешить множественный выбор",
"allow_multiple_files": "Разрешить несколько файлов",
"allow_users_to_select_more_than_one_image": "Разрешить пользователям выбирать более одного изображения",
@@ -1169,6 +1193,9 @@
"assign": "Назначить =",
"audience": "Аудитория",
"auto_close_on_inactivity": "Автоматически закрывать при бездействии",
"auto_save_disabled": "Автосохранение отключено",
"auto_save_disabled_tooltip": "Ваш опрос автоматически сохраняется только в режиме черновика. Это гарантирует, что публичные опросы не будут случайно обновлены.",
"auto_save_on": "Автосохранение включено",
"automatically_close_survey_after": "Автоматически закрыть опрос через",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Автоматически закрывать опрос после определённого количества ответов.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Автоматически закрывать опрос, если пользователь не ответил за определённое количество секунд.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Блокировать дубликаты.",
"bold": "Жирный",
"brand_color": "Фирменный цвет",
"brand_color_description": "Применяется к кнопкам, ссылкам и выделениям.",
"brightness": "Яркость",
"bulk_edit": "Массовое редактирование",
"bulk_edit_description": "Отредактируйте все варианты ниже, по одному на строку. Пустые строки будут пропущены, а дубликаты удалены.",
@@ -1190,10 +1218,14 @@
"cal_username": "Имя пользователя Cal.com или username/event",
"calculate": "Вычислить",
"capture_a_new_action_to_trigger_a_survey_on": "Захватить новое действие для запуска опроса.",
"capture_ip_address": "Сохранять IP-адрес",
"capture_ip_address_description": "Сохранять IP-адрес респондента в метаданных ответа для обнаружения дубликатов и обеспечения безопасности",
"capture_new_action": "Захватить новое действие",
"card_arrangement_for_survey_type_derived": "Расположение карточек для опросов типа {surveyTypeDerived}",
"card_background_color": "Цвет фона карточки",
"card_background_color_description": "Заполняет область карточки опроса.",
"card_border_color": "Цвет рамки карточки",
"card_border_color_description": "Обводит карточку опроса.",
"card_styling": "Оформление карточки",
"casual": "Неформальный",
"caution_edit_duplicate": "Дублировать и редактировать",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Старые и новые ответы смешиваются, что может привести к искажённым итоговым данным.",
"caution_recommendation": "Это может привести к несоответствиям в итогах опроса. Рекомендуем вместо этого дублировать опрос.",
"caution_text": "Изменения приведут к несоответствиям",
"centered_modal_overlay_color": "Цвет оверлея центрированного модального окна",
"change_anyway": "Всё равно изменить",
"change_background": "Изменить фон",
"change_question_type": "Изменить тип вопроса",
"change_survey_type": "Смена типа опроса влияет на существующий доступ",
"change_the_background_color_of_the_card": "Изменить цвет фона карточки.",
"change_the_background_color_of_the_input_fields": "Изменить цвет фона полей ввода.",
"change_the_background_to_a_color_image_or_animation": "Изменить фон на цвет, изображение или анимацию.",
"change_the_border_color_of_the_card": "Изменить цвет рамки карточки.",
"change_the_border_color_of_the_input_fields": "Изменить цвет рамки полей ввода.",
"change_the_border_radius_of_the_card_and_the_inputs": "Изменить скругление углов карточки и полей ввода.",
"change_the_brand_color_of_the_survey": "Изменить фирменный цвет опроса.",
"change_the_placement_of_this_survey": "Изменить размещение этого опроса.",
"change_the_question_color_of_the_survey": "Изменить цвет вопросов в опросе.",
"changes_saved": "Изменения сохранены.",
"changing_survey_type_will_remove_existing_distribution_channels": "Изменение типа опроса повлияет на способы его распространения. Если у респондентов уже есть ссылки для доступа к текущему типу, после смены они могут потерять доступ.",
"character_limit_toggle_description": "Ограничьте минимальную и максимальную длину ответа.",
"character_limit_toggle_title": "Добавить ограничения на количество символов",
"checkbox_label": "Метка флажка",
"choose_the_actions_which_trigger_the_survey": "Выберите действия, которые запускают опрос.",
"choose_the_first_question_on_your_block": "Выберите первый вопрос в вашем блоке",
@@ -1241,7 +1263,6 @@
"contact_fields": "Поля контакта",
"contains": "Содержит",
"continue_to_settings": "Перейти к настройкам",
"control_which_file_types_can_be_uploaded": "Управляйте типами файлов, которые можно загружать.",
"convert_to_multiple_choice": "Преобразовать в мультивыбор",
"convert_to_single_choice": "Преобразовать в одиночный выбор",
"country": "Страна",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Затемните или осветлите выбранный фон.",
"date_format": "Формат даты",
"days_before_showing_this_survey_again": "или больше дней должно пройти между последним показом опроса и показом этого опроса.",
"delete_anyways": "Удалить в любом случае",
"delete_block": "Удалить блок",
"delete_choice": "Удалить вариант",
"disable_the_visibility_of_survey_progress": "Отключить отображение прогресса опроса.",
"display_an_estimate_of_completion_time_for_survey": "Показывать примерное время прохождения опроса",
"display_number_of_responses_for_survey": "Показывать количество ответов на опрос",
"display_type": "Тип отображения",
"divide": "Разделить /",
"does_not_contain": "Не содержит",
"does_not_end_with": "Не заканчивается на",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Не включает все из",
"does_not_include_one_of": "Не включает ни одного из",
"does_not_start_with": "Не начинается с",
"dropdown": "Выпадающий список",
"duplicate_block": "Дублировать блок",
"duplicate_question": "Дублировать вопрос",
"edit_link": "Редактировать ссылку",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Скрыть индикатор прогресса",
"hide_question_settings": "Скрыть настройки вопроса",
"hostname": "Имя хоста",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Насколько необычными вы хотите сделать карточки в опросах типа {surveyTypeDerived}",
"if_you_need_more_please": "Если нужно больше, пожалуйста",
"if_you_need_more_please": "Если вам нужно больше, пожалуйста",
"if_you_really_want_that_answer_ask_until_you_get_it": "Показывать каждый раз при срабатывании, пока не будет получен ответ.",
"ignore_global_waiting_time": "Игнорировать период ожидания",
"ignore_global_waiting_time_description": "Этот опрос может отображаться при выполнении условий, даже если недавно уже был показан другой опрос.",
@@ -1368,7 +1391,9 @@
"initial_value": "Начальное значение",
"inner_text": "Внутренний текст",
"input_border_color": "Цвет рамки поля ввода",
"input_border_color_description": "Обводит текстовые поля и текстовые области.",
"input_color": "Цвет поля ввода",
"input_color_description": "Заполняет внутреннюю часть текстовых полей.",
"insert_link": "Вставить ссылку",
"invalid_targeting": "Некорректный таргетинг: проверьте фильтры аудитории",
"invalid_video_url_warning": "Пожалуйста, введите корректную ссылку на YouTube, Vimeo или Loom. В настоящее время другие видеохостинги не поддерживаются.",
@@ -1395,10 +1420,10 @@
"key": "Ключ",
"last_name": "Фамилия",
"let_people_upload_up_to_25_files_at_the_same_time": "Разрешить загружать до 25 файлов одновременно.",
"limit_file_types": "Ограничить типы файлов",
"limit_the_maximum_file_size": "Ограничить максимальный размер файла",
"limit_the_maximum_file_size": "Ограничьте максимальный размер загружаемых файлов.",
"limit_upload_file_size_to": "Ограничить размер загружаемого файла до",
"link_survey_description": "Поделитесь ссылкой на страницу опроса или вставьте её на веб-страницу или в электронное письмо.",
"list": "Список",
"load_segment": "Загрузить сегмент",
"logic_error_warning": "Изменение приведёт к логическим ошибкам",
"logic_error_warning_text": "Изменение типа вопроса удалит логические условия из этого вопроса",
@@ -1442,22 +1467,22 @@
"picture_idx": "Изображение {idx}",
"pin_can_only_contain_numbers": "PIN-код может содержать только цифры.",
"pin_must_be_a_four_digit_number": "PIN-код должен состоять из четырёх цифр.",
"please_enter_a_file_extension": "Пожалуйста, введите расширение файла.",
"please_enter_a_valid_url": "Пожалуйста, введите корректный URL (например, https://example.com)",
"please_set_a_survey_trigger": "Пожалуйста, установите триггер опроса",
"please_specify": "Пожалуйста, уточните",
"prevent_double_submission": "Предотвратить повторную отправку",
"prevent_double_submission_description": "Разрешить только 1 ответ на один адрес электронной почты",
"progress_saved": "Прогресс сохранён",
"protect_survey_with_pin": "Защитить опрос с помощью PIN-кода",
"protect_survey_with_pin_description": "Только пользователи, у которых есть PIN-код, могут получить доступ к опросу.",
"publish": "Опубликовать",
"question": "Вопрос",
"question_color": "Цвет вопроса",
"question_deleted": "Вопрос удалён.",
"question_duplicated": "Вопрос дублирован.",
"question_id_updated": "ID вопроса обновлён",
"question_used_in_logic": "Этот вопрос используется в логике вопроса {questionIndex}.",
"question_used_in_quota": "Этот вопрос используется в квоте \"{quotaName}\"",
"question_used_in_logic_warning_text": "Элементы из этого блока используются в правиле логики. Вы уверены, что хотите удалить его?",
"question_used_in_logic_warning_title": "Несогласованность логики",
"question_used_in_quota": "Этот вопрос используется в квоте «{quotaName}»",
"question_used_in_recall": "Этот вопрос используется в отзыве в вопросе {questionIndex}.",
"question_used_in_recall_ending_card": "Этот вопрос используется в отзыве на финальной карточке",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Лимиты ответов, перенаправления и другое.",
"response_options": "Параметры ответа",
"roundness": "Скругление",
"roundness_description": "Определяет степень скругления углов карточки.",
"row_used_in_logic_error": "Эта строка используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите её из логики.",
"rows": "Строки",
"save_and_close": "Сохранить и закрыть",
@@ -1520,6 +1546,7 @@
"search_for_images": "Поиск изображений",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "секунд после запуска — опрос будет закрыт, если не будет ответа",
"seconds_before_showing_the_survey": "секунд до показа опроса.",
"select_field": "Выберите поле",
"select_or_type_value": "Выберите или введите значение",
"select_ordering": "Выберите порядок",
"select_saved_action": "Выберите сохранённое действие",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Оформление установлено в соответствии с темой",
"subheading": "Подзаголовок",
"subtract": "Вычесть -",
"suggest_colors": "Предложить цвета",
"survey_completed_heading": "Опрос завершён",
"survey_completed_subheading": "Этот бесплатный и открытый опрос был закрыт",
"survey_display_settings": "Настройки отображения опроса",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Показать один раз, даже если не будет ответа.",
"then": "Затем",
"this_action_will_remove_all_the_translations_from_this_survey": "Это действие удалит все переводы из этого опроса.",
"this_extension_is_already_added": "Это расширение уже добавлено.",
"this_file_type_is_not_supported": "Этот тип файла не поддерживается.",
"three_points": "3 балла",
"times": "раз",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "Чтобы сохранить единое расположение во всех опросах, вы можете",
@@ -1589,8 +1613,51 @@
"upper_label": "Верхняя метка",
"url_filters": "Фильтры URL",
"url_not_supported": "URL не поддерживается",
"validation": {
"add_validation_rule": "Добавить правило проверки",
"answer_all_rows": "Ответьте на все строки",
"characters": "Символы",
"contains": "Содержит",
"delete_validation_rule": "Удалить правило проверки",
"does_not_contain": "Не содержит",
"email": "Корректный email",
"end_date": "Дата окончания",
"file_extension_is": "Расширение файла —",
"file_extension_is_not": "Расширение файла не является",
"is": "Является",
"is_between": "Находится между",
"is_earlier_than": "Ранее чем",
"is_greater_than": "Больше чем",
"is_later_than": "Позже чем",
"is_less_than": "Меньше чем",
"is_not": "Не является",
"is_not_between": "Не находится между",
"kb": "КБ",
"max_length": "Не более",
"max_selections": "Не более",
"max_value": "Не более",
"mb": "МБ",
"min_length": "Не менее",
"min_selections": "Не менее",
"min_value": "Не менее",
"minimum_options_ranked": "Минимальное количество ранжированных вариантов",
"minimum_rows_answered": "Минимальное количество заполненных строк",
"options_selected": "Выбранные опции",
"pattern": "Соответствует шаблону regex",
"phone": "Корректный телефон",
"rank_all_options": "Ранжируйте все опции",
"select_file_extensions": "Выберите расширения файлов...",
"select_option": "Выберите вариант",
"start_date": "Дата начала",
"url": "Корректный URL"
},
"validation_logic_and": "Все условия выполняются",
"validation_logic_or": "выполняется хотя бы одно условие",
"validation_rules": "Правила валидации",
"validation_rules_description": "Принимать только ответы, соответствующие следующим критериям",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} используется в логике вопроса {questionIndex}. Пожалуйста, сначала удалите его из логики.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Переменная «{variableName}» используется в квоте «{quotaName}»",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Переменная «{variableName}» используется в квоте «{quotaName}». Сначала удалите её из квоты.",
"variable_name_conflicts_with_hidden_field": "Имя переменной конфликтует с существующим ID скрытого поля.",
"variable_name_is_already_taken_please_choose_another": "Это имя переменной уже занято, выберите другое.",
"variable_name_must_start_with_a_letter": "Имя переменной должно начинаться с буквы.",
"variable_used_in_recall": "Переменная «{variable}» используется в вопросе {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Произошла ошибка при загрузке ответов",
"first_name": "Имя",
"how_to_identify_users": "Как идентифицировать пользователей",
"ip_address": "IP-адрес",
"last_name": "Фамилия",
"not_completed": "Не завершено ⏳",
"os": "ОС",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Добавить цвет фона",
"add_background_color_description": "Добавьте цвет фона для контейнера с логотипом.",
"advanced_styling_field_border_radius": "Радиус скругления",
"advanced_styling_field_button_bg": "Фон кнопки",
"advanced_styling_field_button_bg_description": "Заполняет кнопку «Далее» / «Отправить».",
"advanced_styling_field_button_border_radius_description": "Скругляет углы кнопки.",
"advanced_styling_field_button_font_size_description": "Масштабирует текст на кнопке.",
"advanced_styling_field_button_font_weight_description": "Делает текст на кнопке тоньше или жирнее.",
"advanced_styling_field_button_height_description": "Определяет высоту кнопки.",
"advanced_styling_field_button_padding_x_description": "Добавляет отступы слева и справа.",
"advanced_styling_field_button_padding_y_description": "Добавляет отступы сверху и снизу.",
"advanced_styling_field_button_text": "Текст кнопки",
"advanced_styling_field_button_text_description": "Задаёт цвет текста на кнопках.",
"advanced_styling_field_description_color": "Цвет описания",
"advanced_styling_field_description_color_description": "Задаёт цвет текста под каждым заголовком.",
"advanced_styling_field_description_size": "Размер шрифта описания",
"advanced_styling_field_description_size_description": "Масштабирует текст описания.",
"advanced_styling_field_description_weight": "Толщина шрифта описания",
"advanced_styling_field_description_weight_description": "Делает текст описания тоньше или жирнее.",
"advanced_styling_field_font_size": "Размер шрифта",
"advanced_styling_field_font_weight": "Толщина шрифта",
"advanced_styling_field_headline_color": "Цвет заголовка",
"advanced_styling_field_headline_color_description": "Задаёт цвет основного текста вопроса.",
"advanced_styling_field_headline_size": "Размер шрифта заголовка",
"advanced_styling_field_headline_size_description": "Масштабирует текст заголовка.",
"advanced_styling_field_headline_weight": "Толщина шрифта заголовка",
"advanced_styling_field_headline_weight_description": "Делает текст заголовка тоньше или жирнее.",
"advanced_styling_field_height": "Высота",
"advanced_styling_field_indicator_bg": "Фон индикатора",
"advanced_styling_field_indicator_bg_description": "Задаёт цвет заполненной части полосы.",
"advanced_styling_field_input_border_radius_description": "Скругляет углы полей ввода.",
"advanced_styling_field_input_font_size_description": "Масштабирует введённый текст в полях ввода.",
"advanced_styling_field_input_height_description": "Определяет высоту поля ввода.",
"advanced_styling_field_input_padding_x_description": "Добавляет отступы слева и справа.",
"advanced_styling_field_input_padding_y_description": "Добавляет пространство сверху и снизу.",
"advanced_styling_field_input_placeholder_opacity_description": "Делает текст подсказки менее заметным.",
"advanced_styling_field_input_shadow_description": "Добавляет тень вокруг полей ввода.",
"advanced_styling_field_input_text": "Текст ввода",
"advanced_styling_field_input_text_description": "Задаёт цвет введённого текста в полях.",
"advanced_styling_field_option_bg": "Фон",
"advanced_styling_field_option_bg_description": "Заливает фон элементов опций.",
"advanced_styling_field_option_border_radius_description": "Скругляет углы опций.",
"advanced_styling_field_option_font_size_description": "Изменяет размер текста метки опции.",
"advanced_styling_field_option_label": "Цвет метки",
"advanced_styling_field_option_label_description": "Задаёт цвет текста метки опции.",
"advanced_styling_field_option_padding_x_description": "Добавляет пространство слева и справа.",
"advanced_styling_field_option_padding_y_description": "Добавляет пространство сверху и снизу.",
"advanced_styling_field_padding_x": "Внутренний отступ по X",
"advanced_styling_field_padding_y": "Внутренний отступ по Y",
"advanced_styling_field_placeholder_opacity": "Прозрачность плейсхолдера",
"advanced_styling_field_shadow": "Тень",
"advanced_styling_field_track_bg": "Фон трека",
"advanced_styling_field_track_bg_description": "Задаёт цвет незаполненной части полосы.",
"advanced_styling_field_track_height": "Высота трека",
"advanced_styling_field_track_height_description": "Управляет толщиной индикатора прогресса.",
"advanced_styling_field_upper_label_color": "Цвет метки заголовка",
"advanced_styling_field_upper_label_color_description": "Задаёт цвет маленькой метки над полями ввода.",
"advanced_styling_field_upper_label_size": "Размер шрифта метки заголовка",
"advanced_styling_field_upper_label_size_description": "Изменяет размер маленькой метки над полями ввода.",
"advanced_styling_field_upper_label_weight": "Толщина шрифта метки заголовка",
"advanced_styling_field_upper_label_weight_description": "Делает метку тоньше или жирнее.",
"advanced_styling_section_buttons": "Кнопки",
"advanced_styling_section_headlines": "Заголовки и описания",
"advanced_styling_section_inputs": "Поля ввода",
"advanced_styling_section_options": "Опции (радио/чекбокс)",
"app_survey_placement": "Размещение опроса в приложении",
"app_survey_placement_settings_description": "Измените, где будут отображаться опросы в вашем веб-приложении или на сайте.",
"centered_modal_overlay_color": "Цвет оверлея центрированного модального окна",
"email_customization": "Настройка email",
"email_customization_description": "Измените внешний вид писем, которые Formbricks отправляет от вашего имени.",
"enable_custom_styling": "Включить пользовательское оформление",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Брендинг Formbricks скрыт.",
"formbricks_branding_settings_description": "Мы ценим вашу поддержку, но понимаем, если вы захотите отключить это.",
"formbricks_branding_shown": "Брендинг Formbricks отображается.",
"generate_theme_btn": "Сгенерировать",
"generate_theme_confirmation": "Сгенерировать подходящую цветовую тему на основе цвета твоего бренда? Это перезапишет текущие цветовые настройки.",
"generate_theme_header": "Сгенерировать цветовую тему?",
"logo_removed_successfully": "Логотип успешно удалён",
"logo_settings_description": "Загрузите логотип вашей компании для брендирования опросов и предпросмотра ссылок.",
"logo_updated_successfully": "Логотип успешно обновлён",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Показывать брендинг Formbricks в опросах типа {type}",
"show_powered_by_formbricks": "Показывать подпись «Работает на Formbricks»",
"styling_updated_successfully": "Стили успешно обновлены",
"suggest_colors": "Предложить цвета",
"theme": "Тема",
"theme_settings_description": "Создайте стиль для всех опросов. Вы можете включить индивидуальное оформление для каждого опроса."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Да, держите меня в курсе.",
"preview_survey_question_2_choice_2_label": "Нет, спасибо!",
"preview_survey_question_2_headline": "Хотите быть в курсе событий?",
"preview_survey_question_2_subheader": "Это пример описания.",
"preview_survey_welcome_card_headline": "Добро пожаловать!",
"prioritize_features_description": "Определите, какие функции наиболее и наименее важны для ваших пользователей.",
"prioritize_features_name": "Приоритизация функций",
+173 -38
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "Blandning av stora och små bokstäver",
"please_verify_captcha": "Vänligen verifiera reCAPTCHA",
"privacy_policy": "Integritetspolicy",
"product_updates_description": "Månatliga produktnyheter och funktionsuppdateringar. Integritetspolicyn gäller.",
"product_updates_title": "Produktuppdateringar",
"security_updates_description": "Endast säkerhetsrelaterad information. Integritetspolicyn gäller.",
"security_updates_title": "Säkerhetsuppdateringar",
"terms_of_service": "Användarvillkor",
"title": "Skapa ditt Formbricks-konto"
},
@@ -197,6 +201,7 @@
"docs": "Dokumentation",
"documentation": "Dokumentation",
"domain": "Domän",
"done": "Klar",
"download": "Ladda ner",
"draft": "Utkast",
"duplicate": "Duplicera",
@@ -238,7 +243,6 @@
"imprint": "Impressum",
"in_progress": "Pågående",
"inactive_surveys": "Inaktiva enkäter",
"input_type": "Inmatningstyp",
"integration": "integration",
"integrations": "Integrationer",
"invalid_date": "Ogiltigt datum",
@@ -250,6 +254,7 @@
"label": "Etikett",
"language": "Språk",
"learn_more": "Läs mer",
"license_expired": "License Expired",
"light_overlay": "Ljust överlägg",
"limits_reached": "Gränser nådda",
"link": "Länk",
@@ -262,13 +267,11 @@
"look_and_feel": "Utseende",
"manage": "Hantera",
"marketing": "Marknadsföring",
"maximum": "Maximum",
"member": "Medlem",
"members": "Medlemmar",
"members_and_teams": "Medlemmar och team",
"membership_not_found": "Medlemskap hittades inte",
"metadata": "Metadata",
"minimum": "Minimum",
"mobile_overlay_app_works_best_on_desktop": "Formbricks fungerar bäst på en större skärm. Byt till en annan enhet för att hantera eller bygga enkäter.",
"mobile_overlay_surveys_look_good": "Oroa dig inte dina enkäter ser bra ut på alla enheter och skärmstorlekar!",
"mobile_overlay_title": "Hoppsan, liten skärm upptäckt!",
@@ -282,6 +285,7 @@
"no_background_image_found": "Ingen bakgrundsbild hittades.",
"no_code": "Ingen kod",
"no_files_uploaded": "Inga filer laddades upp",
"no_overlay": "Ingen overlay",
"no_quotas_found": "Inga kvoter hittades",
"no_result_found": "Inget resultat hittades",
"no_results": "Inga resultat",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "Organisationsteam hittades inte",
"other": "Annat",
"others": "Andra",
"overlay_color": "Overlay-färg",
"overview": "Översikt",
"password": "Lösenord",
"paused": "Pausad",
@@ -321,7 +326,7 @@
"placeholder": "Platshållare",
"please_select_at_least_one_survey": "Vänligen välj minst en enkät",
"please_select_at_least_one_trigger": "Vänligen välj minst en utlösare",
"please_upgrade_your_plan": "Vänligen uppgradera din plan.",
"please_upgrade_your_plan": "Vänligen uppgradera din plan",
"preview": "Förhandsgranska",
"preview_survey": "Förhandsgranska enkät",
"privacy": "Integritetspolicy",
@@ -347,6 +352,7 @@
"request_trial_license": "Begär provlicens",
"reset_to_default": "Återställ till standard",
"response": "Svar",
"response_id": "Svar-ID",
"responses": "Svar",
"restart": "Starta om",
"role": "Roll",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "Du har nått din gräns på {projectLimit} arbetsytor.",
"you_have_reached_your_monthly_miu_limit_of": "Du har nått din månatliga MIU-gräns på",
"you_have_reached_your_monthly_response_limit_of": "Du har nått din månatliga svarsgräns på",
"you_will_be_downgraded_to_the_community_edition_on_date": "Du kommer att nedgraderas till Community Edition den {date}."
"you_will_be_downgraded_to_the_community_edition_on_date": "Du kommer att nedgraderas till Community Edition den {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "Acceptera",
@@ -783,20 +790,26 @@
"add_webhook": "Lägg till webhook",
"add_webhook_description": "Skicka enkätsvardata till en anpassad endpoint",
"all_current_and_new_surveys": "Alla nuvarande och nya enkäter",
"copy_secret_now": "Kopiera din signeringsnyckel",
"created_by_third_party": "Skapad av tredje part",
"discord_webhook_not_supported": "Discord-webhooks stöds för närvarande inte.",
"empty_webhook_message": "Dina webhooks visas här så snart du lägger till dem. ⏲️",
"endpoint_pinged": "Ja! Vi kan nå webhooken!",
"endpoint_pinged_error": "Kunde inte nå webhooken!",
"learn_to_verify": "Lär dig hur du verifierar webhook-signaturer",
"please_check_console": "Vänligen kontrollera konsolen för mer information",
"please_enter_a_url": "Vänligen ange en URL",
"response_created": "Svar skapat",
"response_finished": "Svar slutfört",
"response_updated": "Svar uppdaterat",
"secret_copy_warning": "Förvara denna nyckel säkert. Du kan visa den igen i webhook-inställningarna.",
"secret_description": "Använd denna nyckel för att verifiera webhook-förfrågningar. Se dokumentationen för signaturverifiering.",
"signing_secret": "Signeringsnyckel",
"source": "Källa",
"test_endpoint": "Testa endpoint",
"triggers": "Utlösare",
"webhook_added_successfully": "Webhook tillagd",
"webhook_created": "Webhook skapad",
"webhook_delete_confirmation": "Är du säker på att du vill ta bort denna webhook? Detta kommer att stoppa alla ytterligare notifieringar.",
"webhook_deleted_successfully": "Webhook borttagen",
"webhook_name_placeholder": "Valfritt: Namnge din webhook för enkel identifiering",
@@ -943,19 +956,32 @@
"enterprise_features": "Enterprise-funktioner",
"get_an_enterprise_license_to_get_access_to_all_features": "Skaffa en Enterprise-licens för att få tillgång till alla funktioner.",
"keep_full_control_over_your_data_privacy_and_security": "Behåll full kontroll över din datasekretess och säkerhet.",
"license_invalid_description": "Licensnyckeln i din ENTERPRISE_LICENSE_KEY-miljövariabel är ogiltig. Kontrollera om det finns stavfel eller begär en ny nyckel.",
"license_status": "Licensstatus",
"license_status_active": "Aktiv",
"license_status_description": "Status för din företagslicens.",
"license_status_expired": "Utgången",
"license_status_invalid": "Ogiltig licens",
"license_status_unreachable": "Otillgänglig",
"license_unreachable_grace_period": "Licensservern kan inte nås. Dina enterprise-funktioner är aktiva under en 3-dagars respitperiod som slutar {gracePeriodEnd}.",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "Inget samtal behövs, inga åtaganden: Begär en gratis 30-dagars provlicens för att testa alla funktioner genom att fylla i detta formulär:",
"no_credit_card_no_sales_call_just_test_it": "Inget kreditkort. Inget säljsamtal. Testa bara :)",
"on_request": "På begäran",
"organization_roles": "Organisationsroller (Admin, Redaktör, Utvecklare, etc.)",
"questions_please_reach_out_to": "Frågor? Kontakta",
"recheck_license": "Kontrollera licensen igen",
"recheck_license_failed": "Licenskontrollen misslyckades. Licensservern kan vara otillgänglig.",
"recheck_license_invalid": "Licensnyckeln är ogiltig. Kontrollera din ENTERPRISE_LICENSE_KEY.",
"recheck_license_success": "Licenskontrollen lyckades",
"recheck_license_unreachable": "Licensservern är otillgänglig. Försök igen senare.",
"rechecking": "Kontrollerar igen...",
"request_30_day_trial_license": "Begär 30-dagars provlicens",
"saml_sso": "SAML SSO",
"service_level_agreement": "Servicenivåavtal",
"soc2_hipaa_iso_27001_compliance_check": "SOC2, HIPAA, ISO 27001 efterlevnadskontroll",
"sso": "SSO (Google, Microsoft, OpenID Connect)",
"teams": "Team och åtkomstroller (Läs, Läs och skriv, Hantera)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar.",
"your_enterprise_license_is_active_all_features_unlocked": "Din Enterprise-licens är aktiv. Alla funktioner upplåsta."
"unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar."
},
"general": {
"bulk_invite_warning_description": "På gratisplanen tilldelas alla organisationsmedlemmar alltid rollen \"Ägare\".",
@@ -979,7 +1005,7 @@
"from_your_organization": "från din organisation",
"invitation_sent_once_more": "Inbjudan skickad igen.",
"invite_deleted_successfully": "Inbjudan borttagen",
"invited_on": "Inbjuden den {date}",
"invite_expires_on": "Inbjudan går ut den {date}",
"invites_failed": "Inbjudningar misslyckades",
"leave_organization": "Lämna organisation",
"leave_organization_description": "Du kommer att lämna denna organisation och förlora åtkomst till alla enkäter och svar. Du kan endast återansluta om du blir inbjuden igen.",
@@ -1008,6 +1034,8 @@
"remove_logo": "Ta bort logotyp",
"replace_logo": "Ersätt logotyp",
"resend_invitation_email": "Skicka inbjudningsmejl igen",
"security_list_tip": "Är du med på vår säkerhetslista? Håll dig informerad för att skydda din instans!",
"security_list_tip_link": "Registrera dig här.",
"share_invite_link": "Dela inbjudningslänk",
"share_this_link_to_let_your_organization_member_join_your_organization": "Dela denna länk för att låta din organisationsmedlem gå med i din organisation:",
"test_email_sent_successfully": "Test-e-post skickat",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "Fyll i alla fält för att lägga till en ny arbetsyta.",
"read": "Läs",
"read_write": "Läs och skriv",
"select_member": "Välj medlem",
"select_workspace": "Välj arbetsyta",
"team_admin": "Teamadministratör",
"team_created_successfully": "Team skapat.",
"team_deleted_successfully": "Team borttaget.",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "Lägg till en platshållare att visa om det inte finns något värde att återkalla.",
"add_hidden_field_id": "Lägg till dolt fält-ID",
"add_highlight_border": "Lägg till markerad kant",
"add_highlight_border_description": "Lägg till en yttre kant till ditt enkätkort.",
"add_logic": "Lägg till logik",
"add_none_of_the_above": "Lägg till \"Inget av ovanstående\"",
"add_option": "Lägg till alternativ",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "Ändra meddelandet besökare ser när enkäten är stängd.",
"adjust_the_theme_in_the": "Justera temat i",
"all_other_answers_will_continue_to": "Alla andra svar fortsätter till",
"allow_file_type": "Tillåt filtyp",
"allow_multi_select": "Tillåt flerval",
"allow_multiple_files": "Tillåt flera filer",
"allow_users_to_select_more_than_one_image": "Tillåt användare att välja mer än en bild",
@@ -1169,6 +1193,9 @@
"assign": "Tilldela =",
"audience": "Målgrupp",
"auto_close_on_inactivity": "Stäng automatiskt vid inaktivitet",
"auto_save_disabled": "Automatisk sparning inaktiverad",
"auto_save_disabled_tooltip": "Din enkät sparas endast automatiskt när den är ett utkast. Detta säkerställer att publika enkäter inte uppdateras oavsiktligt.",
"auto_save_on": "Automatisk sparning på",
"automatically_close_survey_after": "Stäng enkäten automatiskt efter",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Stäng enkäten automatiskt efter ett visst antal svar.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Stäng enkäten automatiskt om användaren inte svarar efter ett visst antal sekunder.",
@@ -1178,6 +1205,7 @@
"block_duplicated": "Block duplicerat.",
"bold": "Fet",
"brand_color": "Varumärkesfärg",
"brand_color_description": "Används för knappar, länkar och markeringar.",
"brightness": "Ljusstyrka",
"bulk_edit": "Massredigera",
"bulk_edit_description": "Redigera alla alternativ nedan, ett per rad. Tomma rader kommer att hoppas över och dubbletter tas bort.",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.com-användarnamn eller användarnamn/händelse",
"calculate": "Beräkna",
"capture_a_new_action_to_trigger_a_survey_on": "Fånga en ny åtgärd att utlösa en enkät på.",
"capture_ip_address": "Registrera IP-adress",
"capture_ip_address_description": "Spara respondentens IP-adress i svarsmetadatan för att upptäcka dubbletter och av säkerhetsskäl",
"capture_new_action": "Fånga ny åtgärd",
"card_arrangement_for_survey_type_derived": "Kortarrangemang för {surveyTypeDerived}-enkäter",
"card_background_color": "Kortets bakgrundsfärg",
"card_background_color_description": "Fyller enkätkortets yta.",
"card_border_color": "Kortets kantfärg",
"card_border_color_description": "Markerar enkätkortets kant.",
"card_styling": "Kortstil",
"casual": "Avslappnad",
"caution_edit_duplicate": "Duplicera och redigera",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "Äldre och nyare svar blandas vilket kan leda till vilseledande datasammanfattningar.",
"caution_recommendation": "Detta kan orsaka datainkonsekvenser i enkätsammanfattningen. Vi rekommenderar att duplicera enkäten istället.",
"caution_text": "Ändringar kommer att leda till inkonsekvenser",
"centered_modal_overlay_color": "Centrerad modal överläggsfärg",
"change_anyway": "Ändra ändå",
"change_background": "Ändra bakgrund",
"change_question_type": "Ändra frågetyp",
"change_survey_type": "Byte av enkättyp påverkar befintlig åtkomst",
"change_the_background_color_of_the_card": "Ändra kortets bakgrundsfärg.",
"change_the_background_color_of_the_input_fields": "Ändra inmatningsfältens bakgrundsfärg.",
"change_the_background_to_a_color_image_or_animation": "Ändra bakgrunden till en färg, bild eller animering.",
"change_the_border_color_of_the_card": "Ändra kortets kantfärg.",
"change_the_border_color_of_the_input_fields": "Ändra inmatningsfältens kantfärg.",
"change_the_border_radius_of_the_card_and_the_inputs": "Ändra kantradie för kortet och inmatningsfälten.",
"change_the_brand_color_of_the_survey": "Ändra enkätens varumärkesfärg.",
"change_the_placement_of_this_survey": "Ändra placeringen av denna enkät.",
"change_the_question_color_of_the_survey": "Ändra enkätens frågefärg.",
"changes_saved": "Ändringar sparade.",
"changing_survey_type_will_remove_existing_distribution_channels": "Att ändra enkättypen påverkar hur den kan delas. Om respondenter redan har åtkomstlänkar för den nuvarande typen kan de förlora åtkomst efter bytet.",
"character_limit_toggle_description": "Begränsa hur kort eller långt ett svar kan vara.",
"character_limit_toggle_title": "Lägg till teckengränser",
"checkbox_label": "Kryssruteetikett",
"choose_the_actions_which_trigger_the_survey": "Välj de åtgärder som utlöser enkäten.",
"choose_the_first_question_on_your_block": "Välj den första frågan i ditt block",
@@ -1241,7 +1263,6 @@
"contact_fields": "Kontaktfält",
"contains": "Innehåller",
"continue_to_settings": "Fortsätt till inställningar",
"control_which_file_types_can_be_uploaded": "Kontrollera vilka filtyper som kan laddas upp.",
"convert_to_multiple_choice": "Konvertera till flerval",
"convert_to_single_choice": "Konvertera till enkelval",
"country": "Land",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "Gör bakgrunden mörkare eller ljusare efter eget val.",
"date_format": "Datumformat",
"days_before_showing_this_survey_again": "eller fler dagar måste gå mellan den senaste visade enkäten och att visa denna enkät.",
"delete_anyways": "Ta bort ändå",
"delete_block": "Ta bort block",
"delete_choice": "Ta bort val",
"disable_the_visibility_of_survey_progress": "Inaktivera synligheten av enkätens framsteg.",
"display_an_estimate_of_completion_time_for_survey": "Visa en uppskattning av tid för att slutföra enkäten",
"display_number_of_responses_for_survey": "Visa antal svar för enkäten",
"display_type": "Visningstyp",
"divide": "Dividera /",
"does_not_contain": "Innehåller inte",
"does_not_end_with": "Slutar inte med",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "Inkluderar inte alla av",
"does_not_include_one_of": "Inkluderar inte en av",
"does_not_start_with": "Börjar inte med",
"dropdown": "Rullgardinsmeny",
"duplicate_block": "Duplicera block",
"duplicate_question": "Duplicera fråga",
"edit_link": "Redigera länk",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "Dölj framstegsindikator",
"hide_question_settings": "Dölj frågeinställningar",
"hostname": "Värdnamn",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "Hur coola vill du att dina kort ska vara i {surveyTypeDerived}-enkäter",
"if_you_need_more_please": "Om du behöver fler, vänligen",
"if_you_need_more_please": "Om du behöver mer, vänligen",
"if_you_really_want_that_answer_ask_until_you_get_it": "Fortsätt visa när villkoren är uppfyllda tills ett svar skickas in.",
"ignore_global_waiting_time": "Ignorera väntetid",
"ignore_global_waiting_time_description": "Denna enkät kan visas när dess villkor är uppfyllda, även om en annan enkät nyligen visats.",
@@ -1368,7 +1391,9 @@
"initial_value": "Initialt värde",
"inner_text": "Inre text",
"input_border_color": "Inmatningsfältets kantfärg",
"input_border_color_description": "Markerar kanten på textfält och textområden.",
"input_color": "Inmatningsfärg",
"input_color_description": "Fyller insidan av textfält.",
"insert_link": "Infoga länk",
"invalid_targeting": "Ogiltig målgruppsinriktning: Vänligen kontrollera dina målgruppsfilter",
"invalid_video_url_warning": "Vänligen ange en giltig YouTube-, Vimeo- eller Loom-URL. Vi stöder för närvarande inte andra videohostingleverantörer.",
@@ -1395,10 +1420,10 @@
"key": "Nyckel",
"last_name": "Efternamn",
"let_people_upload_up_to_25_files_at_the_same_time": "Låt personer ladda upp upp till 25 filer samtidigt.",
"limit_file_types": "Begränsa filtyper",
"limit_the_maximum_file_size": "Begränsa maximal filstorlek",
"limit_upload_file_size_to": "Begränsa uppladdningsfilstorlek till",
"limit_the_maximum_file_size": "Begränsa den maximala filstorleken för uppladdningar.",
"limit_upload_file_size_to": "Begränsa uppladdad filstorlek till",
"link_survey_description": "Dela en länk till en enkätsida eller bädda in den på en webbsida eller i e-post.",
"list": "Lista",
"load_segment": "Ladda segment",
"logic_error_warning": "Ändring kommer att orsaka logikfel",
"logic_error_warning_text": "Att ändra frågetypen kommer att ta bort logikvillkoren från denna fråga",
@@ -1410,7 +1435,7 @@
"matrix_all_fields": "Alla fält",
"matrix_rows": "Rader",
"max_file_size": "Max filstorlek",
"max_file_size_limit_is": "Maxgräns för filstorlek är",
"max_file_size_limit_is": "Maximal filstorleksgräns är",
"move_question_to_block": "Flytta fråga till block",
"multiply": "Multiplicera *",
"needed_for_self_hosted_cal_com_instance": "Behövs för en självhostad Cal.com-instans",
@@ -1442,22 +1467,22 @@
"picture_idx": "Bild {idx}",
"pin_can_only_contain_numbers": "PIN kan endast innehålla siffror.",
"pin_must_be_a_four_digit_number": "PIN måste vara ett fyrsiffrigt nummer.",
"please_enter_a_file_extension": "Vänligen ange en filändelse.",
"please_enter_a_valid_url": "Vänligen ange en giltig URL (t.ex. https://example.com)",
"please_set_a_survey_trigger": "Vänligen ställ in en enkätutlösare",
"please_specify": "Vänligen specificera",
"prevent_double_submission": "Förhindra dubbelinskickning",
"prevent_double_submission_description": "Tillåt endast 1 svar per e-postadress",
"progress_saved": "Framsteg sparade",
"protect_survey_with_pin": "Skydda enkäten med en PIN",
"protect_survey_with_pin_description": "Endast användare som har PIN-koden kan komma åt enkäten.",
"publish": "Publicera",
"question": "Fråga",
"question_color": "Frågefärg",
"question_deleted": "Fråga borttagen.",
"question_duplicated": "Fråga duplicerad.",
"question_id_updated": "Fråge-ID uppdaterat",
"question_used_in_logic": "Denna fråga används i logiken för fråga {questionIndex}.",
"question_used_in_quota": "Denna fråga används i kvoten \"{quotaName}\"",
"question_used_in_logic_warning_text": "Element från det här blocket används i en logikregel. Är du säker på att du vill ta bort det?",
"question_used_in_logic_warning_title": "Logikkonflikt",
"question_used_in_quota": "Denna fråga används i kvoten “{quotaName}”",
"question_used_in_recall": "Denna fråga återkallas i fråga {questionIndex}.",
"question_used_in_recall_ending_card": "Denna fråga återkallas i avslutningskortet",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "Svarsgränser, omdirigeringar och mer.",
"response_options": "Svarsalternativ",
"roundness": "Rundhet",
"roundness_description": "Styr hur rundade kortets hörn är.",
"row_used_in_logic_error": "Denna rad används i logiken för fråga {questionIndex}. Vänligen ta bort den från logiken först.",
"rows": "Rader",
"save_and_close": "Spara och stäng",
@@ -1520,6 +1546,7 @@
"search_for_images": "Sök efter bilder",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "sekunder efter utlösning stängs enkäten om inget svar",
"seconds_before_showing_the_survey": "sekunder innan enkäten visas.",
"select_field": "Välj fält",
"select_or_type_value": "Välj eller skriv värde",
"select_ordering": "Välj ordning",
"select_saved_action": "Välj sparad åtgärd",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "Styling inställd på temastil",
"subheading": "Underrubrik",
"subtract": "Subtrahera -",
"suggest_colors": "Föreslå färger",
"survey_completed_heading": "Enkät slutförd",
"survey_completed_subheading": "Denna gratis och öppenkällkodsenkät har stängts",
"survey_display_settings": "Visningsinställningar för enkät",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "Visa en enda gång, även om de inte svarar.",
"then": "Sedan",
"this_action_will_remove_all_the_translations_from_this_survey": "Denna åtgärd kommer att ta bort alla översättningar från denna enkät.",
"this_extension_is_already_added": "Denna filändelse är redan tillagd.",
"this_file_type_is_not_supported": "Denna filtyp stöds inte.",
"three_points": "3 poäng",
"times": "gånger",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "För att hålla placeringen konsekvent över alla enkäter kan du",
@@ -1589,8 +1613,51 @@
"upper_label": "Övre etikett",
"url_filters": "URL-filter",
"url_not_supported": "URL stöds inte",
"validation": {
"add_validation_rule": "Lägg till valideringsregel",
"answer_all_rows": "Svara på alla rader",
"characters": "Tecken",
"contains": "Innehåller",
"delete_validation_rule": "Ta bort valideringsregel",
"does_not_contain": "Innehåller inte",
"email": "Är en giltig e-postadress",
"end_date": "Slutdatum",
"file_extension_is": "Filändelsen är",
"file_extension_is_not": "Filändelsen är inte",
"is": "Är",
"is_between": "Är mellan",
"is_earlier_than": "Är tidigare än",
"is_greater_than": "Är större än",
"is_later_than": "Är senare än",
"is_less_than": "Är mindre än",
"is_not": "Är inte",
"is_not_between": "Är inte mellan",
"kb": "KB",
"max_length": "Högst",
"max_selections": "Högst",
"max_value": "Högst",
"mb": "MB",
"min_length": "Minst",
"min_selections": "Minst",
"min_value": "Minst",
"minimum_options_ranked": "Minsta antal rangordnade alternativ",
"minimum_rows_answered": "Minsta antal besvarade rader",
"options_selected": "Valda alternativ",
"pattern": "Matchar regexmönster",
"phone": "Är ett giltigt telefonnummer",
"rank_all_options": "Rangordna alla alternativ",
"select_file_extensions": "Välj filändelser...",
"select_option": "Välj alternativ",
"start_date": "Startdatum",
"url": "Är en giltig URL"
},
"validation_logic_and": "Alla är sanna",
"validation_logic_or": "någon är sann",
"validation_rules": "Valideringsregler",
"validation_rules_description": "Acceptera endast svar som uppfyller följande kriterier",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} används i logiken för fråga {questionIndex}. Vänligen ta bort den från logiken först.",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabel \"{variableName}\" används i kvoten \"{quotaName}\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "Variabeln “{variableName} används i kvoten {quotaName}",
"variable_name_conflicts_with_hidden_field": "Variabelnamnet krockar med ett befintligt dolt fält-ID.",
"variable_name_is_already_taken_please_choose_another": "Variabelnamnet är redan taget, vänligen välj ett annat.",
"variable_name_must_start_with_a_letter": "Variabelnamnet måste börja med en bokstav.",
"variable_used_in_recall": "Variabel \"{variable}\" återkallas i fråga {questionIndex}.",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "Ett fel uppstod vid nedladdning av svar",
"first_name": "Förnamn",
"how_to_identify_users": "Hur man identifierar användare",
"ip_address": "IP-adress",
"last_name": "Efternamn",
"not_completed": "Inte slutförd ⏳",
"os": "OS",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "Lägg till bakgrundsfärg",
"add_background_color_description": "Lägg till en bakgrundsfärg i logobehållaren.",
"advanced_styling_field_border_radius": "Hörnradie",
"advanced_styling_field_button_bg": "Knappens bakgrund",
"advanced_styling_field_button_bg_description": "Fyller Nästa / Skicka-knappen.",
"advanced_styling_field_button_border_radius_description": "Rundar av knappens hörn.",
"advanced_styling_field_button_font_size_description": "Ändrar storleken på knappens text.",
"advanced_styling_field_button_font_weight_description": "Gör knapptexten tunnare eller fetare.",
"advanced_styling_field_button_height_description": "Styr knappens höjd.",
"advanced_styling_field_button_padding_x_description": "Lägger till utrymme till vänster och höger.",
"advanced_styling_field_button_padding_y_description": "Lägger till utrymme upptill och nedtill.",
"advanced_styling_field_button_text": "Knapptext",
"advanced_styling_field_button_text_description": "Färglägger texten i knappar.",
"advanced_styling_field_description_color": "Beskrivningsfärg",
"advanced_styling_field_description_color_description": "Färglägger texten under varje rubrik.",
"advanced_styling_field_description_size": "Beskrivningens teckenstorlek",
"advanced_styling_field_description_size_description": "Ändrar storleken på beskrivningstexten.",
"advanced_styling_field_description_weight": "Beskrivningens teckentjocklek",
"advanced_styling_field_description_weight_description": "Gör beskrivningstexten tunnare eller fetare.",
"advanced_styling_field_font_size": "Teckenstorlek",
"advanced_styling_field_font_weight": "Teckentjocklek",
"advanced_styling_field_headline_color": "Rubrikfärg",
"advanced_styling_field_headline_color_description": "Färglägger huvudfrågan.",
"advanced_styling_field_headline_size": "Rubrikens teckenstorlek",
"advanced_styling_field_headline_size_description": "Ändrar storleken på rubriken.",
"advanced_styling_field_headline_weight": "Rubrikens teckentjocklek",
"advanced_styling_field_headline_weight_description": "Gör rubriktexten tunnare eller fetare.",
"advanced_styling_field_height": "Höjd",
"advanced_styling_field_indicator_bg": "Indikatorns bakgrund",
"advanced_styling_field_indicator_bg_description": "Färglägger den fyllda delen av stapeln.",
"advanced_styling_field_input_border_radius_description": "Rundar av hörnen på inmatningsfält.",
"advanced_styling_field_input_font_size_description": "Ändrar storleken på texten i inmatningsfält.",
"advanced_styling_field_input_height_description": "Styr höjden på inmatningsfältet.",
"advanced_styling_field_input_padding_x_description": "Lägger till utrymme till vänster och höger.",
"advanced_styling_field_input_padding_y_description": "Lägger till utrymme upptill och nedtill.",
"advanced_styling_field_input_placeholder_opacity_description": "Tonar ut platshållartexten.",
"advanced_styling_field_input_shadow_description": "Lägger till en skugga runt inmatningsfälten.",
"advanced_styling_field_input_text": "Inmatningstext",
"advanced_styling_field_input_text_description": "Färgar den inmatade texten i fälten.",
"advanced_styling_field_option_bg": "Bakgrund",
"advanced_styling_field_option_bg_description": "Fyller alternativraderna.",
"advanced_styling_field_option_border_radius_description": "Rundar hörnen på alternativen.",
"advanced_styling_field_option_font_size_description": "Skalar textstorleken på alternativetiketten.",
"advanced_styling_field_option_label": "Etikettfärg",
"advanced_styling_field_option_label_description": "Färgar texten på alternativetiketten.",
"advanced_styling_field_option_padding_x_description": "Lägger till utrymme till vänster och höger.",
"advanced_styling_field_option_padding_y_description": "Lägger till utrymme upptill och nedtill.",
"advanced_styling_field_padding_x": "Horisontell padding",
"advanced_styling_field_padding_y": "Vertikal padding",
"advanced_styling_field_placeholder_opacity": "Platshållarens opacitet",
"advanced_styling_field_shadow": "Skugga",
"advanced_styling_field_track_bg": "Spårets bakgrund",
"advanced_styling_field_track_bg_description": "Färgar den ofyllda delen av stapeln.",
"advanced_styling_field_track_height": "Spårets höjd",
"advanced_styling_field_track_height_description": "Styr tjockleken på förloppsstapeln.",
"advanced_styling_field_upper_label_color": "Rubriketikettens färg",
"advanced_styling_field_upper_label_color_description": "Färgar den lilla etiketten ovanför fälten.",
"advanced_styling_field_upper_label_size": "Rubriketikettens teckenstorlek",
"advanced_styling_field_upper_label_size_description": "Skalar storleken på den lilla etiketten ovanför fälten.",
"advanced_styling_field_upper_label_weight": "Rubriketikettens teckentjocklek",
"advanced_styling_field_upper_label_weight_description": "Gör etiketten tunnare eller fetare.",
"advanced_styling_section_buttons": "Knappar",
"advanced_styling_section_headlines": "Rubriker & beskrivningar",
"advanced_styling_section_inputs": "Inmatningar",
"advanced_styling_section_options": "Alternativ (Radio/Checkbox)",
"app_survey_placement": "App-enkätplacering",
"app_survey_placement_settings_description": "Ändra var enkäter visas i din webbapp eller på din webbplats.",
"centered_modal_overlay_color": "Centrerad modal överläggsfärg",
"email_customization": "E-postanpassning",
"email_customization_description": "Ändra utseendet på de e-postmeddelanden som Formbricks skickar åt dig.",
"enable_custom_styling": "Aktivera anpassad styling",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricks-varumärket är dolt.",
"formbricks_branding_settings_description": "Vi uppskattar ditt stöd men förstår om du vill stänga av det.",
"formbricks_branding_shown": "Formbricks-varumärket visas.",
"generate_theme_btn": "Generera",
"generate_theme_confirmation": "Vill du generera ett matchande färgtema baserat på din varumärkesfärg? Detta kommer att skriva över dina nuvarande färginställningar.",
"generate_theme_header": "Generera färgtema?",
"logo_removed_successfully": "Logotyp borttagen",
"logo_settings_description": "Ladda upp företagets logotyp för att profilera enkäter och länkförhandsvisningar.",
"logo_updated_successfully": "Logotyp uppdaterad",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "Visa Formbricks-varumärket i {type}-enkäter",
"show_powered_by_formbricks": "Visa 'Powered by Formbricks'-signatur",
"styling_updated_successfully": "Stiluppdatering lyckades",
"suggest_colors": "Föreslå färger",
"theme": "Tema",
"theme_settings_description": "Skapa ett stilmall för alla undersökningar. Du kan aktivera anpassad stil för varje undersökning."
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "Ja, håll mig informerad.",
"preview_survey_question_2_choice_2_label": "Nej, tack!",
"preview_survey_question_2_headline": "Vill du hållas uppdaterad?",
"preview_survey_question_2_subheader": "Det här är ett exempel på en beskrivning.",
"preview_survey_welcome_card_headline": "Välkommen!",
"prioritize_features_description": "Identifiera vilka funktioner dina användare behöver mest och minst.",
"prioritize_features_name": "Prioritera funktioner",
+174 -39
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "大小写混合",
"please_verify_captcha": "请 验证 reCAPTCHA",
"privacy_policy": "隐私政策",
"product_updates_description": "每月产品新闻和功能更新,适用隐私政策。",
"product_updates_title": "产品更新",
"security_updates_description": "仅限安全相关信息,适用隐私政策。",
"security_updates_title": "安全更新",
"terms_of_service": "服务条款",
"title": "创建你的 Formbricks 账户"
},
@@ -197,6 +201,7 @@
"docs": "文档",
"documentation": "文档",
"domain": "域名",
"done": "完成",
"download": "下载",
"draft": "草稿",
"duplicate": "复制",
@@ -238,7 +243,6 @@
"imprint": "印记",
"in_progress": "进行中",
"inactive_surveys": "不 活跃 调查",
"input_type": "输入类型",
"integration": "集成",
"integrations": "集成",
"invalid_date": "无效 日期",
@@ -250,6 +254,7 @@
"label": "标签",
"language": "语言",
"learn_more": "了解 更多",
"license_expired": "License Expired",
"light_overlay": "浅色遮罩层",
"limits_reached": "限制 达到",
"link": "链接",
@@ -262,13 +267,11 @@
"look_and_feel": "外观 & 感觉",
"manage": "管理",
"marketing": "市场营销",
"maximum": "最大值",
"member": "成员",
"members": "成员",
"members_and_teams": "成员和团队",
"membership_not_found": "未找到会员资格",
"metadata": "元数据",
"minimum": "最低",
"mobile_overlay_app_works_best_on_desktop": "Formbricks 在 更大 的 屏幕 上 效果 最佳。 若 需要 管理 或 构建 调查, 请 切换 到 其他 设备。",
"mobile_overlay_surveys_look_good": "别 担心 – 您 的 调查 在 每 一 种 设备 和 屏幕 尺寸 上 看起来 都 很 棒!",
"mobile_overlay_title": "噢, 检测 到 小 屏幕!",
@@ -282,6 +285,7 @@
"no_background_image_found": "未找到 背景 图片。",
"no_code": "无代码",
"no_files_uploaded": "没有 文件 被 上传",
"no_overlay": "无覆盖层",
"no_quotas_found": "未找到配额",
"no_result_found": "没有 结果",
"no_results": "没有 结果",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "未找到 组织 团队",
"other": "其他",
"others": "其他",
"overlay_color": "覆盖层颜色",
"overview": "概览",
"password": "密码",
"paused": "暂停",
@@ -321,7 +326,7 @@
"placeholder": "占位符",
"please_select_at_least_one_survey": "请选择至少 一个调查",
"please_select_at_least_one_trigger": "请选择至少 一个触发条件",
"please_upgrade_your_plan": "请 升级 您的 计划",
"please_upgrade_your_plan": "请升级您的计划",
"preview": "预览",
"preview_survey": "预览 Survey",
"privacy": "隐私政策",
@@ -347,6 +352,7 @@
"request_trial_license": "申请试用许可证",
"reset_to_default": "重置为 默认",
"response": "响应",
"response_id": "响应 ID",
"responses": "反馈",
"restart": "重新启动",
"role": "角色",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "您已达到 {projectLimit} 个工作区的上限。",
"you_have_reached_your_monthly_miu_limit_of": "您 已经 达到 每月 的 MIU 限制",
"you_have_reached_your_monthly_response_limit_of": "您 已经 达到 每月 的 响应 限制",
"you_will_be_downgraded_to_the_community_edition_on_date": "您将在 {date} 降级到社区版。"
"you_will_be_downgraded_to_the_community_edition_on_date": "您将在 {date} 降级到社区版。",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "接受",
@@ -783,20 +790,26 @@
"add_webhook": "添加 Webhook",
"add_webhook_description": "发送 调查 响应 数据 到 自定义 端点",
"all_current_and_new_surveys": "所有 当前 和 新的 调查",
"copy_secret_now": "复制您的签名密钥",
"created_by_third_party": "由 第三方 创建",
"discord_webhook_not_supported": "Discord webhooks 目前不 支持。",
"empty_webhook_message": "您的 Webhooks 会在您 添加 后 出现在这里。 ⏲️",
"endpoint_pinged": "太好了! 我们能 ping 该 webhook!",
"endpoint_pinged_error": "无法 ping 该 webhook",
"learn_to_verify": "了解如何验证 webhook 签名",
"please_check_console": "请查看控制台以获取更多详情",
"please_enter_a_url": "请输入一个 URL",
"response_created": "创建 响应",
"response_finished": "响应 完成",
"response_updated": "更新 响应",
"secret_copy_warning": "请妥善保存此密钥。您可以在 Webhook 设置中再次查看。",
"secret_description": "使用此密钥验证 Webhook 请求。有关签名验证,请参阅文档。",
"signing_secret": "签名密钥",
"source": "来源",
"test_endpoint": "测试 端点",
"triggers": "触发器",
"webhook_added_successfully": "Webhook 添加成功",
"webhook_created": "Webhook 已创建",
"webhook_delete_confirmation": "您 确定 要 删除 此 Webhook 吗?这 将 停止 向 您 发送 更多 通知 。",
"webhook_deleted_successfully": "Webhook 删除 成功",
"webhook_name_placeholder": "可选 为 您的 Webhook 标注 标签 以 便于 识别",
@@ -943,19 +956,32 @@
"enterprise_features": "企业 功能",
"get_an_enterprise_license_to_get_access_to_all_features": "获取 企业 许可证 来 访问 所有 功能。",
"keep_full_control_over_your_data_privacy_and_security": "保持 对 您 的 数据 隐私 和 安全 的 完全 控制。",
"license_invalid_description": "你在 ENTERPRISE_LICENSE_KEY 环境变量中填写的许可证密钥无效。请检查是否有拼写错误,或者申请一个新的密钥。",
"license_status": "许可证状态",
"license_status_active": "已激活",
"license_status_description": "你的企业许可证状态。",
"license_status_expired": "已过期",
"license_status_invalid": "许可证无效",
"license_status_unreachable": "无法访问",
"license_unreachable_grace_period": "无法连接到许可证服务器。在为期 3 天的宽限期内,你的企业功能仍然可用,宽限期将于 {gracePeriodEnd} 结束。",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "无需 电话 ,无需 附加 条件: 申请 免费 30 天 试用 授权以 通过 填写 此 表格 测试 所有 功能:",
"no_credit_card_no_sales_call_just_test_it": "无需信用卡 。无需销售电话 。只需测试一下 :)",
"on_request": "按请求",
"organization_roles": "组织角色(管理员,编辑,开发者等)",
"questions_please_reach_out_to": "问题 请 联系",
"recheck_license": "重新检查许可证",
"recheck_license_failed": "许可证检查失败。许可证服务器可能无法访问。",
"recheck_license_invalid": "许可证密钥无效。请确认你的 ENTERPRISE_LICENSE_KEY。",
"recheck_license_success": "许可证检查成功",
"recheck_license_unreachable": "许可证服务器无法访问,请稍后再试。",
"rechecking": "正在重新检查...",
"request_30_day_trial_license": "申请 30 天 的 试用许可证",
"saml_sso": "SAML SSO",
"service_level_agreement": "服务水平协议",
"soc2_hipaa_iso_27001_compliance_check": "SOC2 HIPAA ISO 27001 合规检查",
"sso": "SSO Google 、Microsoft 、OpenID Connect",
"teams": "团队 & 访问 角色(读取, 读取 & 写入, 管理)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。",
"your_enterprise_license_is_active_all_features_unlocked": "您的企业许可证已激活 所有功能已解锁"
"unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。"
},
"general": {
"bulk_invite_warning_description": "在免费计划中,所有组织成员都会被分配为 \"Owner \"角色。",
@@ -979,7 +1005,7 @@
"from_your_organization": "来自你的组织",
"invitation_sent_once_more": "再次发送邀请。",
"invite_deleted_successfully": "邀请 删除 成功",
"invited_on": "邀于 {date}",
"invite_expires_on": "邀请将于 {date} 过期",
"invites_failed": "邀请失败",
"leave_organization": "离开 组织",
"leave_organization_description": "您将离开此组织,并失去对所有调查和响应的访问权限。只有再次被邀请后,您才能重新加入。",
@@ -1008,6 +1034,8 @@
"remove_logo": "移除 logo",
"replace_logo": "替换 logo",
"resend_invitation_email": "重新发送邀请邮件",
"security_list_tip": "您已订阅我们的安全列表了吗?保持关注,保障您的实例安全!",
"security_list_tip_link": "点击此处注册。",
"share_invite_link": "分享邀请链接",
"share_this_link_to_let_your_organization_member_join_your_organization": "分享 这个 链接 以 让 你的 组织 成员 加入 你的 组织:",
"test_email_sent_successfully": "测试 邮件 发送 成功",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "请填写所有字段以添加新工作区。",
"read": "阅读",
"read_write": "读 & 写",
"select_member": "选择成员",
"select_workspace": "选择工作区",
"team_admin": "团队管理员",
"team_created_successfully": "团队 创建 成功",
"team_deleted_successfully": "团队 删除 成功",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "添加 占位符 显示 如果 没有 值以 回忆",
"add_hidden_field_id": "添加 隐藏 字段 ID",
"add_highlight_border": "添加 高亮 边框",
"add_highlight_border_description": "在 你的 调查 卡片 添加 外 边框。",
"add_logic": "添加逻辑",
"add_none_of_the_above": "添加 “以上 都 不 是”",
"add_option": "添加 选项",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "更改 访客 看到 调查 关闭 时 的 消息。",
"adjust_the_theme_in_the": "调整主题在",
"all_other_answers_will_continue_to": "所有其他答案将继续",
"allow_file_type": "允许 文件类型",
"allow_multi_select": "允许 多选",
"allow_multiple_files": "允许 多 个 文件",
"allow_users_to_select_more_than_one_image": "允许 用户 选择 多于 一个 图片",
@@ -1169,6 +1193,9 @@
"assign": "指派 =",
"audience": "受众",
"auto_close_on_inactivity": "自动关闭 在 无活动时",
"auto_save_disabled": "自动保存已禁用",
"auto_save_disabled_tooltip": "您的调查仅在草稿状态时自动保存。这确保公开的调查不会被意外更新。",
"auto_save_on": "自动保存已启用",
"automatically_close_survey_after": "自动 关闭 调查 后",
"automatically_close_the_survey_after_a_certain_number_of_responses": "自动 关闭 调查 在 达到 一定数量 的 回应 后",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "用户未在一定秒数内应答时 自动关闭 问卷",
@@ -1178,6 +1205,7 @@
"block_duplicated": "区块已复制。",
"bold": "粗体",
"brand_color": "品牌 颜色",
"brand_color_description": "应用于按钮、链接和高亮部分。",
"brightness": "亮度",
"bulk_edit": "批量编辑",
"bulk_edit_description": "编辑以下所有选项,每行一个。空行将被跳过,重复项将被移除。",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.com 用户名 或 用户名/事件",
"calculate": "计算",
"capture_a_new_action_to_trigger_a_survey_on": "捕获一个新动作以触发调查。",
"capture_ip_address": "记录IP地址",
"capture_ip_address_description": "将答题者的IP地址存储在响应元数据中,用于重复检测和安全目的",
"capture_new_action": "捕获 新动作",
"card_arrangement_for_survey_type_derived": "{surveyTypeDerived} 调查 的 卡片 布局",
"card_background_color": "卡片 的 背景 颜色",
"card_background_color_description": "填充调查卡区域。",
"card_border_color": "卡片 的 边框 颜色",
"card_border_color_description": "勾勒调查卡边框。",
"card_styling": "卡片样式",
"casual": "休闲",
"caution_edit_duplicate": "复制 并 编辑",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "旧 与 新 的 回复 混合 , 这 可能 导致 数据 总结 有误 。",
"caution_recommendation": "这 可能 会 导致 调查 统计 数据 的 不一致 。 我们 建议 复制 调查 。",
"caution_text": "更改 会导致 不一致",
"centered_modal_overlay_color": "居中 模态遮罩层颜色",
"change_anyway": "还是更改",
"change_background": "更改 背景",
"change_question_type": "更改 问题类型",
"change_survey_type": "更改 调查 类型 会影 响 现有 访问",
"change_the_background_color_of_the_card": "更改 卡片 的 背景 颜色",
"change_the_background_color_of_the_input_fields": "更改 输入字段 的 背景颜色",
"change_the_background_to_a_color_image_or_animation": "将 背景 更改为 颜色 、 图像 或 动画。",
"change_the_border_color_of_the_card": "更改 卡片 的 边框 颜色",
"change_the_border_color_of_the_input_fields": "更改 输入字段 的边框颜色。",
"change_the_border_radius_of_the_card_and_the_inputs": "更改 卡片 和 输入 的 边框 半径",
"change_the_brand_color_of_the_survey": "更改调查的品牌颜色",
"change_the_placement_of_this_survey": "更改 此 调查 的 放置。",
"change_the_question_color_of_the_survey": "更改调查的 问题颜色",
"changes_saved": "更改 已 保存",
"changing_survey_type_will_remove_existing_distribution_channels": "更改 调查 类型 会影 响 分享 方式 。 如果 受访者 已经 拥有 当前 类型 的 访问 链接 , 在 更改 之后 ,他们 可能 会 失去 访问 权限 。",
"character_limit_toggle_description": "限制 答案的短或长程度。",
"character_limit_toggle_title": "添加 字符限制",
"checkbox_label": "复选框 标签",
"choose_the_actions_which_trigger_the_survey": "选择 触发 调查 的 动作 。",
"choose_the_first_question_on_your_block": "选择区块中的第一个问题",
@@ -1241,7 +1263,6 @@
"contact_fields": "联络字段",
"contains": "包含",
"continue_to_settings": "继续 到 设置",
"control_which_file_types_can_be_uploaded": "控制 可以 上传的 文件 类型",
"convert_to_multiple_choice": "转换为 多选",
"convert_to_single_choice": "转换为 单选",
"country": "国家",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "根据 您 的 选择 暗化 或 亮化 背景。",
"date_format": "日期格式",
"days_before_showing_this_survey_again": "距离上次显示问卷后需间隔不少于指定天数,才能再次显示此问卷。",
"delete_anyways": "仍然删除",
"delete_block": "删除区块",
"delete_choice": "删除 选择",
"disable_the_visibility_of_survey_progress": "禁用问卷 进度 的可见性。",
"display_an_estimate_of_completion_time_for_survey": "显示 调查 预计 完成 时间",
"display_number_of_responses_for_survey": "显示 调查 响应 数量",
"display_type": "显示类型",
"divide": "划分 /",
"does_not_contain": "不包含",
"does_not_end_with": "不 以 结尾",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "不包括所有 ",
"does_not_include_one_of": "不包括一 个",
"does_not_start_with": "不 以 开头",
"dropdown": "下拉菜单",
"duplicate_block": "复制区块",
"duplicate_question": "复制问题",
"edit_link": "编辑 链接",
@@ -1357,8 +1381,7 @@
"hide_progress_bar": "隐藏 进度 条",
"hide_question_settings": "隐藏问题设置",
"hostname": "主 机 名",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "在 {surveyTypeDerived} 调查 中,您 想要 卡片 多么 有趣",
"if_you_need_more_please": "如果你需要更多,请",
"if_you_need_more_please": "如果您需要更多,请",
"if_you_really_want_that_answer_ask_until_you_get_it": "每次触发时都会显示,直到提交回应为止。",
"ignore_global_waiting_time": "忽略冷却期",
"ignore_global_waiting_time_description": "只要满足条件,此调查即可显示,即使最近刚显示过其他调查。",
@@ -1368,7 +1391,9 @@
"initial_value": "初始 值",
"inner_text": "内文",
"input_border_color": "输入 边框 颜色",
"input_border_color_description": "勾勒文本输入框和多行文本框的边框。",
"input_color": "输入颜色",
"input_color_description": "填充文本输入框内部。",
"insert_link": "插入 链接",
"invalid_targeting": "无效的目标: 请检查 您 的受众过滤器",
"invalid_video_url_warning": "请输入有效的 YouTube、Vimeo 或 Loom URL 。我们目前不支持其他 视频 托管服务提供商。",
@@ -1395,10 +1420,10 @@
"key": "键",
"last_name": "姓",
"let_people_upload_up_to_25_files_at_the_same_time": "允许 人们 同时 上传 最多 25 个 文件",
"limit_file_types": "限制 文件 类型",
"limit_the_maximum_file_size": "限制 最大 文件 大小",
"limit_upload_file_size_to": "将 上传 文件 大小 限制 为",
"limit_the_maximum_file_size": "限制上传文件的最大大小。",
"limit_upload_file_size_to": "将上传文件大小限制为",
"link_survey_description": "分享 问卷 页面 链接 或 将其 嵌入 网页 或 电子邮件 中。",
"list": "列表",
"load_segment": "载入 段落",
"logic_error_warning": "更改 将 导致 逻辑 错误",
"logic_error_warning_text": "更改问题类型 会 移除 此问题 的 逻辑条件",
@@ -1409,8 +1434,8 @@
"manage_languages": "管理 语言",
"matrix_all_fields": "所有字段",
"matrix_rows": "行",
"max_file_size": "最大 文件 大小",
"max_file_size_limit_is": "最大 文件 大小 限制",
"max_file_size": "最大文件大小",
"max_file_size_limit_is": "最大文件大小限制",
"move_question_to_block": "将问题移动到区块",
"multiply": "乘 *",
"needed_for_self_hosted_cal_com_instance": "需要用于 自建 Cal.com 实例",
@@ -1442,22 +1467,22 @@
"picture_idx": "图片 {idx}",
"pin_can_only_contain_numbers": "PIN 只能包含数字。",
"pin_must_be_a_four_digit_number": "PIN 必须是 四 位数字。",
"please_enter_a_file_extension": "请输入 文件 扩展名。",
"please_enter_a_valid_url": "请输入有效的 URL(例如, https://example.com ",
"please_set_a_survey_trigger": "请 设置 一个 调查 触发",
"please_specify": "请 指定",
"prevent_double_submission": "防止 重复 提交",
"prevent_double_submission_description": "只允许每个 email 地址提供 1 个回复",
"progress_saved": "进度已保存",
"protect_survey_with_pin": "使用 PIN 保护 调查",
"protect_survey_with_pin_description": "只有 拥有 PIN 的 用户 可以 访问 调查。",
"publish": "发布",
"question": "问题",
"question_color": "问题颜色",
"question_deleted": "问题 已删除",
"question_duplicated": "问题重复。",
"question_id_updated": "问题 ID 更新",
"question_used_in_logic": "\"这个 问题 在 问题 {questionIndex} 的 逻辑 中 使用。\"",
"question_used_in_quota": "此 问题 正在 被 \"{quotaName}\" 配额 使用",
"question_used_in_logic_warning_text": "此区块中的元素已被用于逻辑规则,您确定要删除吗?",
"question_used_in_logic_warning_title": "逻辑不一致",
"question_used_in_quota": "此问题正在被“{quotaName}”配额使用",
"question_used_in_recall": "此问题正在召回于问题 {questionIndex}。",
"question_used_in_recall_ending_card": "此 问题 正在召回于结束 卡片。",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "响应 限制 、 重定向 和 更多 。",
"response_options": "响应 选项",
"roundness": "圆度",
"roundness_description": "控制卡片角的圆润程度。",
"row_used_in_logic_error": "\"这个 行 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
"rows": "行",
"save_and_close": "保存 和 关闭",
@@ -1520,6 +1546,7 @@
"search_for_images": "搜索 图片",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "触发后 如果 没有 应答 将 在 几秒 后 关闭 调查",
"seconds_before_showing_the_survey": "显示问卷前 几秒",
"select_field": "选择字段",
"select_or_type_value": "选择 或 输入 值",
"select_ordering": "选择排序",
"select_saved_action": "选择 保存的 操作",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "样式 设置 为 主题 风格",
"subheading": "子标题",
"subtract": "减 -",
"suggest_colors": "建议颜色",
"survey_completed_heading": "调查 完成",
"survey_completed_subheading": "此 免费 & 开源 调查 已 关闭",
"survey_display_settings": "调查显示设置",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "仅显示一次,即使他们未回应。",
"then": "然后",
"this_action_will_remove_all_the_translations_from_this_survey": "此操作将删除该调查中的所有翻译。",
"this_extension_is_already_added": "此扩展已经添加。",
"this_file_type_is_not_supported": "此 文件 类型 不 支持。",
"three_points": "3 分",
"times": "次数",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "为了 保持 所有 调查 的 放置 一致,您 可以",
@@ -1589,8 +1613,51 @@
"upper_label": "上限标签",
"url_filters": "URL 过滤器",
"url_not_supported": "URL 不支持",
"validation": {
"add_validation_rule": "添加验证规则",
"answer_all_rows": "请填写所有行",
"characters": "字符",
"contains": "包含",
"delete_validation_rule": "删除验证规则",
"does_not_contain": "不包含",
"email": "是有效的邮箱地址",
"end_date": "结束日期",
"file_extension_is": "文件扩展名为",
"file_extension_is_not": "文件扩展名不是",
"is": "等于",
"is_between": "介于",
"is_earlier_than": "早于",
"is_greater_than": "大于",
"is_later_than": "晚于",
"is_less_than": "小于",
"is_not": "不等于",
"is_not_between": "不介于",
"kb": "KB",
"max_length": "最多",
"max_selections": "最多",
"max_value": "最多",
"mb": "MB",
"min_length": "至少",
"min_selections": "至少",
"min_value": "至少",
"minimum_options_ranked": "最少排序选项数",
"minimum_rows_answered": "最少回答行数",
"options_selected": "已选择的选项",
"pattern": "匹配正则表达式模式",
"phone": "是有效的手机号",
"rank_all_options": "对所有选项进行排序",
"select_file_extensions": "选择文件扩展名...",
"select_option": "选择选项",
"start_date": "开始日期",
"url": "是有效的URL"
},
"validation_logic_and": "全部为真",
"validation_logic_or": "任一为真",
"validation_rules": "校验规则",
"validation_rules_description": "仅接受符合以下条件的回复",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "\"{variable} 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "变量 \"{variableName}\" 正在 被 \"{quotaName}\" 配额 使用",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "变量{variableName}”正在被“{quotaName}配额使用,请先将其从配额中移除",
"variable_name_conflicts_with_hidden_field": "变量名与已有的隐藏字段 ID 冲突。",
"variable_name_is_already_taken_please_choose_another": "变量名已被占用,请选择其他。",
"variable_name_must_start_with_a_letter": "变量名 必须 以字母开头。",
"variable_used_in_recall": "变量 \"{variable}\" 正在召回于问题 {questionIndex}。",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "下载答复时发生错误",
"first_name": "名字",
"how_to_identify_users": "如何 识别 用户",
"ip_address": "IP地址",
"last_name": "姓",
"not_completed": "未完成 ⏳",
"os": "操作系统",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "添加背景色",
"add_background_color_description": "为 logo 容器添加背景色。",
"advanced_styling_field_border_radius": "边框圆角",
"advanced_styling_field_button_bg": "按钮背景",
"advanced_styling_field_button_bg_description": "填充“下一步/提交”按钮。",
"advanced_styling_field_button_border_radius_description": "设置按钮圆角。",
"advanced_styling_field_button_font_size_description": "调整按钮标签文字大小。",
"advanced_styling_field_button_font_weight_description": "设置按钮文字的粗细。",
"advanced_styling_field_button_height_description": "控制按钮高度。",
"advanced_styling_field_button_padding_x_description": "增加左右间距。",
"advanced_styling_field_button_padding_y_description": "增加上下间距。",
"advanced_styling_field_button_text": "按钮文字",
"advanced_styling_field_button_text_description": "设置按钮内标签的颜色。",
"advanced_styling_field_description_color": "描述颜色",
"advanced_styling_field_description_color_description": "设置每个标题下方文字的颜色。",
"advanced_styling_field_description_size": "描述字体大小",
"advanced_styling_field_description_size_description": "调整描述文字大小。",
"advanced_styling_field_description_weight": "描述字体粗细",
"advanced_styling_field_description_weight_description": "设置描述文字的粗细。",
"advanced_styling_field_font_size": "字体大小",
"advanced_styling_field_font_weight": "字体粗细",
"advanced_styling_field_headline_color": "标题颜色",
"advanced_styling_field_headline_color_description": "设置主问题文字的颜色。",
"advanced_styling_field_headline_size": "标题字体大小",
"advanced_styling_field_headline_size_description": "调整主标题文字大小。",
"advanced_styling_field_headline_weight": "标题字体粗细",
"advanced_styling_field_headline_weight_description": "设置主标题文字的粗细。",
"advanced_styling_field_height": "高度",
"advanced_styling_field_indicator_bg": "指示器背景",
"advanced_styling_field_indicator_bg_description": "设置进度条已填充部分的颜色。",
"advanced_styling_field_input_border_radius_description": "设置输入框圆角。",
"advanced_styling_field_input_font_size_description": "调整输入框内文字大小。",
"advanced_styling_field_input_height_description": "控制输入框高度。",
"advanced_styling_field_input_padding_x_description": "增加输入框左右间距。",
"advanced_styling_field_input_padding_y_description": "为输入框上下添加间距。",
"advanced_styling_field_input_placeholder_opacity_description": "调整占位提示文字的透明度。",
"advanced_styling_field_input_shadow_description": "为输入框添加投影效果。",
"advanced_styling_field_input_text": "输入文字",
"advanced_styling_field_input_text_description": "设置输入框内已输入文字的颜色。",
"advanced_styling_field_option_bg": "背景色",
"advanced_styling_field_option_bg_description": "设置选项项的背景色。",
"advanced_styling_field_option_border_radius_description": "设置选项的圆角。",
"advanced_styling_field_option_font_size_description": "调整选项标签文字的大小。",
"advanced_styling_field_option_label": "标签颜色",
"advanced_styling_field_option_label_description": "设置选项标签文字的颜色。",
"advanced_styling_field_option_padding_x_description": "为选项左右添加间距。",
"advanced_styling_field_option_padding_y_description": "为选项上下添加间距。",
"advanced_styling_field_padding_x": "横向内边距",
"advanced_styling_field_padding_y": "纵向内边距",
"advanced_styling_field_placeholder_opacity": "占位符透明度",
"advanced_styling_field_shadow": "阴影",
"advanced_styling_field_track_bg": "轨道背景",
"advanced_styling_field_track_bg_description": "设置进度条未填充部分的颜色。",
"advanced_styling_field_track_height": "轨道高度",
"advanced_styling_field_track_height_description": "控制进度条的粗细。",
"advanced_styling_field_upper_label_color": "标题标签颜色",
"advanced_styling_field_upper_label_color_description": "设置输入框上方小标签的颜色。",
"advanced_styling_field_upper_label_size": "标题标签字体大小",
"advanced_styling_field_upper_label_size_description": "调整输入框上方小标签的大小。",
"advanced_styling_field_upper_label_weight": "标题标签字体粗细",
"advanced_styling_field_upper_label_weight_description": "设置标签文字的粗细。",
"advanced_styling_section_buttons": "按钮",
"advanced_styling_section_headlines": "标题和描述",
"advanced_styling_section_inputs": "输入项",
"advanced_styling_section_options": "选项(单选/多选)",
"app_survey_placement": "应用调查放置位置",
"app_survey_placement_settings_description": "更改调查在您的 Web 应用或网站中显示的位置。",
"centered_modal_overlay_color": "居中模态遮罩层颜色",
"email_customization": "邮件自定义",
"email_customization_description": "更改 Formbricks 代表您发送邮件的外观和风格。",
"enable_custom_styling": "启用自定义样式",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricks 品牌标识已隐藏。",
"formbricks_branding_settings_description": "我们很感谢您的支持,但如果您关闭它,我们也能理解。",
"formbricks_branding_shown": "Formbricks 品牌标识已显示。",
"generate_theme_btn": "生成",
"generate_theme_confirmation": "要根据你的品牌色生成一个匹配的配色主题吗?这将覆盖你当前的颜色设置。",
"generate_theme_header": "生成配色主题?",
"logo_removed_successfully": "logo 移除成功",
"logo_settings_description": "上传您的公司 logo,用于品牌调查和链接预览。",
"logo_updated_successfully": "logo 更新成功",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "在 {type} 调查中显示 Formbricks 品牌标识",
"show_powered_by_formbricks": "显示“Powered by Formbricks”标识",
"styling_updated_successfully": "样式更新成功",
"suggest_colors": "推荐颜色",
"theme": "主题",
"theme_settings_description": "为所有问卷创建一个样式主题。你可以为每个问卷启用自定义样式。"
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "是 保持我 更新 。",
"preview_survey_question_2_choice_2_label": "不,谢谢!",
"preview_survey_question_2_headline": "想 了解 最新信息吗?",
"preview_survey_question_2_subheader": "这是一个示例描述。",
"preview_survey_welcome_card_headline": "欢迎!",
"prioritize_features_description": "确定 用户 最 需要 和 最 不 需要 的 功能。",
"prioritize_features_name": "优先 功能",
+171 -36
View File
@@ -75,6 +75,10 @@
"password_validation_uppercase_and_lowercase": "混合使用大小寫字母",
"please_verify_captcha": "請驗證 reCAPTCHA",
"privacy_policy": "隱私權政策",
"product_updates_description": "每月產品新聞與功能更新,適用隱私權政策。",
"product_updates_title": "產品更新",
"security_updates_description": "僅限安全相關資訊,適用隱私權政策。",
"security_updates_title": "安全更新",
"terms_of_service": "服務條款",
"title": "建立您的 Formbricks 帳戶"
},
@@ -197,6 +201,7 @@
"docs": "文件",
"documentation": "文件",
"domain": "網域",
"done": "完成",
"download": "下載",
"draft": "草稿",
"duplicate": "複製",
@@ -238,7 +243,6 @@
"imprint": "版本訊息",
"in_progress": "進行中",
"inactive_surveys": "停用中的問卷",
"input_type": "輸入類型",
"integration": "整合",
"integrations": "整合",
"invalid_date": "無效日期",
@@ -250,6 +254,7 @@
"label": "標籤",
"language": "語言",
"learn_more": "瞭解更多",
"license_expired": "License Expired",
"light_overlay": "淺色覆蓋",
"limits_reached": "已達上限",
"link": "連結",
@@ -262,13 +267,11 @@
"look_and_feel": "外觀與風格",
"manage": "管理",
"marketing": "行銷",
"maximum": "最大值",
"member": "成員",
"members": "成員",
"members_and_teams": "成員與團隊",
"membership_not_found": "找不到成員資格",
"metadata": "元數據",
"minimum": "最小值",
"mobile_overlay_app_works_best_on_desktop": "Formbricks 適合在大螢幕上使用。若要管理或建立問卷,請切換到其他裝置。",
"mobile_overlay_surveys_look_good": "別擔心 -你的 問卷 在每個 裝置 和 螢幕尺寸 上 都 很出色!",
"mobile_overlay_title": "糟糕 ,偵測到小螢幕!",
@@ -282,6 +285,7 @@
"no_background_image_found": "找不到背景圖片。",
"no_code": "無程式碼",
"no_files_uploaded": "沒有上傳任何檔案",
"no_overlay": "無覆蓋層",
"no_quotas_found": "找不到 配額",
"no_result_found": "找不到結果",
"no_results": "沒有結果",
@@ -308,6 +312,7 @@
"organization_teams_not_found": "找不到組織團隊",
"other": "其他",
"others": "其他",
"overlay_color": "覆蓋層顏色",
"overview": "概覽",
"password": "密碼",
"paused": "已暫停",
@@ -321,7 +326,7 @@
"placeholder": "提示文字",
"please_select_at_least_one_survey": "請選擇至少一個問卷",
"please_select_at_least_one_trigger": "請選擇至少一個觸發器",
"please_upgrade_your_plan": "請升級您的方案",
"please_upgrade_your_plan": "請升級您的方案",
"preview": "預覽",
"preview_survey": "預覽問卷",
"privacy": "隱私權政策",
@@ -347,6 +352,7 @@
"request_trial_license": "請求試用授權",
"reset_to_default": "重設為預設值",
"response": "回應",
"response_id": "回應 ID",
"responses": "回應",
"restart": "重新開始",
"role": "角色",
@@ -458,7 +464,8 @@
"you_have_reached_your_limit_of_workspace_limit": "您已達到 {projectLimit} 個工作區的上限。",
"you_have_reached_your_monthly_miu_limit_of": "您已達到每月 MIU 上限:",
"you_have_reached_your_monthly_response_limit_of": "您已達到每月回應上限:",
"you_will_be_downgraded_to_the_community_edition_on_date": "您將於 '{'date'}' 降級至社群版。"
"you_will_be_downgraded_to_the_community_edition_on_date": "您將於 '{'date'}' 降級至社群版。",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features."
},
"emails": {
"accept": "接受",
@@ -783,20 +790,26 @@
"add_webhook": "新增 Webhook",
"add_webhook_description": "將問卷回應資料傳送至自訂端點",
"all_current_and_new_surveys": "所有目前和新的問卷",
"copy_secret_now": "複製您的簽章密鑰",
"created_by_third_party": "由第三方建立",
"discord_webhook_not_supported": "目前不支援 Discord webhooks。",
"empty_webhook_message": "您的 Webhook 將在您新增後立即顯示在此處。⏲️",
"endpoint_pinged": "耶!我們能夠 ping Webhook",
"endpoint_pinged_error": "無法 ping Webhook",
"learn_to_verify": "了解如何驗證 webhook 簽章",
"please_check_console": "請檢查主控台以取得更多詳細資料",
"please_enter_a_url": "請輸入網址",
"response_created": "已建立回應",
"response_finished": "已完成回應",
"response_updated": "已更新回應",
"secret_copy_warning": "請妥善保存此密鑰。您可以在 Webhook 設定中再次查看。",
"secret_description": "使用此密鑰來驗證 Webhook 請求。請參閱文件以了解簽章驗證方式。",
"signing_secret": "簽章密鑰",
"source": "來源",
"test_endpoint": "測試端點",
"triggers": "觸發器",
"webhook_added_successfully": "Webhook 已成功新增",
"webhook_created": "Webhook 已建立",
"webhook_delete_confirmation": "您確定要刪除此 Webhook 嗎?這將停止向您發送任何進一步的通知。",
"webhook_deleted_successfully": "Webhook 已成功刪除",
"webhook_name_placeholder": "選填:為您的 Webhook 加上標籤以便於識別",
@@ -943,19 +956,32 @@
"enterprise_features": "企業版功能",
"get_an_enterprise_license_to_get_access_to_all_features": "取得企業授權以存取所有功能。",
"keep_full_control_over_your_data_privacy_and_security": "完全掌控您的資料隱私權和安全性。",
"license_invalid_description": "你在 ENTERPRISE_LICENSE_KEY 環境變數中填寫的授權金鑰無效。請檢查是否有輸入錯誤,或申請新的金鑰。",
"license_status": "授權狀態",
"license_status_active": "有效",
"license_status_description": "你的企業授權狀態。",
"license_status_expired": "已過期",
"license_status_invalid": "授權無效",
"license_status_unreachable": "無法連線",
"license_unreachable_grace_period": "無法連線至授權伺服器。在 3 天的寬限期內,你的企業功能仍可使用,寬限期將於 {gracePeriodEnd} 結束。",
"no_call_needed_no_strings_attached_request_a_free_30_day_trial_license_to_test_all_features_by_filling_out_this_form": "無需通話,無附加條件:填寫此表單,請求免費 30 天試用授權以測試所有功能:",
"no_credit_card_no_sales_call_just_test_it": "無需信用卡。無需銷售電話。只需測試一下 :)",
"on_request": "依要求",
"organization_roles": "組織角色(管理員、編輯者、開發人員等)",
"questions_please_reach_out_to": "有任何問題?請聯絡",
"recheck_license": "重新檢查授權",
"recheck_license_failed": "授權檢查失敗。授權伺服器可能無法連線。",
"recheck_license_invalid": "授權金鑰無效。請確認你的 ENTERPRISE_LICENSE_KEY。",
"recheck_license_success": "授權檢查成功",
"recheck_license_unreachable": "授權伺服器無法連線,請稍後再試。",
"rechecking": "正在重新檢查...",
"request_30_day_trial_license": "請求 30 天試用授權",
"saml_sso": "SAML SSO",
"service_level_agreement": "服務等級協定",
"soc2_hipaa_iso_27001_compliance_check": "SOC2、HIPAA、ISO 27001 合規性檢查",
"sso": "SSOGoogle、Microsoft、OpenID Connect",
"teams": "團隊和存取角色(讀取、讀取和寫入、管理)",
"unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。",
"your_enterprise_license_is_active_all_features_unlocked": "您的企業授權處於活動狀態。所有功能都已解鎖。"
"unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。"
},
"general": {
"bulk_invite_warning_description": "在免費方案中,所有組織成員始終會被指派「擁有者」角色。",
@@ -979,7 +1005,7 @@
"from_your_organization": "來自您的組織",
"invitation_sent_once_more": "已再次發送邀請。",
"invite_deleted_successfully": "邀請已成功刪除",
"invited_on": "邀請於 '{'date'}'",
"invite_expires_on": "邀請於 '{'date'}' 過期",
"invites_failed": "邀請失敗",
"leave_organization": "離開組織",
"leave_organization_description": "您將離開此組織並失去對所有問卷和回應的存取權限。只有再次收到邀請,您才能重新加入。",
@@ -1008,6 +1034,8 @@
"remove_logo": "移除標誌",
"replace_logo": "取代標誌",
"resend_invitation_email": "重新發送邀請電子郵件",
"security_list_tip": "您已訂閱我們的安全名單了嗎?保持關注,確保您的實例安全!",
"security_list_tip_link": "請在此註冊。",
"share_invite_link": "分享邀請連結",
"share_this_link_to_let_your_organization_member_join_your_organization": "分享此連結以讓您的組織成員加入您的組織:",
"test_email_sent_successfully": "測試電子郵件已成功發送",
@@ -1090,8 +1118,6 @@
"please_fill_all_workspace_fields": "請填寫所有欄位以新增工作區。",
"read": "讀取",
"read_write": "讀取和寫入",
"select_member": "選擇成員",
"select_workspace": "選擇工作區",
"team_admin": "團隊管理員",
"team_created_successfully": "團隊已成功建立。",
"team_deleted_successfully": "團隊已成功刪除。",
@@ -1141,7 +1167,6 @@
"add_fallback_placeholder": "新增 預設 以顯示是否沒 有 值 可 回憶 。",
"add_hidden_field_id": "新增隱藏欄位 ID",
"add_highlight_border": "新增醒目提示邊框",
"add_highlight_border_description": "在您的問卷卡片新增外邊框。",
"add_logic": "新增邏輯",
"add_none_of_the_above": "新增 \"以上皆非\"",
"add_option": "新增選項",
@@ -1159,7 +1184,6 @@
"adjust_survey_closed_message_description": "變更訪客在問卷關閉時看到的訊息。",
"adjust_the_theme_in_the": "在",
"all_other_answers_will_continue_to": "所有其他答案將繼續",
"allow_file_type": "允許檔案類型",
"allow_multi_select": "允許多重選取",
"allow_multiple_files": "允許上傳多個檔案",
"allow_users_to_select_more_than_one_image": "允許使用者選取多張圖片",
@@ -1169,6 +1193,9 @@
"assign": "等於 =",
"audience": "受眾",
"auto_close_on_inactivity": "非活動時自動關閉",
"auto_save_disabled": "自動儲存已停用",
"auto_save_disabled_tooltip": "您的問卷僅在草稿狀態時自動儲存。這確保公開的問卷不會被意外更新。",
"auto_save_on": "自動儲存已啟用",
"automatically_close_survey_after": "在指定時間自動關閉問卷",
"automatically_close_the_survey_after_a_certain_number_of_responses": "在收到一定數量的回覆後自動關閉問卷。",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "如果用戶在特定秒數後未回應,則自動關閉問卷。",
@@ -1178,6 +1205,7 @@
"block_duplicated": "區塊已複製。",
"bold": "粗體",
"brand_color": "品牌顏色",
"brand_color_description": "應用於按鈕、連結和重點標示。",
"brightness": "亮度",
"bulk_edit": "批次編輯",
"bulk_edit_description": "在下方逐行編輯所有選項。空白行將被略過,重複項目將被移除。",
@@ -1190,10 +1218,14 @@
"cal_username": "Cal.com 使用者名稱或使用者名稱/事件",
"calculate": "計算",
"capture_a_new_action_to_trigger_a_survey_on": "擷取新的操作以觸發問卷。",
"capture_ip_address": "擷取 IP 位址",
"capture_ip_address_description": "將受訪者的 IP 位址儲存在回應中繼資料中,以便進行重複檢測與安全性用途",
"capture_new_action": "擷取新操作",
"card_arrangement_for_survey_type_derived": "'{'surveyTypeDerived'}' 問卷的卡片排列",
"card_background_color": "卡片背景顏色",
"card_background_color_description": "填滿問卷卡片區域。",
"card_border_color": "卡片邊框顏色",
"card_border_color_description": "描繪問卷卡片的邊框。",
"card_styling": "卡片樣式",
"casual": "隨意",
"caution_edit_duplicate": "複製 & 編輯",
@@ -1204,24 +1236,14 @@
"caution_explanation_responses_are_safe": "較舊和較新的回應會混在一起,可能導致數據摘要失準。",
"caution_recommendation": "這可能導致調查摘要中的數據不一致。我們建議複製這個調查。",
"caution_text": "變更會導致不一致",
"centered_modal_overlay_color": "置中彈窗覆蓋顏色",
"change_anyway": "仍然變更",
"change_background": "變更背景",
"change_question_type": "變更問題類型",
"change_survey_type": "切換問卷類型會影響現有訪問",
"change_the_background_color_of_the_card": "變更卡片的背景顏色。",
"change_the_background_color_of_the_input_fields": "變更輸入欄位的背景顏色。",
"change_the_background_to_a_color_image_or_animation": "將背景變更為顏色、圖片或動畫。",
"change_the_border_color_of_the_card": "變更卡片的邊框顏色。",
"change_the_border_color_of_the_input_fields": "變更輸入欄位的邊框顏色。",
"change_the_border_radius_of_the_card_and_the_inputs": "變更卡片和輸入的邊框半徑。",
"change_the_brand_color_of_the_survey": "變更問卷的品牌顏色。",
"change_the_placement_of_this_survey": "變更此問卷的位置。",
"change_the_question_color_of_the_survey": "變更問卷的問題顏色。",
"changes_saved": "已儲存變更。",
"changing_survey_type_will_remove_existing_distribution_channels": "更改問卷類型會影響其共享方式。如果受訪者已擁有當前類型的存取連結,則在切換後可能會失去存取權限。",
"character_limit_toggle_description": "限制答案的長度或短度。",
"character_limit_toggle_title": "新增字元限制",
"checkbox_label": "核取方塊標籤",
"choose_the_actions_which_trigger_the_survey": "選擇觸發問卷的操作。",
"choose_the_first_question_on_your_block": "選擇此區塊的第一個問題",
@@ -1241,7 +1263,6 @@
"contact_fields": "聯絡人欄位",
"contains": "包含",
"continue_to_settings": "繼續設定",
"control_which_file_types_can_be_uploaded": "控制可以上傳哪些檔案類型。",
"convert_to_multiple_choice": "轉換為多選",
"convert_to_single_choice": "轉換為單選",
"country": "國家/地區",
@@ -1254,11 +1275,13 @@
"darken_or_lighten_background_of_your_choice": "變暗或變亮您選擇的背景。",
"date_format": "日期格式",
"days_before_showing_this_survey_again": "距離上次顯示問卷後,需間隔指定天數才能再次顯示此問卷。",
"delete_anyways": "仍要刪除",
"delete_block": "刪除區塊",
"delete_choice": "刪除選項",
"disable_the_visibility_of_survey_progress": "停用問卷進度的可見性。",
"display_an_estimate_of_completion_time_for_survey": "顯示問卷的估計完成時間",
"display_number_of_responses_for_survey": "顯示問卷的回應數",
"display_type": "顯示類型",
"divide": "除 /",
"does_not_contain": "不包含",
"does_not_end_with": "不以...結尾",
@@ -1266,6 +1289,7 @@
"does_not_include_all_of": "不包含全部",
"does_not_include_one_of": "不包含其中之一",
"does_not_start_with": "不以...開頭",
"dropdown": "下拉選單",
"duplicate_block": "複製區塊",
"duplicate_question": "複製問題",
"edit_link": "編輯 連結",
@@ -1357,7 +1381,6 @@
"hide_progress_bar": "隱藏進度列",
"hide_question_settings": "隱藏問題設定",
"hostname": "主機名稱",
"how_funky_do_you_want_your_cards_in_survey_type_derived_surveys": "您希望 '{'surveyTypeDerived'}' 問卷中的卡片有多酷炫",
"if_you_need_more_please": "如果您需要更多,請",
"if_you_really_want_that_answer_ask_until_you_get_it": "每次觸發時都顯示,直到提交回應為止。",
"ignore_global_waiting_time": "忽略冷卻期",
@@ -1368,7 +1391,9 @@
"initial_value": "初始值",
"inner_text": "內部文字",
"input_border_color": "輸入邊框顏色",
"input_border_color_description": "描繪文字輸入框和文字區域的邊框。",
"input_color": "輸入顏色",
"input_color_description": "填滿文字輸入框的內部。",
"insert_link": "插入 連結",
"invalid_targeting": "目標設定無效:請檢查您的受眾篩選器",
"invalid_video_url_warning": "請輸入有效的 YouTube、Vimeo 或 Loom 網址。我們目前不支援其他影片託管提供者。",
@@ -1395,10 +1420,10 @@
"key": "金鑰",
"last_name": "姓氏",
"let_people_upload_up_to_25_files_at_the_same_time": "允許使用者同時上傳最多 25 個檔案。",
"limit_file_types": "限制檔案類型",
"limit_the_maximum_file_size": "限制最大檔案大小",
"limit_upload_file_size_to": "限制上傳檔案大小為",
"limit_the_maximum_file_size": "限制上傳檔案的最大大小。",
"limit_upload_file_size_to": "將上傳檔案大小限制為",
"link_survey_description": "分享問卷頁面的連結或將其嵌入網頁或電子郵件中。",
"list": "清單",
"load_segment": "載入區隔",
"logic_error_warning": "變更將導致邏輯錯誤",
"logic_error_warning_text": "變更問題類型將會從此問題中移除邏輯條件",
@@ -1442,22 +1467,22 @@
"picture_idx": "圖片 '{'idx'}'",
"pin_can_only_contain_numbers": "PIN 碼只能包含數字。",
"pin_must_be_a_four_digit_number": "PIN 碼必須是四位數的數字。",
"please_enter_a_file_extension": "請輸入檔案副檔名。",
"please_enter_a_valid_url": "請輸入有效的 URL(例如:https://example.com",
"please_set_a_survey_trigger": "請設定問卷觸發器",
"please_specify": "請指定",
"prevent_double_submission": "防止重複提交",
"prevent_double_submission_description": "每個電子郵件地址僅允許 1 個回應",
"progress_saved": "進度已儲存",
"protect_survey_with_pin": "使用 PIN 碼保護問卷",
"protect_survey_with_pin_description": "只有擁有 PIN 碼的使用者才能存取問卷。",
"publish": "發布",
"question": "問題",
"question_color": "問題顏色",
"question_deleted": "問題已刪除。",
"question_duplicated": "問題已複製。",
"question_id_updated": "問題 ID 已更新",
"question_used_in_logic": "此問題用於問題 '{'questionIndex'}' 的邏輯中。",
"question_used_in_quota": "此問題 正被使用於 \"{quotaName}\" 配額中",
"question_used_in_logic_warning_text": "此區塊中的元素已用於邏輯規則,確定要刪除嗎?",
"question_used_in_logic_warning_title": "邏輯不一致",
"question_used_in_quota": "此問題正被使用於「{quotaName}」配額中",
"question_used_in_recall": "此問題於問題 {questionIndex} 中被召回。",
"question_used_in_recall_ending_card": "此問題於結尾卡中被召回。",
"quotas": {
@@ -1513,6 +1538,7 @@
"response_limits_redirections_and_more": "回應限制、重新導向等。",
"response_options": "回應選項",
"roundness": "圓角",
"roundness_description": "調整卡片邊角的圓弧度。",
"row_used_in_logic_error": "此 row 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
"rows": "列",
"save_and_close": "儲存並關閉",
@@ -1520,6 +1546,7 @@
"search_for_images": "搜尋圖片",
"seconds_after_trigger_the_survey_will_be_closed_if_no_response": "如果沒有回應,則在觸發後幾秒關閉問卷",
"seconds_before_showing_the_survey": "秒後顯示問卷。",
"select_field": "選擇欄位",
"select_or_type_value": "選取或輸入值",
"select_ordering": "選取排序",
"select_saved_action": "選取已儲存的操作",
@@ -1553,7 +1580,6 @@
"styling_set_to_theme_styles": "樣式設定為主題樣式",
"subheading": "副標題",
"subtract": "減 -",
"suggest_colors": "建議顏色",
"survey_completed_heading": "問卷已完成",
"survey_completed_subheading": "此免費且開源的問卷已關閉",
"survey_display_settings": "問卷顯示設定",
@@ -1567,8 +1593,6 @@
"the_survey_will_be_shown_once_even_if_person_doesnt_respond": "僅顯示一次,即使他們未回應。",
"then": "然後",
"this_action_will_remove_all_the_translations_from_this_survey": "此操作將從此問卷中移除所有翻譯。",
"this_extension_is_already_added": "已新增此擴充功能。",
"this_file_type_is_not_supported": "不支援此檔案類型。",
"three_points": "3 分",
"times": "次",
"to_keep_the_placement_over_all_surveys_consistent_you_can": "若要保持所有問卷的位置一致,您可以",
@@ -1589,8 +1613,51 @@
"upper_label": "上標籤",
"url_filters": "網址篩選器",
"url_not_supported": "不支援網址",
"validation": {
"add_validation_rule": "新增驗證規則",
"answer_all_rows": "請填答所有列",
"characters": "字元",
"contains": "包含",
"delete_validation_rule": "刪除驗證規則",
"does_not_contain": "不包含",
"email": "是有效的電子郵件",
"end_date": "結束日期",
"file_extension_is": "檔案副檔名為",
"file_extension_is_not": "檔案副檔名不是",
"is": "等於",
"is_between": "介於",
"is_earlier_than": "早於",
"is_greater_than": "大於",
"is_later_than": "晚於",
"is_less_than": "小於",
"is_not": "不等於",
"is_not_between": "不介於",
"kb": "KB",
"max_length": "最多",
"max_selections": "最多",
"max_value": "最多",
"mb": "MB",
"min_length": "至少",
"min_selections": "至少",
"min_value": "至少",
"minimum_options_ranked": "最少排序選項數",
"minimum_rows_answered": "最少作答列數",
"options_selected": "已選擇的選項",
"pattern": "符合正則表達式樣式",
"phone": "是有效的電話號碼",
"rank_all_options": "請為所有選項排序",
"select_file_extensions": "請選擇檔案副檔名...",
"select_option": "選擇選項",
"start_date": "開始日期",
"url": "是有效的 URL"
},
"validation_logic_and": "全部為真",
"validation_logic_or": "任一為真",
"validation_rules": "驗證規則",
"validation_rules_description": "僅接受符合下列條件的回應",
"variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "'{'variable'}' 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "變數 \"{variableName}\" 正被使用於 \"{quotaName}\" 配額中",
"variable_is_used_in_quota_please_remove_it_from_quota_first": "變數{variableName}正被使用於{quotaName}配額中",
"variable_name_conflicts_with_hidden_field": "變數名稱與現有的隱藏欄位 ID 衝突。",
"variable_name_is_already_taken_please_choose_another": "已使用此變數名稱,請選擇另一個名稱。",
"variable_name_must_start_with_a_letter": "變數名稱必須以字母開頭。",
"variable_used_in_recall": "變數 \"{variable}\" 於問題 {questionIndex} 中被召回。",
@@ -1646,6 +1713,7 @@
"error_downloading_responses": "下載回應時發生錯誤",
"first_name": "名字",
"how_to_identify_users": "如何識別使用者",
"ip_address": "IP 位址",
"last_name": "姓氏",
"not_completed": "未完成 ⏳",
"os": "作業系統",
@@ -1995,9 +2063,71 @@
"look": {
"add_background_color": "新增背景顏色",
"add_background_color_description": "為標誌容器新增背景顏色。",
"advanced_styling_field_border_radius": "邊框圓角",
"advanced_styling_field_button_bg": "按鈕背景",
"advanced_styling_field_button_bg_description": "填滿「下一步」/「送出」按鈕。",
"advanced_styling_field_button_border_radius_description": "調整按鈕的圓角。",
"advanced_styling_field_button_font_size_description": "調整按鈕標籤文字的大小。",
"advanced_styling_field_button_font_weight_description": "讓按鈕文字變細或變粗。",
"advanced_styling_field_button_height_description": "調整按鈕的高度。",
"advanced_styling_field_button_padding_x_description": "在左右兩側增加間距。",
"advanced_styling_field_button_padding_y_description": "在上下兩側增加間距。",
"advanced_styling_field_button_text": "按鈕文字",
"advanced_styling_field_button_text_description": "設定按鈕內標籤的顏色。",
"advanced_styling_field_description_color": "說明文字顏色",
"advanced_styling_field_description_color_description": "設定每個標題下方文字的顏色。",
"advanced_styling_field_description_size": "說明文字大小",
"advanced_styling_field_description_size_description": "調整說明文字的大小。",
"advanced_styling_field_description_weight": "說明文字粗細",
"advanced_styling_field_description_weight_description": "讓說明文字變細或變粗。",
"advanced_styling_field_font_size": "字體大小",
"advanced_styling_field_font_weight": "字體粗細",
"advanced_styling_field_headline_color": "標題顏色",
"advanced_styling_field_headline_color_description": "設定主要問題文字的顏色。",
"advanced_styling_field_headline_size": "標題字體大小",
"advanced_styling_field_headline_size_description": "調整標題文字的大小。",
"advanced_styling_field_headline_weight": "標題字體粗細",
"advanced_styling_field_headline_weight_description": "讓標題文字變細或變粗。",
"advanced_styling_field_height": "高度",
"advanced_styling_field_indicator_bg": "指示器背景",
"advanced_styling_field_indicator_bg_description": "設定進度條已填滿部分的顏色。",
"advanced_styling_field_input_border_radius_description": "調整輸入框的圓角。",
"advanced_styling_field_input_font_size_description": "調整輸入框內輸入文字的大小。",
"advanced_styling_field_input_height_description": "調整輸入欄位的高度。",
"advanced_styling_field_input_padding_x_description": "在左右兩側增加間距。",
"advanced_styling_field_input_padding_y_description": "在上方和下方增加間距。",
"advanced_styling_field_input_placeholder_opacity_description": "讓提示文字變得更淡。",
"advanced_styling_field_input_shadow_description": "在輸入框周圍加上陰影。",
"advanced_styling_field_input_text": "輸入文字",
"advanced_styling_field_input_text_description": "設定輸入文字的顏色。",
"advanced_styling_field_option_bg": "背景",
"advanced_styling_field_option_bg_description": "填滿選項項目背景。",
"advanced_styling_field_option_border_radius_description": "讓選項的邊角變圓。",
"advanced_styling_field_option_font_size_description": "調整選項標籤文字的大小。",
"advanced_styling_field_option_label": "標籤顏色",
"advanced_styling_field_option_label_description": "設定選項標籤文字的顏色。",
"advanced_styling_field_option_padding_x_description": "在左側和右側增加間距。",
"advanced_styling_field_option_padding_y_description": "在上方和下方增加間距。",
"advanced_styling_field_padding_x": "左右內距",
"advanced_styling_field_padding_y": "上下內距",
"advanced_styling_field_placeholder_opacity": "預設文字透明度",
"advanced_styling_field_shadow": "陰影",
"advanced_styling_field_track_bg": "軌道背景",
"advanced_styling_field_track_bg_description": "設定進度條未填滿部分的顏色。",
"advanced_styling_field_track_height": "軌道高度",
"advanced_styling_field_track_height_description": "調整進度條的厚度。",
"advanced_styling_field_upper_label_color": "標題標籤顏色",
"advanced_styling_field_upper_label_color_description": "設定輸入框上方小標籤的顏色。",
"advanced_styling_field_upper_label_size": "標題標籤字體大小",
"advanced_styling_field_upper_label_size_description": "調整輸入框上方小標籤的大小。",
"advanced_styling_field_upper_label_weight": "標題標籤字體粗細",
"advanced_styling_field_upper_label_weight_description": "讓標籤字體變細或變粗。",
"advanced_styling_section_buttons": "按鈕",
"advanced_styling_section_headlines": "標題與說明",
"advanced_styling_section_inputs": "輸入欄位",
"advanced_styling_section_options": "選項(單選/複選)",
"app_survey_placement": "應用程式問卷位置",
"app_survey_placement_settings_description": "變更問卷在您的網頁應用程式或網站中顯示的位置。",
"centered_modal_overlay_color": "置中彈窗覆蓋顏色",
"email_customization": "電子郵件自訂化",
"email_customization_description": "變更 Formbricks 代表您發送的電子郵件外觀與風格。",
"enable_custom_styling": "啟用自訂樣式",
@@ -2008,6 +2138,9 @@
"formbricks_branding_hidden": "Formbricks 品牌標示已隱藏。",
"formbricks_branding_settings_description": "我們很感謝您的支持,但若您選擇關閉我們也能理解。",
"formbricks_branding_shown": "Formbricks 品牌標示已顯示。",
"generate_theme_btn": "產生",
"generate_theme_confirmation": "你想根據品牌色產生一組相符的主題色嗎?這將會覆蓋你目前的顏色設定。",
"generate_theme_header": "要產生主題色嗎?",
"logo_removed_successfully": "標誌已成功移除",
"logo_settings_description": "上傳您的公司標誌,以用於問卷和連結預覽的品牌展示。",
"logo_updated_successfully": "標誌已成功更新",
@@ -2022,6 +2155,7 @@
"show_formbricks_branding_in": "在 {type} 問卷中顯示 Formbricks 品牌標示",
"show_powered_by_formbricks": "顯示「Powered by Formbricks」標記",
"styling_updated_successfully": "樣式已成功更新",
"suggest_colors": "建議顏色",
"theme": "主題",
"theme_settings_description": "為所有調查建立樣式主題。您可以為每個調查啟用自訂樣式。"
},
@@ -2785,6 +2919,7 @@
"preview_survey_question_2_choice_1_label": "是,請保持通知我。",
"preview_survey_question_2_choice_2_label": "不用了,謝謝!",
"preview_survey_question_2_headline": "想要緊跟最新動態嗎?",
"preview_survey_question_2_subheader": "這是一個範例說明。",
"preview_survey_welcome_card_headline": "歡迎!",
"prioritize_features_description": "找出您的使用者最需要和最不需要的功能。",
"prioritize_features_name": "優先排序功能",
@@ -1,11 +1,15 @@
import { Languages } from "lucide-react";
import { useRef, useState } from "react";
import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { getEnabledLanguages } from "@/lib/i18n/utils";
import { useClickOutside } from "@/lib/utils/hooks/useClickOutside";
import { Button } from "@/modules/ui/components/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
interface LanguageDropdownProps {
survey: TSurvey;
@@ -14,38 +18,31 @@ interface LanguageDropdownProps {
}
export const LanguageDropdown = ({ survey, setLanguage, locale }: LanguageDropdownProps) => {
const [showLanguageSelect, setShowLanguageSelect] = useState(false);
const containerRef = useRef(null);
const enabledLanguages = getEnabledLanguages(survey.languages ?? []);
useClickOutside(containerRef, () => setShowLanguageSelect(false));
if (enabledLanguages.length <= 1) {
return null;
}
return (
enabledLanguages.length > 1 && (
<div className="relative" ref={containerRef}>
{showLanguageSelect && (
<div className="absolute top-12 z-30 max-h-64 max-w-48 overflow-auto rounded-lg border bg-slate-900 p-1 text-sm text-white">
{enabledLanguages.map((surveyLanguage) => (
<button
key={surveyLanguage.language.code}
className="w-full truncate rounded-md p-2 text-start hover:cursor-pointer hover:bg-slate-700"
onClick={() => {
setLanguage(surveyLanguage.language.code);
setShowLanguageSelect(false);
}}>
{getLanguageLabel(surveyLanguage.language.code, locale)}
</button>
))}
</div>
)}
<Button
variant="secondary"
title="Select Language"
aria-label="Select Language"
onClick={() => setShowLanguageSelect(!showLanguageSelect)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" title="Select Language" aria-label="Select Language">
<Languages className="h-5 w-5" />
</Button>
</div>
)
</DropdownMenuTrigger>
<DropdownMenuContent
className="max-h-64 max-w-48 overflow-auto bg-slate-900 p-1 text-sm text-white"
align="start">
{enabledLanguages.map((surveyLanguage) => (
<DropdownMenuItem
key={surveyLanguage.language.code}
className="w-full truncate rounded-md p-2 text-start text-white hover:cursor-pointer hover:bg-slate-700 focus:bg-slate-700"
onSelect={() => setLanguage(surveyLanguage.language.code)}>
{getLanguageLabel(surveyLanguage.language.code, locale)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};
@@ -3,6 +3,7 @@
import { CheckCircle2Icon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TResponseWithQuotas } from "@formbricks/types/responses";
import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/constants";
import { TSurvey } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { getLocalizedValue } from "@/lib/i18n/utils";
@@ -67,6 +68,16 @@ export const SingleResponseCardBody = ({
<VerifiedEmail responseData={response.data} />
)}
{elements.map((question) => {
// Skip CTA elements without external buttons only if they have no response data
// This preserves historical data from when buttonExternal was true
if (
question.type === TSurveyElementTypeEnum.CTA &&
!question.buttonExternal &&
!response.data[question.id]
) {
return null;
}
const skipped = skippedQuestions.find((skippedQuestionElement) =>
skippedQuestionElement.includes(question.id)
);

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