Compare commits

...
Author SHA1 Message Date
emrysal a4be822636 chore: release v5.7.8
Post release / release (push) Canceled after 0s
2025-10-04 14:20:17 +00:00
Syed Ali Shahbaz 7cd8dbf7d8 chore: Watchlist schema update (#24246)
* watchlist schema update

* changes addressed

* no backfill audit

* fix type

* fix flow of migration

* fix err

* type err fix

* add if exists check in migration
2025-10-04 01:44:37 +01:00
654a865007 refactor: Salesforce getContacts (#24235)
* lint fixes

* Create getContactOrLeadFromEmail method

* Use getContactOrLeadFromEmail in getContacts

* Move normal getContact query

* fix: correct return type in getContactOrLeadFromEmail and add search mock

- Fix getContactOrLeadFromEmail to return single ContactRecord instead of array
- Add conn.search() implementation to salesforceMock for integration tests
- Ensure id field is always string with proper fallbacks for AccountId and Id
- Fix Owner.Email to use undefined instead of null in mock

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* test: add comprehensive tests for getContactOrLeadFromEmail method

- Add 6 new unit tests covering all key scenarios
- Test contact lookup, lead fallback, and preference logic
- Test both roundRobinSkipFallbackToLeadOwner and createEventOnLeadCheckForContact code paths
- Add search mock support to mockConnection in beforeEach
- All 34 tests passing, type-check clean

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Implement change to error from #24090

* Address feedback

* Type fix

* test: update SOQL query expectations to include Account.OwnerId

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-10-03 14:53:00 -04:00
Alex van AndelandDevin AI 409a0277fb fix: Resolve BillingPlan tree-shaking issue causing runtime error (#24229)
* fix: Convert BillingPlan enum to const object to prevent webpack tree-shaking

TypeScript enums compile to IIFEs which can be incorrectly tree-shaken by webpack
when 'sideEffects: false' is set in package.json. Converting to a const object
with 'as const' avoids the IIFE pattern while maintaining full type safety.

This fixes the 'TRPCError: BillingPlan is not defined' error that occurred in
the hasTeamPlan tRPC handler.

Follows the existing CHECKOUT_SESSION_TYPES pattern in the same file.

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* fix: Re-export BillingPlan from billing-plans to prevent tree-shaking

The issue was a module initialization order problem. When BillingPlan was
imported from constants.ts into billing-plans.ts and used inside the
BillingPlanService class, webpack's tree-shaker (with sideEffects: false)
couldn't properly track the value dependency across the package boundary.

By re-exporting BillingPlan from the same module that exports
BillingPlanService, we ensure the constant is bundled together with the
class that uses it, preventing tree-shaking.

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* revert: Restore BillingPlan enum format in constants.ts

The const object conversion didn't fix the tree-shaking issue. The real
fix is re-exporting BillingPlan from billing-plans.ts to ensure it's
part of the same module as BillingPlanService.

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* fix: Add BillingPlan as private static member to prevent tree-shaking

By making BillingPlan a private static member of BillingPlanService, webpack
now sees it as part of the class definition rather than just used inside
methods. This creates a strong reference that prevents webpack's tree-shaker
from removing the enum initialization IIFE when 'sideEffects: false' is set.

With the previous approach where BillingPlan was only imported and used inside
methods, webpack's static analysis couldn't properly track the enum usage
across package boundaries (@calcom/features -> @calcom/trpc), causing it to
incorrectly determine the enum initialization was unused code.

Fixes the TRPCError: BillingPlan is not defined runtime error.

Thread: https://calendso.slack.com/archives/C08LT9BLEET/p1759420015428149
Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* fix: Clean up BillingPlan references and add explanatory comment

- Remove unnecessary re-export statement
- Use direct BillingPlan references instead of BillingPlanService.BillingPlan
- Add detailed comment explaining webpack tree-shaking workaround
- Keep import from constants.ts and private static member for webpack reference

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* fix: Remove Slack reference from comment and restore static usage

- Remove Slack URL from explanatory comment
- Restore BillingPlanService.BillingPlan usage throughout class methods
- This static member usage is essential for webpack to track the enum reference

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* Fix BillingPlan issue by finding root cause

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-10-03 16:37:45 +01:00
Anik Dhabal Babu 7adab1f65f fix: event not showing up on outlook through ics file (#24249) 2025-10-03 13:52:25 +00:00
Calcom BotandLingo.dev 3b92c86582 feat: update translations via @LingoDotDev (#24248)
Co-authored-by: Lingo.dev <support@lingo.dev>
2025-10-03 12:42:14 +00:00
Devanshu Sharma de195a1c0f feat: add email filtering to team memberships endpoint (#23923)
* Controller Layer updates.

* adding email filtering and pagination to team memberships endpoint..

* Minor enhancements.

* Improve addressed.

* refactor: update team memberships input to handle comma-separated emails

- Replace array format with comma-separated string handling
- Add proper email validation with BadRequestException
- Remove ArrayMaxSize constraint for better flexibility
- Update API documentation and examples
- Align with codebase patterns from get-managed-users.input.ts

* Morgan suggestions addressed.

* More improvements......
2025-10-03 18:09:47 +05:30
an_ifrah24andDevanshu Sharma d7d2487f6d fix: hydration error warning in scheduleListItem component (#24240)
Co-authored-by: Devanshu Sharma <devanshusharma658@gmail.com>
2025-10-03 17:52:32 +05:30
sean-brydon 3e31945157 Fix nits (#24244) 2025-10-03 11:03:07 +00:00
Lauris Skraucis c11814c342 fix: managed user timeZone validation (#24226)
* fix: managed user timeZone validation

* fix: managed user timeZone validation

* fix: managed user timeZone validation
2025-10-03 08:23:54 +00:00
Syed Ali Shahbaz 60e3244088 Migrate watchlist severity to action(#24195) 2025-10-03 11:18:14 +04:00
Rajiv Sahal a1ac5f36f0 feat: add atoms section inside developing guides (#24220) 2025-10-02 21:59:43 +05:30
Lauris SkraucisandMorgan 6f38d40613 refactor: dont allow scheduling team event type without hosts (#24206)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-10-02 15:58:42 +00:00
Lauris Skraucis 6b7d22de3f refactor: fail breaking change check on error only (#24221) 2025-10-02 14:42:49 +00:00
Anik Dhabal Babu 2f763dceda fix: Getting an unauthorized error when accessing the workflow tab for a child-managed event (#24219)
* Update get.handler.ts

* fix: Ignore userIds form filter segment if no permission

* add tests

* Update booking-filters.e2e.ts

* Update booking-filters.e2e.ts

* fix test

* fix: got unautorized error when viewing workflows

* fix
2025-10-02 13:38:21 +00:00
Benny Joo 96468c4083 refactor: move @calcom/lib/di folder to @calcom/features (#24199)
* mv di folder

* update imports

* fix

* fix

* fix test
2025-10-02 08:12:06 -03:00
sean-brydonandBenny Joo 858d52563f feat: pbac - org pages check update permissions (#24164)
* pbac - org pages check update permissions

* restore permision service

* fix type check

* more type fixes

---------

Co-authored-by: Benny Joo <sldisek783@gmail.com>
2025-10-02 20:04:21 +09:00
7505b3b271 feat: add missing sensitive fields to redactSensitiveData (#24121)
- Add private_key, encrypted_credentials, tenant_id, client_email, serviceAccountKey to SENSITIVE_FIELDS array
- Add comprehensive test cases for delegation credential sensitive fields
- Prevents logging of sensitive delegation credential data including private keys

Fixes security issue where delegation credential sensitive fields were not being redacted from logs

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: morgan@cal.com <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-10-01 20:26:58 -03:00
Calcom BotandLingo.dev 758dc88b55 feat: update translations via @LingoDotDev (#24203)
Co-authored-by: Lingo.dev <support@lingo.dev>
2025-10-01 23:24:17 +00:00
Udit Takkar 9be418dc65 chore: update description (#24189) 2025-10-01 23:35:41 +05:30
Volnei Munhoz 2ac310456e fix: Selected calendar delegation credentials (#24190)
* Fix selected calendar delegation credentials
2025-10-01 14:20:36 +00:00
ff264d6f7a fix: allow team with same slug for diff cases (#24029)
* fix: aalow team with slug for diff cases

* addressed review

* fix type error

* update test

* addressed review

* fix test

* Update team.ts

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2025-10-01 14:04:20 +00:00
8019c414e1 chore: CSRF protect forgot-password functionality (#22361)
* chore: CSRF protect forgot-password functionality

* feat: implement conditional sameSite cookie setting for CSRF tokens

- Use WEBAPP_URL to determine secure cookie settings
- Set sameSite to 'none' for HTTPS environments to support cross-origin scenarios
- Fall back to 'lax' for HTTP development environments
- Follows patterns from PRs #23439 and #23556

Co-Authored-By: alex@cal.com <me@alexvanandel.com>

* update

* change

* NIT

* minor

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-10-01 13:38:29 +00:00
chauhan_s a3fbe3731e feat: add host validation for team event types (#24170)
* feat: add host validation for team event types

* refactor: remove hosts validator in favor of documentation-based validation approach

* fix: remove HostsOrAssignAllValidator from event type update inputs

* docs: improve clarity of hosts and assignAllTeamMembers field descriptions in event type inputs
2025-10-01 16:15:40 +05:30
sean-brydonandcoderabbitai[bot] e3742dbc66 fix: Always enforce one owner with pbac (#24144)
* Always enforce one owner

* updates from merge

* remove redudant eslint rules

* Update packages/features/pbac/services/pbac-role-manager.service.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-01 11:06:18 +01:00
Rajiv SahalandMorgan 83bf717d6d fix: customReplyEmailTo feedback (#23738)
* fix: move validateRoundRobinSlotAvailability to core libraries

* fix: implement PR feedback

* fix: merge conflicts

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-10-01 15:32:32 +05:30
Peer RichelsenandDevin AI 7fcdaa1c46 feat: add created_at field display to admin user edit page (#23805)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-10-01 09:58:20 +00:00
Kartik LabhshetwarandKartik Saini a8154f9405 feat(ui/api-keys): add confirmation dialog for API key deletion (#24118)
* feat(api-keys): add confirmation dialog for API key deletion

* fix: changed text color to subtle

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
2025-10-01 09:56:55 +00:00
chauhan_sandLauris Skraucis 52a5afeba5 fix: different calendar hosts (#24000)
* fis: pass the missing `platformClientId` to `handleNewBooking` in api/v2

* Update formatCalendarEvent.ts

* only add the externald in case of same calendar, otherwise add if from the .ics file sent in email

* Revert "fis: pass the missing `platformClientId` to `handleNewBooking` in api/v2"

This reverts commit 030ac0f0981c1135d4973fbdafa62d58e8985831.

* fix: alow the host change logic to run for collective envets as well

* fix: run the changedOrganizer logic for both collective and round-robin events

* Update handleNewBooking.ts

* fix: failing unit test

---------

Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2025-10-01 12:15:34 +03:00
Anik Dhabal Babu 43cc40d7fe fix: add permissions to workflows (#24192) 2025-10-01 06:53:08 +00:00
Hariom Balhara 42a98f000e fix: 405 method not allowed error for queuedFormResponseCleanup (#24191) 2025-10-01 04:55:48 +00:00
Joe Au-Yeung f003ef935b Remove safeStringify from graphql errors (#24188) 2025-10-01 09:50:15 +05:30
Volnei Munhoz e2b2a184c8 fix: Calendar Cache sync page (#24182)
* fix

* fix calendar sync page
2025-09-30 17:10:51 +01:00
Calcom BotandLingo.dev 094595c435 feat: update translations via @LingoDotDev (#24178)
Co-authored-by: Lingo.dev <support@lingo.dev>
2025-09-30 13:51:13 +00:00
Benny Joo 4118a86c0d refactor: do a permission check in removeHostsFromEventTypes trpc handler (#24176) 2025-09-30 13:30:12 +00:00
sean-brydon 928d6f3821 chore: PBAC routingform entity permissions to favour pbac (#24130)
* Remove routingform entity permissions to favour pbac

* push typo

* fix types

* update returns

* Remove unused function

* nits

* fix hariom feedback

* fix correct write permissions
2025-09-30 18:55:43 +05:30
Benny Joo b2ade568c8 refactor: more pbac replacements (#24135)
Post release / release (push) Canceled after 0s
* getUserConnectedApps

* create teams

* use permission check in team deletion

* team read

* revert

* publish handler

* wip

* revert

* wip

* wip
2025-09-30 13:11:02 +00:00
27820ce897 feat: auto-accept team invitations for existing users (#24091)
* feat: auto-accept team invitations for existing users

- Change email button text from 'View Invitation' to 'Accept Invite'
- Implement auto-accept flow when clicking email CTA
- Update TeamService.inviteMemberByToken to support auto-acceptance
- Add new autoAcceptInvite tRPC endpoint for handling auto-acceptance
- Update invitation link generation to include autoAccept parameter
- Handle both team and organization invitation scenarios
- Maintain payment/billing flow integration with TeamBilling.updateQuantity
- Preserve backward compatibility with existing manual flow
- Update all locale files with new 'Accept Invite' button text

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* revert: locale changes except English

- Keep only English 'Accept Invite' translation
- Revert all other locale files to original 'View Invitation' translations
- Maintain core auto-accept invitation functionality

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* simplify: remove autoAccept parameter and make auto-acceptance default

- Remove autoAccept parameter from TeamService.inviteMemberByToken
- Always auto-accept invitations for existing users clicking email links
- Remove autoAccept logic from teams server-page.tsx
- Remove autoAccept=true from invitation URLs
- Delete autoAcceptInvite handler and schema files
- Remove autoAcceptInvite endpoint from tRPC router
- Simplify invitation flow to match new user pattern

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* test: update teamService test to expect auto-accepted memberships

- Change expectation from accepted: false to accepted: true
- Update test description to reflect auto-accept behavior
- Fix TypeScript type casting to use Pick<TeamRepository, 'deleteById'>
- Aligns with new default auto-acceptance for team invitations

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* update

* Update utils.ts

* fix type error

* delete token

* add prisma transaction

* update

* update param

* test: fix mock objects in teamService tests with realistic data

- Fix duplicate property assignments in mock user objects
- Use proper email format (user@example.com) for email fields
- Use proper username format (testuser) for username fields
- Fix logic error in acceptInvitationByToken (|| to &&)
- Add autoAccept parameter to resendInvitation.handler.ts
- All 16 tests passing with proper TypeScript types

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* test: add e2e tests for team invitation auto-accept flow

- Add test for existing user auto-accepting team invitation via email link
- Add test for error handling when wrong user tries to use invitation link
- Verify proper user identity validation and database state changes
- Follow existing e2e test patterns with browser context isolation
- Fix ESLint warnings: replace conditional with assertion and remove unused browser parameter

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* fix

* fix

* fix: update team owner creation in e2e tests to include proper names

- Fix email subject mismatch in auto-accept invitation tests
- Team owners now created with explicit names instead of undefined
- Matches pattern used in other working team invitation tests

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* fix: update organization invitation test helper to match new auto-accept link format

- Change expectExistingUserToBeInvitedToOrganization to look for 'teams?token' instead of 'settings/team'
- Fixes 'Invite link not found' error in organization booking e2e test
- Aligns with auto-accept invitation URL changes that use /teams?token= format
- Fix eslint disable comment for playwright rule

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* address coderrabit review

* fix failing test

* addressed review

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-30 13:07:21 +00:00
emrysal fea7e66996 chore: release v5.7.7 2025-09-30 12:57:27 +00:00
sean-brydon 474d1571ae Remove checkAdminOrOwner from settings layout client (#24162) 2025-09-30 12:08:05 +01:00
Rajiv Sahal 2ef627b87e fix: css issue for 12/24 styles (#24141) 2025-09-30 08:38:17 +00:00
sean-brydon 2dfb82b623 Bring back correct routing form teams (#24158) 2025-09-30 08:37:16 +00:00
sean-brydon ccd7fdfd52 refactor role manage (#24146) 2025-09-30 08:35:26 +01:00
Keith Williams f92e59ed34 chore: Update yarn.lock (#24153) 2025-09-30 02:19:30 +00:00
Keith WilliamsandDevin AI d29477349a test: replace prismaMock with BookingRepository mock in checkBookingLimits tests (#24152)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-29 23:34:20 +00:00
Alex van Andel 259e480baa chore: Handle stripe refunds slightly differently to reduce hard errors (#24150)
* chore: Handle stripe refunds slightly differently to reduce hard errors

* Remove 'Received and discarded' error when a credential is not found for a triggered subscription

* Fix handling of no credential found (do show in stripe debugger)

* Minimal overhaul of delete handling

* Fix doc typo
2025-09-29 23:07:08 +00:00
Benny Joo 0055dd1142 perf: dynamically import sentry in Next.js api routes' wrapper (#23710)
* dynamically import sentry in nextjs api routes wrapper

* refactor

* same for pages router

* fixes

* address feedback

* refactor

* address feedback
2025-09-30 02:13:50 +05:30
Volnei Munhoz 61c44780f0 chore: Optional skip warnings for lint-staged (#24102)
* add optional skip warnings

* remove test changes

* allow underscore variables pattern being ignored by eslint (#24103)
2025-09-29 19:51:18 +00:00
Calcom BotandLingo.dev 9710d3b15c feat: update translations via @LingoDotDev (#23991)
Co-authored-by: Lingo.dev <support@lingo.dev>
2025-09-29 19:23:59 +00:00
sean-brydon 04cefebd75 add permissions.canUpdateOrganization to show/hide items in org navbar (#24142) 2025-09-29 15:11:39 +00:00
Amit Sharma dbd927c002 fix: intercom zindex (#24096) 2025-09-29 11:43:11 -03:00
e6b2116a2b feat: Calendar Cache and Sync (#23876)
* feat: calendar cache and sync - wip

* Add env.example

* refactor on CalendarCacheEventService

* remove test console.log

* Fix type checks errors

* chore: remove pt comment

* add route.ts

* chore: fix tests

* Improve cache impl

* chore: update recurring event id

* chore: small improvements

* calendar cache improvements

* Fix remove dynamic imports

* Add cleanup stale cache

* Fix tests

* add event update

* type fixes

* feat: add comprehensive tests for new calendar subscription API routes

- Add tests for /api/cron/calendar-subscriptions-cleanup route (9 tests)
- Add tests for /api/cron/calendar-subscriptions route (10 tests)
- Add tests for /api/webhooks/calendar-subscription/[provider] route (11 tests)
- Add missing feature flags for calendar-subscription-cache and calendar-subscription-sync
- All 30 tests pass with comprehensive coverage of authentication, feature flags, error handling, and service instantiation

Tests cover:
- Authentication scenarios (API key validation, Bearer tokens, query parameters)
- Feature flag combinations (cache/sync enabled/disabled states)
- Success and error handling (including non-Error exceptions)
- Service instantiation with proper dependency injection
- Provider validation for webhook endpoints

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* feat: add comprehensive tests for calendar subscription services, repositories, and adapters

- Add unit tests for CalendarSubscriptionService with subscription, webhook, and event processing
- Add unit tests for CalendarCacheEventService with cache operations and cleanup
- Add unit tests for CalendarSyncService with Cal.com event filtering and booking operations
- Add unit tests for CalendarCacheEventRepository with CRUD operations
- Add unit tests for SelectedCalendarRepository with calendar selection management
- Add unit tests for GoogleCalendarSubscriptionAdapter with subscription and event fetching
- Add unit tests for Office365CalendarSubscriptionAdapter with placeholder implementation
- Add unit tests for AdaptersFactory with provider management and adapter creation
- Fix lint issues by removing explicit 'any' type casting and unused variables
- All tests follow Cal.com conventions using Vitest framework with proper mocking

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* fix: improve calendar-subscriptions-cleanup test performance by adding missing mocks

- Add comprehensive mocks for defaultResponderForAppDir, logger, performance monitoring, and Sentry
- Fix slow test execution (933ms -> <100ms) caused by missing dependency mocks
- Ensure consistent test performance across different environments

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* Fix tests

* Fix tests

* type fix

* Fix coderabbit comments

* Fix types

* Fix test

* Update apps/web/app/api/cron/calendar-subscriptions/route.ts

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Fixes by first review

* feat: add database migrations for calendar cache and sync fields

- Add CalendarCacheEventStatus enum with confirmed, tentative, cancelled values
- Add new fields to SelectedCalendar: channelId, channelKind, channelResourceId, channelResourceUri, channelExpiration, syncSubscribedAt, syncToken, syncedAt, syncErrorAt, syncErrorCount
- Create CalendarCacheEvent table with foreign key to SelectedCalendar
- Add necessary indexes and constraints for performance and data integrity

Fixes database schema issues causing e2e test failures with 'column does not exist' errors.

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* only google-calendar for now

* docs: add Calendar Cache and Sync feature documentation

- Add comprehensive feature overview and motivation
- Document feature flags with SQL examples
- Include SQL examples for enabling features for users and teams
- Reference technical documentation files

Addresses PR #23876 documentation requirements

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* docs: update calendar subscription README with comprehensive documentation

- Undo incorrect changes to main README.md
- Update packages/features/calendar-subscription/README.md with:
  - Feature overview and motivation
  - Environment variables section
  - Complete feature flags documentation with SQL examples
  - SQL examples for enabling features for users and teams
  - Detailed architecture documentation

Addresses PR #23876 documentation requirements

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* fix docs

* Fix test to available calendars

* Fix test to available calendars

* add migration and sync boilerplate

* fix typo

* remove double log

* sync boilerplate

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-09-29 14:26:14 +00:00
Udit Takkar 855a02091f chore: update tooltip text (#24143) 2025-09-29 11:20:28 -03:00
Volnei MunhozandDevin AI 358434429e feat: add booking flags for skipAvailabilityCheck, skipEventLimitsCheck, skipCalendarSyncTaskCreation (#24122)
- Add three optional boolean flags to BookingHandlerInput interface
- Implement conditional logic to skip availability checks when skipAvailabilityCheck is true
- Implement conditional logic to skip event limits checks when skipEventLimitsCheck is true
- Implement conditional logic to skip calendar sync when skipCalendarSyncTaskCreation is true
- Add comprehensive tests for all three flags and combined scenarios
- Maintain backward compatibility with default false values
- Fix linting issues (unused variables and expressions)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-29 14:02:31 +00:00
sean-brydonandhbjORbj b00061d85e chore: Add addMembersToTeams logic from 24099 (#24140)
* Add addMembersToTeams logic from 24099

* remove import

---------

Co-authored-by: hbjORbj <sldisek783@gmail.com>
2025-09-29 12:54:35 +00:00
Udit Takkarandsean-brydon 022f08c05e feat: add 10 minute cooldown in instant meetings (#24119)
* feat: add 10 minute

* feat: add 10 minute

* chore: change name

* chore: change name

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2025-09-29 16:03:03 +04:00
emrysal 6ff5c8ce5f chore: release v5.7.6
Post release / release (push) Canceled after 0s
2025-09-29 11:22:43 +00:00
Lauris Skraucis 3be7fb9dd8 fix: v2 create org user with time zone (#24133)
* fix: tzdata undefined import

* refactor: capitalize org user timezone

* test: org user creation with time zone
2025-09-29 11:12:18 +00:00
Benny Joo 72dd970b6e refactor: use permission check service in /organizations endpoints (#24099)
* getUserConnectedApps

* create teams

* use permission check in team deletion

* team read

* revert

* publish handler

* wip
2025-09-29 11:07:19 +00:00
sean-brydon f4d09ece43 chore: (PBAC) readonly eventTypes (#24131)
* Chore(PBAC): readonly eventTypes

* Set readonly value
2025-09-29 11:06:55 +00:00
sean-brydonandEunjae Lee 239e6085ba feat: pbac - private teams (#23998)
* Add permission + migration for listMembersPrivate + depends on lsitMember

* Add legacy list members PBAC private logic

* Add checks in team pages + handler

* add list memeber checks for private orgs

* Add permision visibility scope to loading permissions

* getMembers handler

* fix fallback permission

* Verify private teams work with private org

* Apply suggestion from @eunjae-lee

Co-authored-by: Eunjae Lee <hey@eunjae.dev>

* Remove and private scope function and add param to old

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
2025-09-29 11:30:54 +01:00
sean-brydon b17eab3aeb chore: Use enums instead of hardcoded strings (#24132)
* Use enums instead of hardcoded strings

* use membership role
2025-09-29 10:57:36 +01:00
752f7fcde0 refactor: replace checkAdminOrOwner with team.create permission in teams page (#24116)
* refactor: replace checkAdminOrOwner with team.create permission in teams page

- Replace role-based access control with PBAC using team.create permission
- Follow PBAC refactoring guide pattern for server-side permission checking
- Use permission-specific variable naming (canCreateTeam)
- Maintain existing TeamsListing component interface

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: implement permissions object pattern for TeamsListing

- Replace isOrgAdmin prop with permissions object containing canCreateTeam
- Follow PBAC refactoring guide pattern for UI components
- Update TeamsListing component to use permissions.canCreateTeam
- Maintain existing functionality while improving code structure
- All type checking and linting verification passed

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* use different method

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2025-09-29 14:11:42 +05:30
f6b061f787 fix: update handler to not delete banner (#24106)
* Fix update handler to not delete banner

* code cleanup

* add banner-upload.e2e

* fix: add slug fallback in banner-upload e2e test

- Use org.slug as fallback when requestedSlug is undefined
- Addresses GitHub comment about guarding against undefined slug
- Prevents test failures when requestedSlug is not set
- Fix ESLint warnings: remove unnecessary escape characters and unused variable

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* test: add banner preservation test for profile updates

- Verify that updating organization profile without banner changes preserves existing banner
- Ensures update handler doesn't overwrite banner when undefined is passed
- Addresses user request to test banner preservation during profile saves

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* remove banner e2e wip

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-29 09:31:49 +01:00
Eunjae LeeandDevin AI cc8a1290ee docs: add comprehensive DataTable usage guide (#24107)
* docs: add comprehensive DataTable usage guide

- Document DataTableProvider, DataTableWrapper, and DataTable components
- Cover filter system with all filter types and operators
- Explain segment system (system vs user segments)
- Document pagination modes (infinite vs standard)
- Include real-world usage examples from UserListTable, BookingsListingView, MemberList
- Add TypeScript types reference and best practices

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* docs: update DataTable guide to recommend standard pagination

- De-emphasize virtualized rendering and infinite mode
- Position standard pagination as the recommended approach
- Document known issues with infinite loading mode
- Reorder pagination section to prioritize standard mode
- Update best practices to recommend standard pagination

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* docs: add missing DataTable guide topics

- Document getFacetedUniqueValues for faceted filters with real examples
- Add useSegments prop requirement for filter segment enablement
- Enhance server-side filtering documentation with comprehensive patterns
- Include examples from useFacetedUniqueValues and useInsightsRoutingParameters

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* docs: add missing DataTable guide topics

- Document getFacetedUniqueValues for faceted filters with real examples
- Add useSegments prop requirement for filter segment enablement
- Enhance server-side filtering documentation with comprehensive patterns
- Include examples from useFacetedUniqueValues and useInsightsRoutingParameters
- Correct server-side vs client-side filtering rationale

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* docs: enhance DataTable guide with troubleshooting and testing

- Remove duplicate Key Hooks section (lines 783-803)
- Add visual ASCII diagrams for architecture and component hierarchy
- Add comprehensive troubleshooting section with 7 common issues and solutions
- Add testing patterns section with Vitest examples and best practices
- Update table of contents to include new sections
- Maintain existing high-quality documentation standards

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* remove some sections

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-29 07:59:55 +00:00
Eunjae LeeandDevin AI b7cf893169 refactor: replace checkAdminOrOwner with PBAC availability.read permission (#24115)
- Replace role-based check with PermissionCheckService
- Use availability.read permission with OWNER/ADMIN fallback roles
- Maintain existing privacy logic for organizations
- Follow established PBAC pattern from bookings implementation

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-29 07:59:23 +00:00
Lauris Skraucis 1458cacdbb fix: v2 api event type splitName booking field (#24111)
* fix: v2 api event type splitName booking field

* fix: test
2025-09-29 09:19:24 +02:00
322 changed files with 11545 additions and 1684 deletions
+6
View File
@@ -144,6 +144,12 @@ GOOGLE_WEBHOOK_TOKEN=
# Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
GOOGLE_WEBHOOK_URL=
# Token to verify incoming webhooks from Microsoft Calendar
MICROSOFT_WEBHOOK_TOKEN=
# Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
MICROSOFT_WEBHOOK_URL=
# Inbox to send user feedback
SEND_FEEDBACK_EMAIL=
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
with:
base: https://raw.githubusercontent.com/calcom/cal.com/refs/heads/main/docs/api-reference/v2/openapi.json
revision: docs/api-reference/v2/openapi.json
fail-on: WARN
fail-on: ERR
- name: Run Tests
working-directory: apps/api/v2
View File
View File
View File
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NextApiRequest } from "next";
import { z } from "zod";
import { getUserAvailabilityService } from "@calcom/lib/di/containers/GetUserAvailability";
import { getUserAvailabilityService } from "@calcom/features/di/containers/GetUserAvailability";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
+1 -1
View File
@@ -4,7 +4,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { isSupportedTimeZone } from "@calcom/lib/dayjs";
import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots";
import { getAvailableSlotsService } from "@calcom/features/di/containers/AvailableSlots";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import { createContext } from "@calcom/trpc/server/createContext";
+20 -19
View File
@@ -4,6 +4,7 @@ import { purchaseTeamOrOrgSubscription } from "@calcom/features/ee/teams/lib/pay
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import { TeamRepository } from "@calcom/lib/server/repository/team";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
@@ -61,24 +62,24 @@ export async function patchHandler(req: NextApiRequest) {
const { teamId } = schemaQueryTeamId.parse(req.query);
/** Only OWNERS and ADMINS can edit teams */
const _team = await prisma.team.findFirst({
const team = await prisma.team.findFirst({
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
include: { members: true },
where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
});
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
if (!team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
const slugAlreadyExists = await prisma.team.findFirst({
where: {
slug: {
mode: "insensitive",
equals: data.slug,
},
},
});
if (slugAlreadyExists && data.slug !== _team.slug)
throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
if (data.slug) {
const teamRepository = new TeamRepository(prisma);
const isSlugAvailable = await teamRepository.isSlugAvailableForUpdate({
slug: data.slug,
teamId: team.id,
parentId: team.parentId,
});
if (!isSlugAvailable) {
throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
}
}
// Check if parentId is related to this user
if (data.parentId && data.parentId === teamId) {
@@ -99,16 +100,16 @@ export async function patchHandler(req: NextApiRequest) {
}
let paymentUrl;
if (_team.slug === null && data.slug) {
if (team.slug === null && data.slug) {
data.metadata = {
...(_team.metadata as Prisma.JsonObject),
...(team.metadata as Prisma.JsonObject),
requestedSlug: data.slug,
};
delete data.slug;
if (IS_TEAM_BILLING_ENABLED) {
const checkoutSession = await purchaseTeamOrOrgSubscription({
teamId: _team.id,
seatsUsed: _team.members.length,
teamId: team.id,
seatsUsed: team.members.length,
userId,
pricePerSeat: null,
});
@@ -131,9 +132,9 @@ export async function patchHandler(req: NextApiRequest) {
bookingLimits: data.bookingLimits === null ? {} : data.bookingLimits,
metadata: data.metadata === null ? {} : data.metadata || undefined,
};
const team = await prisma.team.update({ where: { id: teamId }, data: cloneData });
const updatedTeam = await prisma.team.update({ where: { id: teamId }, data: cloneData });
const result = {
team: schemaTeamReadPublic.parse(team),
team: schemaTeamReadPublic.parse(updatedTeam),
paymentUrl,
};
if (!paymentUrl) {
+1 -1
View File
@@ -76,7 +76,7 @@ vi.mock("@calcom/features/watchlist/operations/check-if-users-are-blocked.contro
checkIfUsersAreBlocked: vi.fn().mockResolvedValue(false),
}));
vi.mock("@calcom/lib/di/containers/QualifiedHosts", () => ({
vi.mock("@calcom/features/di/containers/QualifiedHosts", () => ({
getQualifiedHostsService: vi.fn().mockReturnValue({
findQualifiedHostsWithDelegationCredentials: vi.fn().mockResolvedValue({
qualifiedRRHosts: [],
@@ -6,6 +6,7 @@ import { createMocks } from "node-mocks-http";
import { describe, expect, test } from "vitest";
import { HttpError } from "@calcom/lib/http-error";
import type { User } from "@calcom/prisma/client";
import handler from "../../../pages/api/selected-calendars/_post";
@@ -72,7 +73,7 @@ describe("POST /api/selected-calendars", () => {
prismaMock.user.findFirstOrThrow.mockResolvedValue({
id: 444444,
} as any);
} as User);
prismaMock.selectedCalendar.create.mockResolvedValue({
credentialId: 1,
@@ -95,6 +96,16 @@ describe("POST /api/selected-calendars", () => {
unwatchAttempts: 0,
createdAt: new Date(),
updatedAt: new Date(),
channelId: null,
channelKind: null,
channelResourceId: null,
channelResourceUri: null,
channelExpiration: null,
syncSubscribedAt: null,
syncToken: null,
syncedAt: null,
syncErrorAt: null,
syncErrorCount: null,
});
await handler(req, res);
@@ -140,6 +151,16 @@ describe("POST /api/selected-calendars", () => {
unwatchAttempts: 0,
createdAt: new Date(),
updatedAt: new Date(),
channelId: null,
channelKind: null,
channelResourceId: null,
channelResourceUri: null,
channelExpiration: null,
syncSubscribedAt: null,
syncToken: null,
syncedAt: null,
syncErrorAt: null,
syncErrorCount: null,
});
await handler(req, res);
@@ -1,9 +1,8 @@
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
import type { NextApiRequest } from "next";
import { describe, expect, it, beforeEach } from "vitest";
import { WatchlistSeverity } from "@calcom/prisma/enums";
import { isLockedOrBlocked } from "../../../lib/utils/isLockedOrBlocked";
describe("isLockedOrBlocked", () => {
@@ -18,7 +17,6 @@ describe("isLockedOrBlocked", () => {
{
type: "DOMAIN",
value: "blocked.com",
severity: WatchlistSeverity.CRITICAL,
createdById: 1,
},
],
@@ -26,13 +24,13 @@ describe("isLockedOrBlocked", () => {
});
it("should return false if no user in request", async () => {
const req = { userId: null, user: null } as any;
const req = { userId: null, user: null } as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(false);
});
it("should return false if user has no email", async () => {
const req = { userId: 123, user: { email: null } } as any;
const req = { userId: 123, user: { email: null } } as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(false);
});
@@ -44,7 +42,7 @@ describe("isLockedOrBlocked", () => {
locked: true,
email: "test@example.com",
},
} as any;
} as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(true);
@@ -57,7 +55,7 @@ describe("isLockedOrBlocked", () => {
locked: false,
email: "test@blocked.com",
},
} as any;
} as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(true);
@@ -70,7 +68,7 @@ describe("isLockedOrBlocked", () => {
locked: false,
email: "test@example.com",
},
} as any;
} as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(false);
@@ -83,7 +81,7 @@ describe("isLockedOrBlocked", () => {
locked: false,
email: "test@BLOCKED.COM",
},
} as any;
} as unknown as NextApiRequest;
const result = await isLockedOrBlocked(req);
expect(result).toBe(true);
@@ -65,6 +65,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
let team1EventTypeId: number;
let team2EventTypeId: number;
let phoneOnlyEventTypeId: number;
let collectiveEventWithoutHostsId: number;
let roundRobinEventWithoutHostsId: number;
const team1EventTypeSlug = `team-bookings-event-type-${randomString()}`;
const team2EventTypeSlug = `team-bookings-event-type-${randomString()}`;
@@ -223,6 +225,28 @@ describe("Bookings Endpoints 2024-08-13", () => {
team1EventTypeId = team1EventType.id;
const team1CollectiveEventTypeWithoutHosts = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team1.id },
},
title: `team-bookings-2024-08-13-collective-event-type-without-hosts-${randomString()}`,
slug: `team-bookings-2024-08-13-collective-event-type-without-hosts-${randomString()}`,
length: 60,
});
collectiveEventWithoutHostsId = team1CollectiveEventTypeWithoutHosts.id;
const team1RoundRobinEventTypeWithoutHosts = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "ROUND_ROBIN",
team: {
connect: { id: team1.id },
},
title: `team-bookings-2024-08-13-round-robin-event-type-without-hosts-${randomString()}`,
slug: `team-bookings-2024-08-13-round-robin-event-type-without-hosts-${randomString()}`,
length: 60,
});
roundRobinEventWithoutHostsId = team1RoundRobinEventTypeWithoutHosts.id;
const phoneOnlyEventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "ROUND_ROBIN",
team: {
@@ -363,6 +387,52 @@ describe("Bookings Endpoints 2024-08-13", () => {
await app.init();
});
describe("cant book event types without hosts", () => {
it("should fail to book a collective event type without hosts", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
eventTypeId: collectiveEventWithoutHostsId,
attendee: {
name: "alice",
timeZone: "Europe/Madrid",
email: "alice@gmail.com",
},
};
const response = await request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13);
expect(response.status).toBe(422);
expect(response.body.error.message).toBe(
`Can't book this team event type because it has no hosts. Please, add at least 1 host to event type with id=${collectiveEventWithoutHostsId} belonging to team with id=${team1.id} and try again.`
);
});
it("should fail to book a round robin event type without hosts", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
eventTypeId: roundRobinEventWithoutHostsId,
attendee: {
name: "alice",
timeZone: "Europe/Madrid",
email: "alice@gmail.com",
},
};
const response = await request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13);
expect(response.status).toBe(422);
expect(response.body.error.message).toBe(
`Can't book this team event type because it has no hosts. Please, add at least 1 host to event type with id=${roundRobinEventWithoutHostsId} belonging to team with id=${team1.id} and try again.`
);
});
});
describe("create team bookings", () => {
it("should create a team 1 booking", async () => {
const body: CreateBookingInput_2024_08_13 = {
@@ -765,8 +835,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
return client;
}
function responseDataIsBooking(data: any): data is BookingOutput_2024_08_13 {
return !Array.isArray(data) && typeof data === "object" && data && "id" in data;
function responseDataIsBooking(data: unknown): data is BookingOutput_2024_08_13 {
return !Array.isArray(data) && typeof data === "object" && data !== null && data && "id" in data;
}
afterAll(async () => {
@@ -29,6 +29,7 @@ import {
Logger,
NotFoundException,
UnauthorizedException,
UnprocessableEntityException,
} from "@nestjs/common";
import { BadRequestException } from "@nestjs/common";
import { Request } from "express";
@@ -132,6 +133,10 @@ export class BookingsService_2024_08_13 {
);
}
if (eventType.schedulingType === "COLLECTIVE" || eventType.schedulingType === "ROUND_ROBIN") {
await this.checkEventTypeHasHosts(eventType.id);
}
body.eventTypeId = eventType.id;
if ("instant" in body && body.instant) {
@@ -159,6 +164,15 @@ export class BookingsService_2024_08_13 {
}
}
async checkEventTypeHasHosts(eventTypeId: number) {
const eventType = await this.eventTypesRepository.getEventTypeWithHosts(eventTypeId);
if (!eventType?.hosts?.length) {
throw new UnprocessableEntityException(
`Can't book this team event type because it has no hosts. Please, add at least 1 host to event type with id=${eventTypeId} belonging to team with id=${eventType?.teamId} and try again.`
);
}
}
async checkBookingRequiresAuthenticationSetting(
eventType: EventTypeWithOwnerAndTeam,
authUser: AuthOptionalUser,
@@ -38,6 +38,7 @@ import type {
GuestsDefaultFieldOutput_2024_06_14,
NameDefaultFieldInput_2024_06_14,
NotesDefaultFieldInput_2024_06_14,
SplitNameDefaultFieldOutput_2024_06_14,
UpdateEventTypeInput_2024_06_14,
} from "@calcom/platform-types";
import type { PlatformOAuthClient, Team, User, Schedule, EventType } from "@calcom/prisma/client";
@@ -1447,23 +1448,23 @@ describe("Event types Endpoints", () => {
try {
await eventTypesRepositoryFixture.delete(eventType.id);
} catch (e) {
// Event type might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(falseTestUser.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(orgUser.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
await app.close();
});
@@ -2187,6 +2188,47 @@ describe("Event types Endpoints", () => {
});
});
describe("split name booking field", () => {
it("should create an event type with split name booking field", async () => {
const splitNameBookingField: SplitNameDefaultFieldOutput_2024_06_14 = {
isDefault: true,
type: "splitName",
slug: "splitName",
firstNameLabel: "First name",
firstNamePlaceholder: "",
lastNameLabel: "last name",
lastNamePlaceholder: "",
lastNameRequired: false,
disableOnPrefill: false,
};
const body: CreateEventTypeInput_2024_06_14 = {
title: "Coding class 9",
slug: "coding-class-9",
description: "Let's learn how to code like a pro.",
lengthInMinutes: 60,
bookingFields: [splitNameBookingField],
};
return request(app.getHttpServer())
.post("/api/v2/event-types")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const createdEventType = responseBody.data;
const splitNameBookingFieldResponse = createdEventType.bookingFields.find(
(field) => field.type === "splitName"
) as SplitNameDefaultFieldOutput_2024_06_14 | undefined;
expect(splitNameBookingFieldResponse).toEqual(splitNameBookingField);
await eventTypesRepositoryFixture.delete(createdEventType.id);
});
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
@@ -2194,12 +2236,12 @@ describe("Event types Endpoints", () => {
await eventTypesRepositoryFixture.delete(legacyEventTypeId1);
await eventTypesRepositoryFixture.delete(legacyEventTypeId2);
} catch (e) {
// Event type might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
await app.close();
});
@@ -2379,7 +2421,7 @@ describe("Event types Endpoints", () => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const updatedEventType = responseBody.data;
+expect(updatedEventType.hidden).toBe(false);
expect(updatedEventType.hidden).toBe(false);
});
});
@@ -2389,12 +2431,12 @@ describe("Event types Endpoints", () => {
try {
await eventTypesRepositoryFixture.delete(legacyEventTypeId1);
} catch (e) {
// Event type might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
await app.close();
});
@@ -0,0 +1,51 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
@Injectable()
export class AtomsSecondaryEmailsRepository {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async getSecondaryEmailsVerified(userId: number) {
return await this.dbRead.prisma.secondaryEmail.findMany({
where: {
userId,
emailVerified: {
not: null,
},
},
});
}
async getExistingSecondaryEmailByUserAndEmail(userId: number, email: string) {
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
where: {
userId_email: { userId, email },
},
});
return existingSecondaryEmailRecord?.email;
}
async getExistingSecondaryEmail(email: string) {
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
where: {
email,
},
});
return existingSecondaryEmailRecord?.email;
}
async addSecondaryEmailVerified(userId: number, email: string) {
const existingSecondaryEmailRecord = await this.dbWrite.prisma.secondaryEmail.create({
data: {
userId,
email,
emailVerified: new Date(),
},
});
return existingSecondaryEmailRecord?.email;
}
}
@@ -1,4 +1,5 @@
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
import { AtomsSecondaryEmailsRepository } from "@/modules/atoms/atoms-secondary-emails.repository";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { AtomsConferencingAppsController } from "@/modules/atoms/controllers/atoms.conferencing-apps.controller";
import { AtomsController } from "@/modules/atoms/controllers/atoms.controller";
@@ -33,6 +34,7 @@ import { Module } from "@nestjs/common";
CredentialsRepository,
UsersRepository,
AtomsRepository,
AtomsSecondaryEmailsRepository,
UsersService,
SchedulesAtomsService,
VerificationAtomsService,
@@ -64,47 +64,4 @@ export class AtomsRepository {
return userTeams;
}
async getSecondaryEmails(userId: number) {
return await this.dbRead.prisma.secondaryEmail.findMany({
where: {
userId,
emailVerified: {
not: null,
},
},
});
}
async getExistingSecondaryEmailByUserAndEmail(userId: number, email: string) {
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
where: {
userId_email: { userId, email },
},
});
return existingSecondaryEmailRecord?.email;
}
async getExistingSecondaryEmail(email: string) {
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
where: {
email,
},
});
return existingSecondaryEmailRecord?.email;
}
async addSecondaryEmail(userId: number, email: string) {
const existingSecondaryEmailRecord = await this.dbWrite.prisma.secondaryEmail.create({
data: {
userId,
email,
emailVerified: new Date(),
},
});
return existingSecondaryEmailRecord?.email;
}
}
@@ -1,10 +1,10 @@
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { AtomsSecondaryEmailsRepository } from "@/modules/atoms/atoms-secondary-emails.repository";
import { CheckEmailVerificationRequiredParams } from "@/modules/atoms/inputs/check-email-verification-required-params";
import { GetVerifiedEmailsInput } from "@/modules/atoms/inputs/get-verified-emails-params";
import { SendVerificationEmailInput } from "@/modules/atoms/inputs/send-verification-email.input";
import { VerifyEmailCodeInput } from "@/modules/atoms/inputs/verify-email-code.input";
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { UsersRepository } from "@/modules/users/users.repository";
import { Injectable, BadRequestException, UnauthorizedException } from "@nestjs/common";
import {
@@ -17,8 +17,8 @@ import {
@Injectable()
export class VerificationAtomsService {
constructor(
private readonly atomsRepository: AtomsRepository,
private readonly teamsRepository: TeamsRepository
private readonly atomsSecondaryEmailsRepository: AtomsSecondaryEmailsRepository,
private readonly usersRepository: UsersRepository
) {}
async checkEmailVerificationRequired(input: CheckEmailVerificationRequiredParams) {
@@ -76,7 +76,8 @@ export class VerificationAtomsService {
if (teamId) {
const verifiedEmails: string[] = [];
const teamMembers = await this.teamsRepository.getTeamMemberEmails(teamId);
const teamMembers = await this.usersRepository.getUserEmailsVerifiedForTeam(teamId);
if (teamMembers.length === 0) {
return verifiedEmails;
@@ -96,7 +97,7 @@ export class VerificationAtomsService {
let verifiedEmails = [userEmailWithoutOauthClientId];
const secondaryEmails = await this.atomsRepository.getSecondaryEmails(userId);
const secondaryEmails = await this.atomsSecondaryEmailsRepository.getSecondaryEmailsVerified(userId);
verifiedEmails = verifiedEmails.concat(
secondaryEmails.map((secondaryEmail) => this.removeClientIdFromEmail(secondaryEmail.email))
);
@@ -113,11 +114,9 @@ export class VerificationAtomsService {
existingPrimaryEmail: string;
email: string;
}): Promise<boolean> {
const existingSecondaryEmail = await this.atomsRepository.getExistingSecondaryEmailByUserAndEmail(
userId,
email
);
const alreadyExistingEmail = await this.atomsRepository.getExistingSecondaryEmail(email);
const existingSecondaryEmail =
await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmailByUserAndEmail(userId, email);
const alreadyExistingEmail = await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmail(email);
if (alreadyExistingEmail) {
throw new BadRequestException("Email already exists");
@@ -127,7 +126,7 @@ export class VerificationAtomsService {
return true;
}
await this.atomsRepository.addSecondaryEmail(userId, email);
await this.atomsSecondaryEmailsRepository.addSecondaryEmailVerified(userId, email);
return true;
}
@@ -705,6 +705,111 @@ describe("OAuth Client Users Endpoints", () => {
.expect(200);
});
describe("managed user time zone", () => {
describe("negative tests", () => {
it("should not allow '' time zone", async () => {
const requestBody = {
email: "whatever2@gmail.com",
timeZone: "",
name: "Bob Smithson",
};
await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(400);
});
it("should not allow 'invalid-timezone' time zone", async () => {
const requestBody = {
email: "whatever2@gmail.com",
timeZone: "invalid-timezone",
name: "Bob Smithson",
};
await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(400);
});
});
describe("positive tests", () => {
it("should allow null timezone", async () => {
const requestBody = {
email: "whatever1@gmail.com",
timeZone: null,
name: "Bob Smithson",
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.data.user.timeZone).toEqual("Europe/London");
await userRepositoryFixture.delete(responseBody.data.user.id);
});
it("should allow undefined time zone", async () => {
const requestBody = {
email: "whatever3@gmail.com",
timeZone: undefined,
name: "Bob Smithson",
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.data.user.timeZone).toEqual("Europe/London");
await userRepositoryFixture.delete(responseBody.data.user.id);
});
it("should allow valid time zone", async () => {
const requestBody = {
email: "whatever4@gmail.com",
timeZone: "Europe/Rome",
name: "Bob Smithson",
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.data.user.timeZone).toBe("Europe/Rome");
await userRepositoryFixture.delete(responseBody.data.user.id);
});
it("should allow without any time zone", async () => {
const requestBody = {
email: "whatever5@gmail.com",
name: "Bob Smithson",
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient.id}/users`)
.set("x-cal-secret-key", oAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.data.user.timeZone).toEqual("Europe/London");
await userRepositoryFixture.delete(responseBody.data.user.id);
});
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await oauthClientRepositoryFixture.delete(oAuthClientEventTypesDisabled.id);
@@ -712,17 +817,17 @@ describe("OAuth Client Users Endpoints", () => {
try {
await userRepositoryFixture.delete(postResponseData.user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(postResponseData2.user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
try {
await userRepositoryFixture.delete(platformAdmin.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
await app.close();
});
@@ -931,7 +1036,7 @@ describe("OAuth Client Users Endpoints", () => {
try {
await userRepositoryFixture.delete(postResponseData.user.id);
} catch (e) {
// User might have been deleted by the test
console.log(e);
}
await app.close();
});
@@ -351,6 +351,7 @@ describe("Organizations Users Endpoints", () => {
email: `organizations-users-new-member-${randomString()}@api.com`,
bio,
metadata,
timeZone: "Europe/Rome",
};
const emailSpy = jest
@@ -367,6 +368,7 @@ describe("Organizations Users Endpoints", () => {
expect(userData.email).toBe(newOrgUser.email);
expect(userData.bio).toBe(newOrgUser.bio);
expect(userData.metadata).toEqual(newOrgUser.metadata);
expect(userData.timeZone).toBe(newOrgUser.timeZone);
expect(emailSpy).toHaveBeenCalledWith({
usernameOrEmail: newOrgUser.email,
orgName: org.name,
@@ -328,6 +328,131 @@ describe("Teams Memberships Endpoints", () => {
return request(app.getHttpServer()).get(`/v2/teams/${team.id}/memberships/123132145`).expect(404);
});
it("should filter memberships by single email", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${teamAdminEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(1);
expect(responseBody.data[0].user.email).toEqual(teamAdminEmail);
expect(responseBody.data[0].userId).toEqual(teamAdmin.id);
expect(responseBody.data[0].role).toEqual("ADMIN");
});
});
it("should filter memberships by multiple emails", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${teamAdminEmail},${teamMemberEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(2);
const emails = responseBody.data.map((membership) => membership.user.email);
expect(emails).toContain(teamAdminEmail);
expect(emails).toContain(teamMemberEmail);
const adminMembership = responseBody.data.find((m) => m.user.email === teamAdminEmail);
const memberMembership = responseBody.data.find((m) => m.user.email === teamMemberEmail);
expect(adminMembership).toBeDefined();
expect(memberMembership).toBeDefined();
expect(adminMembership?.role).toEqual("ADMIN");
expect(memberMembership?.role).toEqual("MEMBER");
});
});
it("should return empty array when filtering by non-existent email", async () => {
const nonExistentEmail = `nonexistent-${randomString()}@test.com`;
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${nonExistentEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(0);
});
});
it("should return partial results when filtering by mix of existing and non-existent emails", async () => {
const nonExistentEmail = `nonexistent-${randomString()}@test.com`;
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${teamAdminEmail},${nonExistentEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(1);
expect(responseBody.data[0].user.email).toEqual(teamAdminEmail);
});
});
it("should work with pagination and email filtering combined", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${teamAdminEmail},${teamMemberEmail}&skip=1&take=1`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(1);
const returnedEmail = responseBody.data[0].user.email;
expect([teamAdminEmail, teamMemberEmail]).toContain(returnedEmail);
});
});
it("should handle empty emails array gracefully", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(2);
});
});
it("should handle URL encoded email addresses in filter", async () => {
const encodedEmail = encodeURIComponent(teamAdminEmail);
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${encodedEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(1);
expect(responseBody.data[0].user.email).toEqual(teamAdminEmail);
});
});
it("should filter by email and maintain all user properties", async () => {
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${teamMemberEmail}`)
.expect(200)
.then((response) => {
const responseBody: GetTeamMembershipsOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.length).toEqual(1);
const membership = responseBody.data[0];
expect(membership.user.email).toEqual(teamMemberEmail);
expect(membership.user.bio).toEqual(teamMember.bio);
expect(membership.user.metadata).toEqual(teamMember.metadata);
expect(membership.user.username).toEqual(teamMember.username);
expect(membership.teamId).toEqual(team.id);
expect(membership.userId).toEqual(teamMember.id);
expect(membership.role).toEqual("MEMBER");
});
});
it("should validate email array size limits", async () => {
const tooManyEmails = Array.from({ length: 21 }, (_, i) => `test${i}@example.com`).join(",");
return request(app.getHttpServer())
.get(`/v2/teams/${team.id}/memberships?emails=${tooManyEmails}`)
.expect(400);
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(teamAdmin.email);
await userRepositoryFixture.deleteByEmail(teammateInvitedViaApi.email);
@@ -4,6 +4,7 @@ import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { CreateTeamMembershipInput } from "@/modules/teams/memberships/inputs/create-team-membership.input";
import { GetTeamMembershipsInput } from "@/modules/teams/memberships/inputs/get-team-memberships.input";
import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/update-team-membership.input";
import { CreateTeamMembershipOutput } from "@/modules/teams/memberships/outputs/create-team-membership.output";
import { DeleteTeamMembershipOutput } from "@/modules/teams/memberships/outputs/delete-team-membership.output";
@@ -32,7 +33,6 @@ import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries/event-types";
import { SkipTakePagination } from "@calcom/platform-types";
@Controller({
path: "/v2/teams/:teamId/memberships",
@@ -85,16 +85,20 @@ export class TeamsMembershipsController {
}
@Get("/")
@ApiOperation({ summary: "Get all memberships" })
@ApiOperation({
summary: "Get all memberships",
description: "Retrieve team memberships with optional filtering by email addresses. Supports pagination.",
})
@Roles("TEAM_ADMIN")
@HttpCode(HttpStatus.OK)
async getTeamMemberships(
@Param("teamId", ParseIntPipe) teamId: number,
@Query() queryParams: SkipTakePagination
@Query() queryParams: GetTeamMembershipsInput
): Promise<GetTeamMembershipsOutput> {
const { skip, take } = queryParams;
const { skip, take, emails } = queryParams;
const orgTeamMemberships = await this.teamsMembershipsService.getPaginatedTeamMemberships(
teamId,
emails,
skip ?? 0,
take ?? 250
);
@@ -0,0 +1,32 @@
import { GetUsersInput } from "@/modules/users/inputs/get-users.input";
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { ArrayMaxSize, ArrayNotEmpty, IsEmail, IsOptional } from "class-validator";
export class GetTeamMembershipsInput extends GetUsersInput {
@IsOptional()
@Transform(({ value }) => {
if (value == null) return undefined;
const rawValues = (Array.isArray(value) ? value : [value]).flatMap((entry) =>
typeof entry === "string" ? entry.split(",") : []
);
const normalized = rawValues
.map((email) => email.trim())
.filter((email) => email.length > 0)
.map((email) => email.toLowerCase());
const deduplicated = [...new Set(normalized)];
return deduplicated.length > 0 ? deduplicated : undefined;
})
@ArrayNotEmpty({ message: "emails cannot be empty." })
@ArrayMaxSize(20, {
message: "emails array cannot contain more than 20 email addresses for team membership filtering",
})
@IsEmail({}, { each: true, message: "Each email must be a valid email address" })
@ApiPropertyOptional({
type: [String],
description:
"Filter team memberships by email addresses. If you want to filter by multiple emails, separate them with a comma (max 20 emails for performance).",
example: "?emails=user1@example.com,user2@example.com",
})
emails?: string[];
}
@@ -23,13 +23,15 @@ export class TeamsMembershipsService {
return teamMembership;
}
async getPaginatedTeamMemberships(teamId: number, skip = 0, take = 250) {
const teamMemberships = await this.teamsMembershipsRepository.findTeamMembershipsPaginated(
async getPaginatedTeamMemberships(teamId: number, emails?: string[], skip = 0, take = 250) {
const emailArray = !emails ? [] : emails;
return await this.teamsMembershipsRepository.findTeamMembershipsPaginatedWithFilters(
teamId,
{ emails: emailArray },
skip,
take
);
return teamMemberships;
}
async getTeamMembership(teamId: number, membershipId: number) {
@@ -6,6 +6,10 @@ import { Injectable } from "@nestjs/common";
import type { Prisma } from "@calcom/prisma/client";
export interface TeamMembershipFilters {
emails?: string[];
}
export const MembershipUserSelect: Prisma.UserSelect = {
username: true,
email: true,
@@ -41,6 +45,30 @@ export class TeamsMembershipsRepository {
});
}
async findTeamMembershipsPaginatedWithFilters(
teamId: number,
filters: TeamMembershipFilters,
skip: number,
take: number
) {
const whereClause: Prisma.MembershipWhereInput = {
teamId: teamId,
};
if (filters.emails && filters.emails.length > 0) {
whereClause.user = {
email: { in: filters.emails },
};
}
return await this.dbRead.prisma.membership.findMany({
where: whereClause,
include: { user: { select: MembershipUserSelect } },
skip,
take,
});
}
async findTeamMembership(teamId: number, membershipId: number) {
return this.dbRead.prisma.membership.findUnique({
where: {
@@ -120,30 +120,4 @@ export class TeamsRepository {
},
});
}
async getTeamMemberEmails(teamId: number) {
return this.dbRead.prisma.user.findMany({
where: {
teams: {
some: {
teamId,
},
},
},
select: {
id: true,
email: true,
secondaryEmails: {
where: {
emailVerified: {
not: null,
},
},
select: {
email: true,
},
},
},
});
}
}
@@ -1,6 +1,7 @@
import { Locales } from "@/lib/enums/locales";
import { CapitalizeTimeZone } from "@/lib/inputs/capitalize-timezone";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsOptional, IsTimeZone, IsString, IsEnum, IsIn, IsUrl, IsObject } from "class-validator";
import { ValidateMetadata } from "@calcom/platform-types";
@@ -29,6 +30,7 @@ export class CreateManagedUserInput {
})
weekStart?: WeekDay;
@Transform(({ value }) => (value === null ? undefined : value))
@IsTimeZone()
@IsOptional()
@CapitalizeTimeZone()
@@ -1,3 +1,4 @@
import { CapitalizeTimeZone } from "@/lib/inputs/capitalize-timezone";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { Expose, Transform } from "class-transformer";
import {
@@ -112,6 +113,7 @@ export class CreateUserInput {
@ApiProperty({ type: String, required: false, description: "Time zone", example: "America/New_York" })
@IsOptional()
@IsString()
@CapitalizeTimeZone()
@Validate(TimeZoneValidator)
@Expose()
timeZone?: string;
@@ -28,6 +28,7 @@ export class GetUsersInput {
@ApiPropertyOptional({
type: [String],
description: "The email address or an array of email addresses to filter by",
example: ["user1@example.com", "user2@example.com"],
})
emails?: string[];
}
@@ -408,4 +408,30 @@ export class UsersRepository {
},
});
}
async getUserEmailsVerifiedForTeam(teamId: number) {
return this.dbRead.prisma.user.findMany({
where: {
teams: {
some: {
teamId,
},
},
},
select: {
id: true,
email: true,
secondaryEmails: {
where: {
emailVerified: {
not: null,
},
},
select: {
email: true,
},
},
},
});
}
}
@@ -1,6 +1,6 @@
import type { ValidatorConstraintInterface } from "class-validator";
import { ValidatorConstraint } from "class-validator";
import tzdata from "tzdata";
import * as tzdata from "tzdata";
@ValidatorConstraint({ name: "timezoneValidator", async: false })
export class TimeZoneValidator implements ValidatorConstraintInterface {
-8
View File
@@ -636,10 +636,6 @@ select:focus {
@apply !border-r-default left-0.5 !border-y-0 !border-l-0;
}
.intercom-lightweight-app {
@apply z-40 !important;
}
@import "../../web/styles/tokens.css";
@layer base {
@@ -1027,10 +1023,6 @@ select:focus {
@apply !border-r-default left-0.5 !border-y-0 !border-l-0;
}
.intercom-lightweight-app {
@apply z-40 !important;
}
.prose :where(a:not([data-card])):not(:where([class~="not-prose"] *)) {
text-decoration: none;
}
@@ -5,10 +5,11 @@ import { unstable_cache } from "next/cache";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable";
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
import { MembershipRole } from "@calcom/prisma/enums";
import { availabilityRouter } from "@calcom/trpc/server/routers/viewer/availability/_router";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
@@ -72,8 +73,14 @@ const Page = async ({ searchParams: _searchParams }: PageProps) => {
orgId: organizationId,
})
: false;
const isOrgAdminOrOwner = checkAdminOrOwner(session?.user?.org?.role);
const canViewTeamAvailability = isOrgAdminOrOwner || !isOrgPrivate;
const permissionService = new PermissionCheckService();
const teamIdsWithPermission = await permissionService.getTeamIdsWithPermission({
userId: session.user.id,
permission: "availability.read",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
const canViewTeamAvailability = teamIdsWithPermission.length > 0 || !isOrgPrivate;
return (
<ShellMainAppDir
@@ -23,7 +23,8 @@ const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const searchParams = await _searchParams;
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
const autoAccept = Array.isArray(searchParams?.autoAccept) ? searchParams.autoAccept[0] : searchParams?.autoAccept;
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}${autoAccept ? `&autoAccept=${encodeURIComponent(autoAccept)}` : ''}` : null;
if (!session) {
redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
@@ -2,11 +2,12 @@ import type { SearchParams } from "app/_types";
import type { Session } from "next-auth";
import { unstable_cache } from "next/cache";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import { TeamsListing } from "@calcom/features/ee/teams/components/TeamsListing";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { TeamRepository } from "@calcom/lib/server/repository/team";
import { TeamService } from "@calcom/lib/server/service/teamService";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
@@ -32,14 +33,21 @@ export const ServerTeamsListing = async ({
session: Session;
}) => {
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
const autoAccept = Array.isArray(searchParams?.autoAccept) ? searchParams.autoAccept[0] : searchParams?.autoAccept;
const userId = session.user.id;
let invitationAccepted = false;
let teamNameFromInvite,
errorMsgFromInvite = null;
if (token) {
try {
teamNameFromInvite = await TeamService.inviteMemberByToken(token, userId);
if (autoAccept === "true") {
await TeamService.acceptInvitationByToken(token, userId);
invitationAccepted = true;
} else {
teamNameFromInvite = await TeamService.inviteMemberByToken(token, userId);
}
} catch (e) {
errorMsgFromInvite = "Error while fetching teams";
if (e instanceof TRPCError) errorMsgFromInvite = e.message;
@@ -49,21 +57,30 @@ export const ServerTeamsListing = async ({
const teams = await getCachedTeams(userId);
const userProfile = session?.user?.profile;
const orgId = userProfile?.organizationId ?? session?.user.org?.id;
const orgRole =
session?.user?.org?.role ??
userProfile?.organization?.members.find((m: { userId: number }) => m.userId === userId)?.role;
const isOrgAdminOrOwner = checkAdminOrOwner(orgRole);
const permissionCheckService = new PermissionCheckService();
const canCreateTeam = orgId
? await permissionCheckService.checkPermission({
userId: session.user.id,
teamId: orgId,
permission: "team.create",
fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
})
: false;
return {
Main: (
<TeamsListing
invitationAccepted={invitationAccepted}
teams={teams}
orgId={orgId ?? null}
isOrgAdmin={isOrgAdminOrOwner}
permissions={{
canCreateTeam: canCreateTeam,
}}
teamNameFromInvite={teamNameFromInvite ?? null}
errorMsgFromInvite={errorMsgFromInvite}
/>
),
CTA: !orgId || isOrgAdminOrOwner ? <TeamsCTA /> : null,
CTA: !orgId || canCreateTeam ? <TeamsCTA /> : null,
};
};
@@ -18,6 +18,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { ArrowButton } from "@calcom/ui/components/arrow-button";
import { Badge } from "@calcom/ui/components/badge";
@@ -46,6 +47,10 @@ function NewFormButton({ setNewFormDialogState }: { setNewFormDialogState: SetNe
createFunction={(teamId) => {
setNewFormDialogState({ action: "new", target: teamId ? String(teamId) : "" });
}}
withPermission={{
permission: "routingForm.create",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
}}
/>
);
}
@@ -175,6 +175,7 @@ const organizationAdminKeys = ["privacy", "OAuth Clients", "SSO", "directory_syn
export interface SettingsPermissions {
canViewRoles?: boolean;
canViewOrganizationBilling?: boolean;
canUpdateOrganization?: boolean;
}
const useTabs = ({
@@ -190,7 +191,6 @@ const useTabs = ({
const { data: user } = trpc.viewer.me.get.useQuery({ includePasswordAdded: true });
const orgBranding = useOrgBranding();
const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN;
const isOrgAdminOrOwner = checkAdminOrOwner(orgBranding?.role);
const processTabsMemod = useMemo(() => {
const processedTabs = getTabs(orgBranding).map((tab) => {
@@ -203,10 +203,10 @@ const useTabs = ({
};
} else if (tab.href === "/settings/organizations") {
const newArray = (tab?.children ?? []).filter(
(child) => isOrgAdminOrOwner || !organizationAdminKeys.includes(child.name)
(child) => permissions?.canUpdateOrganization || !organizationAdminKeys.includes(child.name)
);
if (isOrgAdminOrOwner) {
if (permissions?.canUpdateOrganization) {
newArray.splice(4, 0, {
name: "attributes",
href: "/settings/organizations/attributes",
@@ -238,7 +238,7 @@ const useTabs = ({
});
}
} else {
if (isOrgAdminOrOwner) {
if (permissions?.canUpdateOrganization) {
newArray.push({
name: "billing",
href: "/settings/organizations/billing",
@@ -264,7 +264,7 @@ const useTabs = ({
return { ...tab, children: filtered };
} else if (tab.href === "/settings/developer") {
const filtered = tab?.children?.filter(
(childTab) => isOrgAdminOrOwner || childTab.name !== "admin_api"
(childTab) => permissions?.canUpdateOrganization || childTab.name !== "admin_api"
);
return { ...tab, children: filtered };
}
@@ -274,12 +274,12 @@ const useTabs = ({
// check if name is in adminRequiredKeys
return processedTabs.filter((tab) => {
if (organizationRequiredKeys.includes(tab.name)) return !!orgBranding;
if (tab.name === "other_teams" && !isOrgAdminOrOwner) return false;
if (tab.name === "other_teams" && !permissions?.canUpdateOrganization) return false;
if (isAdmin) return true;
return !adminRequiredKeys.includes(tab.name);
});
}, [isAdmin, orgBranding, isOrgAdminOrOwner, user, isDelegationCredentialEnabled, isPbacEnabled, permissions]);
}, [isAdmin, orgBranding, user, isDelegationCredentialEnabled, isPbacEnabled, permissions]);
return processTabsMemod;
};
@@ -560,8 +560,6 @@ const SettingsSidebarContainer = ({
}
}, [searchParams?.get("id"), otherTeams]);
const isOrgAdminOrOwner = checkAdminOrOwner(orgBranding?.role);
return (
<nav
style={{ maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px` }}
@@ -638,12 +636,12 @@ const SettingsSidebarContainer = ({
as="p"
className="text-subtle truncate text-sm font-medium leading-5"
loadingClassName="ms-3">
{t(isOrgAdminOrOwner ? "my_teams" : tab.name)}
{t("my_teams")}
</Skeleton>
</div>
</Link>
<TeamListCollapsible teamFeatures={teamFeatures} />
{(!orgBranding?.id || isOrgAdminOrOwner) && (
{(!orgBranding?.id || permissions?.canUpdateOrganization) && (
<VerticalTabItem
name={t("add_a_team")}
href={`${WEBAPP_URL}/settings/teams/new`}
@@ -47,6 +47,7 @@ export default async function SettingsLayoutAppDir(props: SettingsLayoutProps) {
let teamFeatures: Record<number, TeamFeatures> | null = null;
let canViewRoles = false;
let canViewOrganizationBilling = false;
let canUpdateOrganization = false;
const orgId = session?.user?.profile?.organizationId ?? session?.user.org?.id;
// For now we only grab organization features but it would be nice to fetch these on the server side for specific team feature flags
@@ -68,6 +69,11 @@ export default async function SettingsLayoutAppDir(props: SettingsLayoutProps) {
canViewRoles = roleActions[CrudAction.Read] ?? false;
const orgActions = PermissionMapper.toActionMap(organizationPermissions, Resource.Organization);
canViewOrganizationBilling = orgActions[CustomAction.ManageBilling] ?? isOrgAdminOrOwner;
canUpdateOrganization = orgActions[CrudAction.Update] ?? isOrgAdminOrOwner;
} else {
canViewRoles = false;
canViewOrganizationBilling = isOrgAdminOrOwner;
canUpdateOrganization = isOrgAdminOrOwner;
}
}
@@ -76,7 +82,7 @@ export default async function SettingsLayoutAppDir(props: SettingsLayoutProps) {
<SettingsLayoutAppDirClient
{...props}
teamFeatures={teamFeatures ?? {}}
permissions={{ canViewRoles, canViewOrganizationBilling }}
permissions={{ canViewRoles, canViewOrganizationBilling, canUpdateOrganization }}
/>
</>
);
@@ -1,8 +1,9 @@
import { _generateMetadata } from "app/_utils";
import OrgAttributesEditPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-edit-view";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrgPerms } from "../../../../actions/validateUserHasOrgPerms";
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata(
@@ -14,7 +15,10 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
);
const OrgAttributesEditPageWrapper = async () => {
await validateUserHasOrgAdmin();
await validateUserHasOrgPerms({
permission: "organization.attributes.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
return <OrgAttributesEditPage />;
};
@@ -1,8 +1,9 @@
import { _generateMetadata } from "app/_utils";
import OrgAttributesCreatePage from "@calcom/ee/organizations/pages/settings/attributes/attributes-create-view";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrgPerms } from "../../../actions/validateUserHasOrgPerms";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -14,7 +15,10 @@ export const generateMetadata = async () =>
);
const OrgAttributesCreatePageWrapper = async () => {
await validateUserHasOrgAdmin();
await validateUserHasOrgPerms({
permission: "organization.attributes.create",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
return <OrgAttributesCreatePage />;
};
@@ -7,7 +7,7 @@ import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permi
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrg } from "../../actions/validateUserHasOrg";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -20,7 +20,7 @@ export const generateMetadata = async () =>
const Page = async () => {
const t = await getTranslate();
const session = await validateUserHasOrgAdmin();
const session = await validateUserHasOrg();
const { canRead, canEdit, canDelete, canCreate } = await getResourcePermissions({
userId: session.user.id,
@@ -2,8 +2,9 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import DelegationCredentialList from "@calcom/features/ee/organizations/pages/settings/delegationCredential";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrgPerms } from "../../actions/validateUserHasOrgPerms";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -17,7 +18,10 @@ export const generateMetadata = async () =>
const Page = async () => {
const t = await getTranslate();
await validateUserHasOrgAdmin();
await validateUserHasOrgPerms({
permission: "organization.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
return (
<SettingsHeader
@@ -6,7 +6,7 @@ import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permi
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrg } from "../../actions/validateUserHasOrg";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -19,7 +19,7 @@ export const generateMetadata = async () =>
const Page = async () => {
const t = await getTranslate();
const session = await validateUserHasOrgAdmin();
const session = await validateUserHasOrg();
const { canEdit } = await getResourcePermissions({
userId: session.user.id,
@@ -7,7 +7,7 @@ import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permi
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrg } from "../../actions/validateUserHasOrg";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -21,7 +21,7 @@ export const generateMetadata = async () =>
const Page = async () => {
const t = await getTranslate();
const session = await validateUserHasOrgAdmin();
const session = await validateUserHasOrg();
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
return redirect("/settings/profile");
@@ -7,7 +7,7 @@ import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permi
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { MembershipRole } from "@calcom/prisma/enums";
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
import { validateUserHasOrg } from "../../actions/validateUserHasOrg";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -20,7 +20,7 @@ export const generateMetadata = async () =>
const Page = async () => {
const t = await getTranslate();
const session = await validateUserHasOrgAdmin();
const session = await validateUserHasOrg();
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
return redirect("/settings/organizations/general");
@@ -22,6 +22,7 @@ interface AdvancedPermissionGroupProps {
onChange: (permissions: string[]) => void;
disabled?: boolean;
scope?: Scope;
isPrivate?: boolean;
}
const INTERNAL_DATAACCESS_KEY = "_resource";
@@ -32,10 +33,11 @@ export function AdvancedPermissionGroup({
onChange,
disabled,
scope = Scope.Organization,
isPrivate = false,
}: AdvancedPermissionGroupProps) {
const { t } = useLocale();
const { toggleSinglePermission, toggleResourcePermissionLevel } = usePermissions(scope);
const scopedRegistry = getPermissionsForScope(scope);
const scopedRegistry = getPermissionsForScope(scope, isPrivate);
const resourceConfig = scopedRegistry[resource];
const [isExpanded, setIsExpanded] = useState(false);
@@ -56,9 +56,17 @@ interface RoleSheetProps {
onOpenChange: (open: boolean) => void;
teamId: number;
scope?: Scope;
isPrivate?: boolean; // Add isPrivate prop to control permission visibility
}
export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Organization }: RoleSheetProps) {
export function RoleSheet({
role,
open,
onOpenChange,
teamId,
scope = Scope.Organization,
isPrivate = false,
}: RoleSheetProps) {
const { t } = useLocale();
const router = useRouter();
const isEditing = Boolean(role);
@@ -108,7 +116,8 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
const { isAdvancedMode, permissions, color } = form.watch();
const { filteredResources, scopedRegistry } = useMemo(() => {
const scopedRegistry = getPermissionsForScope(scope);
// Use privacy-aware filtering if we have privacy information
const scopedRegistry = getPermissionsForScope(scope, isPrivate);
const filteredResources = Object.keys(scopedRegistry).filter((resource) =>
t(
scopedRegistry[resource as Resource][CrudAction.All as keyof (typeof scopedRegistry)[Resource]]
@@ -118,7 +127,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
.includes(searchQuery.toLowerCase())
);
return { filteredResources, scopedRegistry };
}, [searchQuery, t, scope]);
}, [searchQuery, t, scope, isPrivate]);
const createMutation = trpc.viewer.pbac.createRole.useMutation({
onSuccess: async () => {
@@ -182,6 +191,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
{...form.register("name")}
placeholder={t("role_name_placeholder")}
disabled={isSystemRole}
maxLength={50}
/>
</div>
<RoleColorPicker
@@ -199,10 +209,13 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
<Label className="mb-0">{t("permissions")}</Label>
<div className="flex items-center gap-2">
<Checkbox
id="advanced_mode_checkbox"
checked={form.watch("isAdvancedMode")}
onCheckedChange={(checked: boolean) => form.setValue("isAdvancedMode", checked)}
/>
<span className="text-sm">{t("advanced")}</span>
<label htmlFor="advanced_mode_checkbox" className="text-sm">
{t("advanced")}
</label>
</div>
</div>
<TextField
@@ -221,6 +234,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
disabled={isSystemRole}
scope={scope}
isPrivate={isPrivate}
/>
))}{" "}
</div>
@@ -245,6 +259,7 @@ export function RoleSheet({ role, open, onOpenChange, teamId, scope = Scope.Orga
onChange={(newPermissions) => form.setValue("permissions", newPermissions)}
disabled={isSystemRole}
scope={scope}
isPrivate={isPrivate}
/>
))}
</div>{" "}
@@ -48,6 +48,7 @@ interface RolesListProps {
initialSheetOpen?: boolean;
teamId: number;
scope?: Scope;
isPrivate?: boolean;
}
export function RolesList({
@@ -57,6 +58,7 @@ export function RolesList({
initialSheetOpen,
teamId,
scope,
isPrivate,
}: RolesListProps) {
const { t } = useLocale();
const { isOpen, setIsOpen, selectedRoleId, setSelectedRoleId, handleSheetOpenChange } = useRoleStates(
@@ -113,6 +115,7 @@ export function RolesList({
onOpenChange={handleSheetOpenChange}
teamId={teamId}
scope={scope}
isPrivate={isPrivate}
/>
</>
);
@@ -18,6 +18,7 @@ interface SimplePermissionItemProps {
onChange: (permissions: string[]) => void;
disabled?: boolean;
scope?: Scope;
isPrivate?: boolean;
}
export function SimplePermissionItem({
@@ -26,10 +27,11 @@ export function SimplePermissionItem({
onChange,
disabled,
scope = Scope.Organization,
isPrivate = false,
}: SimplePermissionItemProps) {
const { t } = useLocale();
const { getResourcePermissionLevel, toggleResourcePermissionLevel } = usePermissions(scope);
const scopedRegistry = getPermissionsForScope(scope);
const scopedRegistry = getPermissionsForScope(scope, isPrivate);
const registry = scopedRegistry || PERMISSION_REGISTRY;
const isAllResources = resource === "*";
@@ -44,6 +44,18 @@ const getCachedResourcePermissions = unstable_cache(
{ revalidate: 3600 }
);
const getCachedTeamPrivacy = unstable_cache(
async (teamId: number) => {
const team = await prisma.team.findUnique({
where: { id: teamId },
select: { isPrivate: true },
});
return team?.isPrivate ?? false;
},
["team-privacy"],
{ revalidate: 3600 }
);
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("roles_and_permissions"),
@@ -69,9 +81,10 @@ const Page = async ({ searchParams }: { searchParams: Record<string, string | st
roleSearchParamsCache.parse(searchParams);
const [roles, rolePermissions] = await Promise.all([
const [roles, rolePermissions, isPrivate] = await Promise.all([
getCachedTeamRoles(session.user.org.id),
getCachedResourcePermissions(session.user.id, session.user.org.id, Resource.Role),
getCachedTeamPrivacy(session.user.org.id),
]);
// NOTE: this approach of fetching permssions per resource does not account for fall back roles.
@@ -108,6 +121,7 @@ const Page = async ({ searchParams }: { searchParams: Record<string, string | st
}}
initialSelectedRole={selectedRole}
initialSheetOpen={isSheetOpen}
isPrivate={isPrivate}
/>
</SettingsHeader>
);
@@ -102,6 +102,7 @@ const Page = async ({ params }: { params: Promise<{ id: string }> }) => {
CustomAction.ChangeMemberRole,
CustomAction.Remove,
CustomAction.ListMembers,
CustomAction.ListMembersPrivate,
CustomAction.Impersonate,
],
fallbackRoles: {
@@ -120,12 +121,17 @@ const Page = async ({ params }: { params: Promise<{ id: string }> }) => {
[CustomAction.Impersonate]: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
[CustomAction.ListMembersPrivate]: {
roles: fallbackRolesCanListMembers,
},
},
});
// Map specific permissions to member actions
const memberPermissions = {
canListMembers: permissions[CustomAction.ListMembers],
canListMembers: team.isPrivate
? permissions[CustomAction.ListMembersPrivate]
: permissions[CustomAction.ListMembers],
canInvite: permissions[CustomAction.Invite],
canChangeMemberRole: permissions[CustomAction.ChangeMemberRole],
canRemove: permissions[CustomAction.Remove],
@@ -4,6 +4,7 @@ import { cookies, headers } from "next/headers";
import { notFound } from "next/navigation";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getTeamWithMembers } from "@calcom/features/ee/teams/lib/queries";
import type { AppFlags } from "@calcom/features/flags/config";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { PermissionMapper } from "@calcom/features/pbac/domain/mappers/PermissionMapper";
@@ -11,7 +12,6 @@ import { Resource, CrudAction, Scope } from "@calcom/features/pbac/domain/types/
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { RoleService } from "@calcom/features/pbac/services/role.service";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { getTeamWithMembers } from "@calcom/features/ee/teams/lib/queries";
import { prisma } from "@calcom/prisma";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
@@ -51,6 +51,19 @@ const getCachedResourcePermissions = (userId: number, teamId: number, resource:
{ revalidate: 3600, tags: [`resource-permissions-${teamId}`] }
);
const getCachedTeamPrivacy = (teamId: number) =>
unstable_cache(
async () => {
const team = await prisma.team.findUnique({
where: { id: teamId },
select: { isPrivate: true },
});
return team?.isPrivate ?? false;
},
[`team-privacy-${teamId}`],
{ revalidate: 3600, tags: [`team-privacy-${teamId}`] }
);
const getCachedTeam = (teamId: string, userId: number) =>
unstable_cache(
async () => {
@@ -111,9 +124,10 @@ const Page = async ({
roleSearchParamsCache.parse(searchParams);
const [roles, rolePermissions] = await Promise.all([
const [roles, rolePermissions, isPrivate] = await Promise.all([
getCachedTeamRoles(team.id)(),
getCachedResourcePermissions(session.user.id, team.id, Resource.Role)(),
getCachedTeamPrivacy(team.id)(),
]);
// NOTE: this approach of fetching permssions per resource does not account for fall back roles.
@@ -153,6 +167,7 @@ const Page = async ({
initialSelectedRole={selectedRole}
initialSheetOpen={isSheetOpen}
scope={Scope.Team}
isPrivate={isPrivate}
/>
</SettingsHeader>
);
@@ -70,6 +70,7 @@ const Page = async () => {
userRole: session.user.org.role,
actions: [
CustomAction.ListMembers,
CustomAction.ListMembersPrivate,
CustomAction.Invite,
CustomAction.ChangeMemberRole,
CustomAction.Remove,
@@ -79,6 +80,9 @@ const Page = async () => {
[CustomAction.ListMembers]: {
roles: fallbackRolesThatCanSeeMembers,
},
[CustomAction.ListMembersPrivate]: {
roles: fallbackRolesThatCanSeeMembers,
},
[CustomAction.Invite]: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
@@ -96,7 +100,9 @@ const Page = async () => {
// Map specific permissions to member actions
const memberPermissions = {
canListMembers: permissions[CustomAction.ListMembers],
canListMembers: org.isPrivate
? permissions[CustomAction.ListMembersPrivate]
: permissions[CustomAction.ListMembers],
canInvite: permissions[CustomAction.Invite],
canChangeMemberRole: permissions[CustomAction.ChangeMemberRole],
canRemove: permissions[CustomAction.Remove],
+17 -1
View File
@@ -1,5 +1,6 @@
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { parseRequestData } from "app/api/parseRequestData";
import { cookies } from "next/headers";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";
@@ -10,6 +11,7 @@ import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
const passwordResetRequestSchema = z.object({
csrfToken: z.string(),
password: z.string().refine(validPassword, () => ({
message: "Password does not meet the requirements",
})),
@@ -18,8 +20,22 @@ const passwordResetRequestSchema = z.object({
async function handler(req: NextRequest) {
const body = await parseRequestData(req);
const {
password: rawPassword,
requestId: rawRequestId,
csrfToken: submittedToken,
} = passwordResetRequestSchema.parse(body);
const cookieStore = await cookies();
const cookieToken = cookieStore.get("calcom.csrf_token")?.value;
if (submittedToken !== cookieToken) {
return NextResponse.json({ error: "Invalid CSRF token" }, { status: 403 });
}
// token verified, delete the cookie / a resubmit on failure requires a new csrf token.
cookieStore.delete("calcom.csrf_token");
const { password: rawPassword, requestId: rawRequestId } = passwordResetRequestSchema.parse(body);
// rate-limited there is a low, very low chance that a password request stays valid long enough
// to brute force 3.8126967e+40 options.
const maybeRequest = await prisma.resetPasswordRequest.findFirstOrThrow({
@@ -0,0 +1,213 @@
import { NextRequest } from "next/server";
import { describe, test, expect, vi, beforeEach } from "vitest";
import { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
vi.mock("next/server", () => ({
NextRequest: class MockNextRequest {
url: string;
method: string;
nextUrl: { searchParams: URLSearchParams };
private _headers: Map<string, string>;
constructor(url: string, options: { method?: string } = {}) {
this.url = url;
this.method = options.method || "GET";
this._headers = new Map();
this.nextUrl = { searchParams: new URLSearchParams(url.split("?")[1] || "") };
}
headers = {
get: (key: string): string | null => this._headers.get(key.toLowerCase()) || null,
set: (key: string, value: string): void => {
this._headers.set(key.toLowerCase(), value);
},
has: (key: string): boolean => this._headers.has(key.toLowerCase()),
};
},
NextResponse: {
json: vi.fn((body, init) => ({
json: vi.fn().mockResolvedValue(body),
status: init?.status || 200,
})),
},
}));
vi.mock("@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService");
vi.mock("@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository");
vi.mock("@calcom/lib/logger", () => ({
default: {
getSubLogger: vi.fn(() => ({
info: vi.fn(),
error: vi.fn(),
})),
},
}));
vi.mock("@calcom/lib/server/perfObserver", () => ({
performance: {
mark: vi.fn(),
measure: vi.fn(),
},
}));
vi.mock("@sentry/nextjs", () => ({
wrapApiHandlerWithSentry: vi.fn((handler) => handler),
captureException: vi.fn(),
}));
vi.mock("@calcom/lib/server/getServerErrorFromUnknown", () => ({
getServerErrorFromUnknown: vi.fn((error) => ({
message: error instanceof Error ? error.message : "Unknown error",
statusCode: 500,
url: "test-url",
method: "GET",
})),
}));
vi.mock("../../defaultResponderForAppDir", () => ({
defaultResponderForAppDir: vi.fn((handler) => handler),
}));
vi.mock("@calcom/prisma", () => ({
prisma: {},
}));
const mockCalendarCacheEventService = vi.mocked(CalendarCacheEventService);
describe("/api/cron/calendar-subscriptions-cleanup", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubEnv("CRON_API_KEY", "test-cron-key");
vi.stubEnv("CRON_SECRET", "test-cron-secret");
});
describe("Authentication", () => {
test("should return 403 when no API key is provided", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(403);
const body = await response.json();
expect(body.message).toBe("Forbidden");
});
test("should return 403 when invalid API key is provided", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "invalid-key");
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(403);
const body = await response.json();
expect(body.message).toBe("Forbidden");
});
test("should accept CRON_API_KEY in authorization header", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "test-cron-key");
const mockCleanupStaleCache = vi.fn().mockResolvedValue(undefined);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
expect(mockCleanupStaleCache).toHaveBeenCalled();
});
test("should accept CRON_SECRET as Bearer token", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "Bearer test-cron-secret");
const mockCleanupStaleCache = vi.fn().mockResolvedValue(undefined);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
expect(mockCleanupStaleCache).toHaveBeenCalled();
});
test("should accept API key as query parameter", async () => {
const request = new NextRequest(
"http://localhost/api/cron/calendar-subscriptions-cleanup?apiKey=test-cron-key"
);
const mockCleanupStaleCache = vi.fn().mockResolvedValue(undefined);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
expect(mockCleanupStaleCache).toHaveBeenCalled();
});
});
describe("Cleanup functionality", () => {
test("should successfully cleanup stale cache", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "test-cron-key");
const mockCleanupStaleCache = vi.fn().mockResolvedValue(undefined);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
const body = await response.json();
expect(body.ok).toBe(true);
expect(mockCleanupStaleCache).toHaveBeenCalledOnce();
});
test("should handle cleanup errors gracefully", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "test-cron-key");
const mockError = new Error("Database connection failed");
const mockCleanupStaleCache = vi.fn().mockRejectedValue(mockError);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Database connection failed");
});
test("should handle non-Error exceptions", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "test-cron-key");
const mockCleanupStaleCache = vi.fn().mockRejectedValue("String error");
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Unknown error");
});
});
describe("Service instantiation", () => {
test("should instantiate CalendarCacheEventService with correct dependencies", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions-cleanup");
request.headers.set("authorization", "test-cron-key");
const mockCleanupStaleCache = vi.fn().mockResolvedValue(undefined);
mockCalendarCacheEventService.prototype.cleanupStaleCache = mockCleanupStaleCache;
const { GET } = await import("../route");
await GET(request, { params: Promise.resolve({}) });
expect(mockCalendarCacheEventService).toHaveBeenCalledWith({
calendarCacheEventRepository: expect.any(Object),
});
});
});
});
@@ -0,0 +1,39 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { CalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository";
import { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
import { prisma } from "@calcom/prisma";
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
/**
* Cron webhook
* Cleanup stale calendar cache
*
* @param request
* @returns
*/
async function getHandler(request: NextRequest) {
const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey");
if (![process.env.CRON_API_KEY, `Bearer ${process.env.CRON_SECRET}`].includes(`${apiKey}`)) {
return NextResponse.json({ message: "Forbidden" }, { status: 403 });
}
// instantiate dependencies
const calendarCacheEventRepository = new CalendarCacheEventRepository(prisma);
const calendarCacheEventService = new CalendarCacheEventService({
calendarCacheEventRepository,
});
try {
await calendarCacheEventService.cleanupStaleCache();
return NextResponse.json({ ok: true });
} catch (e: unknown) {
const message = e instanceof Error ? e.message : "Unknown error";
console.error(`[calendar-subscriptions-cleanup] ${message}:`, e);
return NextResponse.json({ message }, { status: 500 });
}
}
export const GET = defaultResponderForAppDir(getHandler);
@@ -0,0 +1,229 @@
import { NextRequest } from "next/server";
import { describe, test, expect, vi, beforeEach } from "vitest";
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
vi.mock("next/server", () => ({
NextRequest: class MockNextRequest {
url: string;
method: string;
nextUrl: { searchParams: URLSearchParams };
private _headers: Map<string, string>;
constructor(url: string, options: { method?: string } = {}) {
this.url = url;
this.method = options.method || "GET";
this._headers = new Map();
this.nextUrl = { searchParams: new URLSearchParams(url.split("?")[1] || "") };
}
headers = {
get: (key: string): string | null => this._headers.get(key.toLowerCase()) || null,
set: (key: string, value: string): void => {
this._headers.set(key.toLowerCase(), value);
},
has: (key: string): boolean => this._headers.has(key.toLowerCase()),
};
},
NextResponse: {
json: vi.fn((body, init) => ({
json: vi.fn().mockResolvedValue(body),
status: init?.status || 200,
})),
},
}));
vi.mock("@calcom/features/calendar-subscription/lib/CalendarSubscriptionService");
vi.mock("@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService");
vi.mock("@calcom/features/calendar-subscription/lib/sync/CalendarSyncService");
vi.mock("@calcom/prisma", () => ({
prisma: {},
}));
const mockCalendarSubscriptionService = vi.mocked(CalendarSubscriptionService);
describe("/api/cron/calendar-subscriptions", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.stubEnv("CRON_API_KEY", "test-cron-key");
vi.stubEnv("CRON_SECRET", "test-cron-secret");
});
describe("Authentication", () => {
test("should return 403 when no API key is provided", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(403);
const body = await response.json();
expect(body.message).toBe("Forbiden");
}, 10000);
test("should return 403 when invalid API key is provided", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "invalid-key");
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(403);
const body = await response.json();
expect(body.message).toBe("Forbiden");
});
test("should accept valid API key", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
});
});
describe("Feature flag checks", () => {
test("should return early when cache AND sync are disabled", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(false);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockCheckForNewSubscriptions = vi.fn();
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
const body = await response.json();
expect(body.ok).toBe(true);
expect(mockCheckForNewSubscriptions).not.toHaveBeenCalled();
});
test("should proceed when both cache and sync are enabled", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
const body = await response.json();
expect(body.ok).toBe(true);
expect(mockCheckForNewSubscriptions).toHaveBeenCalledOnce();
});
});
describe("Subscription checking functionality", () => {
test("should successfully check for new subscriptions", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(200);
const body = await response.json();
expect(body.ok).toBe(true);
expect(mockCheckForNewSubscriptions).toHaveBeenCalledOnce();
});
test("should handle subscription checking errors gracefully", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockError = new Error("Subscription service unavailable");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockRejectedValue(mockError);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Subscription service unavailable");
});
test("should handle non-Error exceptions", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockRejectedValue("String error");
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
const response = await GET(request, { params: Promise.resolve({}) });
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Unknown error");
});
});
describe("Service instantiation", () => {
test("should instantiate all services with correct dependencies", async () => {
const request = new NextRequest("http://localhost/api/cron/calendar-subscriptions");
request.headers.set("authorization", "test-cron-key");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockCheckForNewSubscriptions = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.checkForNewSubscriptions = mockCheckForNewSubscriptions;
const { GET } = await import("../route");
await GET(request, { params: Promise.resolve({}) });
expect(mockCalendarSubscriptionService).toHaveBeenCalledWith({
adapterFactory: expect.any(Object),
selectedCalendarRepository: expect.any(Object),
featuresRepository: expect.any(Object),
calendarSyncService: expect.any(Object),
calendarCacheEventService: expect.any(Object),
});
});
});
});
@@ -0,0 +1,66 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
import { CalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository";
import { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
import { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/sync/CalendarSyncService";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
import { BookingRepository } from "@calcom/lib/server/repository/booking";
import { prisma } from "@calcom/prisma";
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
/**
* Cron webhook
* Checks for new calendar subscriptions (rollouts)
*
* @param request
* @returns
*/
async function getHandler(request: NextRequest) {
const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey");
if (![process.env.CRON_API_KEY, `Bearer ${process.env.CRON_SECRET}`].includes(`${apiKey}`)) {
return NextResponse.json({ message: "Forbiden" }, { status: 403 });
}
// instantiate dependencies
const bookingRepository = new BookingRepository(prisma);
const calendarSyncService = new CalendarSyncService({
bookingRepository,
});
const calendarCacheEventRepository = new CalendarCacheEventRepository(prisma);
const calendarCacheEventService = new CalendarCacheEventService({
calendarCacheEventRepository,
});
const calendarSubscriptionService = new CalendarSubscriptionService({
adapterFactory: new DefaultAdapterFactory(),
selectedCalendarRepository: new SelectedCalendarRepository(prisma),
featuresRepository: new FeaturesRepository(prisma),
calendarSyncService,
calendarCacheEventService,
});
// are features globally enabled
const [isCacheEnabled, isSyncEnabled] = await Promise.all([
calendarSubscriptionService.isCacheEnabled(),
calendarSubscriptionService.isSyncEnabled(),
]);
if (!isCacheEnabled && !isSyncEnabled) {
return NextResponse.json({ ok: true });
}
try {
await calendarSubscriptionService.checkForNewSubscriptions();
return NextResponse.json({ ok: true });
} catch (e) {
const message = e instanceof Error ? e.message : "Unknown error";
return NextResponse.json({ message }, { status: 500 });
}
}
export const GET = defaultResponderForAppDir(getHandler);
@@ -2,4 +2,4 @@ import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { handleQueuedFormResponseCleanup } from "@calcom/app-store/routing-forms/cron/queuedFormResponseCleanup";
export const POST = defaultResponderForAppDir(handleQueuedFormResponseCleanup);
export const GET = defaultResponderForAppDir(handleQueuedFormResponseCleanup);
+13 -7
View File
@@ -3,19 +3,25 @@ import { NextResponse } from "next/server";
import { WEBAPP_URL } from "@calcom/lib/constants";
export async function GET() {
const token = randomBytes(32).toString("hex");
export async function GET(req: Request) {
const url = new URL(req.url);
const sameSiteParam = url.searchParams.get("sameSite");
const useSecureCookies = WEBAPP_URL.startsWith("https://");
// Validate the param, default to "lax"
let sameSite: "lax" | "strict" | "none" = "lax";
if (sameSiteParam === "strict" || (sameSiteParam === "none" && useSecureCookies)) {
sameSite = sameSiteParam;
}
const token = randomBytes(32).toString("hex");
const res = NextResponse.json({ csrfToken: token });
// We need this cookie to be accessible from embeds where the booking flow is displayed within an iframe on a different origin.
// For third‑party iframe contexts (embeds on other sites), browsers require SameSite=None and Secure to make the cookie available.
// For local development on http://localhost we fall back to SameSite=Lax to avoid requiring https during development.
const useSecureCookies = WEBAPP_URL.startsWith("https://");
res.cookies.set("calcom.csrf_token", token, {
httpOnly: true,
secure: useSecureCookies,
sameSite: useSecureCookies ? "none" : "lax",
sameSite,
path: "/",
});
@@ -1,5 +1,3 @@
import { wrapApiHandlerWithSentry } from "@sentry/nextjs";
import { captureException } from "@sentry/nextjs";
import type { Params } from "app/_types";
import { ApiError } from "next/dist/server/api-utils";
import type { NextRequest } from "next/server";
@@ -21,10 +19,13 @@ export const defaultResponderForAppDir = <T extends NextResponse | Response = Ne
let ok = false;
try {
performance.mark("Start");
const result = endpointRoute
? await wrapApiHandlerWithSentry(async () => await handler(req, { params }), endpointRoute)()
: await handler(req, { params });
let result: T | undefined;
if (process.env.NODE_ENV === "development" || !endpointRoute) {
result = await handler(req, { params });
} else {
const { wrapApiHandlerWithSentry } = await import("@sentry/nextjs");
result = await wrapApiHandlerWithSentry(async () => await handler(req, { params }), endpointRoute)();
}
ok = true;
if (result) {
@@ -49,6 +50,7 @@ export const defaultResponderForAppDir = <T extends NextResponse | Response = Ne
// Don't report 400-499 errors to Sentry/console
if (!(serverError.statusCode >= 400 && serverError.statusCode < 500)) {
console.error(serverError);
const { captureException } = await import("@sentry/nextjs");
captureException(error);
}
@@ -0,0 +1,315 @@
import { NextRequest } from "next/server";
import { describe, test, expect, vi, beforeEach } from "vitest";
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
vi.mock("next/server", () => ({
NextRequest: class MockNextRequest {
url: string;
method: string;
nextUrl: { searchParams: URLSearchParams };
private _headers: Map<string, string>;
constructor(url: string, options: { method?: string } = {}) {
this.url = url;
this.method = options.method || "POST";
this._headers = new Map();
this.nextUrl = { searchParams: new URLSearchParams(url.split("?")[1] || "") };
}
headers = {
get: (key: string): string | null => this._headers.get(key.toLowerCase()) || null,
set: (key: string, value: string): void => {
this._headers.set(key.toLowerCase(), value);
},
has: (key: string): boolean => this._headers.has(key.toLowerCase()),
};
},
NextResponse: {
json: vi.fn((body, init) => ({
json: vi.fn().mockResolvedValue(body),
status: init?.status || 200,
})),
},
}));
vi.mock("@calcom/features/calendar-subscription/lib/CalendarSubscriptionService");
vi.mock("@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService");
vi.mock("@calcom/features/calendar-subscription/lib/sync/CalendarSyncService");
vi.mock("@calcom/prisma", () => ({
prisma: {},
}));
const mockCalendarSubscriptionService = vi.mocked(CalendarSubscriptionService);
describe("/api/webhooks/calendar-subscription/[provider]", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
});
describe("Provider validation", () => {
test("should accept google_calendar provider", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(200);
expect(mockProcessWebhook).toHaveBeenCalledWith("google_calendar", request);
}, 10000);
test("should accept office365_calendar provider", async () => {
const request = new NextRequest(
"http://localhost/api/webhooks/calendar-subscription/office365_calendar",
{
method: "POST",
}
);
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "office365_calendar" }),
});
expect(response.status).toBe(200);
expect(mockProcessWebhook).toHaveBeenCalledWith("office365_calendar", request);
});
test("should reject unsupported provider", async () => {
const request = new NextRequest(
"http://localhost/api/webhooks/calendar-subscription/unsupported_calendar",
{
method: "POST",
}
);
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "unsupported_calendar" }),
});
expect(response.status).toBe(400);
const body = await response.json();
expect(body.message).toBe("Unsupported provider");
});
});
describe("Feature flag handling", () => {
test("should return 200 when neither cache nor sync is enabled", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(false);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn();
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(200);
const body = await response.json();
expect(body.message).toBe("No cache or sync enabled");
expect(mockProcessWebhook).not.toHaveBeenCalled();
});
test("should process webhook when cache is enabled", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(200);
const body = await response.json();
expect(body.message).toBe("Webhook processed");
expect(mockProcessWebhook).toHaveBeenCalledWith("google_calendar", request);
});
test("should process webhook when sync is enabled", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(false);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(200);
const body = await response.json();
expect(body.message).toBe("Webhook processed");
expect(mockProcessWebhook).toHaveBeenCalledWith("google_calendar", request);
});
test("should process webhook when both cache and sync are enabled", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(true);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(200);
const body = await response.json();
expect(body.message).toBe("Webhook processed");
expect(mockProcessWebhook).toHaveBeenCalledWith("google_calendar", request);
});
});
describe("Error handling", () => {
test("should handle webhook processing errors gracefully", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockError = new Error("Webhook validation failed");
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockRejectedValue(mockError);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Webhook validation failed");
});
test("should handle non-Error exceptions", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockRejectedValue("String error");
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Unknown error");
});
test("should handle feature flag check errors", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockError = new Error("Feature flag service unavailable");
const mockIsCacheEnabled = vi.fn().mockRejectedValue(mockError);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
const { POST } = await import("../route");
const response = await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(response.status).toBe(500);
const body = await response.json();
expect(body.message).toBe("Feature flag service unavailable");
});
});
describe("Service instantiation", () => {
test("should instantiate all services with correct dependencies", async () => {
const request = new NextRequest("http://localhost/api/webhooks/calendar-subscription/google_calendar", {
method: "POST",
});
const mockIsCacheEnabled = vi.fn().mockResolvedValue(true);
const mockIsSyncEnabled = vi.fn().mockResolvedValue(false);
const mockProcessWebhook = vi.fn().mockResolvedValue(undefined);
mockCalendarSubscriptionService.prototype.isCacheEnabled = mockIsCacheEnabled;
mockCalendarSubscriptionService.prototype.isSyncEnabled = mockIsSyncEnabled;
mockCalendarSubscriptionService.prototype.processWebhook = mockProcessWebhook;
const { POST } = await import("../route");
await POST(request, {
params: Promise.resolve({ provider: "google_calendar" }),
});
expect(mockCalendarSubscriptionService).toHaveBeenCalledWith({
adapterFactory: expect.any(Object),
selectedCalendarRepository: expect.any(Object),
featuresRepository: expect.any(Object),
calendarSyncService: expect.any(Object),
calendarCacheEventService: expect.any(Object),
});
});
});
});
@@ -0,0 +1,87 @@
import type { Params } from "app/_types";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import type { CalendarSubscriptionProvider } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
import { CalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository";
import { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
import { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/sync/CalendarSyncService";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import logger from "@calcom/lib/logger";
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
import { BookingRepository } from "@calcom/lib/server/repository/booking";
import { prisma } from "@calcom/prisma";
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
const log = logger.getSubLogger({ prefix: ["calendar-webhook"] });
function extractAndValidateProviderFromParams(params: Params): CalendarSubscriptionProvider | null {
if (!("provider" in params)) {
return null;
}
const { provider } = params;
if (provider === "google_calendar" || provider === "office365_calendar") {
return provider;
}
return null;
}
/**
* Handles incoming POST requests for calendar webhooks.
* It processes the webhook based on the calendar provider specified in the URL.
* If the provider is unsupported, it returns a 400 response.
* If the webhook is processed successfully, it returns a 200 response.
* In case of errors during processing, it returns a 500 response with the error message.
* @param {NextRequest} request - The incoming request object.
* @param {Object} context - The context object containing route parameters.
* @param {Promise<Params>} context.params - A promise that resolves to the route parameters.
* @returns {Promise<NextResponse>} - A promise that resolves to the response object.
*/
async function postHandler(request: NextRequest, ctx: { params: Promise<Params> }) {
const providerFromParams = extractAndValidateProviderFromParams(await ctx.params);
if (!providerFromParams) {
return NextResponse.json({ message: "Unsupported provider" }, { status: 400 });
}
try {
// instantiate dependencies
const bookingRepository = new BookingRepository(prisma);
const calendarSyncService = new CalendarSyncService({
bookingRepository,
});
const calendarCacheEventRepository = new CalendarCacheEventRepository(prisma);
const calendarCacheEventService = new CalendarCacheEventService({
calendarCacheEventRepository,
});
const calendarSubscriptionService = new CalendarSubscriptionService({
adapterFactory: new DefaultAdapterFactory(),
selectedCalendarRepository: new SelectedCalendarRepository(prisma),
featuresRepository: new FeaturesRepository(prisma),
calendarSyncService,
calendarCacheEventService,
});
// are features globally enabled
const [isCacheEnabled, isSyncEnabled] = await Promise.all([
calendarSubscriptionService.isCacheEnabled(),
calendarSubscriptionService.isSyncEnabled(),
]);
if (!isCacheEnabled && !isSyncEnabled) {
log.debug("No cache or sync enabled");
return NextResponse.json({ message: "No cache or sync enabled" }, { status: 200 });
}
await calendarSubscriptionService.processWebhook(providerFromParams, request);
return NextResponse.json({ message: "Webhook processed" }, { status: 200 });
} catch (error) {
log.error("Error processing webhook", { error });
const message = error instanceof Error ? error.message : "Unknown error";
return NextResponse.json({ message }, { status: 500 });
}
}
export const POST = defaultResponderForAppDir(postHandler);
@@ -266,7 +266,7 @@ export default function CancelBooking(props: Props) {
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
const response = await fetch("/api/csrf", { cache: "no-store" });
const response = await fetch("/api/csrf?sameSite=none", { cache: "no-store" });
const { csrfToken } = await response.json();
const res = await fetch("/api/cancel", {
Regular → Executable
+2 -2
View File
@@ -23,9 +23,9 @@ try {
"*/5 * * * * *",
async function () {
await Promise.allSettled([
fetchCron("/calendar-cache/cron"),
fetchCron("/cron/calendar-subscriptions"),
// fetchCron("/calendar-cache/cron"),
// fetchCron("/cron/calVideoNoShowWebhookTriggers"),
//
fetchCron("/tasks/cron"),
]);
},
@@ -5,13 +5,13 @@ import {
getOrganizationSettings,
getVerifiedDomain,
} from "@calcom/features/ee/organizations/lib/orgSettings";
import { getTeamWithMembers } from "@calcom/features/ee/teams/lib/queries";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { IS_CALCOM } from "@calcom/lib/constants";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
import logger from "@calcom/lib/logger";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { getTeamWithMembers } from "@calcom/features/ee/teams/lib/queries";
import slugify from "@calcom/lib/slugify";
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
import prisma from "@calcom/prisma";
@@ -1,7 +1,8 @@
"use client";
import Link from "next/link";
import type { CSSProperties } from "react";
import { useEffect, useReducer, type CSSProperties } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -16,121 +17,176 @@ import type { getServerSideProps } from "@server/lib/auth/forgot-password/[id]/g
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Page({ requestId, isRequestExpired, csrfToken }: PageProps) {
function Success() {
const { t } = useLocale();
const formMethods = useForm<{ new_password: string }>();
const success = formMethods.formState.isSubmitSuccessful;
const loading = formMethods.formState.isSubmitting;
const passwordValue = formMethods.watch("new_password");
const isEmpty = passwordValue?.length === 0;
return (
<>
<div className="space-y-6">
<div>
<h2 className="font-cal text-emphasis mt-6 text-center text-3xl font-extrabold">
{t("password_updated")}
</h2>
</div>
<Button href="/auth/login" className="w-full justify-center">
{t("login")}
</Button>
</div>
</>
);
}
const submitChangePassword = async ({ password, requestId }: { password: string; requestId: string }) => {
function Expired() {
const { t } = useLocale();
return (
<>
<div className="space-y-6">
<div>
<h2 className="font-cal text-emphasis mt-6 text-center text-3xl font-extrabold">{t("whoops")}</h2>
<h2 className="text-emphasis text-center text-3xl font-extrabold">{t("request_is_expired")}</h2>
</div>
<p>{t("request_is_expired_instructions")}</p>
<Link href="/auth/forgot-password" passHref legacyBehavior>
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
{t("try_again")}
</button>
</Link>
</div>
</>
);
}
type FormValues = {
newPassword: string;
csrfToken: string;
};
function PasswordResetForm({
form: formMethods,
requestId,
}: {
form: UseFormReturn<FormValues>;
requestId: string;
}) {
const { t } = useLocale();
const [refreshToken, forceRefresh] = useReducer((x) => x + 1, 0);
const {
watch,
setValue,
setError,
formState: { isSubmitting: loading },
} = formMethods;
useEffect(() => {
fetch("/api/csrf", { cache: "no-store" })
.then((res) => res.json())
.then(({ csrfToken }) => setValue("csrfToken", csrfToken))
.catch(() => setValue("csrfToken", ""));
}, [setValue, refreshToken]);
const submitChangePassword = async ({
password,
requestId,
csrfToken,
}: {
password: string;
requestId: string;
csrfToken: string;
}) => {
const res = await fetch("/api/auth/reset-password", {
method: "POST",
body: JSON.stringify({ requestId, password }),
body: JSON.stringify({ requestId, password, csrfToken }),
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (!res.ok) return formMethods.setError("new_password", { type: "server", message: json.message });
if (!res.ok) {
// if the request fails, we want to force refresh of the CSRF token - this allows resubmit
forceRefresh();
return setError("newPassword", { type: "server", message: json.message });
}
};
const Success = () => {
return (
<>
<div className="space-y-6">
<div>
<h2 className="font-cal text-emphasis mt-6 text-center text-3xl font-extrabold">
{t("password_updated")}
</h2>
</div>
<Button href="/auth/login" className="w-full justify-center">
{t("login")}
</Button>
</div>
</>
);
};
const passwordValue = watch("newPassword");
const isEmpty = passwordValue?.length === 0;
const Expired = () => {
return (
<Form
className="space-y-6"
form={formMethods}
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}
handleSubmit={async (values) => {
await submitChangePassword({
password: values.newPassword,
csrfToken: values.csrfToken,
requestId,
});
}}>
<input {...formMethods.register("csrfToken")} name="csrfToken" type="hidden" hidden />
<div className="mt-1">
<PasswordField
{...formMethods.register("newPassword", {
minLength: {
message: t("password_hint_min"),
value: 7, // We don't have user here so we can't check if they are admin or not
},
pattern: {
message: "Should contain a number, uppercase and lowercase letters",
value: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).*$/gm,
},
})}
label={t("new_password")}
/>
</div>
<div>
<Button
loading={loading}
color="primary"
type="submit"
disabled={loading || isEmpty}
className="w-full justify-center">
{t("reset_password")}
</Button>
</div>
</Form>
);
}
export default function Page({ requestId, isRequestExpired }: PageProps) {
const { t } = useLocale();
const formMethods = useForm<FormValues>({
defaultValues: {
newPassword: "",
csrfToken: "",
},
});
const {
formState: { isSubmitSuccessful: success },
} = formMethods;
if (isRequestExpired) {
return (
<>
<div className="space-y-6">
<div>
<h2 className="font-cal text-emphasis mt-6 text-center text-3xl font-extrabold">{t("whoops")}</h2>
<h2 className="text-emphasis text-center text-3xl font-extrabold">{t("request_is_expired")}</h2>
</div>
<p>{t("request_is_expired_instructions")}</p>
<Link href="/auth/forgot-password" passHref legacyBehavior>
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
{t("try_again")}
</button>
</Link>
</div>
</>
<AuthContainer showLogo heading={t("reset_password")}>
<Expired />
</AuthContainer>
);
};
}
return (
<AuthContainer showLogo heading={!success ? t("reset_password") : undefined}>
{isRequestExpired && <Expired />}
{!isRequestExpired && !success && (
<>
<Form
className="space-y-6"
form={formMethods}
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}
handleSubmit={async (values) => {
await submitChangePassword({
password: values.new_password,
requestId,
});
}}>
<input name="csrfToken" type="hidden" defaultValue={csrfToken} hidden />
<div className="mt-1">
<PasswordField
{...formMethods.register("new_password", {
minLength: {
message: t("password_hint_min"),
value: 7, // We don't have user here so we can't check if they are admin or not
},
pattern: {
message: "Should contain a number, uppercase and lowercase letters",
value: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).*$/gm,
},
})}
label={t("new_password")}
/>
</div>
<div>
<Button
loading={loading}
color="primary"
type="submit"
disabled={loading || isEmpty}
className="w-full justify-center">
{t("reset_password")}
</Button>
</div>
</Form>
</>
)}
{!isRequestExpired && success && (
<>
<Success />
</>
)}
{success ? <Success /> : <PasswordResetForm form={formMethods} requestId={requestId} />}
</AuthContainer>
);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "5.7.5",
"version": "5.7.8",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -138,7 +138,7 @@
"react-select": "^5.7.0",
"react-timezone-select": "^1.4.0",
"react-turnstile": "^1.1.3",
"react-use-intercom": "1.5.1",
"react-use-intercom": "^5.5.0",
"recoil": "^0.7.7",
"remove-markdown": "^0.5.0",
"retell-client-js-sdk": "^2.0.0",
+1 -1
View File
@@ -12,7 +12,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
const userIp = getIP(req);
await checkRateLimitAndThrowError({
rateLimitingType: "core",
rateLimitingType: "instantMeeting",
identifier: `instant.event-${piiHasher.hash(userIp)}`,
});
@@ -5,7 +5,7 @@ import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { ZGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils";
import type { TGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils";
import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots";
import { getAvailableSlotsService } from "@calcom/features/di/containers/AvailableSlots";
import { fetcher } from "@calcom/lib/retellAIFetcher";
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
import prisma from "@calcom/prisma";
@@ -6,7 +6,7 @@ import stripe from "@calcom/features/ee/payments/server/stripe";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
export const config = {
api: {
@@ -27,7 +27,10 @@ const handleSubscriptionUpdate = async (event: Stripe.Event) => {
});
if (!app) {
throw new HttpCode({ statusCode: 202, message: "Received and discarded" });
throw new HttpCode({
statusCode: 202,
message: `No credential found with subscription ID ${subscription.id}`,
});
}
await prisma.credential.update({
@@ -51,7 +54,10 @@ const handleSubscriptionDeleted = async (event: Stripe.Event) => {
});
if (!app) {
throw new HttpCode({ statusCode: 202, message: "Received and discarded" });
throw new HttpCode({
statusCode: 202,
message: `No credential found with subscription ID ${subscription.id}`,
});
}
// should we delete the credential here rather than marking as inactive?
@@ -103,7 +109,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
} catch (_err) {
const err = getErrorFromUnknown(_err);
console.error(`Webhook Error: ${err.message}`);
if (!err.message.includes("No credential found with subscription ID")) {
console.error(`Webhook Error: ${err.message}`);
}
res.status(err.statusCode ?? 500).send({
message: err.message,
stack: IS_PRODUCTION ? undefined : err.stack,
@@ -70,7 +70,7 @@ test.describe("Forgot password", async () => {
// Wait for page to fully load
await page.waitForSelector("text=Reset Password");
await page.fill('input[name="new_password"]', newPassword);
await page.fill('input[name="newPassword"]', newPassword);
await page.click('button[type="submit"]');
await page.waitForSelector("text=Password updated");
+33 -2
View File
@@ -160,6 +160,7 @@ const createTeamAndAddUser = async (
orgRequestedSlug,
schedulingType,
assignAllTeamMembersForSubTeamEvents,
teamSlug,
}: {
user: { id: number; email: string; username: string | null; role?: MembershipRole };
isUnpublished?: boolean;
@@ -172,12 +173,15 @@ const createTeamAndAddUser = async (
orgRequestedSlug?: string;
schedulingType?: SchedulingType;
assignAllTeamMembersForSubTeamEvents?: boolean;
teamSlug?: string;
},
workerInfo: WorkerInfo
) => {
const slugIndex = index ? `-count-${index}` : "";
const slug =
orgRequestedSlug ?? `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}${slugIndex}`;
teamSlug ??
orgRequestedSlug ??
`${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}${slugIndex}`;
const data: PrismaType.TeamCreateInput = {
name: `user-id-${user.id}'s ${isOrg ? "Org" : "Team"}`,
isOrganization: isOrg,
@@ -283,6 +287,7 @@ export const createUsersFixture = (
schedulingType?: SchedulingType;
teamEventTitle?: string;
teamEventSlug?: string;
teamSlug?: string;
teamEventLength?: number;
isOrg?: boolean;
isOrgVerified?: boolean;
@@ -367,6 +372,7 @@ export const createUsersFixture = (
orgRequestedSlug: scenario.orgRequestedSlug,
schedulingType: scenario.schedulingType,
assignAllTeamMembersForSubTeamEvents: scenario.assignAllTeamMembersForSubTeamEvents,
teamSlug: scenario?.teamSlug,
},
workerInfo
);
@@ -465,7 +471,7 @@ export const createUsersFixture = (
},
data: {
orgProfiles: _user.profiles.length
? {
? {
connect: _user.profiles.map((profile) => ({ id: profile.id })),
}
: {
@@ -711,6 +717,31 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
}
return membership;
},
getAllTeamMembership: async () => {
const memberships = await prisma.membership.findMany({
where: {
userId: user.id,
team: {
isOrganization: false,
},
},
include: { team: true, user: true },
});
const filteredMemberships = memberships.map((membership) => ({
...membership,
team: {
...membership.team,
metadata: teamMetadataSchema.parse(membership.team.metadata),
},
}));
if (filteredMemberships.length === 0) {
throw new Error("No team memberships found for user");
}
return filteredMemberships;
},
getOrgMembership: async () => {
const membership = await prisma.membership.findFirstOrThrow({
where: {
+1 -2
View File
@@ -15,7 +15,6 @@ export async function expectInvitationEmailToBeReceived(
) {
if (!emails) return null;
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(2000);
const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail });
expect(receivedEmails?.total).toBe(1);
@@ -36,5 +35,5 @@ export async function expectExistingUserToBeInvitedToOrganization(
userEmail: string,
subject?: string | null
) {
return expectInvitationEmailToBeReceived(page, emails, userEmail, subject, "settings/team");
return expectInvitationEmailToBeReceived(page, emails, userEmail, subject, "teams?token");
}
+126 -3
View File
@@ -47,8 +47,7 @@ test.describe("Team", () => {
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
).toHaveCount(1);
// eslint-disable-next-line playwright/no-conditional-in-test
if (!inviteLink) return null;
expect(inviteLink).toBeTruthy();
// Follow invite link to new window
const context = await browser.newContext();
@@ -103,7 +102,7 @@ test.describe("Team", () => {
});
});
test("Invitation (verified)", async ({ browser, page, users, emails }) => {
test("Invitation (verified)", async ({ page, users, emails }) => {
const t = await localize("en");
const teamOwner = await users.create({ name: `team-owner-${Date.now()}` }, { hasTeam: true });
const { team } = await teamOwner.getFirstTeamMembership();
@@ -192,4 +191,128 @@ test.describe("Team", () => {
});
expect(parentEventType?.children.find((et) => et.userId === invitedMember.id)).toBeTruthy();
});
test("Auto-accept invitation for existing user", async ({ browser, page, users, emails }) => {
const t = await localize("en");
const teamOwner = await users.create({ name: "Invited User" }, { hasTeam: true });
const { team } = await teamOwner.getFirstTeamMembership();
const invitedUser = await users.create({
email: `invited-user-${Date.now()}@example.com`,
name: "Invited User",
});
await teamOwner.apiLogin();
await page.goto(`/settings/teams/${team.id}/members`);
let inviteLink: string;
await test.step("Send invitation to existing user", async () => {
await page.waitForLoadState("domcontentloaded");
await page.waitForTimeout(500);
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUser.email);
await page.getByText(t("send_invite")).click();
inviteLink = await expectInvitationEmailToBeReceived(
page,
emails,
invitedUser.email,
`${teamOwner.name} invited you to join the team ${team.name} on Cal.com`,
"teams?token"
);
expect(inviteLink).toContain("autoAccept=true");
const membership = await prisma.membership.findFirst({
where: {
userId: invitedUser.id,
teamId: team.id,
},
});
expect(membership?.accepted).toBe(false);
});
await test.step("Auto-accept invitation by clicking link", async () => {
const [secondContext, secondPage] = await invitedUser.apiLoginOnNewBrowser(browser);
await secondPage.goto(inviteLink);
await expect(secondPage.getByText("Successfully joined")).toBeVisible();
const membership = await prisma.membership.findFirst({
where: {
userId: invitedUser.id,
teamId: team.id,
},
});
expect(membership?.accepted).toBe(true);
await secondPage.close();
await secondContext.close();
});
});
test("Error when wrong user tries to use invitation link", async ({ browser, page, users, emails }) => {
const t = await localize("en");
const teamOwner = await users.create({ name: "Wrong User" }, { hasTeam: true });
const { team } = await teamOwner.getFirstTeamMembership();
const invitedUser = await users.create({
email: `invited-user-${Date.now()}@example.com`,
name: "Invited User",
});
const wrongUser = await users.create({
email: `wrong-user-${Date.now()}@example.com`,
name: "Wrong User",
});
await teamOwner.apiLogin();
await page.goto(`/settings/teams/${team.id}/members`);
let inviteLink: string;
await test.step("Send invitation to specific user", async () => {
await page.waitForLoadState("domcontentloaded");
await page.waitForTimeout(500);
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUser.email);
await page.getByText(t("send_invite")).click();
inviteLink = await expectInvitationEmailToBeReceived(
page,
emails,
invitedUser.email,
`${teamOwner.name} invited you to join the team ${team.name} on Cal.com`,
"teams?token"
);
expect(inviteLink).toContain("autoAccept=true");
});
await test.step("Wrong user tries to use invitation link", async () => {
const [secondContext, secondPage] = await wrongUser.apiLoginOnNewBrowser(browser);
await secondPage.goto(inviteLink);
await expect(secondPage.getByText("This invitation is not for your account")).toBeVisible();
const membership = await prisma.membership.findFirst({
where: {
userId: wrongUser.id,
teamId: team.id,
},
});
expect(membership).toBeNull();
const invitedMembership = await prisma.membership.findFirst({
where: {
userId: invitedUser.id,
teamId: team.id,
},
});
expect(invitedMembership?.accepted).toBe(false);
await secondPage.close();
await secondContext.close();
});
});
});
+106
View File
@@ -10,6 +10,7 @@ import {
confirmReschedule,
fillStripeTestCheckout,
selectFirstAvailableTimeSlotNextMonth,
submitAndWaitForResponse,
testName,
} from "./lib/testUtils";
@@ -301,3 +302,108 @@ test.describe("Teams - NonOrg", () => {
});
todo("Reschedule a Round Robin EventType booking");
});
test.describe("Team Slug Validation", () => {
test.afterEach(({ users, orgs }) => {
users.deleteAll();
orgs.deleteAll();
});
test("Teams in different organizations can have the same slug", async ({ page, users, orgs }) => {
const org1 = await orgs.create({ name: "Organization 1" });
const org2 = await orgs.create({ name: "Organization 2" });
const owner1 = await users.create(
{
organizationId: org1.id,
roleInOrganization: "OWNER",
},
{
hasTeam: true,
teamSlug: "cal",
teamRole: "OWNER",
}
);
const owner2 = await users.create(
{
organizationId: org2.id,
roleInOrganization: "OWNER",
},
{
hasTeam: true,
teamSlug: "calCom",
teamRole: "OWNER",
}
);
const { team: team1 } = await owner1.getFirstTeamMembership();
await owner1.apiLogin();
await page.goto(`/settings/teams/${team1.id}/profile`);
await page.locator('input[name="slug"]').fill("calCom");
await submitAndWaitForResponse(page, "/api/trpc/teams/update?batch=1", {
action: () => page.locator("[data-testid=update-team-profile]").click(),
});
});
test("Teams within same organization cannot have duplicate slugs", async ({ page, users, orgs }) => {
const org = await orgs.create({ name: "Organization 1" });
const owner = await users.create(
{
organizationId: org.id,
roleInOrganization: "OWNER",
},
{
hasTeam: true,
numberOfTeams: 2,
teamRole: "OWNER",
}
);
const teams = await owner.getAllTeamMembership();
await owner.apiLogin();
await page.goto(`/settings/teams/${teams[0].team.id}/profile`);
if (!teams[1].team.slug) throw new Error("Slug not found for team 2");
await page.locator('input[name="slug"]').fill(teams[1].team.slug);
await submitAndWaitForResponse(page, "/api/trpc/teams/update?batch=1", {
action: () => page.locator("[data-testid=update-team-profile]").click(),
expectedStatusCode: 409,
});
});
test("Teams without organization can have same slug as teams in organizations", async ({
page,
users,
orgs,
}) => {
const org = await orgs.create({ name: "Organization 1" });
const orgOwner = await users.create(
{
organizationId: org.id,
roleInOrganization: "OWNER",
},
{
hasTeam: true,
teamSlug: "calCom",
teamRole: "OWNER",
}
);
const teamOwner = await users.create(
{ username: "pro-user", name: "pro-user" },
{
hasTeam: true,
}
);
const { team } = await teamOwner.getFirstTeamMembership();
await teamOwner.apiLogin();
await page.goto(`/settings/teams/${team.id}/profile`);
await page.locator('input[name="slug"]').fill("calCom");
await submitAndWaitForResponse(page, "/api/trpc/teams/update?batch=1", {
action: () => page.locator("[data-testid=update-team-profile]").click(),
});
});
});
+28 -5
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "لم يتم العثور على مستخدمين متاحين. هل يمكنك تجرِبة وقت آخر؟",
"timeslot_unavailable_book_a_new_time": "الفترة الزمنية المحددة لم تعد متاحة. <0>يرجى اختيار وقت جديد</0>",
"timeslot_unavailable_short": "محجوز",
"just_connected_description": "لقد اتصلت للتو. يرجى الحجز من الفترات المتاحة أعلاه أو إعادة المحاولة لاحقًا.",
"unavailable_timeslot_title": "فترة زمنية غير متاحة",
"booking_time_out_of_bounds_error": "لا يمكن حجز هذا النوع من الفعاليات في هذا الوقت. هل يمكنك تجربة وقت آخر؟",
"request_body_end_time_internal_error": "خطأ داخلي. محتوى الطلب لا يحتوي على وقت النهاية",
@@ -321,6 +322,7 @@
"secret_meeting": "اجتماع سري",
"login_instead": "تسجيل الدخول بدلًا من ذلك",
"create_account": "إنشاء حساب",
"get_started": "ابدأ الآن",
"confirm_password": "تأكيد كلمة المرور",
"reset_your_password": "عيّن كلمة المرور الجديدة متبعاً الإرشادات المرسلة إلى عنوان بريدك الإلكتروني.",
"org_banner_instructions": "الرجاء رفع صورة بعرض {{width}} وطول {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "الحضور",
"add_attendees": "إضافة الحضور",
"show_advanced_settings": "عرض الإعدادات المتقدمة",
"hide_advanced_settings": "إخفاء الإعدادات المتقدمة",
"event_name": "اسم الحدث",
"event_name_in_calendar": "اسم الحدث في التقويم",
"event_name_tooltip": "الاسم الذي سيظهر في التقاويم",
@@ -1231,7 +1234,7 @@
"contact_sales": "تواصل مع قسم المبيعات",
"error_404": "خطأ 404",
"default": "الافتراضي",
"set_to_default": "تعيين إلى الافتراضي",
"set_to_default": "تعيين كافتراضي",
"new_schedule_btn": "جدول جديد",
"add_new_schedule": "إضافة جدول جديد",
"add_new_calendar": "إضافة تقويم جديد",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "لن يتمكن أعضاء فريقك من رؤية أعضاء الفريق الآخرين عند تشغيل هذا الإعداد.",
"you_cannot_see_team_members": "لا يمكنك رؤية جميع أعضاء الفريق السري.",
"you_cannot_see_teams_of_org": "لا يمكنك رؤية فرق المؤسسة الخاصة.",
"allow_booker_to_select_duration": "السماح لمن يقوم بالحجز بتحديد المدة",
"allow_multiple_durations": "السماح بمدد متعددة",
"impersonate_user_tip": "يتم مراجعة جميع استخدامات هذه الميزة.",
"impersonating_user_warning": "انتحال اسم مستخدم \"{{user}}\".",
"impersonating_stop_instructions": "انقر هنا للإيقاف",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "هل تريد بالتأكيد حذف تكوين {{connectionType}}؟ لن يتمكن أعضاء فريقك الذين يستخدمون معلومات تسجيل الدخول إلى {{connectionType}} من الوصول إلى Cal.com بعد الآن.",
"organizer_timezone": "منظم المناطق الزمنية",
"email_user_cta": "عرض الدعوة",
"accept_invite": "قبول الدعوة",
"join_now": "انضم الآن",
"successfully_joined": "تم الانضمام بنجاح",
"email_no_user_invite_heading_subteam": "تمت دعوتك للانضمام إلى فريق من منظمة {{parentTeamName}}",
"email_no_user_invite_heading_org": "تمت دعوتك للانضمام إلى منظمة {{appName}}",
"email_no_user_invite_subheading": "دعاك {{invitedBy}} للانضمام إلى فريقه على {{appName}}. إن {{appName}} هو برنامج جدولة الأحداث الذي يمكّنك أنت وفريقك من جدولة الاجتماعات دون الحاجة إلى المراسلة عبر البريد الإلكتروني.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "فشل تأكيد الحجز",
"not_enough_seats": "لا توجد مقاعد كافية",
"form_builder_field_already_exists": "يوجد حقل بهذا الاسم بالفعل",
"guests_field_must_be_multiemail": "يجب أن يكون حقل الضيوف من نوع 'بريد إلكتروني متعدد'",
"show_on_booking_page": "إظهار في صفحة الحجز",
"visit_cancelled_booking": "يمكنك زيارة صفحة الحجز الملغى",
"get_started_zapier_templates": "البدء في استخدام قوالب Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "تم نسخ رابط الدعوة",
"invite_link_deleted": "تم حذف رابط الدعوة",
"api_key_deleted": "تم حذف مفتاح API",
"delete_api_key_warning": "سيؤدي هذا إلى حذف مفتاح API بشكل دائم. ستفقد أي تطبيقات تستخدم هذا المفتاح الوصول إلى حسابك على الفور. لا يمكن التراجع عن هذا الإجراء.",
"invite_link_updated": "تم حفظ إعدادات رابط الدعوة",
"link_expires_after": "تم تعيين انتهاء صلاحية الروابط بعد...",
"one_day": "1 يوم",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "بالمتابعة، فإنك توافق على",
"terms": "الشروط",
"always_show_x_days": "دائمًا {{x}} أيام متاحة",
"always_show_x_days_description": "ينقل تلقائيًا نافذة الحجز لمدة {{x}} يوم إلى الأمام كل يوم، مما يضمن أن تقويمك يعرض دائمًا الفترات المتاحة للـ {{x}} يومًا القادمة.",
"team_subscription_cancelled_successfully": "تم إلغاء اشتراك الفريق بنجاح",
"team_subscription_cancellation_error": "حدث خطأ أثناء إلغاء اشتراك الفريق",
"unable_to_subscribe_to_the_platform": "حدث خطأ أثناء محاولة الاشتراك في خطة المنصة، يرجى المحاولة مرة أخرى لاحقًا",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "ينتهي بـ",
"filter_operator_is_empty": "فارغ",
"filter_operator_not_empty": "غير فارغ",
"translate_description_button": "ترجمة العنوان/الوصف إلى لغة متصفح الزائر باستخدام الذكاء الاصطناعي",
"translate_description_button": "ترجمة العنوان والوصف تلقائيًا",
"translate_description_button_description": "ترجمة العناوين والأوصاف تلقائيًا إلى لغة متصفح الزائر باستخدام الذكاء الاصطناعي.",
"rr_distribution_method": "التوزيع",
"rr_distribution_method_description": "يتيح تحسين التوزيع لتحقيق أقصى قدر من التوفر أو لتحقيق توزيع أكثر توازناً.",
"rr_distribution_method_availability_title": "تعظيم التوفر",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "ميزة المقاعد لا تدعم الفعاليات المتكررة. قم بتعطيل ميزة المقاعد لجعلها متكررة.",
"recurring_event_seats_error": "الفعالية المتكررة لا تدعم ميزة المقاعد. قم بتعطيل ميزة المقاعد أو اجعل الفعالية غير متكررة.",
"segment": "شريحة",
"saved_filters": "الفلاتر المحفوظة",
"rename": "إعادة تسمية",
"rename_segment": "إعادة تسمية الشريحة",
"delete_segment": "حذف الشريحة",
@@ -3338,6 +3349,7 @@
"picklist": "قائمة الاختيار",
"most_cancelled_bookings": "الأكثر إلغاءً للحجوزات",
"interface_language": "لغة الواجهة",
"interface_language_description": "اختر لغتك المفضلة لواجهة الحجز",
"visitors_browser_language": "لغة متصفح الزائر",
"name_or_email": "الاسم أو البريد الإلكتروني",
"salesforce_round_robin_skip_fallback_to_lead_owner": "إذا لم يتم العثور على جهة اتصال، يتم الرجوع إلى مالك العميل المحتمل إذا كان موجودًا",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "إدارة الأدوار والصلاحيات لمؤسستك",
"learn_more_permissions": "تعرف على المزيد حول كيفية عمل الصلاحيات",
"role_name": "اسم الدور",
"role_name_required": "اسم الدور مطلوب",
"role_name_placeholder": "مثال: فريق المبيعات، موظفو الدعم",
"role_description": "وصف الدور",
"role_description_placeholder": "صف الغرض من هذا الدور والصلاحيات التي يحتاجها",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "تم إنشاء الدور بنجاح",
"role_updated_successfully": "تم تحديث الدور بنجاح",
"delete_role": "حذف الدور",
"confirm_delete_role": "هل أنت متأكد من رغبتك في حذف هذا الدور؟ لا يمكن التراجع عن هذا الإجراء.",
"confirm_delete_role": "هل أنت متأكد أنك تريد حذف الدور: {{roleName}}. لا يمكن التراجع عن هذا الإجراء.",
"role_deleted_successfully": "تم حذف الدور بنجاح",
"error_creating_role": "خطأ في إنشاء الدور",
"error_updating_role": "خطأ في تحديث الدور",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "تعديل وتغيير إعدادات سير العمل",
"pbac_desc_delete_workflows": "إزالة سير العمل من النظام",
"pbac_resource_webhook": "ويب هوك",
"pbac_resource_availability": "الأوقات المتاحة",
"pbac_resource_out_of_office": "خارج المكتب",
"pbac_desc_create_webhooks": "إنشاء ويب هوكس",
"pbac_desc_view_webhooks": "عرض ويب هوكس",
"pbac_desc_update_webhooks": "تحديث الويب هوك",
"pbac_desc_delete_webhooks": "حذف الويب هوك",
"pbac_desc_create_availability": "إنشاء وقت متاح",
"pbac_desc_view_availability": "عرض الأوقات المتاحة",
"pbac_desc_update_availability": "تحديث الأوقات المتاحة",
"pbac_desc_delete_availability": "حذف الأوقات المتاحة",
"pbac_desc_create_out_of_office": "إنشاء حالة خارج المكتب",
"pbac_desc_view_out_of_office": "عرض حالة خارج المكتب",
"pbac_desc_update_out_of_office": "تحديث حالة خارج المكتب",
"pbac_desc_delete_out_of_office": "حذف حالة خارج المكتب",
"pbac_desc_manage_workflows": "وصول كامل لإدارة جميع سير العمل",
"pbac_desc_create_event_types": "إنشاء أنواع الأحداث",
"pbac_desc_view_event_types": "عرض أنواع الأحداث",
@@ -3732,4 +3755,4 @@
"use_voice": "استخدم الصوت",
"current_voice": "الصوت الحالي",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ أضف السلاسل الجديدة أعلاه هنا ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+30 -7
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Mövcud istifadəçilər tapılmadı. Başqa bir vaxt aralığını yoxlaya bilərsinizmi?",
"timeslot_unavailable_book_a_new_time": "Seçilmiş vaxt artıq mövcud deyil. <0>Zəhmət olmasa yeni vaxt seçin</0>",
"timeslot_unavailable_short": "Tutulub",
"just_connected_description": "Siz indicə qoşuldunuz. Zəhmət olmasa, yuxarıdakı slotlardan birini seçin və ya sonra yenidən cəhd edin.",
"unavailable_timeslot_title": "Mövcud olmayan vaxt intervalı",
"booking_time_out_of_bounds_error": "Tədbir növünü bu vaxtda rezerv etmək mümkün deyil. Başqa vaxt aralığını sınamaq istərdinizmi?",
"request_body_end_time_internal_error": "Daxili səhv. Sorğu bədənində bitmə vaxtı yoxdur",
@@ -321,6 +322,7 @@
"secret_meeting": "Gizli Görüş",
"login_instead": "Əvəzinə daxil olun",
"create_account": "Hesab yaradın",
"get_started": "Başlayın",
"confirm_password": "Şifrəni təsdiqləyin",
"reset_your_password": "E-poçt ünvanınıza göndərilən təlimatlarla yeni şifrənizi təyin edin.",
"org_banner_instructions": "Zəhmət olmasa {{width}} enində və {{height}} hündürlüyündə şəkil yükləyin.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Tədbir Tipi Rəngi (İşıq Mövzusu)",
"dark_event_type_color": "Tədbir Tipi Rəngi (Qaranlıq Mövzu)",
"file_not_named": "Fayl adlandırılmayıb [idOrSlug]/[user]",
"create_team": "Komanda Yarat",
"create_team": "Komanda yaradın",
"name": "Ad",
"nameless_team": "Adsız Komanda",
"oauth_clients": "OAuth Müştəriləri",
@@ -1019,6 +1021,7 @@
"attendees": "İştirakçılar",
"add_attendees": "İştirakçı əlavə et",
"show_advanced_settings": "Ətraflı parametrləri göstər",
"hide_advanced_settings": "Ətraflı parametrləri gizlədin",
"event_name": "Tədbirin Adı",
"event_name_in_calendar": "Tədbirin adı ilə",
"event_name_tooltip": "Təqvimlərdə görünəcək ad",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Bu aktiv olduqda komanda üzvləri digər komanda üzvlərini görə bilməyəcək.",
"you_cannot_see_team_members": "Gizli komandanın bütün üzvlərini görə bilməzsiniz.",
"you_cannot_see_teams_of_org": "Gizli təşkilatın komandalarını görə bilməzsiniz.",
"allow_booker_to_select_duration": "Rezervasiya edənə müddəti seçməyə icazə ver",
"allow_multiple_durations": "Çoxsaylı müddətlərə icazə verin",
"impersonate_user_tip": "Bu xüsusiyyətin bütün istifadələri auditi olunur.",
"impersonating_user_warning": "\"{{user}}\" istifadəçi adını təqlid edirsiniz.",
"impersonating_stop_instructions": "Dayandırmaq üçün bura klikləyin",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "{{connectionType}} konfiqurasiyasını silmək istədiyinizə əminsinizmi? {{connectionType}} girişi istifadə edən komanda üzvləriniz artıq Cal.com-a daxil ola bilməyəcəklər.",
"organizer_timezone": "Təşkilatçının saat qurşağı",
"email_user_cta": "Dəvəti Görüntülə",
"accept_invite": "Dəvəti qəbul edin",
"join_now": "İndi qoşulun",
"successfully_joined": "Uğurla qoşuldunuz",
"email_no_user_invite_heading_subteam": "{{parentTeamName}} təşkilatının komandasına qoşulmağa dəvət olunmusunuz",
"email_no_user_invite_heading_org": "{{appName}} təşkilatına qoşulmağa dəvət olunmusunuz",
"email_no_user_invite_subheading": "{{invitedBy}} sizi {{appName}} üzərindəki komandasına qoşulmağa dəvət edib. {{appName}} sizə və komandanıza görüşləri e-poçt mübadiləsi olmadan planlaşdırmağa imkan verən tədbir idarəetmə planlayıcısıdır.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Rezervasiya təsdiqi uğursuz oldu",
"not_enough_seats": "Kifayət qədər oturacaq yoxdur",
"form_builder_field_already_exists": "Bu adla bir sahə artıq mövcuddur",
"guests_field_must_be_multiemail": "Qonaqlar sahəsi 'Çoxsaylı e-poçtlar' tipində olmalıdır",
"show_on_booking_page": "Rezervasiya səhifəsində göstər",
"visit_cancelled_booking": "Ləğv edilmiş rezervasiya səhifəsinə baxa bilərsiniz",
"get_started_zapier_templates": "Zapier şablonları ilə başlayın",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Dəvət linki kopyalandı",
"invite_link_deleted": "Dəvət linki silindi",
"api_key_deleted": "API açarı silindi",
"delete_api_key_warning": "Bu, API açarını həmişəlik siləcək. Bu açardan istifadə edən bütün tətbiqlər dərhal hesabınıza girişi itirəcək. Bu əməliyyat geri qaytarıla bilməz.",
"invite_link_updated": "Dəvət linki parametrləri yadda saxlanıldı",
"link_expires_after": "Linklər bu müddətdən sonra bitir...",
"one_day": "1 gün",
@@ -2688,7 +2696,7 @@
"enterprise": "Müəssisə",
"enterprise_description": "Təşkilatınızı yaratmaq üçün Enterprise-ə yüksəldin",
"create_your_org": "Təşkilatınızı Yaradın",
"create_org": "Təşkilat Yarat",
"create_org": "Təşkilat yaradın",
"create_your_org_description": "Təşkilatlara yüksəlin və subdomen, birləşdirilmiş ödəniş, Göstəricilər, geniş ağ etiketləmə və daha çoxunu əldə edin",
"create_your_enterprise_description": "Enterprise-ə yüksəlin və Active Directory Sync, SCIM Avtomatik İstifadəçi təminatı, Cal.ai Səs Agentləri, Admin API-ləri və daha çoxuna giriş əldə edin!",
"other_payment_app_enabled": "Hər tədbir növü üçün yalnız bir ödəniş tətbiqini aktivləşdirə bilərsiniz",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Davam etməklə, bizim",
"terms": "Şərtlər",
"always_show_x_days": "Həmişə {{x}} gün mövcuddur",
"always_show_x_days_description": "Avtomatik olaraq {{x}}-günlük rezervasiya pəncərəsini hər gün irəli aparır, təqviminizin həmişə növbəti {{x}} günün əlçatan slotlarını göstərməsini təmin edir.",
"team_subscription_cancelled_successfully": "Komanda abunəliyi uğurla ləğv edildi",
"team_subscription_cancellation_error": "Komanda abunəliyini ləğv edərkən xəta baş verdi",
"unable_to_subscribe_to_the_platform": "Platforma planına abunə olmağa çalışarkən bir səhv baş verdi, zəhmət olmasa bir az sonra yenidən cəhd edin",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "İlə bitir",
"filter_operator_is_empty": "Boşdur",
"filter_operator_not_empty": "Boş deyil",
"translate_description_button": "Süni intellekt vasitəsilə başlıq/təsviri ziyarətçinin brauzer dilinə tərcümə et",
"translate_description_button": "Başlıq və təsviri auto tərcümə et",
"translate_description_button_description": "Başlıqları və təsvirləri ziyarətçinin brauzer dilinə AI vasitəsilə avtomatik tərcümə edin.",
"rr_distribution_method": "Paylama",
"rr_distribution_method_description": "Maksimum əlçatanlıq üçün paylamanı optimallaşdırmağa və ya daha balanslı bir təyinat üçün çalışmağa imkan verir.",
"rr_distribution_method_availability_title": "Əlçatanlığı maksimuma çatdırmaq",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Yerlər funksiyası təkrarlanan tədbiri dəstəkləmir. Təkrarlanma funksiyasını aktivləşdirmək üçün yerlər funksiyasını deaktiv edin.",
"recurring_event_seats_error": "Təkrarlanan tədbir yerlər funksiyasını dəstəkləmir. Yerlər funksiyasını deaktiv edin və ya tədbiri təkrarlanmayan edin.",
"segment": "Seqment",
"saved_filters": "Saxlanılmış filtrlər",
"rename": "Adını dəyişdir",
"rename_segment": "Seqmentin adını dəyişdir",
"delete_segment": "Seqmenti sil",
@@ -3338,6 +3349,7 @@
"picklist": "Seçim siyahısı",
"most_cancelled_bookings": "Ən Çox Ləğv Edilən",
"interface_language": "İnterfeys dili",
"interface_language_description": "Rezervasiya interfeysi üçün üstünlük verdiyiniz dili təyin edin",
"visitors_browser_language": "Ziyarətçinin brauzer dili",
"name_or_email": "Ad və ya E-poçt",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Əgər əlaqə tapılmazsa, mövcud olduğu təqdirdə potensial müştəri sahibinə qayıt",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Təşkilatınız üçün rolları və icazələri idarə edin",
"learn_more_permissions": "İcazələrin necə işlədiyini daha ətraflı öyrənin",
"role_name": "Rol adı",
"role_name_required": "Rol adı tələb olunur",
"role_name_placeholder": "məs. Satış Komandası, Dəstək İşçiləri",
"role_description": "Rol təsviri",
"role_description_placeholder": "Bu rolun nə üçün olduğunu və hansı icazələrə ehtiyacı olduğunu təsvir edin",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rol uğurla yaradıldı",
"role_updated_successfully": "Rol uğurla yeniləndi",
"delete_role": "Rolu sil",
"confirm_delete_role": "Bu rolu silmək istədiyinizə əminsiniz? Bu əməliyyat geri qaytarıla bilməz.",
"confirm_delete_role": "{{roleName}} rolunu silmək istədiyinizə əminsiniz? Bu əməliyyat geri qaytarıla bilməz.",
"role_deleted_successfully": "Rol uğurla silindi",
"error_creating_role": "Rol yaradılarkən xəta baş verdi",
"error_updating_role": "Rol yenilənərkən xəta baş verdi",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "İş axını parametrlərini redaktə etmək və dəyişdirmək",
"pbac_desc_delete_workflows": "İş axınlarını sistemdən silmək",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Mövcudluq",
"pbac_resource_out_of_office": "Ofisdən kənarda",
"pbac_desc_create_webhooks": "Webhook yaradın",
"pbac_desc_view_webhooks": "Webhook-lara baxın",
"pbac_desc_update_webhooks": "Webhook-ləri yeniləyin",
"pbac_desc_delete_webhooks": "Webhook-ləri silin",
"pbac_desc_create_availability": "Mövcudluq yaratmaq",
"pbac_desc_view_availability": "Mövcudluğa baxmaq",
"pbac_desc_update_availability": "Mövcudluğu yeniləmək",
"pbac_desc_delete_availability": "Mövcudluğu silmək",
"pbac_desc_create_out_of_office": "Ofisdən kənar yaratmaq",
"pbac_desc_view_out_of_office": "Ofisdən kənara baxmaq",
"pbac_desc_update_out_of_office": "Ofisdən kənarı yeniləmək",
"pbac_desc_delete_out_of_office": "Ofisdən kənarı silmək",
"pbac_desc_manage_workflows": "Bütün iş axınlarına tam idarəetmə girişi",
"pbac_desc_create_event_types": "Tədbir növləri yaratmaq",
"pbac_desc_view_event_types": "Tədbir növlərinə baxmaq",
@@ -3561,7 +3584,7 @@
"round_robin_settings_description": "Bu komanda üçün standart növbəli təqvim parametrlərini fərdiləşdirin",
"round_robin_settings_updated_successfully": "Növbəli təqvim parametrləri uğurla yeniləndi",
"booking_creation_time": "Rezervasiya yaradılma vaxtı",
"meeting_start_time": "Görüş başlama vaxtı",
"meeting_start_time": "Görüşün başlama vaxtı",
"members_affected_by_disabling_delegation_credential": "Təsir edilən üzvlər",
"no_members_affected_by_disabling_delegation_credential": "Nümayəndəlik etimadnaməsinin deaktiv edilməsindən təsirlənən üzv yoxdur",
"download_expense_log": "Xərc jurnalını yüklə",
@@ -3732,4 +3755,4 @@
"use_voice": "Səsdən istifadə edin",
"current_voice": "Cari səs",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Yeni sətirləri bura əlavə edin ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Не са намерени свободни потребители. Бихте ли опитали друг времеви интервал?",
"timeslot_unavailable_book_a_new_time": "Избраният времеви интервал вече не е наличен. <0>Моля, изберете ново време</0>",
"timeslot_unavailable_short": "Заето",
"just_connected_description": "Току-що се свързахте. Моля, резервирайте от наличните часове по-горе или опитайте отново по-късно.",
"unavailable_timeslot_title": "Недостъпен времеви интервал",
"booking_time_out_of_bounds_error": "Този тип събитие не може да бъде резервирано в този час. Бихте ли опитали друг часови интервал?",
"request_body_end_time_internal_error": "Вътрешна грешка. Заявката не съдържа краен час",
@@ -321,6 +322,7 @@
"secret_meeting": "Тайна среща",
"login_instead": "Вместо това влезте",
"create_account": "Създаване на акаунт",
"get_started": "Започнете",
"confirm_password": "Потвърдете паролата",
"reset_your_password": "Задайте новата си парола с инструкциите, изпратени на вашия имейл адрес.",
"org_banner_instructions": "Моля, качете изображение с ширина {{width}} и височина {{height}}.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Цвят на типа събитие (светла тема)",
"dark_event_type_color": "Цвят на типа събитие (тъмна тема)",
"file_not_named": "Файлът не е именуван [idOrSlug]/[user]",
"create_team": "Създай екип",
"create_team": "Създаване на екип",
"name": "Име",
"nameless_team": "Безименен екип",
"oauth_clients": "OAuth клиенти",
@@ -1019,6 +1021,7 @@
"attendees": "Участници",
"add_attendees": "Добавяне на участници",
"show_advanced_settings": "Показване на разширени настройки",
"hide_advanced_settings": "Скриване на разширените настройки",
"event_name": "Име на събитието",
"event_name_in_calendar": "С име на събитието",
"event_name_tooltip": "Името, което ще се показва в календарите",
@@ -1231,7 +1234,7 @@
"contact_sales": "Свържи се с отдел продажби",
"error_404": "Грешка 404",
"default": "По подразбиране",
"set_to_default": "Задай по подразбиране",
"set_to_default": "Задаване по подразбиране",
"new_schedule_btn": "Нов график",
"add_new_schedule": "Добави нов график",
"add_new_calendar": "Добави нов календар",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Когато това е включено, членовете на екипа няма да могат да виждат другите членове.",
"you_cannot_see_team_members": "Не можеш да видиш всички членове на частния екип.",
"you_cannot_see_teams_of_org": "Не можеш да видиш екипите на частна организация.",
"allow_booker_to_select_duration": "Позволи на резервиращия да избере продължителност",
"allow_multiple_durations": "Разрешаване на множество продължителности",
"impersonate_user_tip": "Всяко използване на тази функция се одитира.",
"impersonating_user_warning": "Представяш се като потребител \"{{user}}\".",
"impersonating_stop_instructions": "Кликни тук, за да спреш",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Сигурни ли сте, че искате да изтриете {{connectionType}} конфигурацията? Членовете на вашия екип, които използват {{connectionType}} вход, вече няма да имат достъп до Cal.com.",
"organizer_timezone": "Часова зона на организатора",
"email_user_cta": "Преглед на поканата",
"accept_invite": "Приемане на покана",
"join_now": "Присъединете се сега",
"successfully_joined": "Успешно се присъединихте",
"email_no_user_invite_heading_subteam": "Поканени сте да се присъедините към екип от организацията {{parentTeamName}}",
"email_no_user_invite_heading_org": "Поканени сте да се присъедините към организация в {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} ви покани да се присъедините към техния екип в {{appName}}. {{appName}} е платформа за управление на събития, която позволява на вас и вашия екип да планирате срещи без безкрайна размяна на имейли.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Потвърждението на резервацията е неуспешно",
"not_enough_seats": "Няма достатъчно места",
"form_builder_field_already_exists": "Поле с това име вече съществува",
"guests_field_must_be_multiemail": "Полето за гости трябва да бъде от тип 'Множество имейли'",
"show_on_booking_page": "Покажи на страницата за резервации",
"visit_cancelled_booking": "Можете да посетите страницата с отменената резервация",
"get_started_zapier_templates": "Започнете със Zapier шаблони",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Линкът за покана е копиран",
"invite_link_deleted": "Линкът за покана е изтрит",
"api_key_deleted": "API ключът е изтрит",
"delete_api_key_warning": "Това ще изтрие API ключа завинаги. Всички приложения, използващи този ключ, незабавно ще загубят достъп до вашия акаунт. Това действие не може да бъде отменено.",
"invite_link_updated": "Настройките на линка са запазени",
"link_expires_after": "Линковете изтичат след...",
"one_day": "1 ден",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "С продължаването си, вие се съгласявате с нашите",
"terms": "Условия",
"always_show_x_days": "Винаги показвай {{x}} свободни дни",
"always_show_x_days_description": "Автоматично премества {{x}}-дневния прозорец за резервации напред всеки ден, гарантирайки, че вашият календар винаги показва следващите {{x}} дни с налични слотове.",
"team_subscription_cancelled_successfully": "Абонаментът на екипа е отменен успешно",
"team_subscription_cancellation_error": "Грешка при отмяна на абонамента на екипа",
"unable_to_subscribe_to_the_platform": "Възникна грешка при опит за абониране за плана на платформата, моля опитайте отново по-късно",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Завършва с",
"filter_operator_is_empty": "Е празно",
"filter_operator_not_empty": "Не е празно",
"translate_description_button": "Преведи заглавието/описанието на езика на браузъра на посетителя чрез AI",
"translate_description_button": "Автоматичен превод на заглавие и описание",
"translate_description_button_description": "Автоматично превеждане на заглавия и описания на езика на браузъра на посетителя с помощта на изкуствен интелект.",
"rr_distribution_method": "Разпределение",
"rr_distribution_method_description": "Позволява оптимизиране на разпределението за максимална наличност или за по-балансирано разпределение.",
"rr_distribution_method_availability_title": "Максимизиране на наличността",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Функцията за места не поддържа повтарящи се събития. Деактивирайте функцията за места, за да го направите повтарящо се.",
"recurring_event_seats_error": "Повтарящото се събитие не поддържа функцията за места. Деактивирайте функцията за места или направете събитието неповтарящо се.",
"segment": "Сегмент",
"saved_filters": "Запазени филтри",
"rename": "Преименуване",
"rename_segment": "Преименуване на сегмент",
"delete_segment": "Изтриване на сегмент",
@@ -3338,6 +3349,7 @@
"picklist": "Списък за избор",
"most_cancelled_bookings": "Най-много отменени",
"interface_language": "Език на интерфейса",
"interface_language_description": "Задайте предпочитания от вас език за интерфейса за резервации",
"visitors_browser_language": "Език на браузъра на посетителя",
"name_or_email": "Име или имейл",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Ако не е намерен контакт, връщане към собственика на лийда, ако съществува",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Управлявайте роли и разрешения за вашата организация",
"learn_more_permissions": "Научете повече за това как работят разрешенията",
"role_name": "Име на ролята",
"role_name_required": "Името на ролята е задължително",
"role_name_placeholder": "напр. Търговски екип, Екип за поддръжка",
"role_description": "Описание на ролята",
"role_description_placeholder": "Опишете за какво е тази роля и какви разрешения са ѝ необходими",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Ролята е създадена успешно",
"role_updated_successfully": "Ролята е актуализирана успешно",
"delete_role": "Изтрий роля",
"confirm_delete_role": "Сигурни ли сте, че искате да изтриете тази роля? Това действие не може да бъде отменено.",
"confirm_delete_role": "Сигурни ли сте, че искате да изтриете ролята: {{roleName}}. Това действие не може да бъде отменено.",
"role_deleted_successfully": "Ролята е изтрита успешно",
"error_creating_role": "Грешка при създаване на роля",
"error_updating_role": "Грешка при актуализиране на роля",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Редактиране и модифициране на настройките на работните процеси",
"pbac_desc_delete_workflows": "Премахване на работни процеси от системата",
"pbac_resource_webhook": "Уебхук",
"pbac_resource_availability": "Наличност",
"pbac_resource_out_of_office": "Извън офиса",
"pbac_desc_create_webhooks": "Създаване на уебхуци",
"pbac_desc_view_webhooks": "Преглед на уебхуци",
"pbac_desc_update_webhooks": "Актуализиране на уебхукове",
"pbac_desc_delete_webhooks": "Изтриване на уебхукове",
"pbac_desc_create_availability": "Създаване на наличност",
"pbac_desc_view_availability": "Преглед на наличност",
"pbac_desc_update_availability": "Актуализиране на наличност",
"pbac_desc_delete_availability": "Изтриване на наличност",
"pbac_desc_create_out_of_office": "Създаване на статус \"Извън офиса\"",
"pbac_desc_view_out_of_office": "Преглед на статус \"Извън офиса\"",
"pbac_desc_update_out_of_office": "Актуализиране на статус \"Извън офиса\"",
"pbac_desc_delete_out_of_office": "Изтриване на статус \"Извън офиса\"",
"pbac_desc_manage_workflows": "Пълен достъп за управление на всички работни процеси",
"pbac_desc_create_event_types": "Създаване на типове събития",
"pbac_desc_view_event_types": "Преглед на типове събития",
@@ -3732,4 +3755,4 @@
"use_voice": "Използвай глас",
"current_voice": "Текущ глас",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Добавете новите си низове над този ред ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "কোনও উপলভ্য ব্যবহারকারী খুঁজে পাওয়া যায় নি। ",
"timeslot_unavailable_book_a_new_time": "নির্বাচিত সময়সীমা আর উপলব্ধ নেই। <0>অনুগ্রহ করে একটি নতুন সময় নির্বাচন করুন</0>",
"timeslot_unavailable_short": "নেওয়া হয়েছে",
"just_connected_description": "আপনি সবেমাত্র সংযুক্ত হয়েছেন। অনুগ্রহ করে উপরের স্লট থেকে বুক করুন অথবা পরে আবার চেষ্টা করুন।",
"unavailable_timeslot_title": "অনুপলব্ধ সময়সীমা",
"booking_time_out_of_bounds_error": "এই সময়ে ইভেন্ট টাইপ বুক করা যাবে না। আপনি কি অন্য সময়সীমা চেষ্টা করতে পারেন?",
"request_body_end_time_internal_error": "অভ্যন্তরীণ ত্রুটি। ",
@@ -321,6 +322,7 @@
"secret_meeting": "গোপন সভা",
"login_instead": "পরিবর্তে লগইন",
"create_account": "অ্যাকাউন্ট তৈরি করুন",
"get_started": "শুরু করুন",
"confirm_password": "পাসওয়ার্ড নিশ্চিত করুন",
"reset_your_password": "আপনার ইমেল ঠিকানায় প্রেরিত নির্দেশাবলী সহ আপনার নতুন পাসওয়ার্ড সেট করুন।",
"org_banner_instructions": "এর চিত্র আপলোড করুন {{width}} প্রস্থ এবং {{height}} উচ্চতা।",
@@ -1019,6 +1021,7 @@
"attendees": "অংশগ্রহণকারীরা",
"add_attendees": "অংশগ্রহণকারীদের যোগ করুন",
"show_advanced_settings": "উন্নত সেটিংস দেখান",
"hide_advanced_settings": "উন্নত সেটিংস লুকান",
"event_name": "ইভেন্টের নাম",
"event_name_in_calendar": "ইভেন্টের নাম সহ",
"event_name_tooltip": "নাম যে ক্যালেন্ডারে প্রদর্শিত হবে",
@@ -1231,7 +1234,7 @@
"contact_sales": "যোগাযোগ বিক্রয়",
"error_404": "ত্রুটি 404",
"default": "ডিফল্ট",
"set_to_default": "ডিফল্ট সেট",
"set_to_default": "ডিফল্ট হিসাবে সেট করুন",
"new_schedule_btn": "নতুন সময়সূচী",
"add_new_schedule": "একটি নতুন সময়সূচি যুক্ত করুন",
"add_new_calendar": "একটি নতুন ক্যালেন্ডার যুক্ত করুন",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "আপনার দলের সদস্যরা এটি চালু থাকলে অন্য দলের সদস্যদের দেখতে সক্ষম হবেন না।",
"you_cannot_see_team_members": "আপনি কোনও বেসরকারী দলের সমস্ত দলের সদস্য দেখতে পারবেন না।",
"you_cannot_see_teams_of_org": "আপনি কোনও বেসরকারী সংস্থার দল দেখতে পাচ্ছেন না।",
"allow_booker_to_select_duration": "বুকারকে সময়কাল নির্বাচন করার অনুমতি দিন",
"allow_multiple_durations": "একাধিক সময়কাল অনুমতি দিন",
"impersonate_user_tip": "এই বৈশিষ্ট্যের সমস্ত ব্যবহার নিরীক্ষণ করা হয়।",
"impersonating_user_warning": "ব্যবহারকারীর নাম \"{{user}}\"।",
"impersonating_stop_instructions": "থামাতে এখানে ক্লিক করুন",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "আপনি কি নিশ্চিত যে আপনি এটি মুছতে চান? {{connectionType}} কনফিগারেশন? {{connectionType}} লগইন আর Cal.com অ্যাক্সেস করতে সক্ষম হবে না।",
"organizer_timezone": "আয়োজক টাইমজোন",
"email_user_cta": "আমন্ত্রণ দেখুন",
"accept_invite": "আমন্ত্রণ গ্রহণ করুন",
"join_now": "এখনই যোগ দিন",
"successfully_joined": "সফলভাবে যোগ দেওয়া হয়েছে",
"email_no_user_invite_heading_subteam": "আপনাকে {{parentTeamName}} সংস্থার একটি দলে যোগদানের আমন্ত্রণ জানানো হয়েছে",
"email_no_user_invite_heading_org": "আপনাকে একটি {{appName}} সংস্থায় যোগদানের আমন্ত্রণ জানানো হয়েছে",
"email_no_user_invite_subheading": "{{invitedBy}} আপনাকে তাদের দলে যোগদানের জন্য আমন্ত্রণ জানিয়েছে {{appName}}। {{appName}} ইভেন্ট-জ্যাকলিং শিডিয়ুলার যা আপনাকে এবং আপনার দলকে ইমেল টেনিস ছাড়াই সভাগুলির সময়সূচী করতে সক্ষম করে।",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "বুকিং নিশ্চিতকরণ ব্যর্থ হয়েছে",
"not_enough_seats": "পর্যাপ্ত আসন নেই",
"form_builder_field_already_exists": "এই নাম সহ একটি ক্ষেত্র ইতিমধ্যে বিদ্যমান",
"guests_field_must_be_multiemail": "অতিথি ক্ষেত্রটি অবশ্যই 'একাধিক ইমেইল' ধরনের হতে হবে",
"show_on_booking_page": "বুকিং পৃষ্ঠায় দেখান",
"visit_cancelled_booking": "আপনি বাতিল হওয়া বুকিং পৃষ্ঠাটি দেখতে পারেন",
"get_started_zapier_templates": "জ্যাপিয়ার টেম্পলেটগুলি দিয়ে শুরু করুন",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "আমন্ত্রণ লিঙ্ক অনুলিপি",
"invite_link_deleted": "লিঙ্ক মুছে ফেলা আমন্ত্রণ",
"api_key_deleted": "এপিআই কী মুছে ফেলা হয়েছে",
"delete_api_key_warning": "এটি স্থায়ীভাবে API কী মুছে ফেলবে। এই কী ব্যবহার করে যেকোনো অ্যাপ্লিকেশন অবিলম্বে আপনার অ্যাকাউন্টে অ্যাক্সেস হারাবে। এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।",
"invite_link_updated": "সংরক্ষণের লিঙ্ক সেটিংসকে আমন্ত্রণ করুন",
"link_expires_after": "লিঙ্কগুলি পরে শেষ হওয়ার জন্য সেট ...",
"one_day": "1 দিন",
@@ -2688,7 +2696,7 @@
"enterprise": "এন্টারপ্রাইজ",
"enterprise_description": "আপনার সংস্থা তৈরি করতে এন্টারপ্রাইজে আপগ্রেড করুন",
"create_your_org": "আপনার সংস্থা তৈরি করুন",
"create_org": "সংস্থা তৈরি করুন",
"create_org": "সংগঠন তৈরি করুন",
"create_your_org_description": "সংস্থাগুলিতে আপগ্রেড করুন এবং একটি সাবডোমেন, ইউনিফাইড বিলিং, অন্তর্দৃষ্টি, বিস্তৃত হোয়াইটেলবেলিং এবং আরও অনেক কিছু পান",
"create_your_enterprise_description": "এন্টারপ্রাইজে আপগ্রেড করুন এবং অ্যাক্টিভ ডিরেক্টরি সিঙ্ক, এসসিআইএম স্বয়ংক্রিয় ব্যবহারকারীর প্রভিশনিং, ক্যাল.এই ভয়েস এজেন্টস, অ্যাডমিন এপিআই এবং আরও অনেক কিছুতে অ্যাক্সেস পান!",
"other_payment_app_enabled": "আপনি প্রতি ইভেন্টের জন্য কেবল একটি পেমেন্ট অ্যাপ্লিকেশন সক্ষম করতে পারেন",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "এগিয়ে যাওয়ার মাধ্যমে, আপনি আমাদের নিম্নলিখিত শর্তাবলীতে সম্মত হচ্ছেন",
"terms": "শর্তাবলী",
"always_show_x_days": "সর্বদা {{x}} দিন উপলব্ধ",
"always_show_x_days_description": "প্রতিদিন স্বয়ংক্রিয়ভাবে {{x}}-দিনের বুকিং উইন্ডো সামনে সরিয়ে দেয়, যা নিশ্চিত করে যে আপনার ক্যালেন্ডার সবসময় পরবর্তী {{x}} দিনের উপলব্ধ স্লট দেখায়।",
"team_subscription_cancelled_successfully": "দলের সাবস্ক্রিপশন সফলভাবে বাতিল করা হয়েছে",
"team_subscription_cancellation_error": "দলের সাবস্ক্রিপশন বাতিল করার সময় ত্রুটি",
"unable_to_subscribe_to_the_platform": "প্ল্যাটফর্ম পরিকল্পনায় সাবস্ক্রাইব করার চেষ্টা করার সময় একটি ত্রুটি ঘটেছে, দয়া করে পরে আবার চেষ্টা করুন",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "সাথে শেষ",
"filter_operator_is_empty": "খালি আছে",
"filter_operator_not_empty": "খালি নয়",
"translate_description_button": "এআই ব্যবহার করে দর্শনার্থীর ব্রাউজার ভাষায় বর্ণনা অনুবাদ করুন",
"translate_description_button": "শিরোনাম এবং বিবরণ অটো অনুবাদ করুন",
"translate_description_button_description": "AI ব্যবহার করে দর্শকের ব্রাউজার ভাষায় শিরোনাম এবং বিবরণ স্বয়ংক্রিয়ভাবে অনুবাদ করুন।",
"rr_distribution_method": "বিতরণ",
"rr_distribution_method_description": "সর্বাধিক প্রাপ্যতার জন্য বিতরণকে অনুকূলকরণের জন্য বা আরও সুষম অ্যাসাইনমেন্টের জন্য লক্ষ্য করার অনুমতি দেয়।",
"rr_distribution_method_availability_title": "প্রাপ্যতা সর্বাধিক করুন",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "সিট ফিচার পুনরাবৃত্ত ইভেন্ট সমর্থন করে না। এটিকে পুনরাবৃত্ত করতে সিট ফিচার নিষ্ক্রিয় করুন।",
"recurring_event_seats_error": "পুনরাবৃত্ত ইভেন্ট সিট ফিচার সমর্থন করে না। সিট ফিচার নিষ্ক্রিয় করুন অথবা ইভেন্টটিকে অপুনরাবৃত্ত করুন।",
"segment": "সেগমেন্ট",
"saved_filters": "সংরক্ষিত ফিল্টার",
"rename": "পুনঃনামকরণ",
"rename_segment": "সেগমেন্ট পুনঃনামকরণ",
"delete_segment": "সেগমেন্ট মুছুন",
@@ -3338,6 +3349,7 @@
"picklist": "পিকলিস্ট",
"most_cancelled_bookings": "সবচেয়ে বেশি বাতিল করা বুকিং",
"interface_language": "ইন্টারফেস ভাষা",
"interface_language_description": "বুকিং ইন্টারফেসের জন্য আপনার পছন্দের ভাষা সেট করুন",
"visitors_browser_language": "দর্শনার্থীর ব্রাউজার ভাষা",
"name_or_email": "নাম বা ইমেইল",
"salesforce_round_robin_skip_fallback_to_lead_owner": "যদি কোন কন্টাক্ট না পাওয়া যায়, লিড মালিকে ফিরে যান যদি তা বিদ্যমান থাকে",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "আপনার সংস্থার জন্য রোল এবং অনুমতি পরিচালনা করুন",
"learn_more_permissions": "অনুমতি কীভাবে কাজ করে সে সম্পর্কে আরও জানুন",
"role_name": "ভূমিকার নাম",
"role_name_required": "ভূমিকার নাম আবশ্যক",
"role_name_placeholder": "যেমন বিক্রয় দল, সহায়তা কর্মী",
"role_description": "ভূমিকার বিবরণ",
"role_description_placeholder": "এই ভূমিকাটি কীসের জন্য এবং কোন অনুমতি প্রয়োজন তা বর্ণনা করুন",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "ভূমিকা সফলভাবে তৈরি করা হয়েছে",
"role_updated_successfully": "ভূমিকা সফলভাবে আপডেট করা হয়েছে",
"delete_role": "ভূমিকা মুছুন",
"confirm_delete_role": "আপনি কি নিশ্চিত যে আপনি এই ভূমিকা মুছতে চান? এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।",
"confirm_delete_role": "আপনি কি নিশ্চিত যে আপনি ভূমিকা মুছতে চান: {{roleName}}। এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।",
"role_deleted_successfully": "ভূমিকা সফলভাবে মুছে ফেলা হয়েছে",
"error_creating_role": "ভূমিকা তৈরি করতে ত্রুটি",
"error_updating_role": "ভূমিকা আপডেট করতে ত্রুটি",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "কর্মপ্রবাহ সেটিংস সম্পাদনা ও পরিবর্তন করুন",
"pbac_desc_delete_workflows": "সিস্টেম থেকে কর্মপ্রবাহ সরান",
"pbac_resource_webhook": "ওয়েবহুক",
"pbac_resource_availability": "প্রাপ্যতা",
"pbac_resource_out_of_office": "অফিসের বাইরে",
"pbac_desc_create_webhooks": "ওয়েবহুক তৈরি করুন",
"pbac_desc_view_webhooks": "ওয়েবহুক দেখুন",
"pbac_desc_update_webhooks": "ওয়েবহুক আপডেট করুন",
"pbac_desc_delete_webhooks": "ওয়েবহুক মুছুন",
"pbac_desc_create_availability": "প্রাপ্যতা তৈরি করুন",
"pbac_desc_view_availability": "প্রাপ্যতা দেখুন",
"pbac_desc_update_availability": "প্রাপ্যতা আপডেট করুন",
"pbac_desc_delete_availability": "প্রাপ্যতা মুছুন",
"pbac_desc_create_out_of_office": "অফিসের বাইরে তৈরি করুন",
"pbac_desc_view_out_of_office": "অফিসের বাইরে দেখুন",
"pbac_desc_update_out_of_office": "অফিসের বাইরে আপডেট করুন",
"pbac_desc_delete_out_of_office": "অফিসের বাইরে মুছুন",
"pbac_desc_manage_workflows": "সমস্ত কর্মপ্রবাহের জন্য সম্পূর্ণ ব্যবস্থাপনা অ্যাক্সেস",
"pbac_desc_create_event_types": "ইভেন্টের ধরণ তৈরি করুন",
"pbac_desc_view_event_types": "ইভেন্টের ধরণ দেখুন",
@@ -3732,4 +3755,4 @@
"use_voice": "ভয়েস ব্যবহার করুন",
"current_voice": "বর্তমান ভয়েস",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑ুষান্ত"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "No s'han trobat usuaris disponibles. Podries provar una altra franja horària?",
"timeslot_unavailable_book_a_new_time": "La franja horària seleccionada ja no està disponible. <0>Si us plau, selecciona una nova hora</0>",
"timeslot_unavailable_short": "Ocupat",
"just_connected_description": "Acabes de connectar-te. Si us plau, reserva des dels espais de temps disponibles o torna-ho a provar més tard.",
"unavailable_timeslot_title": "Franja horària no disponible",
"booking_time_out_of_bounds_error": "El tipus d'esdeveniment no es pot reservar en aquest moment. Podries provar una altra franja horària?",
"request_body_end_time_internal_error": "Error intern. El cos de la sol·licitud no conté l'hora de finalització",
@@ -321,6 +322,7 @@
"secret_meeting": "Reunió secreta",
"login_instead": "Inicia sessió en lloc seu",
"create_account": "Crea un compte",
"get_started": "Comença",
"confirm_password": "Confirma la contrasenya",
"reset_your_password": "Estableix la teva nova contrasenya amb les instruccions enviades al teu correu electrònic.",
"org_banner_instructions": "Si us plau, puja una imatge de {{width}} d'amplada i {{height}} d'alçada.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Color del tipus d'esdeveniment (tema clar)",
"dark_event_type_color": "Color del tipus d'esdeveniment (tema fosc)",
"file_not_named": "L'arxiu no té el nom [idOrSlug]/[user]",
"create_team": "Crea un equip",
"create_team": "Crea equip",
"name": "Nom",
"nameless_team": "Equip sense nom",
"oauth_clients": "Clients OAuth",
@@ -1019,6 +1021,7 @@
"attendees": "Assistents",
"add_attendees": "Afegir assistents",
"show_advanced_settings": "Mostrar configuració avançada",
"hide_advanced_settings": "Amaga la configuració avançada",
"event_name": "Nom de l'esdeveniment",
"event_name_in_calendar": "Amb nom d'esdeveniment",
"event_name_tooltip": "El nom que apareixerà als calendaris",
@@ -1231,7 +1234,7 @@
"contact_sales": "Contacta amb vendes",
"error_404": "Error 404",
"default": "Per defecte",
"set_to_default": "Estableix per defecte",
"set_to_default": "Estableix com a predeterminat",
"new_schedule_btn": "Nou horari",
"add_new_schedule": "Afegeix un nou horari",
"add_new_calendar": "Afegeix un nou calendari",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Els membres del teu equip no podran veure altres membres quan això estigui activat.",
"you_cannot_see_team_members": "No pots veure tots els membres d'un equip privat.",
"you_cannot_see_teams_of_org": "No pots veure els equips d'una organització privada.",
"allow_booker_to_select_duration": "Permetre que el reservant seleccioni la durada",
"allow_multiple_durations": "Permet múltiples duracions",
"impersonate_user_tip": "Tots els usos d'aquesta funcionalitat són auditats.",
"impersonating_user_warning": "Suplantant l'usuari \"{{user}}\".",
"impersonating_stop_instructions": "Fes clic aquí per aturar",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Estàs segur que vols eliminar la configuració de {{connectionType}}? Els membres del teu equip que utilitzin l'inici de sessió {{connectionType}} ja no podran accedir a Cal.com.",
"organizer_timezone": "Zona horària de l'organitzador",
"email_user_cta": "Veure invitació",
"accept_invite": "Accepta la invitació",
"join_now": "Uneix-te ara",
"successfully_joined": "T'has unit correctament",
"email_no_user_invite_heading_subteam": "Has estat convidat a unir-te a un equip de l'organització {{parentTeamName}}",
"email_no_user_invite_heading_org": "Has estat convidat a unir-te a una organització de {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} t'ha convidat a unir-te al seu equip a {{appName}}. {{appName}} és el programador de reunions que us permet a tu i al teu equip programar trobades sense el ping-pong dels correus electrònics.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "La confirmació de la reserva ha fallat",
"not_enough_seats": "No hi ha prou places",
"form_builder_field_already_exists": "Ja existeix un camp amb aquest nom",
"guests_field_must_be_multiemail": "El camp de convidats ha de ser del tipus 'Múltiples correus electrònics'",
"show_on_booking_page": "Mostra a la pàgina de reserva",
"visit_cancelled_booking": "Pots visitar la pàgina de reserva cancel·lada",
"get_started_zapier_templates": "Comença amb les plantilles de Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Enllaç d'invitació copiat",
"invite_link_deleted": "Enllaç d'invitació eliminat",
"api_key_deleted": "Clau API eliminada",
"delete_api_key_warning": "Això eliminarà permanentment la clau API. Qualsevol aplicació que utilitzi aquesta clau perdrà immediatament l'accés al teu compte. Aquesta acció no es pot desfer.",
"invite_link_updated": "Configuració de l'enllaç d'invitació desada",
"link_expires_after": "Els enllaços caduquen després de...",
"one_day": "1 dia",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "En continuar, acceptes les nostres",
"terms": "Condicions",
"always_show_x_days": "Sempre {{x}} dies disponibles",
"always_show_x_days_description": "Mou automàticament la finestra de reserva de {{x}} dies cap endavant cada dia, assegurant que el teu calendari sempre mostri els propers {{x}} dies d'espais disponibles.",
"team_subscription_cancelled_successfully": "Subscripció d'equip cancel·lada amb èxit",
"team_subscription_cancellation_error": "Error en cancel·lar la subscripció d'equip",
"unable_to_subscribe_to_the_platform": "S'ha produït un error en intentar subscriure's al pla de la plataforma, torna-ho a provar més tard",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Acaba amb",
"filter_operator_is_empty": "És buit",
"filter_operator_not_empty": "No és buit",
"translate_description_button": "Tradueix el títol/descripció a l'idioma del navegador del visitant utilitzant IA",
"translate_description_button": "Tradueix automàticament el títol i la descripció",
"translate_description_button_description": "Tradueix automàticament els títols i les descripcions a l'idioma del navegador del visitant utilitzant IA.",
"rr_distribution_method": "Distribució",
"rr_distribution_method_description": "Permet optimitzar la distribució per maximitzar la disponibilitat o aconseguir una assignació més equilibrada",
"rr_distribution_method_availability_title": "Maximitzar disponibilitat",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "La funció de seients no admet esdeveniments recurrents. Desactiveu la funció de seients per fer-lo recurrent.",
"recurring_event_seats_error": "Els esdeveniments recurrents no admeten la funció de seients. Desactiveu la funció de seients o feu que l'esdeveniment no sigui recurrent.",
"segment": "Segment",
"saved_filters": "Filtres desats",
"rename": "Canviar el nom",
"rename_segment": "Canviar el nom del segment",
"delete_segment": "Eliminar segment",
@@ -3338,6 +3349,7 @@
"picklist": "Llista de selecció",
"most_cancelled_bookings": "Més reserves cancel·lades",
"interface_language": "Idioma de la interfície",
"interface_language_description": "Estableix el teu idioma preferit per a la interfície de reserva",
"visitors_browser_language": "Idioma del navegador del visitant",
"name_or_email": "Nom o correu electrònic",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Si no es troba cap contacte, recorre al propietari del lead si existeix",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Gestiona els rols i permisos per a la teva organització",
"learn_more_permissions": "Aprèn més sobre com funcionen els permisos",
"role_name": "Nom del rol",
"role_name_required": "El nom del rol és obligatori",
"role_name_placeholder": "p. ex. Equip de vendes, Personal de suport",
"role_description": "Descripció del rol",
"role_description_placeholder": "Descriu per a què serveix aquest rol i quins permisos necessita",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rol creat correctament",
"role_updated_successfully": "Rol actualitzat correctament",
"delete_role": "Eliminar rol",
"confirm_delete_role": "Estàs segur que vols eliminar aquest rol? Aquesta acció no es pot desfer.",
"confirm_delete_role": "Esteu segur que voleu eliminar el rol: {{roleName}}? Aquesta acció no es pot desfer.",
"role_deleted_successfully": "Rol eliminat correctament",
"error_creating_role": "Error en crear el rol",
"error_updating_role": "Error en actualitzar el rol",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Editar i modificar la configuració dels fluxos de treball",
"pbac_desc_delete_workflows": "Eliminar fluxos de treball del sistema",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Disponibilitat",
"pbac_resource_out_of_office": "Fora de l'oficina",
"pbac_desc_create_webhooks": "Crea webhooks",
"pbac_desc_view_webhooks": "Visualitza webhooks",
"pbac_desc_update_webhooks": "Actualitza els webhooks",
"pbac_desc_delete_webhooks": "Elimina els webhooks",
"pbac_desc_create_availability": "Crear disponibilitat",
"pbac_desc_view_availability": "Veure disponibilitat",
"pbac_desc_update_availability": "Actualitzar disponibilitat",
"pbac_desc_delete_availability": "Eliminar disponibilitat",
"pbac_desc_create_out_of_office": "Crear fora de l'oficina",
"pbac_desc_view_out_of_office": "Veure fora de l'oficina",
"pbac_desc_update_out_of_office": "Actualitzar fora de l'oficina",
"pbac_desc_delete_out_of_office": "Eliminar fora de l'oficina",
"pbac_desc_manage_workflows": "Accés complet de gestió a tots els fluxos de treball",
"pbac_desc_create_event_types": "Crear tipus d'esdeveniments",
"pbac_desc_view_event_types": "Veure tipus d'esdeveniments",
@@ -3732,4 +3755,4 @@
"use_voice": "Utilitza la veu",
"current_voice": "Veu actual",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Afegiu les vostres noves cadenes a dalt ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+27 -4
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Nebyli nalezeni žádní dostupní uživatelé. Můžete zkusit jiný časový slot?",
"timeslot_unavailable_book_a_new_time": "Vybraný časový úsek již není k dispozici. <0>Prosím vyberte nový čas</0>",
"timeslot_unavailable_short": "Obsazeno",
"just_connected_description": "Právě jste se připojili. Prosím, rezervujte si termín z výše uvedených časových slotů nebo to zkuste později.",
"unavailable_timeslot_title": "Nedostupný časový slot",
"booking_time_out_of_bounds_error": "Tento typ události nelze v tomto čase rezervovat. Můžete zkusit jiný časový termín?",
"request_body_end_time_internal_error": "Interní chyba. Tělo požadavku neobsahuje čas ukončení",
@@ -321,6 +322,7 @@
"secret_meeting": "Tajná schůzka",
"login_instead": "Namísto toho se přihlásit",
"create_account": "Vytvořit účet",
"get_started": "Začít",
"confirm_password": "Potvrdit heslo",
"reset_your_password": "Nastavte si nové heslo podle pokynů odeslaných na vaši e-mailovou adresu.",
"org_banner_instructions": "Nahrajte prosím obrázek o šířce {{width}} a výšce {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "Účastníci",
"add_attendees": "Přidat účastníky",
"show_advanced_settings": "Zobrazit pokročilá nastavení",
"hide_advanced_settings": "Skrýt pokročilá nastavení",
"event_name": "Název události",
"event_name_in_calendar": "Název události v kalendáři",
"event_name_tooltip": "Název, který se zobrazí v kalendářích",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Po zapnutí této funkce neuvidí členové vašeho týmu ostatní členy týmu.",
"you_cannot_see_team_members": "V soukromém týmu nelze vidět všechny členy týmu.",
"you_cannot_see_teams_of_org": "Nemůžete vidět týmy soukromé organizace.",
"allow_booker_to_select_duration": "Umožnit rezervující osobě vybrat dobu trvání",
"allow_multiple_durations": "Povolit více délek trvání",
"impersonate_user_tip": "Všechna použití této funkce jsou kontrolována.",
"impersonating_user_warning": "Zosobnění uživatelského jména „{{user}}“.",
"impersonating_stop_instructions": "Kliknutím sem zastavíte",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Opravdu chcete odstranit konfiguraci {{connectionType}}? Členové vašeho týmu, kteří používají přihlášení {{connectionType}}, ztratí přístup k webu Cal.com.",
"organizer_timezone": "Časové pásmo organizátora",
"email_user_cta": "Zobrazit pozvánku",
"accept_invite": "Přijmout pozvánku",
"join_now": "Připojit se nyní",
"successfully_joined": "Úspěšně připojeno",
"email_no_user_invite_heading_subteam": "Byli jste pozváni k připojení do týmu organizace {{parentTeamName}}",
"email_no_user_invite_heading_org": "Byli jste pozváni k připojení do organizace {{appName}}",
"email_no_user_invite_subheading": "Uživatel {{invitedBy}} vás pozval, abyste se připojili k jeho týmu v aplikaci {{appName}}. {{appName}} je plánovač událostí, který vám a vašemu týmu umožňuje plánovat schůzky bez e-mailového ping-pongu.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Potvrzení rezervace se nezdařilo",
"not_enough_seats": "Nedostatek míst",
"form_builder_field_already_exists": "Pole s tímto názvem již existuje",
"guests_field_must_be_multiemail": "Pole pro hosty musí být typu 'Více e-mailů'",
"show_on_booking_page": "Zobrazit na stránce rezervace",
"visit_cancelled_booking": "Můžete navštívit stránku zrušené rezervace",
"get_started_zapier_templates": "Začněte používat šablony Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Odkaz pozvánky byl zkopírován",
"invite_link_deleted": "Odkaz pozvánky byl odstraněn",
"api_key_deleted": "Klíč API odstraněn",
"delete_api_key_warning": "Tímto trvale smažete API klíč. Všechny aplikace používající tento klíč okamžitě ztratí přístup k vašemu účtu. Tuto akci nelze vrátit zpět.",
"invite_link_updated": "Nastavení odkazu pozvánky bylo uloženo",
"link_expires_after": "Platnost odkazů vyprší za...",
"one_day": "1 den",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Pokračováním souhlasíte s našimi",
"terms": "Podmínkami",
"always_show_x_days": "Vždy dostupné {{x}} dní",
"always_show_x_days_description": "Automaticky posouvá {{x}}-denní rezervační okno každý den vpřed, což zajišťuje, že váš kalendář vždy zobrazuje dostupné termíny na příštích {{x}} dní.",
"team_subscription_cancelled_successfully": "Předplatné týmu bylo úspěšně zrušeno",
"team_subscription_cancellation_error": "Chyba při rušení předplatného týmu",
"unable_to_subscribe_to_the_platform": "Při pokusu o přihlášení k plánu platformy došlo k chybě, zkuste to prosím znovu později",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Končí na",
"filter_operator_is_empty": "Je prázdné",
"filter_operator_not_empty": "Není prázdné",
"translate_description_button": "Přeložit název/popis do jazyka prohlížeče návštěvníka pomocí AI",
"translate_description_button": "Automaticky přeložit název a popis",
"translate_description_button_description": "Automaticky překládá názvy a popisy do jazyka prohlížeče návštěvníka pomocí umělé inteligence.",
"rr_distribution_method": "Distribuce",
"rr_distribution_method_description": "Umožňuje optimalizovat distribuci pro maximální dostupnost nebo pro vyváženější přidělování.",
"rr_distribution_method_availability_title": "Maximalizovat dostupnost",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Funkce míst nepodporuje opakující se události. Zakažte funkci míst pro vytvoření opakující se události.",
"recurring_event_seats_error": "Opakující se událost nepodporuje funkci míst. Zakažte funkci míst nebo změňte událost na neopakující se.",
"segment": "Segment",
"saved_filters": "Uložené filtry",
"rename": "Přejmenovat",
"rename_segment": "Přejmenovat segment",
"delete_segment": "Smazat segment",
@@ -3338,6 +3349,7 @@
"picklist": "Výběrový seznam",
"most_cancelled_bookings": "Nejvíce zrušených rezervací",
"interface_language": "Jazyk rozhraní",
"interface_language_description": "Nastavte preferovaný jazyk pro rezervační rozhraní",
"visitors_browser_language": "Jazyk prohlížeče návštěvníka",
"name_or_email": "Jméno nebo e-mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Pokud není nalezen žádný kontakt, použít vlastníka leadu, pokud existuje",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Správa rolí a oprávnění pro vaši organizaci",
"learn_more_permissions": "Zjistěte více o tom, jak fungují oprávnění",
"role_name": "Název role",
"role_name_required": "Název role je povinný",
"role_name_placeholder": "např. Prodejní tým, Podpora",
"role_description": "Popis role",
"role_description_placeholder": "Popište, k čemu tato role slouží a jaká oprávnění potřebuje",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Role byla úspěšně vytvořena",
"role_updated_successfully": "Role byla úspěšně aktualizována",
"delete_role": "Odstranit roli",
"confirm_delete_role": "Opravdu chcete tuto roli odstranit? Tuto akci nelze vrátit zpět.",
"confirm_delete_role": "Opravdu chcete smazat roli: {{roleName}}? Tuto akci nelze vrátit zpět.",
"role_deleted_successfully": "Role byla úspěšně odstraněna",
"error_creating_role": "Chyba při vytváření role",
"error_updating_role": "Chyba při aktualizaci role",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Upravovat a měnit nastavení pracovních postupů",
"pbac_desc_delete_workflows": "Odstraňovat pracovní postupy ze systému",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Dostupnost",
"pbac_resource_out_of_office": "Mimo kancelář",
"pbac_desc_create_webhooks": "Vytvářet webhooky",
"pbac_desc_view_webhooks": "Zobrazit webhooky",
"pbac_desc_update_webhooks": "Aktualizovat webhooky",
"pbac_desc_delete_webhooks": "Smazat webhooky",
"pbac_desc_create_availability": "Vytvořit dostupnost",
"pbac_desc_view_availability": "Zobrazit dostupnost",
"pbac_desc_update_availability": "Aktualizovat dostupnost",
"pbac_desc_delete_availability": "Smazat dostupnost",
"pbac_desc_create_out_of_office": "Vytvořit mimo kancelář",
"pbac_desc_view_out_of_office": "Zobrazit mimo kancelář",
"pbac_desc_update_out_of_office": "Aktualizovat mimo kancelář",
"pbac_desc_delete_out_of_office": "Smazat mimo kancelář",
"pbac_desc_manage_workflows": "Plný přístup ke správě všech pracovních postupů",
"pbac_desc_create_event_types": "Vytvářet typy událostí",
"pbac_desc_view_event_types": "Zobrazit typy událostí",
@@ -3732,4 +3755,4 @@
"use_voice": "Použít hlas",
"current_voice": "Aktuální hlas",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Přidejte své nové řetězce nahoru ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Ingen tilgængelige brugere fundet. Kan du prøve et andet tidsrum?",
"timeslot_unavailable_book_a_new_time": "Det valgte tidspunkt er ikke længere tilgængeligt. <0>Vælg venligst et nyt tidspunkt</0>",
"timeslot_unavailable_short": "Optaget",
"just_connected_description": "Du har lige forbundet. Book venligst fra de ovenstående tidsrum eller prøv igen senere.",
"unavailable_timeslot_title": "Utilgængeligt tidsrum",
"booking_time_out_of_bounds_error": "Begivenheden kan ikke bookes på dette tidspunkt. Kunne du prøve et andet tidspunkt?",
"request_body_end_time_internal_error": "Intern fejl. Anmodningskroppen indeholder ikke sluttidspunkt",
@@ -321,6 +322,7 @@
"secret_meeting": "Skjult Møde",
"login_instead": "Log ind i stedet",
"create_account": "Opret Konto",
"get_started": "Kom i gang",
"confirm_password": "Bekræft adgangskode",
"reset_your_password": "Indstil din nye adgangskode med instruktionerne sendt til din e-mailadresse.",
"org_banner_instructions": "Upload venligst et billede med {{width}} bredde og {{height}} højde.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Begivenhedstype farve (Lyst tema)",
"dark_event_type_color": "Begivenhedstype farve (Mørkt tema)",
"file_not_named": "Filen er ikke navngivet [idOrSlug]/[user]",
"create_team": "Opret Team",
"create_team": "Opret team",
"name": "Navn",
"nameless_team": "Unavngivet team",
"oauth_clients": "OAuth-klienter",
@@ -1019,6 +1021,7 @@
"attendees": "Deltagere",
"add_attendees": "Tilføj deltagere",
"show_advanced_settings": "Vis avancerede indstillinger",
"hide_advanced_settings": "Skjul avancerede indstillinger",
"event_name": "Begivenhedsnavn",
"event_name_in_calendar": "Begivenhedsnavn i kalender",
"event_name_tooltip": "Navnet, der vises i kalendere",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Dine teammedlemmer vil ikke kunne se andre teammedlemmer, når dette er slået til.",
"you_cannot_see_team_members": "Du kan ikke se alle teammedlemmer i et privat team.",
"you_cannot_see_teams_of_org": "Du kan ikke se teams i en privat organisation.",
"allow_booker_to_select_duration": "Tillad bookeren at vælge varighed",
"allow_multiple_durations": "Tillad flere varigheder",
"impersonate_user_tip": "Al brug af denne funktion er revideret.",
"impersonating_user_warning": "Efterligner brugernavnet \"{{user}}\".",
"impersonating_stop_instructions": "Klik her for at stoppe",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Er du sikker på, at du vil slette {{connectionType}} konfigurationen? Dine teammedlemmer, der bruger {{connectionType}} login, vil ikke længere kunne få adgang til Cal.com.",
"organizer_timezone": "Arrangørens tidszone",
"email_user_cta": "Vis Invitation",
"accept_invite": "Acceptér invitation",
"join_now": "Deltag nu",
"successfully_joined": "Deltog med succes",
"email_no_user_invite_heading_subteam": "Du er blevet inviteret til at blive medlem af et team i {{parentTeamName}}-organisationen.",
"email_no_user_invite_heading_org": "Du er blevet inviteret til at blive medlem af en {{appName}}-organisation.",
"email_no_user_invite_subheading": "{{invitedBy}} har inviteret dig til at deltage i deres team på {{appName}}. {{appName}} er den begivenheds-jonglerende planlægger, der gør det muligt for dig og dit team at planlægge møder uden e-mail tennis.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Reservationsbekræftelse mislykkedes",
"not_enough_seats": "Ikke nok pladser",
"form_builder_field_already_exists": "Et felt med dette navn findes allerede",
"guests_field_must_be_multiemail": "Gæstefeltet skal være af typen 'Flere e-mails'",
"show_on_booking_page": "Vis på bookingsiden",
"visit_cancelled_booking": "Du kan besøge siden for den aflyste booking",
"get_started_zapier_templates": "Kom i gang med Zapier skabeloner",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Invitationslink kopieret",
"invite_link_deleted": "Invitationslink slettet",
"api_key_deleted": "API-nøgle slettet",
"delete_api_key_warning": "Dette vil permanent slette API-nøglen. Alle applikationer, der bruger denne nøgle, vil straks miste adgang til din konto. Denne handling kan ikke fortrydes.",
"invite_link_updated": "Linkindstillinger gemt",
"link_expires_after": "Links indstillet til at udløbe efter...",
"one_day": "1 dag",
@@ -2688,7 +2696,7 @@
"enterprise": "Virksomhed",
"enterprise_description": "Opgrader til Enterprise for at oprette din organisation",
"create_your_org": "Opret din Organisation",
"create_org": "Opret Organisation",
"create_org": "Opret organisation",
"create_your_org_description": "Opgrader til Organisationer og få et subdomæne, samlet fakturering, Insights, omfattende whitelabeling og mere",
"create_your_enterprise_description": "Opgrader til Enterprise og få adgang til Active Directory Sync, SCIM Automatisk Brugerprovisionering, Cal.ai Voice Agents, Admin API'er og mere!",
"other_payment_app_enabled": "Du kan kun aktivere én betalingsapp pr. begivenhedstype",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Ved at fortsætte accepterer du vores",
"terms": "Vilkår",
"always_show_x_days": "Vis altid {{x}} dage tilgængelige",
"always_show_x_days_description": "Flytter automatisk {{x}}-dages bookingvinduet frem hver dag, så din kalender altid viser de næste {{x}} dages tilgængelige tidsrum.",
"team_subscription_cancelled_successfully": "Teamabonnement annulleret med succes",
"team_subscription_cancellation_error": "Fejl under annullering af teamabonnement",
"unable_to_subscribe_to_the_platform": "Der opstod en fejl under forsøget på at abonnere på platformplanen, prøv venligst igen senere",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Slutter med",
"filter_operator_is_empty": "Er tom",
"filter_operator_not_empty": "Er ikke tom",
"translate_description_button": "Oversæt titel/beskrivelse til besøgendes browsersprog ved hjælp af AI",
"translate_description_button": "Autooversæt titel og beskrivelse",
"translate_description_button_description": "Oversæt automatisk titler og beskrivelser til besøgendes browsersprog ved hjælp af AI.",
"rr_distribution_method": "Distribution",
"rr_distribution_method_description": "Gør det muligt at optimere distributionen for maksimal tilgængelighed eller for at opnå en mere balanceret tildeling.",
"rr_distribution_method_availability_title": "Maksimer tilgængelighed",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Sædefunktionen understøtter ikke tilbagevendende begivenheder. Deaktiver sædefunktionen for at gøre den tilbagevendende.",
"recurring_event_seats_error": "Tilbagevendende begivenheder understøtter ikke sædefunktionen. Deaktiver sædefunktionen eller gør begivenheden ikke-tilbagevendende.",
"segment": "Segment",
"saved_filters": "Gemte filtre",
"rename": "Omdøb",
"rename_segment": "Omdøb segment",
"delete_segment": "Slet segment",
@@ -3338,6 +3349,7 @@
"picklist": "Valgliste",
"most_cancelled_bookings": "Flest annullerede",
"interface_language": "Grænsefladesprog",
"interface_language_description": "Indstil dit foretrukne sprog for bookinggrænsefladen",
"visitors_browser_language": "Besøgerens browsersprog",
"name_or_email": "Navn eller e-mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Hvis ingen kontakt findes, fald tilbage til lead-ejeren, hvis denne eksisterer",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Administrer roller og tilladelser for din organisation",
"learn_more_permissions": "Lær mere om, hvordan tilladelser fungerer",
"role_name": "Rollenavn",
"role_name_required": "Rollenavn er påkrævet",
"role_name_placeholder": "f.eks. Salgsteam, Supportpersonale",
"role_description": "Rollebekskrivelse",
"role_description_placeholder": "Beskriv, hvad denne rolle er til, og hvilke tilladelser den har brug for",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rolle oprettet med succes",
"role_updated_successfully": "Rolle opdateret med succes",
"delete_role": "Slet rolle",
"confirm_delete_role": "Er du sikker på, at du vil slette denne rolle? Denne handling kan ikke fortrydes.",
"confirm_delete_role": "Er du sikker på, at du vil slette rollen: {{roleName}}? Denne handling kan ikke fortrydes.",
"role_deleted_successfully": "Rolle slettet med succes",
"error_creating_role": "Fejl ved oprettelse af rolle",
"error_updating_role": "Fejl ved opdatering af rolle",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Rediger og tilpas workflowindstillinger",
"pbac_desc_delete_workflows": "Fjern workflows fra systemet",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Tilgængelighed",
"pbac_resource_out_of_office": "Ude af kontoret",
"pbac_desc_create_webhooks": "Opret webhooks",
"pbac_desc_view_webhooks": "Se webhooks",
"pbac_desc_update_webhooks": "Opdater webhooks",
"pbac_desc_delete_webhooks": "Slet webhooks",
"pbac_desc_create_availability": "Opret tilgængelighed",
"pbac_desc_view_availability": "Se tilgængelighed",
"pbac_desc_update_availability": "Opdater tilgængelighed",
"pbac_desc_delete_availability": "Slet tilgængelighed",
"pbac_desc_create_out_of_office": "Opret fravær",
"pbac_desc_view_out_of_office": "Se fravær",
"pbac_desc_update_out_of_office": "Opdater fravær",
"pbac_desc_delete_out_of_office": "Slet fravær",
"pbac_desc_manage_workflows": "Fuld administrationsadgang til alle workflows",
"pbac_desc_create_event_types": "Opret begivenhedstyper",
"pbac_desc_view_event_types": "Vis begivenhedstyper",
@@ -3732,4 +3755,4 @@
"use_voice": "Brug stemme",
"current_voice": "Nuværende stemme",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Tilføj dine nye strenge ovenfor her ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+30 -7
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Keine verfügbaren Benutzer gefunden. Können Sie es mit einem anderen Zeitfenster versuchen?",
"timeslot_unavailable_book_a_new_time": "Der ausgewählte Zeitraum ist nicht mehr verfügbar. <0>Bitte wähle eine neue Zeit</0>",
"timeslot_unavailable_short": "Belegt",
"just_connected_description": "Sie haben gerade eine Verbindung hergestellt. Bitte buchen Sie aus den obigen Zeitfenstern oder versuchen Sie es später erneut.",
"unavailable_timeslot_title": "Nicht verfügbarer Zeitslot",
"booking_time_out_of_bounds_error": "Der Termin kann zu diesem Zeitpunkt nicht gebucht werden. Möchten Sie einen anderen Zeitslot versuchen?",
"request_body_end_time_internal_error": "Interner Fehler. Request-Body enthält keinen Endzeitpunkt",
@@ -321,6 +322,7 @@
"secret_meeting": "Geheimer Termin",
"login_instead": "Stattdessen einloggen",
"create_account": "Konto erstellen",
"get_started": "Loslegen",
"confirm_password": "Passwort bestätigen",
"reset_your_password": "Legen Sie Ihr neues Passwort mit den Anweisungen fest, die an Ihre E-Mail-Adresse gesendet wurden.",
"org_banner_instructions": "Bitte laden Sie Bild mit einer Breite von {{width}} und einer Höhe von {{height}} hoch.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Ereignistyp-Farbe (Helles Design)",
"dark_event_type_color": "Ereignistyp-Farbe (Dunkles Design)",
"file_not_named": "Datei heißt nicht [idOrSlug]/[user]",
"create_team": "Team anlegen",
"create_team": "Team erstellen",
"name": "Name",
"nameless_team": "Namenloses Team",
"oauth_clients": "OAuth-Clients",
@@ -1019,6 +1021,7 @@
"attendees": "Teilnehmende",
"add_attendees": "Teilnehmende hinzufügen",
"show_advanced_settings": "Erweiterte Einstellungen anzeigen",
"hide_advanced_settings": "Erweiterte Einstellungen ausblenden",
"event_name": "Termin-Name",
"event_name_in_calendar": "Ereignisname im Kalender",
"event_name_tooltip": "Der Name, der in Kalendern angezeigt wird",
@@ -1231,7 +1234,7 @@
"contact_sales": "Vertrieb kontaktieren",
"error_404": "Fehler 404",
"default": "Standard",
"set_to_default": "Auf Standard setzen",
"set_to_default": "Als Standard festlegen",
"new_schedule_btn": "Neuer Verfügbarkeitsplan",
"add_new_schedule": "Neuen Verfügbarkeitsplan hinzufügen",
"add_new_calendar": "Neuen Kalender hinzufügen",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Wenn diese Einstellung aktiv ist, können deine Teammitglieder keine anderen Teammitglieder sehen.",
"you_cannot_see_team_members": "Sie können nicht alle Teammitglieder eines privaten Teams sehen.",
"you_cannot_see_teams_of_org": "Du kannst keine Teams einer privaten Organisation sehen.",
"allow_booker_to_select_duration": "Bucher erlauben, die Länge zu wählen",
"allow_multiple_durations": "Mehrere Dauern erlauben",
"impersonate_user_tip": "Alle Verwendungen dieser Funktion werden geprüft.",
"impersonating_user_warning": "Imitiere den Account {{user}}.",
"impersonating_stop_instructions": "Klicken Sie hier, um zu stoppen",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Sind Sie sicher, dass Sie die {{connectionType}}-Konfiguration löschen möchten? Ihre Teammitglieder, die sich über {{connectionType}} anmelden, werden keinen Zugriff mehr auf Cal.com haben.",
"organizer_timezone": "Zeitzone des Veranstalters",
"email_user_cta": "Einladung anzeigen",
"accept_invite": "Einladung annehmen",
"join_now": "Jetzt beitreten",
"successfully_joined": "Erfolgreich beigetreten",
"email_no_user_invite_heading_subteam": "Du wurdest eingeladen, einem Team der Organisation {{parentTeamName}} beizutreten",
"email_no_user_invite_heading_org": "Du wurdest eingeladen, einer {{appName}}-Organisation beizutreten",
"email_no_user_invite_subheading": "{{invitedBy}} hat Sie eingeladen, dem Team in {{appName}} beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne ständiges hin und her zu planen.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Buchungsbestätigung fehlgeschlagen",
"not_enough_seats": "Nicht genügend Plätze",
"form_builder_field_already_exists": "Ein Feld mit diesem Namen ist bereits vorhanden",
"guests_field_must_be_multiemail": "Das Gästefeld muss vom Typ 'Mehrere E-Mails' sein",
"show_on_booking_page": "Auf der Buchungsseite anzeigen",
"visit_cancelled_booking": "Sie können die Seite der stornierten Buchung besuchen",
"get_started_zapier_templates": "Legen Sie mit Zapier-Vorlagen los",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Einladungslink kopiert",
"invite_link_deleted": "Einladungslink gelöscht",
"api_key_deleted": "API-Schlüssel gelöscht",
"delete_api_key_warning": "Dies wird den API-Schlüssel dauerhaft löschen. Alle Anwendungen, die diesen Schlüssel verwenden, verlieren sofort den Zugriff auf Ihr Konto. Diese Aktion kann nicht rückgängig gemacht werden.",
"invite_link_updated": "Einladungslink-Einstellungen gespeichert",
"link_expires_after": "Links verfallen nach...",
"one_day": "1 Tag",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Indem Sie fortfahren, stimmen Sie unseren",
"terms": "Nutzungsbedingungen",
"always_show_x_days": "Immer für {{x}} Tage verfügbar",
"always_show_x_days_description": "Verschiebt das {{x}}-Tage-Buchungsfenster automatisch jeden Tag nach vorne und stellt sicher, dass Ihr Kalender immer die nächsten {{x}} Tage mit verfügbaren Zeitfenstern anzeigt.",
"team_subscription_cancelled_successfully": "Team-Abonnement erfolgreich gekündigt",
"team_subscription_cancellation_error": "Fehler bei der Kündigung des Team-Abonnements",
"unable_to_subscribe_to_the_platform": "Beim Abonnieren des Platform-Plans ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Endet mit",
"filter_operator_is_empty": "Ist leer",
"filter_operator_not_empty": "Ist nicht leer",
"translate_description_button": "Titel/Beschreibung mit KI in die Browsersprache des Besuchers übersetzen",
"translate_description_button": "Titel und Beschreibung automatisch übersetzen",
"translate_description_button_description": "Titel und Beschreibungen automatisch in die Browsersprache des Besuchers mittels KI übersetzen.",
"rr_distribution_method": "Verteilung",
"rr_distribution_method_description": "Ermöglicht die Optimierung der Verteilung für maximale Verfügbarkeit oder eine ausgewogenere Zuweisung.",
"rr_distribution_method_availability_title": "Verfügbarkeit maximieren",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Die Sitzplatzfunktion unterstützt keine wiederkehrenden Events. Deaktivieren Sie die Sitzplatzfunktion, um es wiederkehrend zu machen.",
"recurring_event_seats_error": "Wiederkehrende Events unterstützen die Sitzplatzfunktion nicht. Deaktivieren Sie die Sitzplatzfunktion oder machen Sie das Event nicht wiederkehrend.",
"segment": "Segment",
"saved_filters": "Gespeicherte Filter",
"rename": "Umbenennen",
"rename_segment": "Segment umbenennen",
"delete_segment": "Segment löschen",
@@ -3338,6 +3349,7 @@
"picklist": "Auswahlliste",
"most_cancelled_bookings": "Meiste stornierte Buchungen",
"interface_language": "Oberflächensprache",
"interface_language_description": "Legen Sie Ihre bevorzugte Sprache für die Buchungsoberfläche fest",
"visitors_browser_language": "Browsersprache des Besuchers",
"name_or_email": "Name oder E-Mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Wenn kein Kontakt gefunden wird, auf den Lead-Eigentümer zurückgreifen, falls vorhanden",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Verwalte Rollen und Berechtigungen für deine Organisation",
"learn_more_permissions": "Erfahre mehr darüber, wie Berechtigungen funktionieren",
"role_name": "Rollenname",
"role_name_required": "Rollenname ist erforderlich",
"role_name_placeholder": "z.B. Vertriebsteam, Support-Mitarbeiter",
"role_description": "Rollenbeschreibung",
"role_description_placeholder": "Beschreibe, wofür diese Rolle gedacht ist und welche Berechtigungen sie benötigt",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rolle erfolgreich erstellt",
"role_updated_successfully": "Rolle erfolgreich aktualisiert",
"delete_role": "Rolle löschen",
"confirm_delete_role": "Sind Sie sicher, dass Sie diese Rolle löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"confirm_delete_role": "Sind Sie sicher, dass Sie die Rolle löschen möchten: {{roleName}}. Diese Aktion kann nicht rückgängig gemacht werden.",
"role_deleted_successfully": "Rolle erfolgreich gelöscht",
"error_creating_role": "Fehler beim Erstellen der Rolle",
"error_updating_role": "Fehler beim Aktualisieren der Rolle",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Workflow-Einstellungen bearbeiten und ändern",
"pbac_desc_delete_workflows": "Workflows aus dem System entfernen",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Verfügbarkeit",
"pbac_resource_out_of_office": "Abwesenheit",
"pbac_desc_create_webhooks": "Webhooks erstellen",
"pbac_desc_view_webhooks": "Webhooks anzeigen",
"pbac_desc_update_webhooks": "Webhooks aktualisieren",
"pbac_desc_delete_webhooks": "Webhooks löschen",
"pbac_desc_create_availability": "Verfügbarkeit erstellen",
"pbac_desc_view_availability": "Verfügbarkeit anzeigen",
"pbac_desc_update_availability": "Verfügbarkeit aktualisieren",
"pbac_desc_delete_availability": "Verfügbarkeit löschen",
"pbac_desc_create_out_of_office": "Abwesenheit erstellen",
"pbac_desc_view_out_of_office": "Abwesenheit anzeigen",
"pbac_desc_update_out_of_office": "Abwesenheit aktualisieren",
"pbac_desc_delete_out_of_office": "Abwesenheit löschen",
"pbac_desc_manage_workflows": "Vollständiger Verwaltungszugriff auf alle Workflows",
"pbac_desc_create_event_types": "Ereignistypen erstellen",
"pbac_desc_view_event_types": "Ereignistypen anzeigen",
@@ -3560,8 +3583,8 @@
"rr_load_balancing_disabled_with_groups": "Lastausgleich ist mit Round-Robin-Gruppen nicht verfügbar",
"round_robin_settings_description": "Passen Sie die Standard-Round-Robin-Einstellungen für dieses Team an",
"round_robin_settings_updated_successfully": "Round-Robin-Einstellungen erfolgreich aktualisiert",
"booking_creation_time": "Zeitpunkt der Buchungserstellung",
"meeting_start_time": "Beginn des Meetings",
"booking_creation_time": "Erstellungszeit der Buchung",
"meeting_start_time": "Besprechungsstartzeit",
"members_affected_by_disabling_delegation_credential": "Betroffene Mitglieder",
"no_members_affected_by_disabling_delegation_credential": "Keine Mitglieder von der Deaktivierung der Delegationsanmeldedaten betroffen",
"download_expense_log": "Ausgabenprotokoll herunterladen",
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Δεν βρέθηκαν διαθέσιμοι χρήστες. Θα μπορούσατε να δοκιμάσετε άλλη χρονική θυρίδα;",
"timeslot_unavailable_book_a_new_time": "Η επιλεγμένη χρονοθυρίδα δεν είναι πλέον διαθέσιμη. <0>Παρακαλώ επιλέξτε νέο χρόνο</0>",
"timeslot_unavailable_short": "Κατειλημμένο",
"just_connected_description": "Μόλις συνδεθήκατε. Παρακαλώ κάντε κράτηση από τις παραπάνω διαθέσιμες ώρες ή δοκιμάστε ξανά αργότερα.",
"unavailable_timeslot_title": "Μη διαθέσιμη χρονοθυρίδα",
"booking_time_out_of_bounds_error": "Ο τύπος εκδήλωσης δεν μπορεί να κρατηθεί αυτή τη στιγμή. Θα μπορούσατε να δοκιμάσετε μια άλλη χρονική θυρίδα;",
"request_body_end_time_internal_error": "Εσωτερικό σφάλμα. Το σώμα του αιτήματος δεν περιέχει ώρα λήξης",
@@ -321,6 +322,7 @@
"secret_meeting": "Μυστική συνάντηση",
"login_instead": "Σύνδεση αντ' αυτού",
"create_account": "Δημιουργία Λογαριασμού",
"get_started": "Ξεκινήστε",
"confirm_password": "Επιβεβαίωση κωδικού πρόσβασης",
"reset_your_password": "Ορίστε τον νέο σας κωδικό πρόσβασης με τις οδηγίες που στάλθηκαν στη διεύθυνση email σας.",
"org_banner_instructions": "Παρακαλούμε ανεβάστε εικόνα με πλάτος {{width}} και ύψος {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "Συμμετέχοντες",
"add_attendees": "Προσθήκη συμμετεχόντων",
"show_advanced_settings": "Εμφάνιση προχωρημένων ρυθμίσεων",
"hide_advanced_settings": "Απόκρυψη προχωρημένων ρυθμίσεων",
"event_name": "Όνομα εκδήλωσης",
"event_name_in_calendar": "Με όνομα εκδήλωσης",
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
@@ -1231,7 +1234,7 @@
"contact_sales": "Επικοινωνία με Πωλήσεις",
"error_404": "Σφάλμα 404",
"default": "Προεπιλογή",
"set_to_default": "Ορισμός ως Προεπιλογή",
"set_to_default": "Ορισμός ως προεπιλογή",
"new_schedule_btn": "Νέο πρόγραμμα",
"add_new_schedule": "Προσθήκη νέου προγράμματος",
"add_new_calendar": "Προσθήκη νέου ημερολογίου",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Τα μέλη της ομάδας σας δεν θα μπορούν να βλέπουν άλλα μέλη της ομάδας όταν αυτό είναι ενεργοποιημένο.",
"you_cannot_see_team_members": "Δεν μπορείτε να δείτε όλα τα μέλη μιας ιδιωτικής ομάδας.",
"you_cannot_see_teams_of_org": "Δεν μπορείτε να δείτε τις ομάδες ενός ιδιωτικού οργανισμού.",
"allow_booker_to_select_duration": "Επιτρέψτε στον χρήστη να επιλέξει διάρκεια",
"allow_multiple_durations": "Επιτρέψτε πολλαπλές διάρκειες",
"impersonate_user_tip": "Όλες οι χρήσεις αυτής της λειτουργίας καταγράφονται.",
"impersonating_user_warning": "Μιμείστε τον χρήστη \"{{user}}\".",
"impersonating_stop_instructions": "Κάντε κλικ εδώ για διακοπή",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη διαμόρφωση {{connectionType}}; Τα μέλη της ομάδας σας που χρησιμοποιούν σύνδεση {{connectionType}} δεν θα μπορούν πλέον να έχουν πρόσβαση στο Cal.com.",
"organizer_timezone": "Ζώνη ώρας διοργανωτή",
"email_user_cta": "Προβολή πρόσκλησης",
"accept_invite": "Αποδοχή Πρόσκλησης",
"join_now": "Συμμετοχή Τώρα",
"successfully_joined": "Επιτυχής συμμετοχή",
"email_no_user_invite_heading_subteam": "Έχετε προσκληθεί να συμμετάσχετε σε μια ομάδα του οργανισμού {{parentTeamName}}",
"email_no_user_invite_heading_org": "Έχετε προσκληθεί να συμμετάσχετε σε έναν οργανισμό {{appName}}",
"email_no_user_invite_subheading": "Ο/Η {{invitedBy}} σας έχει προσκαλέσει να συμμετάσχετε στην ομάδα τους στο {{appName}}. Το {{appName}} είναι ο προγραμματιστής συναντήσεων που επιτρέπει σε εσάς και την ομάδα σας να κανονίζετε συναντήσεις χωρίς ατελείωτα email.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Η επιβεβαίωση της κράτησης απέτυχε",
"not_enough_seats": "Δεν υπάρχουν αρκετές θέσεις",
"form_builder_field_already_exists": "Υπάρχει ήδη ένα πεδίο με αυτό το όνομα",
"guests_field_must_be_multiemail": "Το πεδίο επισκεπτών πρέπει να είναι τύπου 'Πολλαπλά emails'",
"show_on_booking_page": "Εμφάνιση στη σελίδα κράτησης",
"visit_cancelled_booking": "Μπορείτε να επισκεφθείτε τη σελίδα ακυρωμένων κρατήσεων",
"get_started_zapier_templates": "Ξεκινήστε με τα πρότυπα του Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Ο σύνδεσμος πρόσκλησης αντιγράφηκε",
"invite_link_deleted": "Ο σύνδεσμος πρόσκλησης διαγράφηκε",
"api_key_deleted": "Το κλειδί API διαγράφηκε",
"delete_api_key_warning": "Αυτό θα διαγράψει μόνιμα το κλειδί API. Οποιεσδήποτε εφαρμογές χρησιμοποιούν αυτό το κλειδί θα χάσουν αμέσως την πρόσβαση στον λογαριασμό σας. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.",
"invite_link_updated": "Οι ρυθμίσεις του συνδέσμου πρόσκλησης αποθηκεύτηκαν",
"link_expires_after": "Οι σύνδεσμοι λήγουν μετά από...",
"one_day": "1 ημέρα",
@@ -2688,7 +2696,7 @@
"enterprise": "Enterprise",
"enterprise_description": "Αναβαθμίστε σε Enterprise για να δημιουργήσετε τον οργανισμό σας",
"create_your_org": "Δημιουργήστε τον Οργανισμό σας",
"create_org": "Δημιουργία Οργανισμού",
"create_org": "Δημιουργία οργανισμού",
"create_your_org_description": "Αναβαθμίστε σε Οργανισμούς και λάβετε υποτομέα, ενοποιημένη τιμολόγηση, Αναλύσεις, εκτεταμένο white-labeling και άλλα",
"create_your_enterprise_description": "Αναβαθμίστε σε Enterprise και αποκτήστε πρόσβαση σε Συγχρονισμό Active Directory, Αυτόματη παροχή χρηστών SCIM, Φωνητικούς πράκτορες Cal.ai, APIs διαχειριστή και άλλα!",
"other_payment_app_enabled": "Μπορείτε να ενεργοποιήσετε μόνο μία εφαρμογή πληρωμών ανά τύπο εκδήλωσης",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Προχωρώντας, συμφωνείτε με τους",
"terms": "Όρους",
"always_show_x_days": "Πάντα {{x}} διαθέσιμες ημέρες",
"always_show_x_days_description": "Μετακινεί αυτόματα το παράθυρο κρατήσεων {{x}} ημερών κάθε μέρα, διασφαλίζοντας ότι το ημερολόγιό σας δείχνει πάντα τις επόμενες {{x}} ημέρες διαθέσιμων χρονοθυρίδων.",
"team_subscription_cancelled_successfully": "Η συνδρομή ομάδας ακυρώθηκε με επιτυχία",
"team_subscription_cancellation_error": "Σφάλμα κατά την ακύρωση της συνδρομής ομάδας",
"unable_to_subscribe_to_the_platform": "Παρουσιάστηκε σφάλμα κατά την εγγραφή στο πρόγραμμα της πλατφόρμας, παρακαλώ δοκιμάστε ξανά αργότερα",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Τελειώνει με",
"filter_operator_is_empty": "Είναι κενό",
"filter_operator_not_empty": "Δεν είναι κενό",
"translate_description_button": "Μετάφραση τίτλου/περιγραφής στη γλώσσα του προγράμματος περιήγησης του επισκέπτη με χρήση AI",
"translate_description_button": "Αυτόματη μετάφραση τίτλου και περιγραφής",
"translate_description_button_description": "Αυτόματη μετάφραση τίτλων και περιγραφών στη γλώσσα του προγράμματος περιήγησης του επισκέπτη χρησιμοποιώντας AI.",
"rr_distribution_method": "Κατανομή",
"rr_distribution_method_description": "Επιτρέπει τη βελτιστοποίηση της κατανομής για μέγιστη διαθεσιμότητα ή για πιο ισορροπημένη ανάθεση.",
"rr_distribution_method_availability_title": "Μεγιστοποίηση διαθεσιμότητας",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Η λειτουργία θέσεων δεν υποστηρίζει επαναλαμβανόμενη εκδήλωση. Απενεργοποιήστε τη λειτουργία θέσεων για να την κάνετε επαναλαμβανόμενη.",
"recurring_event_seats_error": "Η επαναλαμβανόμενη εκδήλωση δεν υποστηρίζει τη λειτουργία θέσεων. Απενεργοποιήστε τη λειτουργία θέσεων ή κάντε την εκδήλωση μη επαναλαμβανόμενη.",
"segment": "Τμήμα",
"saved_filters": "Αποθηκευμένα φίλτρα",
"rename": "Μετονομασία",
"rename_segment": "Μετονομασία τμήματος",
"delete_segment": "Διαγραφή τμήματος",
@@ -3338,6 +3349,7 @@
"picklist": "Λίστα επιλογών",
"most_cancelled_bookings": "Περισσότερες ακυρωμένες κρατήσεις",
"interface_language": "Γλώσσα διεπαφής",
"interface_language_description": "Ορίστε την προτιμώμενη γλώσσα για το περιβάλλον κρατήσεων",
"visitors_browser_language": "Γλώσσα προγράμματος περιήγησης επισκέπτη",
"name_or_email": "Όνομα ή Email",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Αν δεν βρεθεί επαφή, επιστροφή στον ιδιοκτήτη του lead εάν υπάρχει",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Διαχειριστείτε ρόλους και δικαιώματα για τον οργανισμό σας",
"learn_more_permissions": "Μάθετε περισσότερα για το πώς λειτουργούν τα Δικαιώματα",
"role_name": "Όνομα ρόλου",
"role_name_required": "Το όνομα ρόλου είναι υποχρεωτικό",
"role_name_placeholder": "π.χ. Ομάδα Πωλήσεων, Προσωπικό Υποστήριξης",
"role_description": "Περιγραφή ρόλου",
"role_description_placeholder": "Περιγράψτε για ποιο σκοπό προορίζεται αυτός ο ρόλος και ποια δικαιώματα χρειάζεται",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Ο ρόλος δημιουργήθηκε με επιτυχία",
"role_updated_successfully": "Ο ρόλος ενημερώθηκε με επιτυχία",
"delete_role": "Διαγραφή ρόλου",
"confirm_delete_role": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον ρόλο; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.",
"confirm_delete_role": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον ρόλο: {{roleName}}; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.",
"role_deleted_successfully": "Ο ρόλος διαγράφηκε με επιτυχία",
"error_creating_role": "Σφάλμα κατά τη δημιουργία ρόλου",
"error_updating_role": "Σφάλμα κατά την ενημέρωση ρόλου",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Επεξεργασία και τροποποίηση ρυθμίσεων ροών εργασίας",
"pbac_desc_delete_workflows": "Αφαίρεση ροών εργασίας από το σύστημα",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Διαθεσιμότητα",
"pbac_resource_out_of_office": "Εκτός Γραφείου",
"pbac_desc_create_webhooks": "Δημιουργία webhooks",
"pbac_desc_view_webhooks": "Προβολή webhooks",
"pbac_desc_update_webhooks": "Ενημέρωση webhooks",
"pbac_desc_delete_webhooks": "Διαγραφή webhooks",
"pbac_desc_create_availability": "Δημιουργία διαθεσιμότητας",
"pbac_desc_view_availability": "Προβολή διαθεσιμότητας",
"pbac_desc_update_availability": "Ενημέρωση διαθεσιμότητας",
"pbac_desc_delete_availability": "Διαγραφή διαθεσιμότητας",
"pbac_desc_create_out_of_office": "Δημιουργία απουσίας",
"pbac_desc_view_out_of_office": "Προβολή απουσίας",
"pbac_desc_update_out_of_office": "Ενημέρωση απουσίας",
"pbac_desc_delete_out_of_office": "Διαγραφή απουσίας",
"pbac_desc_manage_workflows": "Πλήρης πρόσβαση διαχείρισης σε όλες τις ροές εργασίας",
"pbac_desc_create_event_types": "Δημιουργία τύπων εκδηλώσεων",
"pbac_desc_view_event_types": "Προβολή τύπων εκδηλώσεων",
@@ -3732,4 +3755,4 @@
"use_voice": "Χρήση φωνής",
"current_voice": "Τρέχουσα φωνή",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Προσθέστε τις νέες συμβολοσειρές σας πάνω από εδώ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
@@ -111,6 +111,7 @@
"no_available_users_found_error": "No available users found. Could you try another time slot?",
"timeslot_unavailable_book_a_new_time": "The selected time slot is no longer available. <0>Please select a new time</0>",
"timeslot_unavailable_short": "Taken",
"just_connected_description": "You’ve just connected. Please book from above slots or retry later.",
"unavailable_timeslot_title": "Unavailable timeslot",
"booking_time_out_of_bounds_error": "The event type cannot be booked at this time. Could you try another time slot?",
"request_body_end_time_internal_error": "Internal Error. Request body does not contain end time",
@@ -2152,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Are you sure you want to delete the {{connectionType}} configuration? Your team members who use {{connectionType}} login will no longer be able to access Cal.com.",
"organizer_timezone": "Organizer timezone",
"email_user_cta": "View Invitation",
"accept_invite": "Accept Invite",
"join_now": "Join Now",
"successfully_joined": "Successfully joined",
"email_no_user_invite_heading_subteam": "You've been invited to join a team of {{parentTeamName}} organization",
"email_no_user_invite_heading_org": "You've been invited to join a {{appName}} organization",
"email_no_user_invite_subheading": "{{invitedBy}} has invited you to join their team on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.",
@@ -2382,6 +2386,7 @@
"invite_link_copied": "Invite link copied",
"invite_link_deleted": "Invite link deleted",
"api_key_deleted": "API Key deleted",
"delete_api_key_warning": "This will permanently delete the API key. Any applications using this key will immediately lose access to your account. This action cannot be undone.",
"invite_link_updated": "Invite link settings saved",
"link_expires_after": "Links set to expire after...",
"one_day": "1 day",
@@ -3459,6 +3464,7 @@
"roles_and_permissions_description": "Manage roles and permissions for your organization",
"learn_more_permissions": "Learn more about how Permissions work",
"role_name": "Role name",
"role_name_required": "Role name is required",
"role_name_placeholder": "e.g. Sales Team, Support Staff",
"role_description": "Role description",
"role_description_placeholder": "Describe what this role is for and what permissions it needs",
@@ -3497,7 +3503,7 @@
"role_created_successfully": "Role created successfully",
"role_updated_successfully": "Role updated successfully",
"delete_role": "Delete role",
"confirm_delete_role": "Are you sure you want to delete this role? This action cannot be undone.",
"confirm_delete_role": "Are you sure you want to delete role: {{roleName}}. This action cannot be undone.",
"role_deleted_successfully": "Role deleted successfully",
"error_creating_role": "Error creating role",
"error_updating_role": "Error updating role",
@@ -111,6 +111,7 @@
"no_available_users_found_error": "No se encontraron usuarios disponibles. ¿Podrías intentar en otro horario?",
"timeslot_unavailable_book_a_new_time": "El horario seleccionado ya no está disponible. <0>Por favor selecciona un nuevo horario</0>",
"timeslot_unavailable_short": "Ocupado",
"just_connected_description": "Acabas de conectarte. Por favor, reserva desde los espacios disponibles arriba o inténtalo más tarde.",
"unavailable_timeslot_title": "Horario no disponible",
"booking_time_out_of_bounds_error": "El evento no se puede agendar en este momento. ¿Podrías intentar con otro horario?",
"request_body_end_time_internal_error": "Error interno. El cuerpo de la solicitud no contiene la hora de finalización",
@@ -321,6 +322,7 @@
"secret_meeting": "Reunión Secreta",
"login_instead": "Inicia sesión en su lugar",
"create_account": "Crear cuenta",
"get_started": "Comenzar",
"confirm_password": "Confirmar contraseña",
"reset_your_password": "Establece tu nueva contraseña con las instrucciones enviadas a tu dirección de correo electrónico.",
"org_banner_instructions": "Por favor, sube una imagen de {{width}} de ancho y {{height}} de alto.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Color del Tipo de Evento (Tema Claro)",
"dark_event_type_color": "Color del Tipo de Evento (Tema Oscuro)",
"file_not_named": "El archivo no está nombrado [idOrSlug]/[user]",
"create_team": "Crear Equipo",
"create_team": "Crear equipo",
"name": "Nombre",
"nameless_team": "Equipo sin nombre",
"oauth_clients": "Clientes OAuth",
@@ -1019,6 +1021,7 @@
"attendees": "Asistentes",
"add_attendees": "Agregar asistentes",
"show_advanced_settings": "Mostrar configuraciones avanzadas",
"hide_advanced_settings": "Ocultar configuración avanzada",
"event_name": "Nombre del evento",
"event_name_in_calendar": "Con nombre del evento",
"event_name_tooltip": "El nombre que aparecerá en los calendarios",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Los miembros de tu equipo no podrán ver a otros miembros del equipo cuando esto esté activado.",
"you_cannot_see_team_members": "No puedes ver a todos los miembros de un equipo privado.",
"you_cannot_see_teams_of_org": "No puedes ver los equipos de una organización privada.",
"allow_booker_to_select_duration": "Permitir al reservador seleccionar la duración",
"allow_multiple_durations": "Permitir múltiples duraciones",
"impersonate_user_tip": "Todos los usos de esta función son auditados.",
"impersonating_user_warning": "Suplantando al usuario \"{{user}}\".",
"impersonating_stop_instructions": "Haz clic aquí para detener",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "¿Estás seguro de que deseas eliminar la configuración de {{connectionType}}? Los miembros de tu equipo que usan el inicio de sesión de {{connectionType}} ya no podrán acceder a Cal.com.",
"organizer_timezone": "Zona horaria del organizador",
"email_user_cta": "Ver invitación",
"accept_invite": "Aceptar invitación",
"join_now": "Unirse ahora",
"successfully_joined": "Te has unido exitosamente",
"email_no_user_invite_heading_subteam": "Has sido invitado a unirte a un equipo de la organización {{parentTeamName}}",
"email_no_user_invite_heading_org": "Has sido invitado a unirte a una organización de {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} te ha invitado a unirte a su equipo en {{appName}}. {{appName}} es el programador de eventos que te permite a ti y a tu equipo agendar reuniones sin el ir y venir de correos electrónicos.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "La confirmación de la reserva falló",
"not_enough_seats": "No hay suficientes asientos",
"form_builder_field_already_exists": "Ya existe un campo con este nombre",
"guests_field_must_be_multiemail": "El campo de invitados debe ser de tipo 'Múltiples correos electrónicos'",
"show_on_booking_page": "Mostrar en la página de reserva",
"visit_cancelled_booking": "Puedes visitar la página de la reserva cancelada",
"get_started_zapier_templates": "Comienza con las plantillas de Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Enlace de invitación copiado",
"invite_link_deleted": "Enlace de invitación eliminado",
"api_key_deleted": "Clave API eliminada",
"delete_api_key_warning": "Esto eliminará permanentemente la clave API. Cualquier aplicación que use esta clave perderá inmediatamente el acceso a tu cuenta. Esta acción no se puede deshacer.",
"invite_link_updated": "Configuración del enlace de invitación guardada",
"link_expires_after": "Los enlaces están configurados para expirar después de...",
"one_day": "1 día",
@@ -2688,7 +2696,7 @@
"enterprise": "Empresa",
"enterprise_description": "Actualiza a Enterprise para crear tu Organización",
"create_your_org": "Crea tu Organización",
"create_org": "Crear Organización",
"create_org": "Crear organización",
"create_your_org_description": "Actualiza a Organizaciones y recibe un subdominio, facturación unificada, Insights, etiquetado blanco extensivo y más",
"create_your_enterprise_description": "Actualiza a Enterprise y obtén acceso a la sincronización de Active Directory, aprovisionamiento automático de usuarios SCIM, agentes de voz Cal.ai, APIs de administración y más",
"other_payment_app_enabled": "Solo puedes habilitar una aplicación de pago por tipo de evento",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Al continuar, aceptas nuestros",
"terms": "Términos",
"always_show_x_days": "Siempre {{x}} días disponibles",
"always_show_x_days_description": "Mueve automáticamente la ventana de reservas de {{x}} días hacia adelante cada día, asegurando que tu calendario siempre muestre los próximos {{x}} días de espacios disponibles.",
"team_subscription_cancelled_successfully": "Suscripción del equipo cancelada con éxito",
"team_subscription_cancellation_error": "Error al cancelar la suscripción del equipo",
"unable_to_subscribe_to_the_platform": "Ocurrió un error al intentar suscribirse al plan de la plataforma, por favor intenta de nuevo más tarde",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Termina con",
"filter_operator_is_empty": "Está vacío",
"filter_operator_not_empty": "No está vacío",
"translate_description_button": "Traducir título/descripción al idioma del navegador del visitante usando IA",
"translate_description_button": "Traducir automáticamente título y descripción",
"translate_description_button_description": "Traduce automáticamente títulos y descripciones al idioma del navegador del visitante usando IA.",
"rr_distribution_method": "Distribución",
"rr_distribution_method_description": "Permite optimizar la distribución para maximizar la disponibilidad o lograr una asignación más equilibrada.",
"rr_distribution_method_availability_title": "Maximizar disponibilidad",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "La función de asientos no admite eventos recurrentes. Desactiva la función de asientos para hacerlo recurrente.",
"recurring_event_seats_error": "Los eventos recurrentes no admiten la función de asientos. Desactiva la función de asientos o haz que el evento no sea recurrente.",
"segment": "Segmento",
"saved_filters": "Filtros guardados",
"rename": "Renombrar",
"rename_segment": "Renombrar segmento",
"delete_segment": "Eliminar segmento",
@@ -3338,6 +3349,7 @@
"picklist": "Lista de selección",
"most_cancelled_bookings": "Más cancelaciones",
"interface_language": "Idioma de la interfaz",
"interface_language_description": "Establece tu idioma preferido para la interfaz de reservas",
"visitors_browser_language": "Idioma del navegador del visitante",
"name_or_email": "Nombre o correo electrónico",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Si no se encuentra ningún contacto, recurrir al propietario del lead si existe",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Administra roles y permisos para tu organización",
"learn_more_permissions": "Aprende más sobre cómo funcionan los permisos",
"role_name": "Nombre del rol",
"role_name_required": "El nombre del rol es obligatorio",
"role_name_placeholder": "p. ej. Equipo de ventas, Personal de soporte",
"role_description": "Descripción del rol",
"role_description_placeholder": "Describe para qué es este rol y qué permisos necesita",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rol creado exitosamente",
"role_updated_successfully": "Rol actualizado exitosamente",
"delete_role": "Eliminar rol",
"confirm_delete_role": "¿Estás seguro de que quieres eliminar este rol? Esta acción no se puede deshacer.",
"confirm_delete_role": "¿Estás seguro de que quieres eliminar el rol: {{roleName}}? Esta acción no se puede deshacer.",
"role_deleted_successfully": "Rol eliminado exitosamente",
"error_creating_role": "Error al crear el rol",
"error_updating_role": "Error al actualizar el rol",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Editar y modificar configuraciones de flujos de trabajo",
"pbac_desc_delete_workflows": "Eliminar flujos de trabajo del sistema",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Disponibilidad",
"pbac_resource_out_of_office": "Fuera de la oficina",
"pbac_desc_create_webhooks": "Crear webhooks",
"pbac_desc_view_webhooks": "Ver webhooks",
"pbac_desc_update_webhooks": "Actualizar webhooks",
"pbac_desc_delete_webhooks": "Eliminar webhooks",
"pbac_desc_create_availability": "Crear disponibilidad",
"pbac_desc_view_availability": "Ver disponibilidad",
"pbac_desc_update_availability": "Actualizar disponibilidad",
"pbac_desc_delete_availability": "Eliminar disponibilidad",
"pbac_desc_create_out_of_office": "Crear ausencia",
"pbac_desc_view_out_of_office": "Ver ausencia",
"pbac_desc_update_out_of_office": "Actualizar ausencia",
"pbac_desc_delete_out_of_office": "Eliminar ausencia",
"pbac_desc_manage_workflows": "Acceso completo de gestión a todos los flujos de trabajo",
"pbac_desc_create_event_types": "Crear tipos de eventos",
"pbac_desc_view_event_types": "Ver tipos de eventos",
@@ -3732,4 +3755,4 @@
"use_voice": "Usar voz",
"current_voice": "Voz actual",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agrega tus nuevas cadenas arriba de esta línea ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "No se encontraron usuarios disponibles. ¿Podrías intentar en otro horario?",
"timeslot_unavailable_book_a_new_time": "El horario seleccionado ya no está disponible. <0>Por favor selecciona un nuevo horario</0>",
"timeslot_unavailable_short": "Ocupado",
"just_connected_description": "Acabas de conectarte. Por favor, reserva desde los espacios de arriba o inténtalo más tarde.",
"unavailable_timeslot_title": "Horario no disponible",
"booking_time_out_of_bounds_error": "Este tipo de evento no se puede reservar en este momento. ¿Podrías intentar con otro horario?",
"request_body_end_time_internal_error": "Error interno. El cuerpo de la solicitud no contiene la hora de finalización",
@@ -321,6 +322,7 @@
"secret_meeting": "Reunión Secreta",
"login_instead": "Iniciar Sesión en su lugar",
"create_account": "Crear Cuenta",
"get_started": "Comenzar",
"confirm_password": "Confirmar Contraseña",
"reset_your_password": "Establezca su nueva contraseña con las instrucciones enviadas a su dirección de correo electrónico.",
"org_banner_instructions": "Por favor, sube una imagen de {{width}} de ancho y {{height}} de alto.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Color del tipo de evento (Tema claro)",
"dark_event_type_color": "Color del tipo de evento (Tema oscuro)",
"file_not_named": "El archivo no se llama [idOrSlug] / [user]",
"create_team": "Crear Equipo",
"create_team": "Crear equipo",
"name": "Nombre",
"nameless_team": "Equipo sin nombre",
"oauth_clients": "Clientes OAuth",
@@ -1019,6 +1021,7 @@
"attendees": "Asistentes",
"add_attendees": "Añadir Asistentes",
"show_advanced_settings": "Mostrar Opciones Avanzadas",
"hide_advanced_settings": "Ocultar ajustes avanzados",
"event_name": "Nombre del Evento",
"event_name_in_calendar": "Nombre del evento en el calendario",
"event_name_tooltip": "Nombre que aparecerá en los calendarios",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Los miembros de su equipo no podrán ver a otros miembros del equipo cuando esto esté activado.",
"you_cannot_see_team_members": "No puede ver a todos los miembros de un equipo privado.",
"you_cannot_see_teams_of_org": "No puedes ver los equipos de una organización privada.",
"allow_booker_to_select_duration": "Permitir que el encargado de las reservas seleccione la duración",
"allow_multiple_durations": "Permitir múltiples duraciones",
"impersonate_user_tip": "Todos los usos de esta función son revisados.",
"impersonating_user_warning": "Suplantando nombre de usuario \"{{user}}\".",
"impersonating_stop_instructions": "Haga clic aquí para detener.",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "¿Está seguro de que desea eliminar la configuración de {{connectionType}}? Los miembros de su equipo que utilicen el inicio de sesión {{connectionType}} ya no podrán acceder a Cal.com.",
"organizer_timezone": "Zona horaria del organizador",
"email_user_cta": "Ver la invitación",
"accept_invite": "Aceptar invitación",
"join_now": "Unirse ahora",
"successfully_joined": "Unido con éxito",
"email_no_user_invite_heading_subteam": "Has sido invitado a unirte a un equipo de la organización {{parentTeamName}}",
"email_no_user_invite_heading_org": "Has sido invitado a unirte a una organización de {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} lo invitó a unirse a su equipo en {{appName}}. {{appName}} es el programador de eventos que les permite a usted y a su equipo programar reuniones sin necesidad de enviar correos electrónicos.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Error al confirmar la reserva",
"not_enough_seats": "No hay suficientes plazas",
"form_builder_field_already_exists": "Ya existe un campo con este nombre",
"guests_field_must_be_multiemail": "El campo de invitados debe ser de tipo 'Múltiples correos electrónicos'",
"show_on_booking_page": "Mostrar en la página de reserva",
"visit_cancelled_booking": "Puedes visitar la página de la reserva cancelada",
"get_started_zapier_templates": "Comience con las plantillas de Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Enlace de invitación copiado",
"invite_link_deleted": "Enlace de invitación eliminado",
"api_key_deleted": "Clave API eliminada",
"delete_api_key_warning": "Esto eliminará permanentemente la clave API. Cualquier aplicación que use esta clave perderá inmediatamente el acceso a tu cuenta. Esta acción no se puede deshacer.",
"invite_link_updated": "Configuración de enlace de invitación guardada",
"link_expires_after": "Enlaces establecidos para expirar después de...",
"one_day": "1 día",
@@ -2688,7 +2696,7 @@
"enterprise": "Empresa",
"enterprise_description": "Actualiza a Enterprise para crear tu Organización",
"create_your_org": "Crea tu Organización",
"create_org": "Crear Organización",
"create_org": "Crear organización",
"create_your_org_description": "Actualiza a Organizaciones y recibe un subdominio, facturación unificada, Insights, personalización extensa y más",
"create_your_enterprise_description": "Actualiza a Enterprise y obtén acceso a la sincronización de Active Directory, aprovisionamiento automático de usuarios SCIM, agentes de voz de Cal.ai, APIs de administración y más",
"other_payment_app_enabled": "Solo puedes habilitar una aplicación de pago por tipo de evento",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Al continuar, aceptas nuestros",
"terms": "Términos",
"always_show_x_days": "Siempre {{x}} días disponibles",
"always_show_x_days_description": "Mueve automáticamente la ventana de reservas de {{x}} días hacia adelante cada día, asegurando que tu calendario siempre muestre los próximos {{x}} días de espacios disponibles.",
"team_subscription_cancelled_successfully": "Suscripción del equipo cancelada con éxito",
"team_subscription_cancellation_error": "Error al cancelar la suscripción del equipo",
"unable_to_subscribe_to_the_platform": "Ocurrió un error al intentar suscribirse al plan de la plataforma, por favor intente de nuevo más tarde",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Termina con",
"filter_operator_is_empty": "Está vacío",
"filter_operator_not_empty": "No está vacío",
"translate_description_button": "Traducir título/descripción al idioma del navegador del visitante usando IA",
"translate_description_button": "Auto traducir título y descripción",
"translate_description_button_description": "Traduce automáticamente títulos y descripciones al idioma del navegador del visitante usando IA.",
"rr_distribution_method": "Distribución",
"rr_distribution_method_description": "Permite optimizar la distribución para maximizar la disponibilidad o lograr una asignación más equilibrada.",
"rr_distribution_method_availability_title": "Maximizar disponibilidad",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "La función de asientos no admite eventos recurrentes. Desactiva la función de asientos para hacerlo recurrente.",
"recurring_event_seats_error": "Los eventos recurrentes no admiten la función de asientos. Desactiva la función de asientos o haz que el evento no sea recurrente.",
"segment": "Segmento",
"saved_filters": "Filtros guardados",
"rename": "Renombrar",
"rename_segment": "Renombrar segmento",
"delete_segment": "Eliminar segmento",
@@ -3338,6 +3349,7 @@
"picklist": "Lista de selección",
"most_cancelled_bookings": "Más cancelaciones",
"interface_language": "Idioma de la interfaz",
"interface_language_description": "Establece tu idioma preferido para la interfaz de reservas",
"visitors_browser_language": "Idioma del navegador del visitante",
"name_or_email": "Nombre o correo electrónico",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Si no se encuentra ningún contacto, recurrir al propietario del lead si existe",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Gestiona roles y permisos para tu organización",
"learn_more_permissions": "Aprende más sobre cómo funcionan los permisos",
"role_name": "Nombre del rol",
"role_name_required": "El nombre del rol es obligatorio",
"role_name_placeholder": "p. ej. Equipo de ventas, Personal de soporte",
"role_description": "Descripción del rol",
"role_description_placeholder": "Describe para qué sirve este rol y qué permisos necesita",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rol creado con éxito",
"role_updated_successfully": "Rol actualizado con éxito",
"delete_role": "Eliminar rol",
"confirm_delete_role": "¿Está seguro de que desea eliminar este rol? Esta acción no se puede deshacer.",
"confirm_delete_role": "¿Estás seguro de que quieres eliminar el rol: {{roleName}}? Esta acción no se puede deshacer.",
"role_deleted_successfully": "Rol eliminado con éxito",
"error_creating_role": "Error al crear el rol",
"error_updating_role": "Error al actualizar el rol",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Editar y modificar ajustes de flujos de trabajo",
"pbac_desc_delete_workflows": "Eliminar flujos de trabajo del sistema",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Disponibilidad",
"pbac_resource_out_of_office": "Fuera de la oficina",
"pbac_desc_create_webhooks": "Crear webhooks",
"pbac_desc_view_webhooks": "Ver webhooks",
"pbac_desc_update_webhooks": "Actualizar webhooks",
"pbac_desc_delete_webhooks": "Eliminar webhooks",
"pbac_desc_create_availability": "Crear disponibilidad",
"pbac_desc_view_availability": "Ver disponibilidad",
"pbac_desc_update_availability": "Actualizar disponibilidad",
"pbac_desc_delete_availability": "Eliminar disponibilidad",
"pbac_desc_create_out_of_office": "Crear fuera de la oficina",
"pbac_desc_view_out_of_office": "Ver fuera de la oficina",
"pbac_desc_update_out_of_office": "Actualizar fuera de la oficina",
"pbac_desc_delete_out_of_office": "Eliminar fuera de la oficina",
"pbac_desc_manage_workflows": "Acceso completo de gestión a todos los flujos de trabajo",
"pbac_desc_create_event_types": "Crear tipos de eventos",
"pbac_desc_view_event_types": "Ver tipos de eventos",
@@ -3732,4 +3755,4 @@
"use_voice": "Usar voz",
"current_voice": "Voz actual",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agregue sus nuevas cadenas arriba ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Saadaolevaid kasutajaid ei leitud. Kas saaksite proovida teist ajavahemikku?",
"timeslot_unavailable_book_a_new_time": "Valitud ajavahemik pole enam saadaval. <0>Palun vali uus aeg</0>",
"timeslot_unavailable_short": "Võetud",
"just_connected_description": "Olete just ühenduse loonud. Palun broneerige ülaltoodud ajavahemikest või proovige hiljem uuesti.",
"unavailable_timeslot_title": "Mittesaadaval ajaperiood",
"booking_time_out_of_bounds_error": "Sündmust ei saa sellel ajal broneerida. Kas proovid mõnda teist aega?",
"request_body_end_time_internal_error": "Sisemine viga. Taotluse sisu ei sisalda lõpuaega",
@@ -321,6 +322,7 @@
"secret_meeting": "Salajane koosolek",
"login_instead": "Selle asemel logi sisse",
"create_account": "Loo konto",
"get_started": "Alusta",
"confirm_password": "Kinnita salasõna",
"reset_your_password": "Seadke oma uus parool teie e-posti aadressile saadetud juhiste järgi.",
"org_banner_instructions": "Palun laadige üles pilt laiusega {{width}} ja kõrgusega {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "Osalejad",
"add_attendees": "Lisa osalejaid",
"show_advanced_settings": "Kuva täpsemad seaded",
"hide_advanced_settings": "Peida täpsemad seaded",
"event_name": "Sündmuse nimi",
"event_name_in_calendar": "Sündmuse nimega",
"event_name_tooltip": "Kalendrites kuvatav nimi",
@@ -1231,7 +1234,7 @@
"contact_sales": "Võtke ühendust müügiga",
"error_404": "Viga 404",
"default": "Vaikimisi",
"set_to_default": "Set to Default",
"set_to_default": "Määra vaikimisi",
"new_schedule_btn": "Uus ajakava",
"add_new_schedule": "Lisa uus ajakava",
"add_new_calendar": "Lisa uus kalender",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Teie meeskonnaliikmed ei näe teisi meeskonnaliikmeid, kui see on sisse lülitatud.",
"you_cannot_see_team_members": "Te ei saa näha erameeskonna kõiki meeskonnaliikmeid.",
"you_cannot_see_teams_of_org": "Te ei saa näha eraorganisatsiooni meeskondi.",
"allow_booker_to_select_duration": "Luba broneerijal valida kestus",
"allow_multiple_durations": "Luba mitu kestust",
"impersonate_user_tip": "Kõik selle funktsiooni kasutused on auditeeritud.",
"impersonating_user_warning": "Esitan end kasutajanimena \"{{user}}\".",
"impersonating_stop_instructions": "Peatamiseks klõpsake siin",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Kas soovite kindlasti konfiguratsiooni {{connectionType}} kustutada? Teie meeskonnaliikmed, kes kasutavad {{connectionType}} sisselogimist, ei pääse enam saidile Cal.com juurde.",
"organizer_timezone": "Korraldaja ajavöönd",
"email_user_cta": "Vaata kutset",
"accept_invite": "Võta kutse vastu",
"join_now": "Liitu kohe",
"successfully_joined": "Liitumine õnnestus",
"email_no_user_invite_heading_subteam": "Teid on kutsutud liituma {{parentTeamName}} organisatsiooni meeskonnaga.",
"email_no_user_invite_heading_org": "Teid on kutsutud liituma {{appName}} organisatsiooniga.",
"email_no_user_invite_subheading": "{{invitedBy}} kutsus teid liituma oma meeskonnaga rakenduses {{appName}}. {{appName}} on sündmustega žongleerimise ajakava, mis võimaldab teil ja teie meeskonnal koosolekuid ajastada ilma meilitenniseta.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Broneeringu kinnitamine ebaõnnestus",
"not_enough_seats": "Pole piisavalt kohti",
"form_builder_field_already_exists": "Selle nimega väli on juba olemas",
"guests_field_must_be_multiemail": "Külaliste väli peab olema tüüpi 'Mitu e-posti'",
"show_on_booking_page": "Näita broneerimislehel",
"visit_cancelled_booking": "Saate külastada tühistatud broneeringu lehte",
"get_started_zapier_templates": "Alustage Zapieri mallidega",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Kutse link kopeeritud",
"invite_link_deleted": "Kutse link kustutatud",
"api_key_deleted": "API võti kustutatud",
"delete_api_key_warning": "See kustutab API-võtme jäädavalt. Kõik rakendused, mis seda võtit kasutavad, kaotavad kohe juurdepääsu teie kontole. Seda toimingut ei saa tagasi võtta.",
"invite_link_updated": "Kutse lingi seaded on salvestatud",
"link_expires_after": "Lingid aeguma pärast...",
"one_day": "1 päev",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Jätkates nõustute meie",
"terms": "Tingimustega",
"always_show_x_days": "Alati {{x}} päeva saadaval",
"always_show_x_days_description": "Liigutab automaatselt {{x}}-päevase broneerimisakna iga päev edasi, tagades, et teie kalender näitab alati järgmise {{x}} päeva saadavalolevaid aegu.",
"team_subscription_cancelled_successfully": "Meeskonna tellimus tühistati edukalt",
"team_subscription_cancellation_error": "Viga meeskonna tellimuse tühistamisel",
"unable_to_subscribe_to_the_platform": "Platvormipaketi tellimisel ilmnes viga, proovige hiljem uuesti",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Lõpeb",
"filter_operator_is_empty": "On tühi",
"filter_operator_not_empty": "Ei ole tühi",
"translate_description_button": "Tõlgi pealkiri/kirjeldus tehisintellekti abil külastaja brauseri keelde",
"translate_description_button": "Tõlgi pealkiri ja kirjeldus automaatselt",
"translate_description_button_description": "Tõlgib pealkirjad ja kirjeldused automaatselt külastaja brauseri keelde, kasutades tehisintellekti.",
"rr_distribution_method": "Jaotamine",
"rr_distribution_method_description": "Võimaldab optimeerida jaotust maksimaalseks kättesaadavuseks või tasakaalustatumaks määramiseks.",
"rr_distribution_method_availability_title": "Maksimeeri kättesaadavus",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Kohtade funktsioon ei toeta korduvat sündmust. Keelake kohtade funktsioon, et muuta see korduvaks.",
"recurring_event_seats_error": "Korduv sündmus ei toeta kohtade funktsiooni. Keelake kohtade funktsioon või muutke sündmus mittekorduvaks.",
"segment": "Segment",
"saved_filters": "Salvestatud filtrid",
"rename": "Nimeta ümber",
"rename_segment": "Nimeta segment ümber",
"delete_segment": "Kustuta segment",
@@ -3338,6 +3349,7 @@
"picklist": "Valikloend",
"most_cancelled_bookings": "Kõige rohkem tühistatud broneeringuid",
"interface_language": "Liidese keel",
"interface_language_description": "Määra broneerimisliidese eelistatud keel",
"visitors_browser_language": "Külastaja brauseri keel",
"name_or_email": "Nimi või e-post",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Kui kontakti ei leita, kasuta võimalusel liidi omanikku",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Halda oma organisatsiooni rolle ja õigusi",
"learn_more_permissions": "Lisateave õiguste toimimise kohta",
"role_name": "Rolli nimi",
"role_name_required": "Rolli nimi on kohustuslik",
"role_name_placeholder": "nt Müügimeeskond, Tugipersonal",
"role_description": "Rolli kirjeldus",
"role_description_placeholder": "Kirjeldage, milleks see roll on ja milliseid õigusi see vajab",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Roll loodud edukalt",
"role_updated_successfully": "Roll uuendatud edukalt",
"delete_role": "Kustuta roll",
"confirm_delete_role": "Kas olete kindel, et soovite selle rolli kustutada? Seda toimingut ei saa tagasi võtta.",
"confirm_delete_role": "Kas olete kindel, et soovite rolli kustutada: {{roleName}}? Seda toimingut ei saa tagasi võtta.",
"role_deleted_successfully": "Roll kustutatud edukalt",
"error_creating_role": "Viga rolli loomisel",
"error_updating_role": "Viga rolli uuendamisel",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Muuda ja redigeeri töövoo seadeid",
"pbac_desc_delete_workflows": "Eemalda töövood süsteemist",
"pbac_resource_webhook": "Veebihaak",
"pbac_resource_availability": "Saadavus",
"pbac_resource_out_of_office": "Kontorist väljas",
"pbac_desc_create_webhooks": "Loo veebihaake",
"pbac_desc_view_webhooks": "Vaata veebihaake",
"pbac_desc_update_webhooks": "Uuenda veebihaake",
"pbac_desc_delete_webhooks": "Kustuta veebihaake",
"pbac_desc_create_availability": "Loo saadavus",
"pbac_desc_view_availability": "Vaata saadavust",
"pbac_desc_update_availability": "Uuenda saadavust",
"pbac_desc_delete_availability": "Kustuta saadavus",
"pbac_desc_create_out_of_office": "Loo kontorist väljas olek",
"pbac_desc_view_out_of_office": "Vaata kontorist väljas olekut",
"pbac_desc_update_out_of_office": "Uuenda kontorist väljas olekut",
"pbac_desc_delete_out_of_office": "Kustuta kontorist väljas olek",
"pbac_desc_manage_workflows": "Täielik haldusõigus kõikidele töövoogudele",
"pbac_desc_create_event_types": "Loo sündmuse tüüpe",
"pbac_desc_view_event_types": "Vaata sündmuse tüüpe",
@@ -3560,7 +3583,7 @@
"rr_load_balancing_disabled_with_groups": "Koormuse tasakaalustamine ei ole saadaval Round Robin gruppidega",
"round_robin_settings_description": "Kohanda selle meeskonna vaikimisi round robin'i seadeid",
"round_robin_settings_updated_successfully": "Round robin'i seaded on edukalt uuendatud",
"booking_creation_time": "Broneeringu loomise aeg",
"booking_creation_time": "Broneerimise loomise aeg",
"meeting_start_time": "Kohtumise algusaeg",
"members_affected_by_disabling_delegation_credential": "Mõjutatud liikmed",
"no_members_affected_by_disabling_delegation_credential": "Delegatsiooni mandaadi keelamine ei mõjuta ühtegi liiget",
@@ -3732,4 +3755,4 @@
"use_voice": "Kasuta häält",
"current_voice": "Praegune hääl",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+30 -7
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Ez da erabiltzaile eskuragarririk aurkitu. Beste denbora tarte bat probatu nahi duzu?",
"timeslot_unavailable_book_a_new_time": "Hautatutako denbora-tartea ez dago jada eskuragarri. <0>Mesedez, hautatu beste ordu bat</0>",
"timeslot_unavailable_short": "Hartuta",
"just_connected_description": "Konektatu berri zara. Mesedez, erreserbatu goiko ordutegietatik edo saiatu berriro geroago.",
"unavailable_timeslot_title": "Denbora-tarte ez erabilgarria",
"booking_time_out_of_bounds_error": "Ezin da gertaera mota hau momentu honetan erreserbatu. Beste denbora-tarte bat probatuko zenuke?",
"request_body_end_time_internal_error": "Barne errorea. Eskaeraren gorputzak ez du amaiera ordua",
@@ -321,6 +322,7 @@
"secret_meeting": "Bilera sekretua",
"login_instead": "Saioa hasi ordez",
"create_account": "Sortu kontua",
"get_started": "Hasi",
"confirm_password": "Baieztatu pasahitza",
"reset_your_password": "Ezarri zure pasahitz berria zure email helbidera bidalitako argibideak jarraituz.",
"org_banner_instructions": "Mesedez, igo {{width}} zabalera eta {{height}} altuera duen irudia.",
@@ -1019,6 +1021,7 @@
"attendees": "Partaideak",
"add_attendees": "Gehitu partaideak",
"show_advanced_settings": "Erakutsi ezarpen aurreratuak",
"hide_advanced_settings": "Ezkutatu ezarpen aurreratuak",
"event_name": "Gertaeraren izena",
"event_name_in_calendar": "Gertaeraren izenarekin",
"event_name_tooltip": "Egutegian agertuko den izena",
@@ -1231,7 +1234,7 @@
"contact_sales": "Jarri harremanetan salmenta taldearekin",
"error_404": "404 errorea",
"default": "Lehenetsitakoa",
"set_to_default": "Ezarri lehenetsitako gisa",
"set_to_default": "Ezarri lehenetsi gisa",
"new_schedule_btn": "Programazio berria",
"add_new_schedule": "Gehitu programazio berria",
"add_new_calendar": "Gehitu egutegi berria",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Hau aktibatuta dagoenean, zure taldeko kideek ezin izango dituzte beste taldekideak ikusi.",
"you_cannot_see_team_members": "Ezin dituzu talde pribatu bateko kide guztiak ikusi.",
"you_cannot_see_teams_of_org": "Ezin dituzu erakunde pribatu bateko taldeak ikusi.",
"allow_booker_to_select_duration": "Erreserbatzaileari iraupena aukeratzen utzi",
"allow_multiple_durations": "Baimendu iraupen anitzak",
"impersonate_user_tip": "Ezaugarri honen erabilera guztiak auditatzen dira.",
"impersonating_user_warning": "\"{{user}}\" erabiltzailea ordezkatzen.",
"impersonating_stop_instructions": "Klikatu hemen gelditzeko",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Ziur zaude {{connectionType}} konfigurazioa ezabatu nahi duzula? {{connectionType}} saioa hasten duten zure taldekideek ezingo dute Cal.com erabili.",
"organizer_timezone": "Antolatzailearen ordu-zona",
"email_user_cta": "Ikusi gonbidapena",
"accept_invite": "Onartu gonbidapena",
"join_now": "Batu orain",
"successfully_joined": "Arrakastaz batu zara",
"email_no_user_invite_heading_subteam": "{{parentTeamName}} erakundeko talde batera batzeko gonbidapena jaso duzu",
"email_no_user_invite_heading_org": "{{appName}} erakunde batera batzeko gonbidapena jaso duzu",
"email_no_user_invite_subheading": "{{invitedBy}}-k bere taldera gonbidatu zaitu {{appName}}-en. {{appName}} bileren antolaketa errazten duen tresna da, zure taldeak bilerak antolatzeko aukera ematen duena posta elektroniko truke etengabea ekidinez.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Erreserba berrespena huts egin du",
"not_enough_seats": "Ez dago nahikoa eserleku",
"form_builder_field_already_exists": "Izen hori duen eremu bat existitzen da dagoeneko",
"guests_field_must_be_multiemail": "Gonbidatuen eremuak 'Helbide elektroniko anitzak' motakoa izan behar du",
"show_on_booking_page": "Erakutsi erreserba orrian",
"visit_cancelled_booking": "Bertan behera utzitako erreserba orria bisitatu dezakezu",
"get_started_zapier_templates": "Hasi Zapier txantiloiekin",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Gonbidapen-esteka kopiatuta",
"invite_link_deleted": "Gonbidapen-esteka ezabatuta",
"api_key_deleted": "API gakoa ezabatuta",
"delete_api_key_warning": "Honek API gakoa betiko ezabatuko du. Gako hau erabiltzen duten aplikazio guztiek zure konturako sarbidea galduko dute berehala. Ekintza hau ezin da desegin.",
"invite_link_updated": "Gonbidapen-estekaren ezarpenak gordeta",
"link_expires_after": "Estekak iraungitzeko denbora...",
"one_day": "Egun 1",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Jarraituz gero, gure honekin ados zaudela adierazten duzu",
"terms": "Baldintzak",
"always_show_x_days": "Beti {{x}} egun erabilgarri",
"always_show_x_days_description": "Automatikoki mugitzen du {{x}} eguneko erreserba-leihoa egunero, zure egutegiak beti hurrengo {{x}} egunetako txanda erabilgarriak erakusten dituela ziurtatuz.",
"team_subscription_cancelled_successfully": "Taldearen harpidetza behar bezala ezeztatu da",
"team_subscription_cancellation_error": "Errorea gertatu da taldearen harpidetza ezeztatzean",
"unable_to_subscribe_to_the_platform": "Errore bat gertatu da plataformaren planera harpidetzen saiatzean, saiatu berriro geroago",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Honekin amaitzen da",
"filter_operator_is_empty": "Hutsik dago",
"filter_operator_not_empty": "Ez dago hutsik",
"translate_description_button": "Bisitariaren nabigatzailearen hizkuntzara itzuli titulua/deskribapena AI erabiliz",
"translate_description_button": "Auto itzuli izenburua eta deskribapena",
"translate_description_button_description": "Automatikoki itzuli izenburuak eta deskribapenak bisitariaren nabigatzailearen hizkuntzara adimen artifizialaren bidez.",
"rr_distribution_method": "Banaketa",
"rr_distribution_method_description": "Banaketa optimizatzeko aukera ematen du, erabilgarritasun maximoa edo esleipen orekatuagoa lortzeko",
"rr_distribution_method_availability_title": "Erabilgarritasuna maximizatu",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Eserlekuen ezaugarriak ez du ekitaldi errepikakorrik onartzen. Desgaitu eserlekuen ezaugarria errepikakor egiteko.",
"recurring_event_seats_error": "Ekitaldi errepikakorrak ez du eserlekuen ezaugarria onartzen. Desgaitu eserlekuen ezaugarria edo egin ekitaldia ez-errepikakor.",
"segment": "Segmentua",
"saved_filters": "Gordetako iragazkiak",
"rename": "Berrizendatu",
"rename_segment": "Berrizendatu segmentua",
"delete_segment": "Ezabatu segmentua",
@@ -3338,6 +3349,7 @@
"picklist": "Hautaketa-zerrenda",
"most_cancelled_bookings": "Bertan behera utzitako erreserba gehien",
"interface_language": "Interfazearen hizkuntza",
"interface_language_description": "Ezarri zure hizkuntza hobetsia erreserba interfazerako",
"visitors_browser_language": "Bisitariaren nabigatzailearen hizkuntza",
"name_or_email": "Izena edo helbide elektronikoa",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Kontakturik aurkitzen ez bada, itzuli bezeroen arduradunera existitzen bada",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Kudeatu zure erakundearen rolak eta baimenak",
"learn_more_permissions": "Ikasi gehiago baimenek nola funtzionatzen duten",
"role_name": "Rolaren izena",
"role_name_required": "Rolaren izena beharrezkoa da",
"role_name_placeholder": "adib. Salmenta taldea, Laguntza langileak",
"role_description": "Rolaren deskribapena",
"role_description_placeholder": "Deskribatu rol hau zertarako den eta zer baimen behar dituen",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rola arrakastaz sortu da",
"role_updated_successfully": "Rola arrakastaz eguneratu da",
"delete_role": "Ezabatu rola",
"confirm_delete_role": "Ziur zaude rol hau ezabatu nahi duzula? Ekintza hau ezin da desegin.",
"confirm_delete_role": "Ziur zaude {{roleName}} rola ezabatu nahi duzula? Ekintza hau ezin da desegin.",
"role_deleted_successfully": "Rola arrakastaz ezabatu da",
"error_creating_role": "Errorea rola sortzerakoan",
"error_updating_role": "Errorea rola eguneratzerakoan",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Editatu eta aldatu lan-fluxuen ezarpenak",
"pbac_desc_delete_workflows": "Kendu lan-fluxuak sistematik",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Erabilgarritasuna",
"pbac_resource_out_of_office": "Bulegotik kanpo",
"pbac_desc_create_webhooks": "Sortu webhook-ak",
"pbac_desc_view_webhooks": "Ikusi webhook-ak",
"pbac_desc_update_webhooks": "Webhookak eguneratu",
"pbac_desc_delete_webhooks": "Webhookak ezabatu",
"pbac_desc_create_availability": "Sortu erabilgarritasuna",
"pbac_desc_view_availability": "Ikusi erabilgarritasuna",
"pbac_desc_update_availability": "Eguneratu erabilgarritasuna",
"pbac_desc_delete_availability": "Ezabatu erabilgarritasuna",
"pbac_desc_create_out_of_office": "Sortu bulegotik kanpo",
"pbac_desc_view_out_of_office": "Ikusi bulegotik kanpo",
"pbac_desc_update_out_of_office": "Eguneratu bulegotik kanpo",
"pbac_desc_delete_out_of_office": "Ezabatu bulegotik kanpo",
"pbac_desc_manage_workflows": "Lan-fluxu guztien kudeaketa-sarbide osoa",
"pbac_desc_create_event_types": "Gertaera motak sortu",
"pbac_desc_view_event_types": "Gertaera motak ikusi",
@@ -3560,8 +3583,8 @@
"rr_load_balancing_disabled_with_groups": "Karga-orekatzea ez dago eskuragarri Round Robin taldeekin",
"round_robin_settings_description": "Pertsonalizatu talde honentzako txandakako ezarpen lehenetsiak",
"round_robin_settings_updated_successfully": "Txandakako ezarpenak ondo eguneratu dira",
"booking_creation_time": "Erreserba sortze-ordua",
"meeting_start_time": "Bilera hasiera-ordua",
"booking_creation_time": "Erreserba sortzeko ordua",
"meeting_start_time": "Bilera hasteko ordua",
"members_affected_by_disabling_delegation_credential": "Eragindako kideak",
"no_members_affected_by_disabling_delegation_credential": "Ez dago kiderik delegazio kredentzialak desgaitzeak eraginda",
"download_expense_log": "Deskargatu gastuen erregistroa",
@@ -3732,4 +3755,4 @@
"use_voice": "Erabili ahotsa",
"current_voice": "Uneko ahotsa",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Gehitu zure kate berriak honen gainean ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+27 -4
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Vapaita käyttäjiä ei löytynyt. Kokeilitko toista ajankohtaa?",
"timeslot_unavailable_book_a_new_time": "Valittu aikaikkunaa ei ole enää saatavilla. <0>Valitse uusi aika</0>",
"timeslot_unavailable_short": "Varattu",
"just_connected_description": "Olet juuri yhdistänyt. Varaa aika yllä olevista ajoista tai yritä myöhemmin uudelleen.",
"unavailable_timeslot_title": "Aika ei ole saatavilla",
"booking_time_out_of_bounds_error": "Tapahtumaa ei voi varata tähän aikaan. Kokeilitko toista ajankohtaa?",
"request_body_end_time_internal_error": "Sisäinen virhe. Pyynnön rungosta puuttuu lopetusaika",
@@ -321,6 +322,7 @@
"secret_meeting": "Salainen tapaaminen",
"login_instead": "Kirjaudu sen sijaan",
"create_account": "Luo tili",
"get_started": "Aloita",
"confirm_password": "Vahvista salasana",
"reset_your_password": "Aseta uusi salasanasi sähköpostiisi lähetettyjen ohjeiden mukaisesti.",
"org_banner_instructions": "Lataa kuva, jonka leveys on {{width}} ja korkeus {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "Osallistujat",
"add_attendees": "Lisää osallistujia",
"show_advanced_settings": "Näytä lisäasetukset",
"hide_advanced_settings": "Piilota lisäasetukset",
"event_name": "Tapahtuman nimi",
"event_name_in_calendar": "Tapahtuman nimellä",
"event_name_tooltip": "Nimi, joka näkyy kalentereissa",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Tiimisi jäsenet eivät näe muita tiimin jäseniä, kun tämä on käytössä.",
"you_cannot_see_team_members": "Et voi nähdä yksityisen tiimin kaikkia jäseniä.",
"you_cannot_see_teams_of_org": "Et voi nähdä yksityisen organisaation tiimejä.",
"allow_booker_to_select_duration": "Salli varaajan valita kesto",
"allow_multiple_durations": "Salli useita kestoja",
"impersonate_user_tip": "Kaikki tämän toiminnon käyttö kirjataan.",
"impersonating_user_warning": "Esiinnyt käyttäjänä \"{{user}}\".",
"impersonating_stop_instructions": "Klikkaa tästä lopettaaksesi",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Haluatko varmasti poistaa {{connectionType}}-määritykset? Tiimisi jäsenet, jotka käyttävät {{connectionType}}-kirjautumista, eivät enää pääse käyttämään Cal.comia.",
"organizer_timezone": "Järjestäjän aikavyöhyke",
"email_user_cta": "Näytä kutsu",
"accept_invite": "Hyväksy kutsu",
"join_now": "Liity nyt",
"successfully_joined": "Liittyminen onnistui",
"email_no_user_invite_heading_subteam": "Sinut on kutsuttu liittymään {{parentTeamName}}-organisaation tiimiin",
"email_no_user_invite_heading_org": "Sinut on kutsuttu liittymään {{appName}}-organisaatioon",
"email_no_user_invite_subheading": "{{invitedBy}} on kutsunut sinut liittymään tiimiinsä palvelussa {{appName}}. {{appName}} on tapaamisenhallintakalenteri, jonka avulla sinä ja tiimisi voitte sopia tapaamisia ilman loputtomia sähköposteja.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Varauksen vahvistaminen epäonnistui",
"not_enough_seats": "Ei tarpeeksi paikkoja",
"form_builder_field_already_exists": "Tämänniminen kenttä on jo olemassa",
"guests_field_must_be_multiemail": "Vieraiden kentän täytyy olla tyyppiä 'Useita sähköposteja'",
"show_on_booking_page": "Näytä varaussivulla",
"visit_cancelled_booking": "Voit tarkastella peruutettua varausta",
"get_started_zapier_templates": "Aloita Zapier-malleilla",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Kutsulinkki kopioitu",
"invite_link_deleted": "Kutsulinkki poistettu",
"api_key_deleted": "API-avain poistettu",
"delete_api_key_warning": "Tämä poistaa API-avaimen pysyvästi. Kaikki sovellukset, jotka käyttävät tätä avainta, menettävät välittömästi pääsyn tilillesi. Tätä toimintoa ei voi peruuttaa.",
"invite_link_updated": "Linkin asetukset tallennettu",
"link_expires_after": "Linkit vanhenevat...",
"one_day": "1 päivä",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Jatkamalla hyväksyt meidän",
"terms": "Käyttöehdot",
"always_show_x_days": "Aina {{x}} päivää saatavilla",
"always_show_x_days_description": "Siirtää automaattisesti {{x}} päivän varausikkunaa eteenpäin joka päivä, varmistaen että kalenterisi näyttää aina seuraavat {{x}} päivää vapaista ajoista.",
"team_subscription_cancelled_successfully": "Tiimin tilaus peruutettu onnistuneesti",
"team_subscription_cancellation_error": "Virhe tiimin tilauksen peruuttamisessa",
"unable_to_subscribe_to_the_platform": "Tilausta alustaan ei voitu tehdä, yritä myöhemmin uudelleen",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Päättyy",
"filter_operator_is_empty": "On tyhjä",
"filter_operator_not_empty": "Ei tyhjä",
"translate_description_button": "Käännä otsikko/kuvaus kävijän selaimen kielelle tekoälyn avulla",
"translate_description_button": "Käännä otsikko ja kuvaus automaattisesti",
"translate_description_button_description": "Käännä otsikot ja kuvaukset automaattisesti vierailijan selaimen kielelle tekoälyn avulla.",
"rr_distribution_method": "Jakotapa",
"rr_distribution_method_description": "Mahdollistaa jakelun optimoinnin joko maksimaalisen saatavuuden tai tasapainoisen jaon saavuttamiseksi.",
"rr_distribution_method_availability_title": "Maksimoi saatavuus",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Paikkaominaisuus ei tue toistuvaa tapahtumaa. Poista paikkaominaisuus käytöstä tehdäksesi siitä toistuvan.",
"recurring_event_seats_error": "Toistuva tapahtuma ei tue paikkaominaisuutta. Poista paikkaominaisuus käytöstä tai tee tapahtumasta ei-toistuva.",
"segment": "Segmentti",
"saved_filters": "Tallennetut suodattimet",
"rename": "Nimeä uudelleen",
"rename_segment": "Nimeä segmentti uudelleen",
"delete_segment": "Poista segmentti",
@@ -3338,6 +3349,7 @@
"picklist": "Valintalistaus",
"most_cancelled_bookings": "Eniten peruutettuja varauksia",
"interface_language": "Käyttöliittymän kieli",
"interface_language_description": "Aseta varausliittymän haluamasi kieli",
"visitors_browser_language": "Vierailijan selaimen kieli",
"name_or_email": "Nimi tai sähköposti",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Jos yhteyshenkilöä ei löydy, käytä liidin omistajaa, jos sellainen on olemassa",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Hallitse organisaatiosi rooleja ja käyttöoikeuksia",
"learn_more_permissions": "Lue lisää käyttöoikeuksien toiminnasta",
"role_name": "Roolin nimi",
"role_name_required": "Roolin nimi vaaditaan",
"role_name_placeholder": "esim. Myyntitiimi, Tukihenkilöstö",
"role_description": "Roolin kuvaus",
"role_description_placeholder": "Kuvaile mihin tätä roolia käytetään ja mitä käyttöoikeuksia se tarvitsee",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rooli luotu onnistuneesti",
"role_updated_successfully": "Rooli päivitetty onnistuneesti",
"delete_role": "Poista rooli",
"confirm_delete_role": "Haluatko varmasti poistaa tämän roolin? Tätä toimintoa ei voi kumota.",
"confirm_delete_role": "Haluatko varmasti poistaa roolin: {{roleName}}. Tätä toimintoa ei voi kumota.",
"role_deleted_successfully": "Rooli poistettu onnistuneesti",
"error_creating_role": "Virhe roolin luomisessa",
"error_updating_role": "Virhe roolin päivittämisessä",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Muokkaa työnkulkujen asetuksia",
"pbac_desc_delete_workflows": "Poista työnkulkuja järjestelmästä",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Saatavuus",
"pbac_resource_out_of_office": "Poissa toimistolta",
"pbac_desc_create_webhooks": "Luo webhookeja",
"pbac_desc_view_webhooks": "Tarkastele webhookeja",
"pbac_desc_update_webhooks": "Päivitä webhookit",
"pbac_desc_delete_webhooks": "Poista webhookit",
"pbac_desc_create_availability": "Luo saatavuus",
"pbac_desc_view_availability": "Näytä saatavuus",
"pbac_desc_update_availability": "Päivitä saatavuus",
"pbac_desc_delete_availability": "Poista saatavuus",
"pbac_desc_create_out_of_office": "Luo poissaolo",
"pbac_desc_view_out_of_office": "Näytä poissaolo",
"pbac_desc_update_out_of_office": "Päivitä poissaolo",
"pbac_desc_delete_out_of_office": "Poista poissaolo",
"pbac_desc_manage_workflows": "Täydet hallintaoikeudet kaikkiin työnkulkuihin",
"pbac_desc_create_event_types": "Luo tapahtumatyyppejä",
"pbac_desc_view_event_types": "Näytä tapahtumatyypit",
@@ -3732,4 +3755,4 @@
"use_voice": "Käytä ääntä",
"current_voice": "Nykyinen ääni",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Lisää uudet merkkijonot tämän yläpuolelle ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+26 -3
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Aucun utilisateur disponible n'a été trouvé. Pourriez-vous essayer un autre créneau horaire ?",
"timeslot_unavailable_book_a_new_time": "Le créneau horaire sélectionné n'est plus disponible. <0>Veuillez sélectionner un nouvel horaire</0>",
"timeslot_unavailable_short": "Pris",
"just_connected_description": "Vous venez de vous connecter. Veuillez réserver parmi les créneaux ci-dessus ou réessayer plus tard.",
"unavailable_timeslot_title": "Créneau indisponible",
"booking_time_out_of_bounds_error": "Le type d'événement ne peut pas être réservé à ce moment. Pourriez-vous essayer un autre créneau horaire ?",
"request_body_end_time_internal_error": "Erreur interne. Le corps de la demande ne contient pas l'heure de fin",
@@ -321,6 +322,7 @@
"secret_meeting": "Rendez-vous secret",
"login_instead": "Se connecter à la place",
"create_account": "Créer un compte",
"get_started": "Commencer",
"confirm_password": "Confirmer le mot de passe",
"reset_your_password": "Définissez votre nouveau mot de passe avec les instructions envoyées à votre adresse e-mail.",
"org_banner_instructions": "Veuillez télécharger une image d'une largeur de {{width}} et d'une hauteur de {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "Participants",
"add_attendees": "Ajouter des participants",
"show_advanced_settings": "Afficher les paramètres avancés",
"hide_advanced_settings": "Masquer les paramètres avancés",
"event_name": "Nom de l'événement",
"event_name_in_calendar": "Nom de l'événement dans le calendrier",
"event_name_tooltip": "Le nom qui apparaîtra dans les calendriers",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Les membres de votre équipe ne pourront pas voir les autres membres de l'équipe si cette option est activée.",
"you_cannot_see_team_members": "Vous ne pouvez pas voir tous les membres d'une équipe privée.",
"you_cannot_see_teams_of_org": "Vous ne pouvez pas voir les équipes d'une organisation privée.",
"allow_booker_to_select_duration": "Autoriser l'utilisateur à sélectionner la durée",
"allow_multiple_durations": "Autoriser plusieurs durées",
"impersonate_user_tip": "Toutes les utilisations de cette fonctionnalité sont vérifiées.",
"impersonating_user_warning": "Emprunt du nom d'utilisateur « {{user}} ».",
"impersonating_stop_instructions": "Cliquez ici pour arrêter",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Voulez-vous vraiment supprimer la configuration {{connectionType}} ? Les membres de votre équipe utilisant la connexion {{connectionType}} ne pourront plus accéder à Cal.com.",
"organizer_timezone": "Fuseau horaire de l'organisateur",
"email_user_cta": "Voir l'invitation",
"accept_invite": "Accepter l'invitation",
"join_now": "Rejoindre maintenant",
"successfully_joined": "Rejoint avec succès",
"email_no_user_invite_heading_subteam": "Vous avez été invité à rejoindre une équipe de l'organisation {{parentTeamName}}",
"email_no_user_invite_heading_org": "Vous avez été invité à rejoindre une organisation {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} vous a invité à rejoindre son équipe sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Échec de la confirmation de la réservation",
"not_enough_seats": "Pas assez de places",
"form_builder_field_already_exists": "Un champ avec ce nom existe déjà",
"guests_field_must_be_multiemail": "Le champ invités doit être de type 'Emails multiples'",
"show_on_booking_page": "Afficher sur la page de réservation",
"visit_cancelled_booking": "Vous pouvez consulter la page de réservation annulée",
"get_started_zapier_templates": "Démarrer avec les modèles Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Lien d'invitation copié",
"invite_link_deleted": "Lien d'invitation supprimé",
"api_key_deleted": "Clé API supprimée",
"delete_api_key_warning": "Cela supprimera définitivement la clé API. Toutes les applications utilisant cette clé perdront immédiatement l'accès à votre compte. Cette action ne peut pas être annulée.",
"invite_link_updated": "Paramètres de lien d'invitation enregistrés",
"link_expires_after": "Les liens ont été définis pour expirer après...",
"one_day": "1 jour",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "En continuant, vous acceptez nos",
"terms": "Conditions",
"always_show_x_days": "Tout disponible pendant {{x}} jours",
"always_show_x_days_description": "Déplace automatiquement la fenêtre de réservation de {{x}} jours chaque jour, garantissant que votre calendrier affiche toujours les {{x}} prochains jours de créneaux disponibles.",
"team_subscription_cancelled_successfully": "Abonnement d'équipe annulé avec succès",
"team_subscription_cancellation_error": "Erreur lors de l'annulation de l'abonnement d'équipe",
"unable_to_subscribe_to_the_platform": "Une erreur est survenue lors de la souscription à l'offre de plateforme. Veuillez réessayer plus tard",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Se termine par",
"filter_operator_is_empty": "Est vide",
"filter_operator_not_empty": "N'est pas vide",
"translate_description_button": "Traduire le titre/la description dans la langue du navigateur du visiteur grâce à l'IA",
"translate_description_button": "Traduction automatique du titre et de la description",
"translate_description_button_description": "Traduit automatiquement les titres et descriptions dans la langue du navigateur du visiteur en utilisant l'IA.",
"rr_distribution_method": "Distribution",
"rr_distribution_method_description": "Permet d'optimiser la distribution pour une disponibilité maximale ou pour viser une répartition plus équilibrée.",
"rr_distribution_method_availability_title": "Maximiser la disponibilité",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "La fonctionnalité de places ne prend pas en charge les événements récurrents. Désactivez la fonctionnalité de places pour le rendre récurrent.",
"recurring_event_seats_error": "Les événements récurrents ne prennent pas en charge la fonctionnalité de places. Désactivez la fonctionnalité de places ou rendez l'événement non récurrent.",
"segment": "Segment",
"saved_filters": "Filtres enregistrés",
"rename": "Renommer",
"rename_segment": "Renommer le segment",
"delete_segment": "Supprimer le segment",
@@ -3338,6 +3349,7 @@
"picklist": "Liste de sélection",
"most_cancelled_bookings": "Plus d'annulations",
"interface_language": "Langue de l'interface",
"interface_language_description": "Définissez votre langue préférée pour l'interface de réservation",
"visitors_browser_language": "Langue du navigateur du visiteur",
"name_or_email": "Nom ou e-mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Si aucun contact n'est trouvé, se rabattre sur le propriétaire du lead s'il existe",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Gérer les rôles et les permissions pour votre organisation",
"learn_more_permissions": "En savoir plus sur le fonctionnement des permissions",
"role_name": "Nom du rôle",
"role_name_required": "Le nom du rôle est requis",
"role_name_placeholder": "ex. Équipe commerciale, Personnel de support",
"role_description": "Description du rôle",
"role_description_placeholder": "Décrivez à quoi sert ce rôle et quelles permissions il nécessite",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rôle créé avec succès",
"role_updated_successfully": "Rôle mis à jour avec succès",
"delete_role": "Supprimer le rôle",
"confirm_delete_role": "Êtes-vous sûr de vouloir supprimer ce rôle ? Cette action ne peut pas être annulée.",
"confirm_delete_role": "Êtes-vous sûr de vouloir supprimer le rôle : {{roleName}} ? Cette action ne peut pas être annulée.",
"role_deleted_successfully": "Rôle supprimé avec succès",
"error_creating_role": "Erreur lors de la création du rôle",
"error_updating_role": "Erreur lors de la mise à jour du rôle",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Éditer et modifier les paramètres des workflows",
"pbac_desc_delete_workflows": "Supprimer des workflows du système",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Disponibilités",
"pbac_resource_out_of_office": "Absence du bureau",
"pbac_desc_create_webhooks": "Créer des webhooks",
"pbac_desc_view_webhooks": "Consulter les webhooks",
"pbac_desc_update_webhooks": "Mettre à jour les webhooks",
"pbac_desc_delete_webhooks": "Supprimer les webhooks",
"pbac_desc_create_availability": "Créer des disponibilités",
"pbac_desc_view_availability": "Voir les disponibilités",
"pbac_desc_update_availability": "Mettre à jour les disponibilités",
"pbac_desc_delete_availability": "Supprimer des disponibilités",
"pbac_desc_create_out_of_office": "Créer une absence",
"pbac_desc_view_out_of_office": "Voir les absences",
"pbac_desc_update_out_of_office": "Mettre à jour une absence",
"pbac_desc_delete_out_of_office": "Supprimer une absence",
"pbac_desc_manage_workflows": "Accès complet à la gestion de tous les workflows",
"pbac_desc_create_event_types": "Créer des types d'événements",
"pbac_desc_view_event_types": "Voir les types d'événements",
+28 -5
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "לא נמצאו משתמשים זמינים. אפשר לנסות ליצור חלון זמן נוסף?",
"timeslot_unavailable_book_a_new_time": "משבצת הזמן שנבחרה אינה זמינה יותר. <0>אנא בחר/י זמן חדש</0>",
"timeslot_unavailable_short": "תפוס",
"just_connected_description": "התחברת זה עתה. אנא הזמן מהמשבצות למעלה או נסה שוב מאוחר יותר.",
"unavailable_timeslot_title": "חלון זמן לא זמין",
"booking_time_out_of_bounds_error": "לא ניתן לקבוע את סוג האירוע בזמן זה. אולי כדאי לנסות משבצת זמן אחרת?",
"request_body_end_time_internal_error": "שגיאה פנימית. גוף הבקשה לא מכיל זמן סיום",
@@ -321,6 +322,7 @@
"secret_meeting": "פגישה חשאית",
"login_instead": "להיכנס במקום זאת",
"create_account": "צור חשבון",
"get_started": "להתחיל",
"confirm_password": "אישור סיסמה",
"reset_your_password": "הגדר/י את הסיסמה החדשה שלך לפי ההוראות שנשלחו אל כתובת הדוא\"ל שלך.",
"org_banner_instructions": "נא להעלות תמונה ברוחב {{width}} וגובה {{height}}.",
@@ -1019,6 +1021,7 @@
"attendees": "משתתפים",
"add_attendees": "הוסף משתתפים",
"show_advanced_settings": "הצגת הגדרות מתקדמות",
"hide_advanced_settings": "הסתרת הגדרות מתקדמות",
"event_name": "שם האירוע",
"event_name_in_calendar": "שם הארוע בלוח השנה",
"event_name_tooltip": "השם שיופיע בלוחות השנה",
@@ -1231,7 +1234,7 @@
"contact_sales": "פנה למחלקת המכירות",
"error_404": "שגיאת 404",
"default": "ברירת מחדל",
"set_to_default": "הגדר לברירת המחדל",
"set_to_default": "הגדרה כברירת מחדל",
"new_schedule_btn": "תזמון חדש",
"add_new_schedule": "הוסף מועד חדש",
"add_new_calendar": "הוספת לוח שנה חדש",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "כשההגדרה הזו מופעלת, חברי הצוות שלך לא יוכלו לראות חברי צוות אחרים.",
"you_cannot_see_team_members": "אין לך אפשרות לראות את כל חברי הצוות של צוות פרטי.",
"you_cannot_see_teams_of_org": "אי אפשר לצפות בצוותים של ארגון פרטי.",
"allow_booker_to_select_duration": "אפשר למזמין לבחור את משך הזמן",
"allow_multiple_durations": "אפשר משכי זמן מרובים",
"impersonate_user_tip": "כל השימושים בתכונה זו נבדקים.",
"impersonating_user_warning": "בהתחזות לשם המשתמש \"{{user}}\".",
"impersonating_stop_instructions": "לחץ/י כאן כדי להפסיק",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "אתה בטוח שאתה רוצה למחוק את הגדרות {{connectionType}}? חברי הצוות שלך שמשתמשים ב- {{connectionType}} להזדהות לא יוכלו להשתמש בו להכנס ל- Cal.com.",
"organizer_timezone": "מארגן אזורי זמן",
"email_user_cta": "צפה בהזמנה",
"accept_invite": "אישור הזמנה",
"join_now": "הצטרף עכשיו",
"successfully_joined": "הצטרפת בהצלחה",
"email_no_user_invite_heading_subteam": "הוזמנת להצטרף לצוות בארגון {{parentTeamName}}",
"email_no_user_invite_heading_org": "הוזמנת להצטרף לארגון {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} הזמין אותך להצטרף לצוות שלו ב- {{appName}}. {{appName}} הינה מתזמן זימונים שמאפשר לך ולצוות שלך לזמן פגישות בלי כל הפינג פונג במיילים.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "אישור ההזמנה נכשל",
"not_enough_seats": "אין מספיק מושבים",
"form_builder_field_already_exists": "שדה עם שם זה כבר קיים",
"guests_field_must_be_multiemail": "שדה האורחים חייב להיות מסוג 'מספר אימיילים'",
"show_on_booking_page": "להציג בדף ההזמנות",
"visit_cancelled_booking": "ניתן לבקר בדף ההזמנה שבוטלה",
"get_started_zapier_templates": "התחל עם תבניות Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "קישור ההזמנה הועתק",
"invite_link_deleted": "קישור ההזמנה נמחק",
"api_key_deleted": "מפתח API נמחק",
"delete_api_key_warning": "פעולה זו תמחק לצמיתות את מפתח ה-API. כל האפליקציות המשתמשות במפתח זה יאבדו מיד גישה לחשבון שלך. לא ניתן לבטל פעולה זו.",
"invite_link_updated": "הגדרות קישור ההזמנה נשמרו",
"link_expires_after": "הקישורים מוגדרים לפוג לאחר...",
"one_day": "יום אחד",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "בהמשך התהליך, הנך מסכים/ה ל",
"terms": "תנאי השימוש",
"always_show_x_days": "תמיד {{x}} ימים זמינים",
"always_show_x_days_description": "מזיז אוטומטית את חלון ההזמנות של {{x}} ימים קדימה בכל יום, ומבטיח שהלוח שלך תמיד מציג את {{x}} הימים הבאים של משבצות זמינות.",
"team_subscription_cancelled_successfully": "מנוי הצוות בוטל בהצלחה",
"team_subscription_cancellation_error": "שגיאה בעת ביטול מנוי הצוות",
"unable_to_subscribe_to_the_platform": "אירעה שגיאה בעת ניסיון להירשם לתוכנית הפלטפורמה, אנא נסה שוב מאוחר יותר",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "מסתיים ב",
"filter_operator_is_empty": "ריק",
"filter_operator_not_empty": "לא ריק",
"translate_description_button": "תרגם כותרת/תיאור לשפת הדפדפן של המבקר באמצעות בינה מלאכותית",
"translate_description_button": "תרגום אוטומטי של כותרת ותיאור",
"translate_description_button_description": "תרגם אוטומטית כותרות ותיאורים לשפת הדפדפן של המבקר באמצעות בינה מלאכותית.",
"rr_distribution_method": "שיטת חלוקה",
"rr_distribution_method_description": "מאפשר אופטימיזציה של החלוקה למקסימום זמינות או לחלוקה מאוזנת יותר",
"rr_distribution_method_availability_title": "מקסום זמינות",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "תכונת המושבים אינה תומכת באירוע חוזר. השבת את תכונת המושבים כדי להפוך אותו לחוזר.",
"recurring_event_seats_error": "אירוע חוזר אינו תומך בתכונת המושבים. השבת את תכונת המושבים או הפוך את האירוע ללא חוזר.",
"segment": "מקטע",
"saved_filters": "מסננים שמורים",
"rename": "שנה שם",
"rename_segment": "שנה שם מקטע",
"delete_segment": "מחק מקטע",
@@ -3338,6 +3349,7 @@
"picklist": "רשימת בחירה",
"most_cancelled_bookings": "הכי הרבה ביטולים",
"interface_language": "שפת ממשק",
"interface_language_description": "הגדר את השפה המועדפת עליך לממשק ההזמנות",
"visitors_browser_language": "שפת הדפדפן של המבקר",
"name_or_email": "שם או אימייל",
"salesforce_round_robin_skip_fallback_to_lead_owner": "אם לא נמצא איש קשר, חזור לבעל הליד אם הוא קיים",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "ניהול תפקידים והרשאות עבור הארגון שלך",
"learn_more_permissions": "למד עוד על אופן פעולת ההרשאות",
"role_name": "שם תפקיד",
"role_name_required": "שם תפקיד הוא שדה חובה",
"role_name_placeholder": "לדוגמה: צוות מכירות, צוות תמיכה",
"role_description": "תיאור תפקיד",
"role_description_placeholder": "תאר למה מיועד תפקיד זה ואילו הרשאות הוא צריך",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "התפקיד נוצר בהצלחה",
"role_updated_successfully": "התפקיד עודכן בהצלחה",
"delete_role": "מחיקת תפקיד",
"confirm_delete_role": "האם אתה בטוח שברצונך למחוק תפקיד זה? פעולה זו אינה ניתנת לביטול.",
"confirm_delete_role": "האם אתה בטוח שברצונך למחוק את התפקיד: {{roleName}}? פעולה זו אינה ניתנת לביטול.",
"role_deleted_successfully": "התפקיד נמחק בהצלחה",
"error_creating_role": "שגיאה ביצירת תפקיד",
"error_updating_role": "שגיאה בעדכון תפקיד",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "עריכה ושינוי הגדרות תהליכים",
"pbac_desc_delete_workflows": "הסרת תהליכים מהמערכת",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "זמינות",
"pbac_resource_out_of_office": "מחוץ למשרד",
"pbac_desc_create_webhooks": "יצירת רכיבי webhook",
"pbac_desc_view_webhooks": "צפייה ברכיבי webhook",
"pbac_desc_update_webhooks": "עדכון webhooks",
"pbac_desc_delete_webhooks": "מחיקת webhooks",
"pbac_desc_create_availability": "יצירת זמינות",
"pbac_desc_view_availability": "צפייה בזמינות",
"pbac_desc_update_availability": "עדכון זמינות",
"pbac_desc_delete_availability": "מחיקת זמינות",
"pbac_desc_create_out_of_office": "יצירת מצב מחוץ למשרד",
"pbac_desc_view_out_of_office": "צפייה במצב מחוץ למשרד",
"pbac_desc_update_out_of_office": "עדכון מצב מחוץ למשרד",
"pbac_desc_delete_out_of_office": "מחיקת מצב מחוץ למשרד",
"pbac_desc_manage_workflows": "גישת ניהול מלאה לכל התהליכים",
"pbac_desc_create_event_types": "יצירת סוגי אירועים",
"pbac_desc_view_event_types": "צפייה בסוגי אירועים",
@@ -3732,4 +3755,4 @@
"use_voice": "השתמש בקול",
"current_voice": "קול נוכחי",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+29 -6
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Nem található elérhető felhasználó. Kipróbálhatnál egy másik időpontot?",
"timeslot_unavailable_book_a_new_time": "A kiválasztott időpont már nem elérhető. <0>Kérjük, válassz egy új időpontot</0>",
"timeslot_unavailable_short": "Foglalt",
"just_connected_description": "Éppen most csatlakoztál. Kérjük, foglalj a fenti időpontok közül, vagy próbáld újra később.",
"unavailable_timeslot_title": "Nem elérhető időpont",
"booking_time_out_of_bounds_error": "Az esemény nem foglalható le ebben az időpontban. Kipróbálnál egy másik időpontot?",
"request_body_end_time_internal_error": "Belső hiba. A kérelem törzse nem tartalmazza a befejezési időt",
@@ -321,6 +322,7 @@
"secret_meeting": "Titkos megbeszélés",
"login_instead": "Jelentkezzen be helyette",
"create_account": "Fiók létrehozása",
"get_started": "Kezdjük el",
"confirm_password": "Jelszó megerősítése",
"reset_your_password": "Állítsa be új jelszavát az e-mail címére küldött utasítások alapján.",
"org_banner_instructions": "Kérjük, töltsön fel {{width}} szélességű és {{height}} magasságú képet.",
@@ -1019,6 +1021,7 @@
"attendees": "Résztvevők",
"add_attendees": "Résztvevők hozzáadása",
"show_advanced_settings": "Speciális beállítások megjelenítése",
"hide_advanced_settings": "Speciális beállítások elrejtése",
"event_name": "Esemény neve",
"event_name_in_calendar": "Eseménynévvel",
"event_name_tooltip": "A naptárban megjelenő név",
@@ -1231,7 +1234,7 @@
"contact_sales": "Lépjen kapcsolatba az értékesítéssel",
"error_404": "404-es hiba",
"default": "Alapértelmezett",
"set_to_default": "Állítsa alapértelmezettre",
"set_to_default": "Beállítás alapértelmezettként",
"new_schedule_btn": "Új ütemezés",
"add_new_schedule": "Új ütemezés hozzáadása",
"add_new_calendar": "Új naptár hozzáadása",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Ha ez be van kapcsolva, a csapattagok nem láthatják a többi csapattagot.",
"you_cannot_see_team_members": "Nem láthatja a privát csapat összes csapattagját.",
"you_cannot_see_teams_of_org": "Nem láthatja a magánszervezetek csapatait.",
"allow_booker_to_select_duration": "Engedélyezze a foglalónak az időtartam kiválasztását",
"allow_multiple_durations": "Több időtartam engedélyezése",
"impersonate_user_tip": "Ennek a funkciónak minden használata auditált.",
"impersonating_user_warning": "\"{{user}}\" felhasználónév megszemélyesítése.",
"impersonating_stop_instructions": "Kattintson ide a leállításhoz",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Biztosan törli a(z) {{connectionType}} konfigurációt? A(z) {{connectionType}} bejelentkezést használó csapattagok többé nem férhetnek hozzá a Cal.com webhelyhez.",
"organizer_timezone": "Szervező időzónája",
"email_user_cta": "Meghívó megtekintése",
"accept_invite": "Meghívó elfogadása",
"join_now": "Csatlakozás most",
"successfully_joined": "Sikeres csatlakozás",
"email_no_user_invite_heading_subteam": "Meghívást kaptál, hogy csatlakozz a {{parentTeamName}} szervezet egyik csapatához",
"email_no_user_invite_heading_org": "Meghívást kaptál, hogy csatlakozz egy {{appName}} szervezethez",
"email_no_user_invite_subheading": "{{invitedBy}} meghívott téged a csapatába a {{appName}} alkalmazásban. Az {{appName}} az eseményzsonglőr ütemező, amellyel Ön és csapata e-mail-tenisz nélkül is ütemezhet értekezleteket.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "A foglalás megerősítése nem sikerült",
"not_enough_seats": "Nincs elég hely",
"form_builder_field_already_exists": "Már létezik ilyen nevű mező",
"guests_field_must_be_multiemail": "A vendégek mezőnek 'Több e-mail cím' típusúnak kell lennie",
"show_on_booking_page": "Megjelenítés a foglalási oldalon",
"visit_cancelled_booking": "Megtekintheti a lemondott foglalás oldalát",
"get_started_zapier_templates": "Kezdje el a Zapier-sablonokat",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "A meghívó linkje kimásolva",
"invite_link_deleted": "Meghívó link törölve",
"api_key_deleted": "API-kulcs törölve",
"delete_api_key_warning": "Ez véglegesen törli az API kulcsot. Minden alkalmazás, amely ezt a kulcsot használja, azonnal elveszíti a hozzáférést a fiókodhoz. Ez a művelet nem vonható vissza.",
"invite_link_updated": "A meghívólink beállításai mentve",
"link_expires_after": "A linkek lejárnak...",
"one_day": "1 nap",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "A folytatással elfogadod a",
"terms": "Feltételeinket",
"always_show_x_days": "Mindig {{x}} nap áll rendelkezésre",
"always_show_x_days_description": "Automatikusan előrelépteti a {{x}} napos foglalási ablakot minden nap, így a naptárad mindig a következő {{x}} nap elérhető időpontjait mutatja.",
"team_subscription_cancelled_successfully": "A csapat előfizetése sikeresen lemondva",
"team_subscription_cancellation_error": "Hiba történt a csapat előfizetésének lemondása közben",
"unable_to_subscribe_to_the_platform": "Hiba történt a platformcsomagra való előfizetés során. Kérjük, próbálja újra később",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Ezzel végződik",
"filter_operator_is_empty": "Üres",
"filter_operator_not_empty": "Nem üres",
"translate_description_button": "A cím/leírás automatikus fordítása a látogató böngészőjének nyelvére MI segítségével",
"translate_description_button": "Cím és leírás automatikus fordítása",
"translate_description_button_description": "Automatikusan lefordítja a címeket és leírásokat a látogató böngészőjének nyelvére mesterséges intelligencia segítségével.",
"rr_distribution_method": "Elosztás",
"rr_distribution_method_description": "Lehetővé teszi az elosztás optimalizálását a maximális rendelkezésre állás érdekében, vagy a kiegyensúlyozottabb feladatkiosztás eléréséhez.",
"rr_distribution_method_availability_title": "Rendelkezésre állás maximalizálása",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "A helyek funkció nem támogatja az ismétlődő eseményeket. Kapcsolja ki a helyek funkciót az ismétlődés engedélyezéséhez.",
"recurring_event_seats_error": "Az ismétlődő esemény nem támogatja a helyek funkciót. Kapcsolja ki a helyek funkciót vagy állítsa be nem ismétlődőre az eseményt.",
"segment": "Szegmens",
"saved_filters": "Mentett szűrők",
"rename": "Átnevezés",
"rename_segment": "Szegmens átnevezése",
"delete_segment": "Szegmens törlése",
@@ -3338,6 +3349,7 @@
"picklist": "Választólista",
"most_cancelled_bookings": "Legtöbb lemondott foglalás",
"interface_language": "Felhasználói felület nyelve",
"interface_language_description": "Állítsd be a foglalási felület preferált nyelvét",
"visitors_browser_language": "Látogató böngészőjének nyelve",
"name_or_email": "Név vagy e-mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Ha nem található kapcsolat, visszalépés a lead tulajdonoshoz, ha létezik",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Kezelje szervezete szerepköreit és jogosultságait",
"learn_more_permissions": "Tudjon meg többet a jogosultságok működéséről",
"role_name": "Szerepkör neve",
"role_name_required": "A szerepkör neve kötelező",
"role_name_placeholder": "pl. Értékesítési csapat, Ügyfélszolgálat",
"role_description": "Szerepkör leírása",
"role_description_placeholder": "Írja le, mire szolgál ez a szerepkör és milyen jogosultságokra van szüksége",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Szerepkör sikeresen létrehozva",
"role_updated_successfully": "Szerepkör sikeresen frissítve",
"delete_role": "Szerepkör törlése",
"confirm_delete_role": "Biztosan törölni szeretné ezt a szerepkört? Ez a művelet nem vonható vissza.",
"confirm_delete_role": "Biztosan törölni szeretné a következő szerepkört: {{roleName}}? Ez a művelet nem vonható vissza.",
"role_deleted_successfully": "Szerepkör sikeresen törölve",
"error_creating_role": "Hiba történt a szerepkör létrehozásakor",
"error_updating_role": "Hiba történt a szerepkör frissítésekor",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Munkafolyamat-beállítások szerkesztése és módosítása",
"pbac_desc_delete_workflows": "Munkafolyamatok eltávolítása a rendszerből",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Elérhetőség",
"pbac_resource_out_of_office": "Irodán kívül",
"pbac_desc_create_webhooks": "Webhookok létrehozása",
"pbac_desc_view_webhooks": "Webhookok megtekintése",
"pbac_desc_update_webhooks": "Webhookok frissítése",
"pbac_desc_delete_webhooks": "Webhookok törlése",
"pbac_desc_create_availability": "Elérhetőség létrehozása",
"pbac_desc_view_availability": "Elérhetőség megtekintése",
"pbac_desc_update_availability": "Elérhetőség frissítése",
"pbac_desc_delete_availability": "Elérhetőség törlése",
"pbac_desc_create_out_of_office": "Irodán kívüli állapot létrehozása",
"pbac_desc_view_out_of_office": "Irodán kívüli állapot megtekintése",
"pbac_desc_update_out_of_office": "Irodán kívüli állapot frissítése",
"pbac_desc_delete_out_of_office": "Irodán kívüli állapot törlése",
"pbac_desc_manage_workflows": "Teljes kezelési hozzáférés minden munkafolyamathoz",
"pbac_desc_create_event_types": "Eseménytípusok létrehozása",
"pbac_desc_view_event_types": "Eseménytípusok megtekintése",
@@ -3561,7 +3584,7 @@
"round_robin_settings_description": "Testreszabhatja az alapértelmezett körforgó beállításokat ehhez a csapathoz",
"round_robin_settings_updated_successfully": "A körforgó beállítások sikeresen frissítve",
"booking_creation_time": "Foglalás létrehozásának ideje",
"meeting_start_time": "Találkozó kezdési ideje",
"meeting_start_time": "Megbeszélés kezdési ideje",
"members_affected_by_disabling_delegation_credential": "Érintett tagok",
"no_members_affected_by_disabling_delegation_credential": "Nincsenek tagok, akiket érintene a delegálási hitelesítő adatok letiltása",
"download_expense_log": "Költségnapló letöltése",
@@ -3732,4 +3755,4 @@
"use_voice": "Hang használata",
"current_voice": "Jelenlegi hang",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adja hozzá az új karakterláncokat fent ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+31 -8
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Nessun utente disponibile. Puoi provare un altro intervallo temporale?",
"timeslot_unavailable_book_a_new_time": "La fascia oraria selezionata non è più disponibile. <0>Seleziona un nuovo orario</0>",
"timeslot_unavailable_short": "Occupato",
"just_connected_description": "Ti sei appena connesso. Prenota dagli slot sopra indicati o riprova più tardi.",
"unavailable_timeslot_title": "Intervallo non disponibile",
"booking_time_out_of_bounds_error": "Non è possibile prenotare questo tipo di evento in questo momento. Potresti provare un altro orario?",
"request_body_end_time_internal_error": "Errore interno. La richiesta non contiene l'ora finale",
@@ -321,6 +322,7 @@
"secret_meeting": "Riunione Segreta",
"login_instead": "Effettua il login invece",
"create_account": "Crea Account",
"get_started": "Inizia",
"confirm_password": "Conferma password",
"reset_your_password": "Imposta la nuova password seguendo le istruzioni che ti sono state inviate via e-mail.",
"org_banner_instructions": "Carica un'immagine larga {{width}} e alta {{height}}.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Colore del tipo di evento (Tema chiaro)",
"dark_event_type_color": "Colore del tipo di evento (Tema scuro)",
"file_not_named": "Il file non è chiamato [idOrSlug]/[user]",
"create_team": "Crea Team",
"create_team": "Crea team",
"name": "Nome",
"nameless_team": "Team senza nome",
"oauth_clients": "Client OAuth",
@@ -1019,6 +1021,7 @@
"attendees": "Partecipanti",
"add_attendees": "Aggiungi partecipanti",
"show_advanced_settings": "Mostra impostazioni avanzate",
"hide_advanced_settings": "Nascondi impostazioni avanzate",
"event_name": "Nome Dell'Evento",
"event_name_in_calendar": "Nome dell'evento nel calendario",
"event_name_tooltip": "Il nome che apparirà nei calendari",
@@ -1231,7 +1234,7 @@
"contact_sales": "Contatta reparto commerciale",
"error_404": "Errore 404",
"default": "Predefinito",
"set_to_default": "Impostazione predefinita",
"set_to_default": "Imposta come predefinito",
"new_schedule_btn": "Nuovo programma",
"add_new_schedule": "Aggiungi un nuovo programma",
"add_new_calendar": "Aggiungi un nuovo calendario",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Quando questa opzione è attivata, i membri del tuo team non potranno vedere gli altri membri del team.",
"you_cannot_see_team_members": "Non è possibile vedere tutti i membri di un team privato.",
"you_cannot_see_teams_of_org": "Non puoi vedere i team di un'organizzazione privata.",
"allow_booker_to_select_duration": "Consenti alla persona che prenota di selezionare la durata",
"allow_multiple_durations": "Consenti durate multiple",
"impersonate_user_tip": "Ogni utilizzo di questa funzionalità viene controllato.",
"impersonating_user_warning": "Accesso con l'identità dell'utente \"{{user}}\".",
"impersonating_stop_instructions": "Fai clic qui per interrompere",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Eliminare la configurazione {{connectionType}}? I membri del tuo team che utilizzano l'accesso {{connectionType}} non saranno più in grado di accedere a Cal.com.",
"organizer_timezone": "Fuso orario organizzatore",
"email_user_cta": "Visualizza invito",
"accept_invite": "Accetta Invito",
"join_now": "Unisciti Ora",
"successfully_joined": "Iscrizione completata con successo",
"email_no_user_invite_heading_subteam": "Sei stato invitato a unirti a un team dell'organizzazione {{parentTeamName}}",
"email_no_user_invite_heading_org": "Sei stato invitato a unirti a un'organizzazione {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Conferma prenotazione non riuscita",
"not_enough_seats": "Posti insufficienti",
"form_builder_field_already_exists": "Esiste già un campo con questo nome",
"guests_field_must_be_multiemail": "Il campo ospiti deve essere di tipo 'Email multiple'",
"show_on_booking_page": "Mostra nella pagina di prenotazione",
"visit_cancelled_booking": "Puoi visitare la pagina della prenotazione annullata",
"get_started_zapier_templates": "Inizia con i modelli Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Link d'invito copiato",
"invite_link_deleted": "Link d'invito eliminato",
"api_key_deleted": "Chiave API eliminata",
"delete_api_key_warning": "Questa azione eliminerà permanentemente la chiave API. Tutte le applicazioni che utilizzano questa chiave perderanno immediatamente l'accesso al tuo account. Questa azione non può essere annullata.",
"invite_link_updated": "Impostazioni link d'invito salvate",
"link_expires_after": "La scadenza dei link è impostata dopo...",
"one_day": "1 giorno",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Procedendo, accetti i nostri",
"terms": "Termini",
"always_show_x_days": "Sempre {{x}} giorni disponibili",
"always_show_x_days_description": "Sposta automaticamente avanti ogni giorno la finestra di prenotazione di {{x}} giorni, assicurando che il tuo calendario mostri sempre i prossimi {{x}} giorni di slot disponibili.",
"team_subscription_cancelled_successfully": "Abbonamento del team annullato con successo",
"team_subscription_cancellation_error": "Errore durante l'annullamento dell'abbonamento del team",
"unable_to_subscribe_to_the_platform": "Si è verificato un errore durante il tentativo di abbonamento al piano Platform, riprova più tardi",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Finisce con",
"filter_operator_is_empty": "È vuoto",
"filter_operator_not_empty": "Non è vuoto",
"translate_description_button": "Traduci titolo/descrizione nella lingua del browser del visitatore usando l'IA",
"translate_description_button": "Auto traduci titolo e descrizione",
"translate_description_button_description": "Traduci automaticamente titoli e descrizioni nella lingua del browser del visitatore utilizzando l'IA.",
"rr_distribution_method": "Distribuzione",
"rr_distribution_method_description": "Permette di ottimizzare la distribuzione per massimizzare la disponibilità o per ottenere un'assegnazione più bilanciata.",
"rr_distribution_method_availability_title": "Massimizza disponibilità",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "La funzionalità dei posti non supporta eventi ricorrenti. Disabilita la funzionalità dei posti per renderlo ricorrente.",
"recurring_event_seats_error": "L'evento ricorrente non supporta la funzionalità dei posti. Disabilita la funzionalità dei posti o rendi l'evento non ricorrente.",
"segment": "Segmento",
"saved_filters": "Filtri salvati",
"rename": "Rinomina",
"rename_segment": "Rinomina segmento",
"delete_segment": "Elimina segmento",
@@ -3338,6 +3349,7 @@
"picklist": "Lista di selezione",
"most_cancelled_bookings": "Prenotazioni più cancellate",
"interface_language": "Lingua dell'interfaccia",
"interface_language_description": "Imposta la tua lingua preferita per l'interfaccia di prenotazione",
"visitors_browser_language": "Lingua del browser del visitatore",
"name_or_email": "Nome o email",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Se non viene trovato alcun contatto, passa al proprietario del lead se esiste",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Gestisci ruoli e permessi per la tua organizzazione",
"learn_more_permissions": "Scopri di più su come funzionano i permessi",
"role_name": "Nome del ruolo",
"role_name_required": "Il nome del ruolo è obbligatorio",
"role_name_placeholder": "es. Team vendite, Staff di supporto",
"role_description": "Descrizione del ruolo",
"role_description_placeholder": "Descrivi a cosa serve questo ruolo e quali permessi necessita",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Ruolo creato con successo",
"role_updated_successfully": "Ruolo aggiornato con successo",
"delete_role": "Elimina ruolo",
"confirm_delete_role": "Sei sicuro di voler eliminare questo ruolo? Questa azione non può essere annullata.",
"confirm_delete_role": "Sei sicuro di voler eliminare il ruolo: {{roleName}}? Questa azione non può essere annullata.",
"role_deleted_successfully": "Ruolo eliminato con successo",
"error_creating_role": "Errore durante la creazione del ruolo",
"error_updating_role": "Errore durante l'aggiornamento del ruolo",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Modifica e cambia le impostazioni dei flussi di lavoro",
"pbac_desc_delete_workflows": "Rimuovi flussi di lavoro dal sistema",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Disponibilità",
"pbac_resource_out_of_office": "Fuori sede",
"pbac_desc_create_webhooks": "Crea webhook",
"pbac_desc_view_webhooks": "Visualizza webhook",
"pbac_desc_update_webhooks": "Aggiorna webhook",
"pbac_desc_delete_webhooks": "Elimina webhook",
"pbac_desc_create_availability": "Crea disponibilità",
"pbac_desc_view_availability": "Visualizza disponibilità",
"pbac_desc_update_availability": "Aggiorna disponibilità",
"pbac_desc_delete_availability": "Elimina disponibilità",
"pbac_desc_create_out_of_office": "Crea fuori sede",
"pbac_desc_view_out_of_office": "Visualizza fuori sede",
"pbac_desc_update_out_of_office": "Aggiorna fuori sede",
"pbac_desc_delete_out_of_office": "Elimina fuori sede",
"pbac_desc_manage_workflows": "Accesso completo alla gestione di tutti i flussi di lavoro",
"pbac_desc_create_event_types": "Crea tipi di evento",
"pbac_desc_view_event_types": "Visualizza tipi di evento",
@@ -3560,8 +3583,8 @@
"rr_load_balancing_disabled_with_groups": "Il bilanciamento del carico non è disponibile con i gruppi Round Robin",
"round_robin_settings_description": "Personalizza le impostazioni predefinite del round robin per questo team",
"round_robin_settings_updated_successfully": "Impostazioni Round Robin aggiornate con successo",
"booking_creation_time": "Orario di creazione della prenotazione",
"meeting_start_time": "Orario di inizio della riunione",
"booking_creation_time": "Orario di creazione prenotazione",
"meeting_start_time": "Orario di inizio meeting",
"members_affected_by_disabling_delegation_credential": "Membri interessati",
"no_members_affected_by_disabling_delegation_credential": "Nessun membro interessato dalla disabilitazione delle credenziali di delega",
"download_expense_log": "Scarica il registro delle spese",
@@ -3732,4 +3755,4 @@
"use_voice": "Usa voce",
"current_voice": "Voce attuale",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Aggiungi le tue nuove stringhe qui sopra ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+27 -4
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "利用可能なユーザーが見つかりませんでした。別の時間帯をお試しいただけますか?",
"timeslot_unavailable_book_a_new_time": "選択した時間枠はもう利用できません。<0>新しい時間を選択してください</0>",
"timeslot_unavailable_short": "予約済み",
"just_connected_description": "接続が完了しました。上記の空き枠から予約するか、後ほど再試行してください。",
"unavailable_timeslot_title": "利用不可能な時間枠",
"booking_time_out_of_bounds_error": "この時間帯では予約できません。別の時間帯をお試しください。",
"request_body_end_time_internal_error": "内部エラー。リクエストボディに終了時間が含まれていません",
@@ -321,6 +322,7 @@
"secret_meeting": "秘密のミーティング",
"login_instead": "代わりにログインする",
"create_account": "アカウントを作成",
"get_started": "始める",
"confirm_password": "パスワードを確認",
"reset_your_password": "メールアドレスに送信された手順に従って新しいパスワードを設定します。",
"org_banner_instructions": "{{width}}幅と{{height}}高さの画像をアップロードしてください。",
@@ -1019,6 +1021,7 @@
"attendees": "出席者",
"add_attendees": "出席者を追加",
"show_advanced_settings": "詳細設定を表示",
"hide_advanced_settings": "詳細設定を隠す",
"event_name": "イベント名",
"event_name_in_calendar": "カレンダー内のイベント名",
"event_name_tooltip": "カレンダーに表示される名前",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "これをオンにすると、チームメンバーは他のチームメンバーを確認することができなくなります。",
"you_cannot_see_team_members": "非公開チームでは、チームメンバーを確認することができません。",
"you_cannot_see_teams_of_org": "非公開の組織のチームを見ることはできません。",
"allow_booker_to_select_duration": "予約者が時間を選択できるようにする",
"allow_multiple_durations": "複数の時間枠を許可",
"impersonate_user_tip": "この機能の使用はすべて監査されています。",
"impersonating_user_warning": "ユーザー名 \"{{user}}\" になりすましています。",
"impersonating_stop_instructions": "こちらをクリックして停止",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "{{connectionType}} の構成を削除してもよろしいですか?{{connectionType}} でのログインを使用しているチームメンバーは、Cal.com にアクセスできなくなります。",
"organizer_timezone": "主催者のタイムゾーン",
"email_user_cta": "招待を表示",
"accept_invite": "招待を受け入れる",
"join_now": "今すぐ参加",
"successfully_joined": "参加に成功しました",
"email_no_user_invite_heading_subteam": "{{parentTeamName}}組織のチームに参加するよう招待されました",
"email_no_user_invite_heading_org": "{{appName}}組織に参加するよう招待されました",
"email_no_user_invite_subheading": "{{invitedBy}} は {{appName}} のチームに参加するようあなたを招待しました。{{appName}} は、イベント調整スケジューラーです。チームと延々とメールのやりとりをすることなくミーティングのスケジュール設定を行うことができます。",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "予約の確認に失敗しました",
"not_enough_seats": "座席が不足しています",
"form_builder_field_already_exists": "この名前のフィールドは既に存在します",
"guests_field_must_be_multiemail": "ゲストフィールドは「複数のメール」タイプである必要があります",
"show_on_booking_page": "予約ページに表示",
"visit_cancelled_booking": "キャンセルされた予約ページを訪問できます",
"get_started_zapier_templates": "Zapier テンプレートの使用を開始する",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "招待リンクをコピーしました",
"invite_link_deleted": "招待リンクを削除しました",
"api_key_deleted": "API キーを削除しました",
"delete_api_key_warning": "これによりAPIキーは完全に削除されます。このキーを使用しているアプリケーションはすべて、即座にあなたのアカウントへのアクセスを失います。この操作は元に戻すことができません。",
"invite_link_updated": "招待リンクの設定を保存しました",
"link_expires_after": "リンクの期限切れまで...",
"one_day": "1 日",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "続行することで、当社の",
"terms": "利用規約",
"always_show_x_days": "常に{{x}}日間利用可能",
"always_show_x_days_description": "予約ウィンドウを毎日自動的に{{x}}日分前進させ、カレンダーに常に今後{{x}}日間の空き枠を表示します。",
"team_subscription_cancelled_successfully": "チームサブスクリプションが正常にキャンセルされました",
"team_subscription_cancellation_error": "チームサブスクリプションのキャンセル中にエラーが発生しました",
"unable_to_subscribe_to_the_platform": "プラットフォームプランの登録中にエラーが発生しました。後でもう一度お試しください。",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "で終わる",
"filter_operator_is_empty": "が空である",
"filter_operator_not_empty": "が空ではない",
"translate_description_button": "AIを使用して訪問者のブラウザ言語にタイトル/説明を翻訳",
"translate_description_button": "タイトルと説明を自動翻訳",
"translate_description_button_description": "AIを使用して、訪問者のブラウザ言語にタイトルと説明を自動的に翻訳します。",
"rr_distribution_method": "配分方式",
"rr_distribution_method_description": "最大限の予約可能性を確保するため、または均等な配分を目指すための最適化が可能です。",
"rr_distribution_method_availability_title": "予約可能性を最大化",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "座席機能は定期的なイベントをサポートしていません。定期的にするには座席機能を無効にしてください。",
"recurring_event_seats_error": "定期的なイベントは座席機能をサポートしていません。座席機能を無効にするか、イベントを非定期的にしてください。",
"segment": "セグメント",
"saved_filters": "保存済みフィルター",
"rename": "名前を変更",
"rename_segment": "セグメント名を変更",
"delete_segment": "セグメントを削除",
@@ -3338,6 +3349,7 @@
"picklist": "選択リスト",
"most_cancelled_bookings": "最もキャンセルが多い",
"interface_language": "インターフェース言語",
"interface_language_description": "予約インターフェースの希望言語を設定します",
"visitors_browser_language": "訪問者のブラウザ言語",
"name_or_email": "名前またはメールアドレス",
"salesforce_round_robin_skip_fallback_to_lead_owner": "連絡先が見つからない場合、リード所有者が存在すればそちらにフォールバックする",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "組織の役割と権限を管理する",
"learn_more_permissions": "権限の仕組みについて詳しく学ぶ",
"role_name": "役割名",
"role_name_required": "役割名は必須です",
"role_name_placeholder": "例:営業チーム、サポートスタッフ",
"role_description": "役割の説明",
"role_description_placeholder": "この役割の目的と必要な権限について説明してください",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "役割が正常に作成されました",
"role_updated_successfully": "役割が正常に更新されました",
"delete_role": "役割を削除",
"confirm_delete_role": "この役割を削除してもよろしいですか?この操作は元に戻せません。",
"confirm_delete_role": "役割「{{roleName}}」を削除してもよろしいですか?この操作は元に戻せません。",
"role_deleted_successfully": "役割が正常に削除されました",
"error_creating_role": "役割の作成中にエラーが発生しました",
"error_updating_role": "役割の更新中にエラーが発生しました",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "ワークフロー設定を編集および変更する",
"pbac_desc_delete_workflows": "システムからワークフローを削除する",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "空き状況",
"pbac_resource_out_of_office": "不在",
"pbac_desc_create_webhooks": "Webhookを作成",
"pbac_desc_view_webhooks": "Webhookを表示",
"pbac_desc_update_webhooks": "ウェブフックを更新する",
"pbac_desc_delete_webhooks": "ウェブフックを削除する",
"pbac_desc_create_availability": "空き状況を作成",
"pbac_desc_view_availability": "空き状況を表示",
"pbac_desc_update_availability": "空き状況を更新",
"pbac_desc_delete_availability": "空き状況を削除",
"pbac_desc_create_out_of_office": "不在を作成",
"pbac_desc_view_out_of_office": "不在を表示",
"pbac_desc_update_out_of_office": "不在を更新",
"pbac_desc_delete_out_of_office": "不在を削除",
"pbac_desc_manage_workflows": "すべてのワークフローへの完全な管理アクセス",
"pbac_desc_create_event_types": "イベントタイプを作成する",
"pbac_desc_view_event_types": "イベントタイプを表示する",
@@ -3732,4 +3755,4 @@
"use_voice": "音声を使用",
"current_voice": "現在の音声",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ この上に新しい文字列を追加してください ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+28 -5
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "រកមិនឃើញអ្នកប្រើដែលអាចប្រើបានទេ។ តើអ្នកអាចសាកល្បងម៉ោងផ្សេងទៀតបានទេ?",
"timeslot_unavailable_book_a_new_time": "ពេលវេលាដែលបានជ្រើសរើសលែងមានទៀតហើយ។ <0>សូមជ្រើសរើសពេលវេលាថ្មី</0>",
"timeslot_unavailable_short": "បានកក់រួច",
"just_connected_description": "អ្នកទើបតែបានភ្ជាប់។ សូមកក់ពេលវេលាពីខាងលើ ឬព្យាយាមម្តងទៀតនៅពេលក្រោយ។",
"unavailable_timeslot_title": "ពេលវេលាមិនអាចប្រើបាន",
"booking_time_out_of_bounds_error": "មិនអាចកក់ប្រភេទព្រឹត្តិការណ៍នេះនៅពេលនេះបានទេ។ សូមព្យាយាមជ្រើសរើសពេលវេលាផ្សេងទៀត។",
"request_body_end_time_internal_error": "កំហុសខាងក្នុង។ សំណើមិនមានពេលវេលាបញ្ចប់",
@@ -321,6 +322,7 @@
"secret_meeting": "ការជួបជុំសម្ងាត់",
"login_instead": "ចូលប្រើប្រាស់វិញ",
"create_account": "បង្កើតគណនី",
"get_started": "ចាប់ផ្តើម",
"confirm_password": "បញ្ជាក់ពាក្យសម្ងាត់",
"reset_your_password": "កំណត់ពាក្យសម្ងាត់ថ្មីរបស់អ្នកដោយអនុវត្តតាមសេចក្តីណែនាំដែលបានផ្ញើទៅអាសយដ្ឋានអ៊ីមែលរបស់អ្នក។",
"org_banner_instructions": "សូមផ្ទុករូបភាពដែលមានទទឹង {{width}} និងកម្ពស់ {{height}}។",
@@ -1019,6 +1021,7 @@
"attendees": "អ្នកចូលរួម",
"add_attendees": "បន្ថែមអ្នកចូលរួម",
"show_advanced_settings": "បង្ហាញការកំណត់កម្រិតខ្ពស់",
"hide_advanced_settings": "លាក់ការកំណត់កម្រិតខ្ពស់",
"event_name": "ឈ្មោះព្រឹត្តិការណ៍",
"event_name_in_calendar": "ជាមួយឈ្មោះព្រឹត្តិការណ៍",
"event_name_tooltip": "ឈ្មោះដែលនឹងបង្ហាញនៅក្នុងប្រតិទិន",
@@ -1231,7 +1234,7 @@
"contact_sales": "ទាក់ទងផ្នែកលក់",
"error_404": "កំហុស 404",
"default": "លំនាំដើម",
"set_to_default": "កំណត់ទៅលំនាំដើម",
"set_to_default": "កំណត់ជាលំនាំដើម",
"new_schedule_btn": "កាលវិភាគថ្មី",
"add_new_schedule": "បន្ថែមកាលវិភាគថ្មី",
"add_new_calendar": "បន្ថែមប្រតិទិនថ្មី",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "សមាជិកក្រុមរបស់អ្នកនឹងមិនអាចមើលឃើញសមាជិកក្រុមផ្សេងទៀតនៅពេលដែលបើកមុខងារនេះ។",
"you_cannot_see_team_members": "អ្នកមិនអាចមើលឃើញសមាជិកទាំងអស់នៃក្រុមសម្ងាត់បានទេ។",
"you_cannot_see_teams_of_org": "អ្នកមិនអាចមើលឃើញក្រុមនៃអង្គការសម្ងាត់បានទេ។",
"allow_booker_to_select_duration": "អនុញ្ញាតឱ្យអ្នកកក់ជ្រើសរើសរយៈពេល",
"allow_multiple_durations": "អនុញ្ញាតឱ្យមានរយៈពេលច្រើន",
"impersonate_user_tip": "ការប្រើប្រាស់មុខងារនេះទាំងអស់ត្រូវបានកត់ត្រា។",
"impersonating_user_warning": "កំពុងសម្តែងជាអ្នកប្រើប្រាស់ \"{{user}}\"។",
"impersonating_stop_instructions": "ចុចទីនេះដើម្បីបញ្ឈប់",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "តើអ្នកប្រាកដថាអ្នកចង់លុបការកំណត់រចនាសម្ព័ន្ធ {{connectionType}} មែនទេ? សមាជិកក្រុមរបស់អ្នកដែលប្រើការចូល {{connectionType}} នឹងមិនអាចចូលប្រើ Cal.com បានទៀតទេ។",
"organizer_timezone": "ល្វែងម៉ោងរបស់អ្នករៀបចំ",
"email_user_cta": "មើលការអញ្ជើញ",
"accept_invite": "ទទួលយកការអញ្ជើញ",
"join_now": "ចូលរួមឥឡូវនេះ",
"successfully_joined": "បានចូលរួមដោយជោគជ័យ",
"email_no_user_invite_heading_subteam": "អ្នកត្រូវបានអញ្ជើញឱ្យចូលរួមក្នុងក្រុមនៃអង្គការ {{parentTeamName}}",
"email_no_user_invite_heading_org": "អ្នកត្រូវបានអញ្ជើញឱ្យចូលរួមក្នុងអង្គការ {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} បានអញ្ជើញអ្នកឱ្យចូលរួមជាក្រុមរបស់ពួកគេនៅលើ {{appName}}។ {{appName}} គឺជាកម្មវិធីរៀបចំព្រឹត្តិការណ៍ដែលអនុញ្ញាតឱ្យអ្នកនិងក្រុមរបស់អ្នករៀបចំការជួបប្រជុំដោយមិនចាំបាច់ផ្លាស់ប្តូរអ៊ីមែល។",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "ការបញ្ជាក់ការកក់បរាជ័យ",
"not_enough_seats": "កៅអីមិនគ្រប់គ្រាន់",
"form_builder_field_already_exists": "វាលដែលមានឈ្មោះនេះមានរួចហើយ",
"guests_field_must_be_multiemail": "វាលភ្ញៀវត្រូវតែជាប្រភេទ 'អ៊ីមែលច្រើន'",
"show_on_booking_page": "បង្ហាញនៅលើទំព័រការកក់",
"visit_cancelled_booking": "អ្នកអាចចូលទៅកាន់ទំព័រការកក់ដែលបានលុបចោល",
"get_started_zapier_templates": "ចាប់ផ្តើមជាមួយពុម្ព Zapier",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "បានចម្លងតំណភ្ជាប់អញ្ជើញ",
"invite_link_deleted": "បានលុបតំណភ្ជាប់អញ្ជើញ",
"api_key_deleted": "បានលុប API Key",
"delete_api_key_warning": "វានឹងលុបកូនសោ API ជាអចិន្ត្រៃយ៍។ កម្មវិធីណាដែលប្រើកូនសោនេះនឹងបាត់បង់ការចូលប្រើគណនីរបស់អ្នកភ្លាមៗ។ សកម្មភាពនេះមិនអាចត្រឡប់ក្រោយវិញបានទេ។",
"invite_link_updated": "បានរក្សាទុកការកំណត់តំណភ្ជាប់អញ្ជើញ",
"link_expires_after": "តំណភ្ជាប់នឹងផុតកំណត់បន្ទាប់ពី...",
"one_day": "1 ថ្ងៃ",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "ដោយការបន្ត អ្នកយល់ព្រមនឹង",
"terms": "លក្ខខណ្ឌ",
"always_show_x_days": "តែងតែបង្ហាញ {{x}} ថ្ងៃដែលអាចប្រើបាន",
"always_show_x_days_description": "ផ្លាស់ទីបង្អួចកក់រយៈពេល {{x}} ថ្ងៃដោយស្វ័យប្រវត្តិរៀងរាល់ថ្ងៃ ដើម្បីធានាថាប្រតិទិនរបស់អ្នកតែងតែបង្ហាញពេលវេលាដែលអាចប្រើបានសម្រាប់ {{x}} ថ្ងៃបន្ទាប់។",
"team_subscription_cancelled_successfully": "បានលុបចោលការជាវរបស់ក្រុមដោយជោគជ័យ",
"team_subscription_cancellation_error": "មានបញ្ហាពេលលុបចោលការជាវរបស់ក្រុម",
"unable_to_subscribe_to_the_platform": "មានកំហុសមួយបានកើតឡើងខណៈពេលព្យាយាមជាវផែនការក្នុងវេទិកា សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "បញ្ចប់ដោយ",
"filter_operator_is_empty": "ទទេ",
"filter_operator_not_empty": "មិនទទេ",
"translate_description_button": "បកប្រែចំណងជើង/ការពិពណ៌នាទៅជាភាសាកម្មវិធីរុករករបស់ភ្ញៀវដោយប្រើបញ្ញាសិប្បនិម្មិត",
"translate_description_button": "បកប្រែចំណងជើងនិងការពិពណ៌នាដោយស្វ័យប្រវត្តិ",
"translate_description_button_description": "បកប្រែចំណងជើងនិងការពិពណ៌នាដោយស្វ័យប្រវត្តិទៅជាភាសាកម្មវិធីរុករករបស់អ្នកទស្សនាដោយប្រើ AI។",
"rr_distribution_method": "ការចែកចាយ",
"rr_distribution_method_description": "អនុញ្ញាតឱ្យធ្វើការបែងចែកប្រសិទ្ធភាពដើម្បីបង្កើនភាពអាចរកបានអតិបរមា ឬដើម្បីសម្រេចបាននូវការចាត់តាំងដែលមានតុល្យភាពជាងមុន។",
"rr_distribution_method_availability_title": "បង្កើនភាពអាចរកបាន",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "មុខងារកន្លែងអង្គុយមិនគាំទ្រព្រឹត្តិការណ៍ដែលកើតឡើងជាប្រចាំទេ។ បិទមុខងារកន្លែងអង្គុយដើម្បីធ្វើឱ្យវាកើតឡើងជាប្រចាំ។",
"recurring_event_seats_error": "ព្រឹត្តិការណ៍ដែលកើតឡើងជាប្រចាំមិនគាំទ្រមុខងារកន្លែងអង្គុយទេ។ បិទមុខងារកន្លែងអង្គុយឬធ្វើឱ្យព្រឹត្តិការណ៍មិនកើតឡើងជាប្រចាំ។",
"segment": "ផ្នែក",
"saved_filters": "តម្រងដែលបានរក្សាទុក",
"rename": "ប្តូរឈ្មោះ",
"rename_segment": "ប្តូរឈ្មោះផ្នែក",
"delete_segment": "លុបផ្នែក",
@@ -3338,6 +3349,7 @@
"picklist": "បញ្ជីជម្រើស",
"most_cancelled_bookings": "ការកក់ដែលត្រូវបានលុបចោលច្រើនបំផុត",
"interface_language": "ភាសាចំណុចប្រទាក់",
"interface_language_description": "កំណត់ភាសាដែលអ្នកចូលចិត្តសម្រាប់ចំណុចប្រទាក់នៃការកក់",
"visitors_browser_language": "ភាសាកម្មវិធីរុករករបស់អ្នកទស្សនា",
"name_or_email": "ឈ្មោះ ឬអ៊ីមែល",
"salesforce_round_robin_skip_fallback_to_lead_owner": "ប្រសិនបើមិនមានទំនាក់ទំនងត្រូវបានរកឃើញទេ ត្រឡប់ទៅម្ចាស់ Lead ប្រសិនបើមាន",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "គ្រប់គ្រងតួនាទី និងការអនុញ្ញាតសម្រាប់អង្គការរបស់អ្នក",
"learn_more_permissions": "ស្វែងយល់បន្ថែមអំពីរបៀបដែលការអនុញ្ញាតដំណើរការ",
"role_name": "ឈ្មោះតួនាទី",
"role_name_required": "តម្រូវឱ្យមានឈ្មោះតួនាទី",
"role_name_placeholder": "ឧ. ក្រុមលក់, បុគ្គលិកគាំទ្រ",
"role_description": "ការពិពណ៌នាអំពីតួនាទី",
"role_description_placeholder": "ពិពណ៌នាអំពីតួនាទីនេះសម្រាប់អ្វី និងការអនុញ្ញាតអ្វីខ្លះដែលវាត្រូវការ",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "បានបង្កើតតួនាទីដោយជោគជ័យ",
"role_updated_successfully": "បានធ្វើបច្ចុប្បន្នភាពតួនាទីដោយជោគជ័យ",
"delete_role": "លុបតួនាទី",
"confirm_delete_role": "តើអ្នកប្រាកដថាចង់លុបតួនាទីនេះមែនទេ? សកម្មភាពនេះមិនអាចត្រឡប់ក្រោយវិញបានទេ។",
"confirm_delete_role": "តើអ្នកប្រាកដថាចង់លុបតួនាទី៖ {{roleName}} ឬ? សកម្មភាពនេះមិនអាចត្រឡប់ក្រោយវិញបានទេ។",
"role_deleted_successfully": "បានលុបតួនាទីដោយជោគជ័យ",
"error_creating_role": "មានបញ្ហាក្នុងការបង្កើតតួនាទី",
"error_updating_role": "មានបញ្ហាក្នុងការធ្វើបច្ចុប្បន្នភាពតួនាទី",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "កែសម្រួលនិងកែប្រែការកំណត់លំហូរការងារ",
"pbac_desc_delete_workflows": "លុបលំហូរការងារចេញពីប្រព័ន្ធ",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "ភាពអាចប្រើបាន",
"pbac_resource_out_of_office": "ក្រៅការិយាល័យ",
"pbac_desc_create_webhooks": "បង្កើត webhooks",
"pbac_desc_view_webhooks": "មើល webhooks",
"pbac_desc_update_webhooks": "ធ្វើបច្ចុប្បន្នភាព webhooks",
"pbac_desc_delete_webhooks": "លុប webhooks",
"pbac_desc_create_availability": "បង្កើតភាពអាចប្រើបាន",
"pbac_desc_view_availability": "មើលភាពអាចប្រើបាន",
"pbac_desc_update_availability": "ធ្វើបច្ចុប្បន្នភាពភាពអាចប្រើបាន",
"pbac_desc_delete_availability": "លុបភាពអាចប្រើបាន",
"pbac_desc_create_out_of_office": "បង្កើតស្ថានភាពក្រៅការិយាល័យ",
"pbac_desc_view_out_of_office": "មើលស្ថានភាពក្រៅការិយាល័យ",
"pbac_desc_update_out_of_office": "ធ្វើបច្ចុប្បន្នភាពស្ថានភាពក្រៅការិយាល័យ",
"pbac_desc_delete_out_of_office": "លុបស្ថានភាពក្រៅការិយាល័យ",
"pbac_desc_manage_workflows": "ការចូលប្រើប្រាស់គ្រប់គ្រងពេញលេញទៅលើលំហូរការងារទាំងអស់",
"pbac_desc_create_event_types": "បង្កើតប្រភេទព្រឹត្តិការណ៍",
"pbac_desc_view_event_types": "មើលប្រភេទព្រឹត្តិការណ៍",
@@ -3732,4 +3755,4 @@
"use_voice": "ប្រើសំឡេង",
"current_voice": "សំឡេងបច្ចុប្បន្ន",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ បន្ថែមខ្សែអក្សរថ្មីរបស់អ្នកនៅខាងលើនេះ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+27 -4
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "사용 가능한 사용자를 찾을 수 없습니다. 다른 시간대를 시도해 보시겠습니까?",
"timeslot_unavailable_book_a_new_time": "선택한 시간대는 더 이상 사용할 수 없습니다. <0>새로운 시간을 선택해 주세요</0>",
"timeslot_unavailable_short": "예약됨",
"just_connected_description": "방금 연결되었습니다. 위의 시간대에서 예약하거나 나중에 다시 시도해 주세요.",
"unavailable_timeslot_title": "사용 불가능한 시간대",
"booking_time_out_of_bounds_error": "이 시간에는 이벤트 유형을 예약할 수 없습니다. 다른 시간대를 시도해보시겠습니까?",
"request_body_end_time_internal_error": "내부 오류. 요청 본문에 종료 시간이 포함되어 있지 않습니다.",
@@ -321,6 +322,7 @@
"secret_meeting": "비밀 회의",
"login_instead": "로그인하십시오",
"create_account": "계정 만들기",
"get_started": "시작하기",
"confirm_password": "비밀번호 확인",
"reset_your_password": "이메일 주소로 전송된 지침에 따라 새 비밀번호를 설정하십시오.",
"org_banner_instructions": "{{width}} 너비와 {{height}} 높이의 이미지를 업로드해 주세요.",
@@ -1019,6 +1021,7 @@
"attendees": "참석자",
"add_attendees": "참석자 추가하기",
"show_advanced_settings": "고급 설정 표시",
"hide_advanced_settings": "고급 설정 숨기기",
"event_name": "이벤트 이름",
"event_name_in_calendar": "캘린더의 이벤트 이름",
"event_name_tooltip": "캘린더에 표시될 이름",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "이 기능을 켜면 팀원이 다른 팀원을 볼 수 없게 됩니다.",
"you_cannot_see_team_members": "비공개 팀의 모든 팀원을 볼 수 없습니다.",
"you_cannot_see_teams_of_org": "비공개 조직의 팀을 볼 수 없습니다.",
"allow_booker_to_select_duration": "예약자가 기간을 선택하도록 허용",
"allow_multiple_durations": "여러 시간 옵션 허용",
"impersonate_user_tip": "이 기능의 사용은 모두 감사됩니다.",
"impersonating_user_warning": "\"{{user}}\"의 사용자 이름을 가장하는 중입니다.",
"impersonating_stop_instructions": "중지하려면 여기를 클릭",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "{{connectionType}} 구성을 삭제하시겠습니까? {{connectionType}} 로그인을 사용하는 팀원은 더 이상 Cal.com에 액세스할 수 없습니다.",
"organizer_timezone": "주최자 시간대",
"email_user_cta": "초대 보기",
"accept_invite": "초대 수락",
"join_now": "지금 참여하기",
"successfully_joined": "성공적으로 참여했습니다",
"email_no_user_invite_heading_subteam": "{{parentTeamName}} 조직의 팀에 초대되었습니다",
"email_no_user_invite_heading_org": "{{appName}} 조직에 초대되었습니다",
"email_no_user_invite_subheading": "{{invitedBy}} 님이 귀하를 {{appName}}에서 자신의 팀에 초대했습니다. {{appName}} 앱은 귀하와 귀하의 팀이 이메일을 주고 받지 않고 회의 일정을 잡을 수 있게 해주는 이벤트 정리 스케줄러입니다.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "예약 확인 실패",
"not_enough_seats": "좌석이 부족합니다",
"form_builder_field_already_exists": "이 이름을 가진 필드가 이미 존재합니다",
"guests_field_must_be_multiemail": "게스트 필드는 '여러 이메일' 유형이어야 합니다",
"show_on_booking_page": "예약 페이지에 표시",
"visit_cancelled_booking": "취소된 예약 페이지를 방문할 수 있습니다",
"get_started_zapier_templates": "Zapier 템플릿 시작하기",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "초대 링크가 복사되었습니다",
"invite_link_deleted": "초대 링크가 삭제되었습니다",
"api_key_deleted": "API 키 삭제됨",
"delete_api_key_warning": "이 작업은 API 키를 영구적으로 삭제합니다. 이 키를 사용하는 모든 애플리케이션은 즉시 계정 접근 권한을 잃게 됩니다. 이 작업은 취소할 수 없습니다.",
"invite_link_updated": "초대 링크 설정이 저장되었습니다",
"link_expires_after": "링크 만료 기한...",
"one_day": "1일",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "계속 진행하시면 당사의",
"terms": "이용약관",
"always_show_x_days": "항상 {{x}}일 사용 가능",
"always_show_x_days_description": "매일 {{x}}일 예약 창을 자동으로 앞당겨 캘린더에 항상 다음 {{x}}일의 가용 시간대를 표시합니다.",
"team_subscription_cancelled_successfully": "팀 구독이 성공적으로 취소되었습니다",
"team_subscription_cancellation_error": "팀 구독 취소 중 오류가 발생했습니다",
"unable_to_subscribe_to_the_platform": "플랫폼 플랜 구독 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "다음으로 끝남",
"filter_operator_is_empty": "비어 있음",
"filter_operator_not_empty": "비어 있지 않음",
"translate_description_button": "AI를 사용하여 방문자의 브라우저 언어로 제목/설명 번역하기",
"translate_description_button": "제목 및 설명 자동 번역",
"translate_description_button_description": "AI를 사용하여 방문자의 브라우저 언어로 제목과 설명을 자동 번역합니다.",
"rr_distribution_method": "분배 방식",
"rr_distribution_method_description": "최대 가용성을 위한 분배 최적화 또는 균형 잡힌 할당을 위한 설정이 가능합니다.",
"rr_distribution_method_availability_title": "가용성 최대화",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "좌석 기능은 반복 이벤트를 지원하지 않습니다. 반복으로 설정하려면 좌석 기능을 비활성화하세요.",
"recurring_event_seats_error": "반복 이벤트는 좌석 기능을 지원하지 않습니다. 좌석 기능을 비활성화하거나 이벤트를 비반복으로 설정하세요.",
"segment": "세그먼트",
"saved_filters": "저장된 필터",
"rename": "이름 변경",
"rename_segment": "세그먼트 이름 변경",
"delete_segment": "세그먼트 삭제",
@@ -3338,6 +3349,7 @@
"picklist": "선택 목록",
"most_cancelled_bookings": "가장 많이 취소된 예약",
"interface_language": "인터페이스 언어",
"interface_language_description": "예약 인터페이스의 선호 언어 설정",
"visitors_browser_language": "방문자의 브라우저 언어",
"name_or_email": "이름 또는 이메일",
"salesforce_round_robin_skip_fallback_to_lead_owner": "연락처를 찾을 수 없는 경우, 리드 소유자가 있으면 대체",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "조직의 역할 및 권한 관리",
"learn_more_permissions": "권한 작동 방식에 대해 자세히 알아보기",
"role_name": "역할 이름",
"role_name_required": "역할 이름이 필요합니다",
"role_name_placeholder": "예: 영업팀, 지원 직원",
"role_description": "역할 설명",
"role_description_placeholder": "이 역할의 용도와 필요한 권한을 설명하세요",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "역할이 성공적으로 생성되었습니다",
"role_updated_successfully": "역할이 성공적으로 업데이트되었습니다",
"delete_role": "역할 삭제",
"confirm_delete_role": "이 역할을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
"confirm_delete_role": "역할을 삭제하시겠습니까: {{roleName}}. 이 작업은 취소할 수 없습니다.",
"role_deleted_successfully": "역할이 성공적으로 삭제되었습니다",
"error_creating_role": "역할 생성 오류",
"error_updating_role": "역할 업데이트 오류",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "워크플로 설정 편집 및 수정",
"pbac_desc_delete_workflows": "시스템에서 워크플로 제거",
"pbac_resource_webhook": "웹훅",
"pbac_resource_availability": "가용성",
"pbac_resource_out_of_office": "부재중",
"pbac_desc_create_webhooks": "웹훅 생성",
"pbac_desc_view_webhooks": "웹훅 보기",
"pbac_desc_update_webhooks": "웹훅 업데이트",
"pbac_desc_delete_webhooks": "웹훅 삭제",
"pbac_desc_create_availability": "가용성 생성",
"pbac_desc_view_availability": "가용성 보기",
"pbac_desc_update_availability": "가용성 업데이트",
"pbac_desc_delete_availability": "가용성 삭제",
"pbac_desc_create_out_of_office": "부재중 생성",
"pbac_desc_view_out_of_office": "부재중 보기",
"pbac_desc_update_out_of_office": "부재중 업데이트",
"pbac_desc_delete_out_of_office": "부재중 삭제",
"pbac_desc_manage_workflows": "모든 워크플로에 대한 전체 관리 액세스",
"pbac_desc_create_event_types": "이벤트 타입 생성",
"pbac_desc_view_event_types": "이벤트 타입 보기",
@@ -3732,4 +3755,4 @@
"use_voice": "음성 사용",
"current_voice": "현재 음성",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 여기에 새 문자열을 추가하세요 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+31 -8
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Geen beschikbare gebruikers gevonden. Kun je een ander tijdslot proberen?",
"timeslot_unavailable_book_a_new_time": "Het geselecteerde tijdslot is niet meer beschikbaar. <0>Selecteer alstublieft een nieuwe tijd</0>",
"timeslot_unavailable_short": "Bezet",
"just_connected_description": "Je hebt zojuist verbinding gemaakt. Boek een tijdslot hierboven of probeer het later opnieuw.",
"unavailable_timeslot_title": "Niet-beschikbaar tijdslot",
"booking_time_out_of_bounds_error": "Het afspraaktype kan op dit moment niet worden geboekt. Zou je een ander tijdslot willen proberen?",
"request_body_end_time_internal_error": "Interne fout. Verzoek bevat geen eindtijd",
@@ -321,6 +322,7 @@
"secret_meeting": "Geheime Afspraak",
"login_instead": "In plaats daarvan inloggen",
"create_account": "Account Aanmaken",
"get_started": "Aan de slag",
"confirm_password": "Wachtwoord bevestigen",
"reset_your_password": "Stel uw nieuwe wachtwoord in met de instructies die u per e-mail zijn toegestuurd.",
"org_banner_instructions": "Upload een afbeelding van {{width}} breedte en {{height}} hoogte.",
@@ -958,7 +960,7 @@
"light_event_type_color": "Kleur van evenementtype (Licht Thema)",
"dark_event_type_color": "Kleur van evenementtype (Donker Thema)",
"file_not_named": "Bestandsnaam is niet [idOrSlug]/[user]",
"create_team": "Maak Team",
"create_team": "Team aanmaken",
"name": "Naam",
"nameless_team": "Naamloos team",
"oauth_clients": "OAuth-clients",
@@ -1019,6 +1021,7 @@
"attendees": "Deelnemers",
"add_attendees": "Deelnemers toevoegen",
"show_advanced_settings": "Toon geavanceerde instellingen",
"hide_advanced_settings": "Geavanceerde instellingen verbergen",
"event_name": "Naam van evenement",
"event_name_in_calendar": "Gebeurtenisnaam in agenda",
"event_name_tooltip": "De naam die in kalenders wordt weergegeven",
@@ -1231,7 +1234,7 @@
"contact_sales": "Contact met Sales",
"error_404": "Fout 404",
"default": "standaard keuze",
"set_to_default": "Zet als standaard keuze",
"set_to_default": "Als standaard instellen",
"new_schedule_btn": "Nieuwe planning",
"add_new_schedule": "Voeg een nieuwe planning toe",
"add_new_calendar": "Voeg een nieuwe agenda toe",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Uw teamleden kunnen geen andere teamleden zien wanneer dit ingeschakeld is.",
"you_cannot_see_team_members": "U kunt niet alle teamleden van een privéteam zien.",
"you_cannot_see_teams_of_org": "Je kunt de teams van een privéorganisatie niet zien.",
"allow_booker_to_select_duration": "Booker toestaan om een duur te selecteren",
"allow_multiple_durations": "Meerdere tijdsduren toestaan",
"impersonate_user_tip": "Alle gebruik van deze functie wordt gecontroleerd.",
"impersonating_user_warning": "Gebruikersnaam \"{{user}}\" wordt geïmiteerd.",
"impersonating_stop_instructions": "Klik hier om te stoppen",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Weet u zeker dat u de {{connectionType}}-configuratie wilt verwijderen? Uw teamleden die {{connectionType}}-aanmelding gebruiken hebben niet langer toegang tot Cal.com.",
"organizer_timezone": "Tijdzone organisator",
"email_user_cta": "Uitnodiging weergeven",
"accept_invite": "Uitnodiging accepteren",
"join_now": "Nu deelnemen",
"successfully_joined": "Succesvol deelgenomen",
"email_no_user_invite_heading_subteam": "Je bent uitgenodigd om lid te worden van een team binnen de {{parentTeamName}} organisatie",
"email_no_user_invite_heading_org": "Je bent uitgenodigd om lid te worden van een {{appName}} organisatie",
"email_no_user_invite_subheading": "{{invitedBy}} heeft u uitgenodigd om lid te worden van zijn team op {{appName}}. {{appName}} is de gebeurtenissenplanner die u en uw team in staat stelt vergaderingen te plannen zonder heen en weer te e-mailen.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Boekingsbevestiging mislukt",
"not_enough_seats": "Onvoldoende plaatsen",
"form_builder_field_already_exists": "Een veld met deze naam bestaat al",
"guests_field_must_be_multiemail": "Gastveld moet van het type 'Meerdere e-mails' zijn",
"show_on_booking_page": "Weergeven op boekingspagina",
"visit_cancelled_booking": "Je kunt de pagina van de geannuleerde boeking bezoeken",
"get_started_zapier_templates": "Aan de slag met Zapier-sjablonen",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Uitnodigingslink gekopieerd",
"invite_link_deleted": "Uitnodigingslink verwijderd",
"api_key_deleted": "API-sleutel verwijderd",
"delete_api_key_warning": "Dit zal de API-sleutel permanent verwijderen. Alle applicaties die deze sleutel gebruiken, verliezen onmiddellijk toegang tot je account. Deze actie kan niet ongedaan worden gemaakt.",
"invite_link_updated": "Uitnodigingslinkinstellingen opgeslagen",
"link_expires_after": "Links verlopen na...",
"one_day": "1 dag",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Door verder te gaan ga je akkoord met onze",
"terms": "Voorwaarden",
"always_show_x_days": "Altijd {{x}} dagen beschikbaar",
"always_show_x_days_description": "Verplaatst het {{x}}-daagse boekingsvenster elke dag automatisch vooruit, zodat je agenda altijd de komende {{x}} dagen aan beschikbare tijdslots toont.",
"team_subscription_cancelled_successfully": "Teamabonnement succesvol opgezegd",
"team_subscription_cancellation_error": "Fout bij het opzeggen van het teamabonnement",
"unable_to_subscribe_to_the_platform": "Er is een fout opgetreden bij het proberen te abonneren op het platformplan, probeer het later opnieuw",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Eindigt met",
"filter_operator_is_empty": "Is leeg",
"filter_operator_not_empty": "Is niet leeg",
"translate_description_button": "Vertaal titel/beschrijving naar de browsertaal van de bezoeker met behulp van AI",
"translate_description_button": "Titel en beschrijving automatisch vertalen",
"translate_description_button_description": "Titels en beschrijvingen automatisch vertalen naar de browsertaal van de bezoeker met behulp van AI.",
"rr_distribution_method": "Distributie",
"rr_distribution_method_description": "Maakt het mogelijk om de distributie te optimaliseren voor maximale beschikbaarheid of voor een meer gebalanceerde toewijzing.",
"rr_distribution_method_availability_title": "Maximale beschikbaarheid",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Stoelfunctie ondersteunt geen terugkerend evenement. Schakel stoelfunctie uit om het terugkerend te maken.",
"recurring_event_seats_error": "Terugkerend evenement ondersteunt geen stoelfunctie. Schakel stoelfunctie uit of maak het evenement niet-terugkerend.",
"segment": "Segment",
"saved_filters": "Opgeslagen filters",
"rename": "Hernoemen",
"rename_segment": "Segment hernoemen",
"delete_segment": "Segment verwijderen",
@@ -3338,6 +3349,7 @@
"picklist": "Keuzelijst",
"most_cancelled_bookings": "Meest geannuleerd",
"interface_language": "Interfacetaal",
"interface_language_description": "Stel je voorkeurstaal in voor de boekingsinterface",
"visitors_browser_language": "Taal van de browser van de bezoeker",
"name_or_email": "Naam of e-mail",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Als er geen contactpersoon wordt gevonden, val dan terug op de lead-eigenaar als deze bestaat",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Beheer rollen en rechten voor je organisatie",
"learn_more_permissions": "Meer informatie over hoe rechten werken",
"role_name": "Rolnaam",
"role_name_required": "Rolnaam is vereist",
"role_name_placeholder": "bijv. Verkoopteam, Supportmedewerkers",
"role_description": "Rolbeschrijving",
"role_description_placeholder": "Beschrijf waarvoor deze rol dient en welke rechten deze nodig heeft",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rol succesvol aangemaakt",
"role_updated_successfully": "Rol succesvol bijgewerkt",
"delete_role": "Rol verwijderen",
"confirm_delete_role": "Weet u zeker dat u deze rol wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
"confirm_delete_role": "Weet je zeker dat je de rol wilt verwijderen: {{roleName}}. Deze actie kan niet ongedaan worden gemaakt.",
"role_deleted_successfully": "Rol succesvol verwijderd",
"error_creating_role": "Fout bij het aanmaken van rol",
"error_updating_role": "Fout bij het bijwerken van rol",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Werkstroominstellingen bewerken en wijzigen",
"pbac_desc_delete_workflows": "Werkstromen uit het systeem verwijderen",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Beschikbaarheid",
"pbac_resource_out_of_office": "Afwezigheid",
"pbac_desc_create_webhooks": "Webhooks maken",
"pbac_desc_view_webhooks": "Webhooks bekijken",
"pbac_desc_update_webhooks": "Webhooks bijwerken",
"pbac_desc_delete_webhooks": "Webhooks verwijderen",
"pbac_desc_create_availability": "Beschikbaarheid aanmaken",
"pbac_desc_view_availability": "Beschikbaarheid bekijken",
"pbac_desc_update_availability": "Beschikbaarheid bijwerken",
"pbac_desc_delete_availability": "Beschikbaarheid verwijderen",
"pbac_desc_create_out_of_office": "Afwezigheid aanmaken",
"pbac_desc_view_out_of_office": "Afwezigheid bekijken",
"pbac_desc_update_out_of_office": "Afwezigheid bijwerken",
"pbac_desc_delete_out_of_office": "Afwezigheid verwijderen",
"pbac_desc_manage_workflows": "Volledige beheertoegang tot alle werkstromen",
"pbac_desc_create_event_types": "Gebeurtenistypes aanmaken",
"pbac_desc_view_event_types": "Gebeurtenistypes bekijken",
@@ -3560,8 +3583,8 @@
"rr_load_balancing_disabled_with_groups": "Load balancing is niet beschikbaar met round robin groepen",
"round_robin_settings_description": "Pas de standaard round robin-instellingen aan voor dit team",
"round_robin_settings_updated_successfully": "Round Robin-instellingen succesvol bijgewerkt",
"booking_creation_time": "Tijdstip van boekingscreatie",
"meeting_start_time": "Starttijd van de vergadering",
"booking_creation_time": "Aanmaaktijd boeking",
"meeting_start_time": "Starttijd vergadering",
"members_affected_by_disabling_delegation_credential": "Getroffen leden",
"no_members_affected_by_disabling_delegation_credential": "Geen leden getroffen door het uitschakelen van delegatiereferentie",
"download_expense_log": "Download uitgavenlogboek",
@@ -3732,4 +3755,4 @@
"use_voice": "Gebruik stem",
"current_voice": "Huidige stem",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Voeg uw nieuwe strings hierboven toe ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
+31 -8
View File
@@ -111,6 +111,7 @@
"no_available_users_found_error": "Ingen tilgjengelige brukere funnet. Kan du prøve et annet tidspunkt?",
"timeslot_unavailable_book_a_new_time": "Den valgte tidslukken er ikke lenger tilgjengelig. <0>Vennligst velg en ny tid</0>",
"timeslot_unavailable_short": "Opptatt",
"just_connected_description": "Du har nettopp koblet til. Vennligst bestill fra tilgjengelige tider ovenfor eller prøv igjen senere.",
"unavailable_timeslot_title": "Utilgjengelig tidsluke",
"booking_time_out_of_bounds_error": "Avtaletypen kan ikke bestilles på dette tidspunktet. Kan du prøve et annet tidspunkt?",
"request_body_end_time_internal_error": "Intern feil. Forespørselens kropp inneholder ikke sluttid",
@@ -321,6 +322,7 @@
"secret_meeting": "Hemmelig Møte",
"login_instead": "Logg på i stedet",
"create_account": "Opprett Bruker",
"get_started": "Kom i gang",
"confirm_password": "Bekreft passord",
"reset_your_password": "Sett ditt nye passord med instruksjonene sendt til din e-postadresse.",
"org_banner_instructions": "Vennligst last opp et bilde med {{width}} bredde og {{height}} høyde.",
@@ -1019,6 +1021,7 @@
"attendees": "Deltakere",
"add_attendees": "Legg til deltaker",
"show_advanced_settings": "Vis avanserte innstillinger",
"hide_advanced_settings": "Skjul avanserte innstillinger",
"event_name": "Hendelses navn",
"event_name_in_calendar": "Med hendelsesnavn",
"event_name_tooltip": "Navnet som vises i kalenderne",
@@ -1231,7 +1234,7 @@
"contact_sales": "Kontakt Salg",
"error_404": "Feil 404",
"default": "Standard",
"set_to_default": "Sett til Standard",
"set_to_default": "Sett som standard",
"new_schedule_btn": "Ny tidsplan",
"add_new_schedule": "Legg til ny tidsplan",
"add_new_calendar": "Legg til en ny kalender",
@@ -1329,7 +1332,7 @@
"make_team_private_description": "Medlemmene i teamet ditt vil ikke kunne se andre teammedlemmer når dette er slått på.",
"you_cannot_see_team_members": "Du kan ikke se alle teammedlemmene i et privat team.",
"you_cannot_see_teams_of_org": "Du kan ikke se teamene til en privat organisasjon.",
"allow_booker_to_select_duration": "La bookeren velge varighet",
"allow_multiple_durations": "Tillat flere varigheter",
"impersonate_user_tip": "All bruk av denne funksjonen er revidert.",
"impersonating_user_warning": "Etterligner brukernavnet \"{{user}}\".",
"impersonating_stop_instructions": "Klikk her for å stoppe",
@@ -2150,6 +2153,9 @@
"delete_sso_configuration_confirmation_description": "Er du sikker på at du vil slette {{connectionType}}-konfigurasjonen? Teammedlemmene dine som bruker {{connectionType}}-innlogging vil ikke lenger kunne få tilgang til Cal.com.",
"organizer_timezone": "Tidssone for arrangør",
"email_user_cta": "Se invitasjon",
"accept_invite": "Aksepter invitasjon",
"join_now": "Bli med nå",
"successfully_joined": "Vellykket tilslutning",
"email_no_user_invite_heading_subteam": "Du har blitt invitert til å bli med i et team i {{parentTeamName}}-organisasjonen",
"email_no_user_invite_heading_org": "Du har blitt invitert til å bli med i en {{appName}}-organisasjon",
"email_no_user_invite_subheading": "{{invitedBy}} har invitert deg til å bli med i teamet deres på {{appName}}. {{appName}} er den hendelsesorganiserende planleggeren som gjør det mulig for deg og teamet ditt å planlegge møter uten e-postfrem og tilbake.",
@@ -2202,6 +2208,7 @@
"booking_confirmation_failed": "Booking-bekreftelse feilet",
"not_enough_seats": "Ikke nok seter",
"form_builder_field_already_exists": "Et felt med dette navnet eksisterer allerede",
"guests_field_must_be_multiemail": "Gjestfeltet må være av typen 'Flere e-poster'",
"show_on_booking_page": "Vis på bestillingssiden",
"visit_cancelled_booking": "Du kan besøke siden for avbestilt booking",
"get_started_zapier_templates": "Kom i gang med Zapier-maler",
@@ -2379,6 +2386,7 @@
"invite_link_copied": "Invitasjonslenke kopiert",
"invite_link_deleted": "Invitasjonslenke slettet",
"api_key_deleted": "API-nøkkel slettet",
"delete_api_key_warning": "Dette vil permanent slette API-nøkkelen. Alle applikasjoner som bruker denne nøkkelen vil umiddelbart miste tilgang til kontoen din. Denne handlingen kan ikke angres.",
"invite_link_updated": "Lenkeinnstillinger lagret",
"link_expires_after": "Lenker satt til å utløpe etter...",
"one_day": "1 dag",
@@ -2688,7 +2696,7 @@
"enterprise": "Bedrift",
"enterprise_description": "Oppgrader til Enterprise for å opprette din organisasjon",
"create_your_org": "Opprett din Organisasjon",
"create_org": "Opprett Organisasjon",
"create_org": "Opprett organisasjon",
"create_your_org_description": "Oppgrader til Organisasjoner og få et underdomene, enhetlig fakturering, innsikt, omfattende whitelabeling og mer",
"create_your_enterprise_description": "Oppgrader til Enterprise og få tilgang til Active Directory Sync, SCIM Automatisk Brukerprovisjonering, Cal.ai Voice Agents, Admin APIer og mer!",
"other_payment_app_enabled": "Du kan kun aktivere én betalingsapp per hendelsestype",
@@ -2922,6 +2930,7 @@
"proceeding_agreement": "Ved å fortsette godtar du våre",
"terms": "Vilkår",
"always_show_x_days": "Alltid {{x}} dager tilgjengelig",
"always_show_x_days_description": "Flytter automatisk {{x}}-dagers bestillingsvinduet fremover hver dag, slik at kalenderen din alltid viser de neste {{x}} dagene med tilgjengelige tider.",
"team_subscription_cancelled_successfully": "Teamabonnement kansellert",
"team_subscription_cancellation_error": "Feil ved kansellering av teamabonnement",
"unable_to_subscribe_to_the_platform": "En feil oppstod under forsøket på å abonnere på plattformplanen, vennligst prøv igjen senere",
@@ -3143,7 +3152,8 @@
"filter_operator_ends_with": "Slutter med",
"filter_operator_is_empty": "Er tom",
"filter_operator_not_empty": "Er ikke tom",
"translate_description_button": "Oversett tittel/beskrivelse til besøkendes nettleserspråk ved hjelp av AI",
"translate_description_button": "Auto-oversett tittel og beskrivelse",
"translate_description_button_description": "Oversett automatisk titler og beskrivelser til besøkendes nettleserspråk ved hjelp av AI.",
"rr_distribution_method": "Distribusjon",
"rr_distribution_method_description": "Gjør det mulig å optimalisere distribusjonen for maksimal tilgjengelighet eller for å oppnå en mer balansert fordeling.",
"rr_distribution_method_availability_title": "Maksimer tilgjengelighet",
@@ -3309,6 +3319,7 @@
"seats_doesnt_support_recurring": "Seterfunksjonen støtter ikke gjentakende arrangement. Deaktiver seterfunksjonen for å gjøre det gjentakende.",
"recurring_event_seats_error": "Gjentakende arrangement støtter ikke seterfunksjonen. Deaktiver seterfunksjonen eller gjør arrangementet ikke-gjentakende.",
"segment": "Segment",
"saved_filters": "Lagrede filtre",
"rename": "Gi nytt navn",
"rename_segment": "Gi nytt navn til segment",
"delete_segment": "Slett segment",
@@ -3338,6 +3349,7 @@
"picklist": "Valgliste",
"most_cancelled_bookings": "Flest kansellerte bookinger",
"interface_language": "Grensesnittspråk",
"interface_language_description": "Angi ditt foretrukne språk for bestillingsgrensesnittet",
"visitors_browser_language": "Besøkendes nettleserspråk",
"name_or_email": "Navn eller e-post",
"salesforce_round_robin_skip_fallback_to_lead_owner": "Hvis ingen kontakt blir funnet, fall tilbake til leadeier hvis den eksisterer",
@@ -3452,6 +3464,7 @@
"roles_and_permissions_description": "Administrer roller og tillatelser for organisasjonen din",
"learn_more_permissions": "Lær mer om hvordan tillatelser fungerer",
"role_name": "Rollenavn",
"role_name_required": "Rollenavn er påkrevd",
"role_name_placeholder": "f.eks. Salgsteam, Kundestøtte",
"role_description": "Rollebeskrivelse",
"role_description_placeholder": "Beskriv hva denne rollen er for og hvilke tillatelser den trenger",
@@ -3490,7 +3503,7 @@
"role_created_successfully": "Rolle opprettet",
"role_updated_successfully": "Rolle oppdatert",
"delete_role": "Slett rolle",
"confirm_delete_role": "Er du sikker på at du vil slette denne rollen? Denne handlingen kan ikke angres.",
"confirm_delete_role": "Er du sikker på at du vil slette rollen: {{roleName}}? Denne handlingen kan ikke angres.",
"role_deleted_successfully": "Rolle slettet",
"error_creating_role": "Feil ved oppretting av rolle",
"error_updating_role": "Feil ved oppdatering av rolle",
@@ -3509,10 +3522,20 @@
"pbac_desc_update_workflows": "Redigere og endre arbeidsflytinnstillinger",
"pbac_desc_delete_workflows": "Fjerne arbeidsflyter fra systemet",
"pbac_resource_webhook": "Webhook",
"pbac_resource_availability": "Tilgjengelighet",
"pbac_resource_out_of_office": "Ute av kontoret",
"pbac_desc_create_webhooks": "Opprett webhook'er",
"pbac_desc_view_webhooks": "Se webhook'er",
"pbac_desc_update_webhooks": "Oppdater webhooks",
"pbac_desc_delete_webhooks": "Slett webhooks",
"pbac_desc_create_availability": "Opprett tilgjengelighet",
"pbac_desc_view_availability": "Vis tilgjengelighet",
"pbac_desc_update_availability": "Oppdater tilgjengelighet",
"pbac_desc_delete_availability": "Slett tilgjengelighet",
"pbac_desc_create_out_of_office": "Opprett fravær",
"pbac_desc_view_out_of_office": "Vis fravær",
"pbac_desc_update_out_of_office": "Oppdater fravær",
"pbac_desc_delete_out_of_office": "Slett fravær",
"pbac_desc_manage_workflows": "Full administrasjonstilgang til alle arbeidsflyter",
"pbac_desc_create_event_types": "Opprett hendelsestyper",
"pbac_desc_view_event_types": "Vis hendelsestyper",
@@ -3560,8 +3583,8 @@
"rr_load_balancing_disabled_with_groups": "Lastbalansering er ikke tilgjengelig med round robin-grupper",
"round_robin_settings_description": "Tilpass standardinnstillingene for rundgang for dette teamet",
"round_robin_settings_updated_successfully": "Rundgangsinnstillinger oppdatert",
"booking_creation_time": "Tidspunkt for opprettelse av booking",
"meeting_start_time": "Møtestarttid",
"booking_creation_time": "Tidspunkt for opprettelse av bestilling",
"meeting_start_time": "Møtets starttidspunkt",
"members_affected_by_disabling_delegation_credential": "Berørte medlemmer",
"no_members_affected_by_disabling_delegation_credential": "Ingen medlemmer berørt av deaktivering av delegeringslegitimasjon",
"download_expense_log": "Last ned utgiftslogg",
@@ -3732,4 +3755,4 @@
"use_voice": "Bruk stemme",
"current_voice": "Nåværende stemme",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Legg til dine nye strenger over her ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}

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