Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 5f316902ea fix: add array guard in getActivityTargetObjectRecords to prevent crash on non-array field values
https://sonarly.com/issue/15203?type=bug

When rendering relation-from-many fields on the People list view, `useActivityTargetObjectRecords` is called unconditionally with `fieldValue` before validating it's an array, causing `TypeError: a.map is not a function` when the value is truthy but not an array.

Fix: Added an `Array.isArray` guard on the `targets` variable in `getActivityTargetObjectRecords.ts` before the `.map()` call on line 75.

**What changed:** Renamed `targets` to `rawTargets` for the initial derivation, then added a guard that returns `[]` if `rawTargets` is not an array. The validated value is then assigned back to `targets` for the rest of the function.

**Why this fix:** The `RelationFromManyFieldDisplay` component calls `useActivityTargetObjectRecords` unconditionally for ALL relation-from-many fields (not just activity target fields), passing `fieldValue` with an unsafe type cast (`fieldValue as NoteTarget[] | TaskTarget[]`). When `fieldValue` is truthy but not an array (e.g., after a failed/partial GraphQL fetch or a newly created field with unexpected data shape), the `activityTargets` parameter passes the truthiness check on line 32 and flows directly into `.map()`, causing `TypeError: a.map is not a function`.

The `Array.isArray` guard is placed in `getActivityTargetObjectRecords` rather than the component because:
1. It's the function that makes the assumption about the data being an array
2. The component's existing `isArray(fieldValue)` check on line 70 runs AFTER the hook has already executed and crashed
3. Hooks cannot be called conditionally in React, so the component can't skip the hook call
2026-03-16 13:43:50 +00:00
nitinandGitHub e552704201 fix: add viewFieldGroupId to ViewField fragment and connect page layout selectors to live metadata store (#18676) 2026-03-16 14:24:41 +01:00
ddedecbb36 i18n - docs translations (#18677)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-16 14:14:29 +01:00
5011e1d77b i18n - docs translations (#18674)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-16 13:02:42 +01:00
a07337fea0 fix: return method-specific MCP responses (#18671)
## Summary

Fixes #18524 

Fixes the MCP response contract for non-`initialize` methods.

Previously, `/mcp` returned initialize-style metadata for methods like
`tools/list`, which caused strict MCP clients to reject the response
shape. The endpoint also returned `201 Created` for RPC calls even
though no resource was being created.

## Changes

- return only method-specific payloads for MCP list methods
  - `tools/list` -> `{ tools: [...] }`
  - `prompts/list` -> `{ prompts: [] }`
  - `resources/list` -> `{ resources: [] }`
- keep MCP server metadata only on `initialize`
- make `/mcp` return `200 OK` instead of `201 Created`
- add regression tests for:
  - `tools/list` response shape
  - `prompts/list` response shape
  - `resources/list` response shape

## Why

Strict MCP clients expect:
- standard RPC transport semantics over HTTP
- method-specific JSON-RPC result payloads

Returning initialize metadata for non-`initialize` methods breaks that
expectation and can cause client deserialization or protocol validation
failures.

## Verification

- reproduced the issue locally against `/mcp`
- verified `tools/list` was previously returning initialize-style fields
- verified `tools/list` now returns only `result.tools`
- verified `/mcp` now returns `200 OK`
- ran targeted Jest tests:

```bash
cd /Users/apple/MyProjects/OpenSource/twenty/packages/twenty-server
npx jest --runInBand src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts src/engine/api/mcp/services/__tests__/mcp-tool-executor.service.spec.ts

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-16 12:46:35 +01:00
Abdullah.andGitHub 6e36ad9fa2 fix: mailparser vulnerable to cross-site scripting (#18664)
Resolves [Dependabot Alert
595](https://github.com/twentyhq/twenty/security/dependabot/595) and
[Dependabot Alert
596](https://github.com/twentyhq/twenty/security/dependabot/596).
2026-03-16 09:59:36 +01:00
Charles BochetandGitHub 5c745059ad refactor: remove "core" naming from views and eliminate converter layer (#18667)
## Summary

- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion

### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`

### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
2026-03-16 09:57:18 +01:00
6340b9e2f7 i18n - translations (#18669)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-16 09:49:16 +01:00
Abdullah.andGitHub 2c2f66b584 fix: DOMPurify contains a cross-site scripting vulnerability (#18665)
Resolves [Dependabot Alert
597](https://github.com/twentyhq/twenty/security/dependabot/597),
[Dependabot Alert
598](https://github.com/twentyhq/twenty/security/dependabot/598),
[Dependabot Alert
599](https://github.com/twentyhq/twenty/security/dependabot/599) and
[Dependabot Alert
600](https://github.com/twentyhq/twenty/security/dependabot/600).
2026-03-16 09:49:03 +01:00
Félix MalfaitGitHubClaudeclaude[bot] <41898282+claude[bot]@users.noreply.github.com>github-actionscubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
95a35f8a1d Implement OAuth 2.0 Dynamic Client Registration (RFC 7591) (#18608)
## Summary
This PR implements OAuth 2.0 Dynamic Client Registration (RFC 7591) and
OAuth 2.0 Protected Resource Metadata (RFC 9728) support, enabling
third-party applications to dynamically register as OAuth clients
without manual configuration.

## Key Changes

### OAuth Dynamic Client Registration
- **New Controller**: `OAuthRegistrationController` at `POST
/oauth/register` endpoint
  - Validates client metadata according to RFC 7591 specifications
  - Enforces PKCE-only public client model (no client secrets)
- Supports only `authorization_code` grant type and `code` response type
  - Rate limits registrations to 10 per hour per IP address
  - Returns `client_id` and registration metadata in response

- **Input Validation**: `OAuthRegisterInput` DTO with constraints on:
  - Client name (max 256 chars)
  - Redirect URIs (max 20, validated for security)
  - Grant types, response types, scopes, and auth methods
  - Logo and client URIs (max 2048 chars)

- **Discovery Endpoint Update**: Added `registration_endpoint` to OAuth
discovery metadata

### Stale Registration Cleanup
- **Cleanup Service**: Automatically removes OAuth-only registrations
older than 30 days that have no active installations
- **Cron Job**: Runs daily at 02:30 AM UTC with batch processing (100
records per batch)
- **CLI Command**: `cron:stale-registration-cleanup` to manually trigger
cleanup

### MCP (Model Context Protocol) Authentication
- **New Guard**: `McpAuthGuard` implements RFC 9728 compliance
  - Wraps JWT authentication with proper error responses
- Returns `WWW-Authenticate` header with protected resource metadata URL
on 401
  - Enables OAuth-protected MCP endpoints

### Protected Resource Metadata
- **New Endpoint**: `GET /.well-known/oauth-protected-resource` (RFC
9728)
  - Advertises MCP resource as OAuth-protected
  - Lists supported scopes and bearer token methods
  - Enables OAuth clients to discover authorization requirements

### Application Registration Updates
- **New Source Type**: `OAUTH_ONLY` enum value for OAuth-only
registrations
- **Install Service**: Skips artifact installation for OAuth-only apps
(no code artifacts)

### Frontend Updates
- **Authorization Page**: Support both snake_case (standard OAuth) and
camelCase (legacy) query parameters
  - `client_id` / `clientId`
  - `code_challenge` / `codeChallenge`
  - `redirect_uri` / `redirectUrl`

## Implementation Details

- **Rate Limiting**: Uses token bucket algorithm with 10 registrations
per 3,600,000ms window per IP
- **Scope Validation**: Requested scopes are capped to allowed OAuth
scopes; defaults to all scopes if not specified
- **Redirect URI Validation**: Uses existing `validateRedirectUri`
utility for security
- **Cache Headers**: Registration responses include `Cache-Control:
no-store` and `Pragma: no-cache`
- **Batch Processing**: Cleanup operations process 100 records at a time
to avoid memory issues
- **Grace Period**: 30-day grace period before cleanup to allow time for
client activation

https://claude.ai/code/session_01PxcuWFFRuXMASMaMGTLYk2

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-03-16 09:42:28 +01:00
Abdullah.andGitHub 87c519b72f fix: multer vulnerable to denial of service via uncontrolled recursion (#18659)
Resolves [Dependabot Alert
608](https://github.com/twentyhq/twenty/security/dependabot/608).
2026-03-16 09:08:40 +01:00
Abdullah.andGitHub 1b20bdaf6d fix: @isaacs/brace-expansion has uncontrolled resource consumption (#18660)
Resolves [Dependabot Alert
414](https://github.com/twentyhq/twenty/security/dependabot/414).
2026-03-16 09:08:25 +01:00
Abdullah.andGitHub 67866ff59c fix: expr-eval related dependabot alerts (#18661)
Resolves [Dependabot Alert
593](https://github.com/twentyhq/twenty/security/dependabot/593) and
[Dependabot Alert
594](https://github.com/twentyhq/twenty/security/dependabot/594).

expr-eval was last published six years ago and had changes five years
ago. NPM contains a fork that published the changes from five years ago
under the same name with `-fork` suffix. This PR uses that fork as
suggested by Dependabot.
2026-03-16 09:08:19 +01:00
Abdullah.andGitHub e6f1bdd1c8 fix: yauzl contains an off-by-one error (#18662)
Resolves [Dependabot Alert
633](https://github.com/twentyhq/twenty/security/dependabot/633).
2026-03-16 09:08:12 +01:00
Charles BochetandGitHub ba9aa41bba refactor: metadata store cleanup, SSE unification, mock metadata loading & login redirect fix (#18651)
## Summary
- **SSE unification**: Replaced 11 individual SSE effect components with
a single generic `MetadataStoreSSEEffect`
- **Metadata store cleanup**: Merged `metadataCollectionHashesState`
into `metadataStoreState` (currentCollectionHash / draftCollectionHash
per entity), moved `objectMetadataItemsSelector` to `object-metadata`
domain, converted `navigationMenuItemsState` to a derived selector
- **Naming clarity**: Renamed `isAppMetadataReadyState` →
`isMinimalMetadataReadyState`, `MetadataGater` → `MinimalMetadataGater`,
`useIsLogged` → `useHasAccessTokenPair`,
`patchMetadataStoreFromSSEEvent` now takes named object params
- **Mock metadata loading**: Added `generate-navigation-menu-items.ts`
script, rewrote `useLoadMockedMinimalMetadata` to load full
objects/fields/indexes/views/navItems from generated mock data, enabling
proper sign-in background rendering (table columns, view picker,
navigation)
- **Login/logout transitions**: `MinimalMetadataLoadEffect` manages
mocked↔real metadata transitions based on auth state,
`MainContextStoreProvider` computes context on auth pages for view
picker support
- **Login redirect fix**: `handleLoadWorkspaceAfterAuthentication` now
re-enables `isAppEffectRedirectEnabled` after `loadCurrentUser()`
completes, fixing the blocked post-login navigation
- **Dead code removal**: Deleted `useRefreshPageLayouts`,
`useApplyPageLayouts`, `useStaleMetadataEntities`,
`metadataCollectionHashesState`, and all individual SSE effects

## Test plan
- [x] Login from welcome page redirects to companies page
- [x] Logout transitions cleanly to mocked metadata on welcome page
- [x] Sign-in background shows table columns, view picker, and
navigation items
- [x] SSE events still update metadata store entries correctly
- [x] Navigation menu items persist across page refreshes
- [ ] CI: lint, typecheck, tests pass
2026-03-16 00:38:11 +01:00
Charles BochetandGitHub 06efee1eef feat: hash-based metadata staleness detection (#18649)
## Summary

Replace the single `metadataVersion` integer with per-entity-type
**collection hashes** for granular metadata staleness detection. The
backend already generates a UUID per flat entity map on each cache
recompute (`crypto.randomUUID()` in `WorkspaceCacheService`); we now
expose these via the minimal metadata endpoint and SSE events so the
frontend can compare and know exactly which entity types are stale.

### Key changes

**Backend:**
- `WorkspaceCacheService.getCacheHashes()` — new public method that
reads only `:hash` keys from Redis without fetching full data
- `MinimalMetadataDTO` — added `collectionHashes: Record<string,
string>` (JSON scalar mapping `AllMetadataName` → collection hash),
removed `metadataVersion`
- `MetadataEventDTO` — added optional `updatedCollectionHash` field to
SSE events
- `MetadataEventsToDbListener` — reads the collection hash for the
affected entity type after cache invalidation and attaches it to the SSE
event before publishing
- `MinimalMetadataService` — no longer queries the workspace table; uses
`getCacheHashes()` for all flat entity maps and maps cache keys to
`AllMetadataName` locally

**Frontend:**
- `metadataCollectionHashesState` — new Jotai atom with
`atomWithStorage` + `getOnInit: true` storing
`Partial<Record<MetadataEntityKey, string>>`
- `mapAllMetadataNameToEntityKey()` — explicit mapping from backend
`AllMetadataName` to frontend `MetadataEntityKey` (23 entries)
- `useLoadMinimalMetadata` — stores `collectionHashes` from server,
computes `staleEntityKeys` by comparing local vs server hashes
- `patchMetadataStoreFromSSEEvent()` — accepts optional
`updatedCollectionHash` and updates `metadataCollectionHashesState`
- All 11 SSE effect components — pass
`eventDetail.updatedCollectionHash` through to the patch function
- `useStaleMetadataEntities` — new hook returning entity keys missing
from collection hashes (not yet loaded/synced)
- `resetMetadataStore()` — also clears collection hashes
- Deleted `metadataVersionState` (superseded by collection hashes)

### Design decisions

- **No change to hash generation** — existing `crypto.randomUUID()` is
sufficient. Hashes are persisted in Redis, survive server restarts, and
change only on `invalidateAndRecompute`.
- **"Collection hash" naming** — used consistently to clarify the hash
represents an entire entity collection (e.g., all views), not a single
record.
- **Mapping localized** — backend `WorkspaceCacheKeyName` →
`AllMetadataName` mapping lives in the minimal metadata service.
Frontend `AllMetadataName` → `MetadataEntityKey` mapping lives in a
local utility. Nothing in `twenty-shared`.
- **Backward compatible** — `collectionHashes` is additive;
`updatedCollectionHash` is nullable.
2026-03-14 23:38:37 +01:00
Charles BochetandGitHub 7a3540788a feat: uniformize metadata store with flat types, SSE alignment, presentation endpoint & localStorage (#18647)
## Summary

Uniformizes the metadata store to support **all** backend flat metadata
types, introduces a **minimal metadata endpoint** for fast initial
renders, replaces custom localStorage persistence with **Jotai's
built-in `atomWithStorage`**, and wires up a
**MinimalMetadataLoadEffect** for stale-while-revalidate loading.

### Key changes

- **All flat metadata types**: Added `FlatCommandMenuItem`,
`FlatFrontComponent`, `FlatWebhook`, `FlatRole`, `FlatRoleTarget`,
`FlatAgent`, `FlatSkill`, `FlatRowLevelPermissionPredicate`,
`FlatRowLevelPermissionPredicateGroup` — every entity in the backend
`MetadataEntityTypeMap` now has a corresponding frontend flat type
registered in `ALL_METADATA_ENTITY_KEYS` and `MetadataEntityTypeMap`.

- **Minimal metadata endpoint** (`minimalMetadata` GraphQL query): New
backend module (`MinimalMetadataModule`) returns lightweight object
metadata (names, icons, labels, flags) and basic views (id, type, key,
objectMetadataId) plus a `metadataVersion`. This enables fast first
paint before full metadata loads.

- **Jotai `atomWithStorage` for persistence**: Replaced the custom
`MetadataLocalStorageEffect` with Jotai's built-in `atomWithStorage` on
both `metadataStoreState` (family) and `metadataVersionState`. Added
`localStorageOptions` support to `createAtomFamilyState` for `{
getOnInit: true }` synchronous hydration. Each entity atom auto-persists
under keys like `metadataStoreState__objectMetadataItems`.

- **MinimalMetadataLoadEffect**: New effect mounted before
`MetadataProviderInitialEffects` that checks if the store already has
data (from Jotai localStorage hydration). If empty, it fetches minimal
metadata from the new endpoint. The full metadata load continues in
parallel, eventually enriching the store with complete data.

- **SSE effects alignment**: All metadata entity types now have
corresponding SSE effects that directly patch the metadata store via
`patchMetadataStoreFromSSEEvent`.

- **Existing selectors and joining logic**:
`objectMetadataItemsWithFieldsSelector`, `viewsWithRelationsSelector`,
`pageLayoutsWithRelationsSelector` reconstruct nested data from flat
entities for components that need it.

### Loading flow

```
App mount
  → Jotai atomWithStorage hydrates store from localStorage (sync, getOnInit)
  → MinimalMetadataLoadEffect
      → Store has data? → skip (app renders immediately)
      → Store empty? → fetch minimalMetadata endpoint → populate objects + views
  → MetadataProviderInitialEffects (full metadata load, runs in parallel)
  → LazyMetadataLoadEffect (page layouts, logic functions, nav menu, etc.)
  → IsAppMetadataReadyEffect (sets isAppMetadataReady)
```

## Test plan

- [ ] Verify app loads with empty localStorage (should fetch minimal
metadata, then full)
- [ ] Verify app loads with populated localStorage (should skip minimal
fetch, render immediately)
- [ ] Verify SSE events correctly update metadata store for all entity
types
- [ ] Verify logout clears metadata store (atom reset propagates to
localStorage)
- [ ] Verify all metadata selectors return correct joined data
- [ ] CI: lint, typecheck, tests pass
2026-03-14 20:32:25 +01:00
6711b40922 i18n - docs translations (#18645)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-14 16:49:41 +01:00
c753b2bee1 i18n - docs translations (#18644)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-14 13:57:57 +01:00
70a060b4ee docs: fix contributor docs links and typos (#18637)
## Summary

This PR fixes several small documentation issues in the contributor and
setup guides:

- fixes broken docs links in the root README
- corrects multiple typos and capitalization issues in contributor docs
- fixes malformed Markdown for the Redis command in local setup
- improves wording in the Docker Compose self-hosting guide

## Changes

- updated README installation links to the current docs routes
- changed `Open-source` to `open-source`
- fixed `specially` -> `especially` in the frontend style guide
- normalized `MacOS` -> `macOS`, `powershell` -> `PowerShell`, and
`Postgresql` -> `PostgreSQL`
- replaced the invalid `localhost:5432` Markdown link with inline code
- fixed the malformed fenced code block for `brew services start redis`
- cleaned up Redis naming/capitalization and a few grammar issues in the
setup docs
- improved the warning and environment-variable wording in the Docker
Compose guide

## Testing

- not run; docs-only changes

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-14 12:54:31 +01:00
Charles BochetandGitHub 40ff109179 feat: migrate objectMetadata reads to granular metadata store (#18643)
## Summary

Consolidates `objectMetadataItems` onto the metadata store as the
**single source of truth**, replacing the previous dual-store approach
(separate `objectMetadataItemsState` atom + untyped
`metadataStoreState`).

### Architecture: three-layer design

```
┌─────────────────────────────────────────────────────────┐
│ Store Layer (granular, typed)                           │
│  objectMetadataItems → FlatObjectMetadataItem[]         │
│  fieldMetadataItems  → FlatFieldMetadataItem[]          │
│  indexMetadataItems  → FlatIndexMetadataItem[]           │
└────────────────┬────────────────────────────────────────┘
                 │ .current (never draft)
┌────────────────▼────────────────────────────────────────┐
│ Selectors (typed read-only)                             │
│  objectMetadataItemsSelector                            │
│  fieldMetadataItemsSelector                             │
│  indexMetadataItemsSelector                             │
│  metadataStoreStatusFamilySelector                      │
│  isSystemObjectByNameSingularFamilySelector (narrow)    │
│  activeObjectNameSingularsSelector (narrow)             │
└────────────────┬────────────────────────────────────────┘
                 │ joins objects + fields + indexes + permissions
┌────────────────▼────────────────────────────────────────┐
│ Joining Selector                                        │
│  objectMetadataItemsWithFieldsSelector                  │
│  → produces full ObjectMetadataItem[] with              │
│    readableFields / updatableFields from permissions    │
│  → 12 existing selectors repointed here                 │
└─────────────────────────────────────────────────────────┘
```

### Key changes

- **Granular flat types** (`FlatObjectMetadataItem`,
`FlatFieldMetadataItem`, `FlatIndexMetadataItem`) — objects stored
without embedded fields/indexes, matching backend "Flat" naming
convention
- **Typed write API** — `updateDraft` is now generic via
`MetadataEntityTypeMap`, giving compile-time safety on what data shape
goes to each key
- **Write path refactored** — fetch → split into flat entities via
`splitObjectMetadataItemWithRelated` → write to metadata store directly.
No more dual-write through `objectMetadataItemsState`. Permissions
enrichment moved from write path into the joining selector.
- **SSE effects write directly** — `ObjectMetadataItemSSEEffect` and
`FieldMetadataSSEEffect` now patch the store from the SSE event payload
(create/update/delete) instead of triggering a full re-fetch
- **`objectMetadataItemsState` bridge** — converted from writable
`createAtomState` to read-only `createAtomSelector` that delegates to
the joining selector. All 100+ existing consumers continue to work
without code changes.
- **All selectors use Twenty state API** — `createAtomSelector` /
`createAtomFamilySelector` throughout, no raw `atom()`
- **Narrow selectors** for hot paths —
`isSystemObjectByNameSingularFamilySelector` and
`activeObjectNameSingularsSelector` read from flat objects only,
avoiding re-renders when fields/indexes/permissions change. Placed in
`object-metadata/states/` as higher-level business selectors.
- **Test helper** — `setTestObjectMetadataItemsInMetadataStore` for
tests that need to set up composite object metadata through the store
(clearly named as a testing utility)

### Naming conventions

- `ObjectMetadataItemWithRelated` — type for objects with embedded
fields/indexes (input to split utility)
- `FlatObjectMetadataItem` / `FlatFieldMetadataItem` /
`FlatIndexMetadataItem` — granular store types
- Selector names don't expose "Current" — that's an internal detail of
the metadata store API

### Future work

- Optimistic update API (`updateCurrentOptimistically` with rollback)
- Migrate remaining entities (views, pageLayouts, etc.) to the same
pattern
- Gradually remove `objectMetadataItemsState` bridge once all direct
imports are replaced

## Test plan

- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx lint:diff-with-main twenty-front` passes
- [ ] Verify app loads correctly with metadata from the store
- [ ] Verify SSE updates (object/field changes) propagate correctly
- [ ] Run existing test suites to confirm no regressions
2026-03-14 12:54:19 +01:00
WeikoandGitHub 48172d60fd View field override (#18572)
## Context
This PR introduces overrides for view fields which will be useful for
page layout FIELDS widgets fields position/groups/visibility override +
restore logic.
2026-03-14 12:40:15 +01:00
Thomas des FrancsandGitHub 0b0ffcb8fa Add pitfall reminders to LLMS guidance (#18627)
please chat, no scroll in scroll on dashboards 🙏
2026-03-14 10:53:42 +00:00
6552ec83ec i18n - translations (#18642)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-14 11:03:11 +01:00
Charles BochetandGitHub 602db4ffea feat: enable Rich Text as a creatable field type (#18634)
## Summary

- Removes `RICH_TEXT` from the excluded/hidden field types in the
settings UI so users can create rich text fields on any object (not just
Note/Task)
- Creates a generic `RichTextFieldEditor` component that uses standard
`useUpdateOneRecord` for persistence, decoupled from the
Note/Task-specific `ActivityRichTextEditor`
- Updates the inline `RichTextFieldInput` and side panel to route to the
appropriate editor based on object type (activity editor for Note/Task,
generic editor for everything else)

## Details

### Tier 1 — Settings UI unlock
- Removed `RICH_TEXT` from `excludedFieldTypes` in
`SettingsObjectNewFieldSelect.tsx`
- Removed `RICH_TEXT` from `SettingsExcludedFieldType` type union
- Added `RICH_TEXT` to `previewableTypes` in
`SettingsDataModelFieldSettingsFormCard`

### Tier 2 — Generic inline editing
- New `RichTextFieldEditor` — a generic BlockNote editor that works for
any object using `useUpdateOneRecord` (no activity-specific coupling)
- `RichTextFieldInput` now branches: `ActivityRichTextEditor` for
Note/Task, `RichTextFieldEditor` for all other objects
- Generalized side panel state (`viewableRichTextComponentState`) from
`activityId`/`activityObjectNameSingular` to
`recordId`/`objectNameSingular`/`fieldName`
- `useOpenRichTextInSidePanel` now accepts an optional `fieldName`
parameter

### Tier 3 — Verification
- Search: only `markdown` subfield is indexed (correct behavior)
- Filters: `RichTextFilter` GraphQL input type already exists
- Import/export: `markdown` subfield is already marked `isImportable:
true`
2026-03-14 10:57:27 +01:00
3a9247d9d1 i18n - translations (#18639)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-13 20:02:02 +01:00
6b48f197d4 feat: deprecate WorkspaceFavorite in favor of NavigationMenuItem (#18624)
## Summary

- **Removes the entire `modules/favorites/` directory** (~66 files,
~5000 lines deleted) — components, hooks, states, types, utils, tests,
and the favorite-folder-picker sub-module
- **Eliminates the dual-write pattern** where creating a favorite also
created a NavigationMenuItem — all consumers now use
`useCreateNavigationMenuItem` directly
- **Removes `IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED` feature flag
checks** from ~12 files, always taking the NavigationMenuItem code path
- **Cleans up backend dual-writes** in `object-metadata.service.ts` and
`twenty-standard-application.service.ts` that were creating Favorite
records alongside NavigationMenuItems
- **Updates prefetch system** to only load NavigationMenuItems (removes
favorites prefetch effects and states)
- **Cleans up test infrastructure** — updates Storybook decorators, mock
data, and graphql mocks to remove favorites references

### What was intentionally kept
- **Backend entity definitions** (`FavoriteWorkspaceEntity`,
`FavoriteFolderWorkspaceEntity`) — these define the database schema and
need a proper database migration to remove
- **Cascade deletion listeners** — still needed to clean up existing
Favorite data in workspaces that haven't been fully migrated
- **v1.18 migration commands** — needed for workspaces upgrading from
older versions

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:45:40 +01:00
2a8912b17a i18n - docs translations (#18636)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-13 19:40:17 +01:00
55d675bba7 i18n - translations (#18633)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-13 19:23:23 +01:00
Charles BochetandGitHub d9eb317bb5 feat: rename RICH_TEXT_V2 → RICH_TEXT in codebase (keep DB value) (#18628)
## Summary

- Renames the `FieldMetadataType` enum key from `RICH_TEXT_V2` to
`RICH_TEXT` across the entire codebase, while keeping the underlying
string value as `'RICH_TEXT_V2'` to maintain PostgreSQL database
compatibility
- Renames all related types, guards, hooks, components, and files from
`*RichTextV2*` / `*rich-text-v2*` to `*RichText*` / `*rich-text*` (e.g.
`FormRichTextV2FieldInput` → `FormRichTextFieldInput`,
`isFieldRichTextV2` → `isFieldRichText`)
- Updates generated files (GraphQL schema, SDK types) to use the new key
while preserving the `RICH_TEXT_V2` string value for DB/API layer
- Updates i18n locale files, test snapshots, and integration tests to
reflect the rename

## Context

The legacy `RICH_TEXT` (V1) field type was deprecated and migrated to
`TEXT` in a previous PR (#18623). With V1 gone, the `RICH_TEXT_V2`
naming is no longer necessary — `RICH_TEXT` is now the canonical name.
The DB enum value stays `'RICH_TEXT_V2'` to avoid confusion with the
just-deprecated V1 type and to prevent a database migration.

## Test plan

- [x] `twenty-server` typecheck passes
- [x] `twenty-front` typecheck passes (only pre-existing Apollo client
errors remain)
- [x] `twenty-server` lint passes
- [x] `twenty-front` lint passes
- [x] `twenty-shared` build passes
- [ ] CI passes


Made with [Cursor](https://cursor.com)
2026-03-13 19:07:55 +01:00
1110 changed files with 13488 additions and 16331 deletions
+3 -3
View File
@@ -40,7 +40,8 @@ jobs:
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Install dependencies
uses: ./.github/actions/yarn-install
@@ -111,7 +112,7 @@ jobs:
run: yarn docs:generate-paths
- name: Commit artifacts to pull request branch
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
run: |
git add packages/twenty-docs/docs.json packages/twenty-docs/navigation/navigation.template.json packages/twenty-shared/src/constants/DocumentationPaths.ts
if git diff --staged --quiet --exit-code; then
@@ -149,4 +150,3 @@ jobs:
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -3
View File
@@ -25,8 +25,8 @@
# Installation
See:
🚀 [Self-hosting](https://docs.twenty.com/developers/self-hosting/docker-compose)
🖥️ [Local Setup](https://docs.twenty.com/developers/local-setup)
🚀 [Self-hosting](https://docs.twenty.com/developers/self-host/capabilities/docker-compose)
🖥️ [Local Setup](https://docs.twenty.com/developers/contribute/capabilities/local-setup)
# Why Twenty
@@ -36,7 +36,7 @@ We built Twenty for three reasons:
**A fresh start is required to build a better experience.** We can learn from past mistakes and craft a cohesive experience inspired by new UX patterns from tools like Notion, Airtable or Linear.
**We believe in Open-source and community.** Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
**We believe in open-source and community.** Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
<br />
@@ -11,3 +11,4 @@
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -30,7 +30,7 @@ type AnalysisResult = {
commitments: Commitment[];
};
type RichTextV2Data = {
type RichTextData = {
markdown: string;
blocknote: null;
};
@@ -123,7 +123,7 @@ const createNoteInTwenty = async (
bodyV2: {
markdown: noteBodyMarkdown,
blocknote: null,
} satisfies RichTextV2Data,
} satisfies RichTextData,
};
try {
@@ -159,7 +159,7 @@ const createTaskInTwenty = async (
const taskData: {
title: string;
bodyV2: RichTextV2Data;
bodyV2: RichTextData;
dueAt?: string;
} = {
title: actionItem.title,
@@ -10,3 +10,4 @@
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -32,7 +32,7 @@ type AnalysisResult = {
commitments: Commitment[];
};
type RichTextV2Data = {
type RichTextData = {
markdown: string;
blocknote: null;
};
@@ -362,7 +362,7 @@ const createNoteInTwenty = async (
bodyV2: {
markdown: noteBodyMarkdown,
blocknote: null,
} satisfies RichTextV2Data,
} satisfies RichTextData,
};
try {
@@ -451,7 +451,7 @@ const createTaskInTwenty = async (
const taskData: {
title: string;
bodyV2: RichTextV2Data;
bodyV2: RichTextData;
dueAt?: string;
assigneeId?: string;
} = {
+1
View File
@@ -10,3 +10,4 @@
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -7,3 +7,4 @@
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -81,7 +81,7 @@ export default defineObject({
},
{
universalIdentifier: TRANSCRIPT_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.RICH_TEXT_V2,
type: FieldType.RICH_TEXT,
name: 'transcript',
label: 'Transcript',
description: 'Human-readable transcript of the call',
@@ -114,7 +114,7 @@ export default defineObject({
},
{
universalIdentifier: SUMMARY_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.RICH_TEXT_V2,
type: FieldType.RICH_TEXT,
name: 'summary',
label: 'Summary',
description: 'AI-generated summary of the call',
@@ -16,7 +16,7 @@ Use this skill when a user asks you to summarize, analyze, or extract insights f
## How to Access the Data
1. Use \`find_one_callRecording\` to fetch the call recording by its ID.
2. Read the \`transcript\` field (RICH_TEXT_V2, markdown format) which contains the full conversation.
2. Read the \`transcript\` field (RICH_TEXT, markdown format) which contains the full conversation.
3. The transcript uses the format: **Speaker Name:** spoken text
## What to Produce
@@ -9,7 +9,7 @@ The goal here is to have a consistent codebase, which is easy to read and easy t
For this, it's better to be a bit more verbose than to be too concise.
Always keep in mind that people read code more often than they write it, specially on an open source project, where anyone can contribute.
Always keep in mind that people read code more often than they write it, especially on an open source project, where anyone can contribute.
There are a lot of rules that are not defined here, but that are automatically checked by linters.
@@ -150,7 +150,7 @@ type MyType = {
### Use string literals instead of enums
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, specially with code completion.
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, especially with code completion.
You can see why TypeScript recommends avoiding enums [here](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
@@ -288,4 +288,3 @@ An Oxlint rule, `typescript/consistent-type-imports`, enforces the no-type impor
Please note that this rule specifically addresses rare edge cases where unintentional type imports occur. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
To ensure your code complies with this rule, make sure to run Oxlint as part of your development workflow.
@@ -7,7 +7,7 @@ description: "The guide for contributors (or curious developers) who want to run
## Prerequisites
<Tabs>
<Tab title="Linux and MacOS">
<Tab title="Linux and macOS">
Before you can install and use Twenty, make sure you install the following on your computer:
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -31,7 +31,7 @@ wsl --install
```
You should now see a prompt to restart your computer. If not, restart it manually.
Upon restart, a powershell window will open and install Ubuntu. This may take up some time.
Upon restart, a PowerShell window will open and install Ubuntu. This may take up some time.
You'll see a prompt to create a username and password for your Ubuntu installation.
2. Install and configure git
@@ -104,7 +104,7 @@ You should run all commands in the following steps from the root of the project.
<Tabs>
<Tab title="Linux">
**Option 1 (preferred):** To provision your database locally:
Use the following link to install Postgresql on your Linux machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
Use the following link to install PostgreSQL on your Linux machine: [PostgreSQL Installation](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -131,7 +131,7 @@ You should run all commands in the following steps from the root of the project.
```
The installer might not create the `postgres` user by default when installing
via Homebrew on MacOS. Instead, it creates a PostgreSQL role that matches your macOS
via Homebrew on macOS. Instead, it creates a PostgreSQL role that matches your macOS
username (e.g., "john").
To check and create the `postgres` user if necessary, follow these steps:
```bash
@@ -174,8 +174,8 @@ You should run all commands in the following steps from the root of the project.
<Tab title="Windows (WSL)">
All the following steps are to be run in the WSL terminal (within your virtual machine)
**Option 1:** To provision your Postgresql locally:
Use the following link to install Postgresql on your Linux virtual machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
**Option 1:** To provision your PostgreSQL locally:
Use the following link to install PostgreSQL on your Linux virtual machine: [PostgreSQL Installation](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -190,10 +190,12 @@ You should run all commands in the following steps from the root of the project.
</Tab>
</Tabs>
You can now access the database at [localhost:5432](localhost:5432), with user `postgres` and password `postgres` .
You can now access the database at `localhost:5432`.
If you used the Docker option above, the default credentials are user `postgres` and password `postgres`. For native PostgreSQL installations, use the credentials and roles configured on your machine.
## Step 4: Set up a Redis Database (cache)
Twenty requires a redis cache to provide the best performance
Twenty requires a Redis cache to provide the best performance.
<Tabs>
<Tab title="Linux">
@@ -210,8 +212,10 @@ Twenty requires a redis cache to provide the best performance
```bash
brew install redis
```
Start your redis server:
```brew services start redis```
Start your Redis server:
```bash
brew services start redis
```
**Option 2:** If you have docker installed:
```bash
@@ -229,11 +233,11 @@ Twenty requires a redis cache to provide the best performance
</Tab>
</Tabs>
If you need a Client GUI, we recommend [redis insight](https://redis.io/insight/) (free version available)
If you need a client GUI, we recommend [Redis Insight](https://redis.io/insight/) (free version available).
## Step 5: Setup environment variables
## Step 5: Set up environment variables
Use environment variables or `.env` files to configure your project. More info [here](/developers/self-host/capabilities/setup)
Use environment variables or `.env` files to configure your project. More info [here](/developers/self-host/capabilities/setup).
Copy the `.env.example` files in `/front` and `/server`:
```bash
@@ -4,7 +4,7 @@ title: 1-Click w/ Docker Compose
<Warning>
Docker containers are for production hosting or self-hosting, for the contribution please check the [Local Setup](/developers/contribute/capabilities/local-setup).
Docker containers are for production hosting or self-hosting. For contributing, please check the [Local Setup](/developers/contribute/capabilities/local-setup).
</Warning>
## Overview
@@ -13,7 +13,7 @@ This guide provides step-by-step instructions to install and configure the Twent
**Important:** Only modify settings explicitly mentioned in this guide. Altering other configurations may lead to issues.
See docs [Setup Environment Variables](/developers/self-host/capabilities/setup) for advanced configuration. All environment variables must be declared in the docker-compose.yml file at the server and / or worker level depending on the variable.
See [Setup Environment Variables](/developers/self-host/capabilities/setup) for advanced configuration. All environment variables must be declared in the `docker-compose.yml` file at the server and/or worker level, depending on the variable.
## System Requirements
@@ -237,4 +237,3 @@ docker compose up -d
If you encounter any problem, check [Troubleshooting](/developers/self-host/capabilities/troubleshooting) for solutions.
+4
View File
@@ -6302,6 +6302,10 @@
"source": "/developers/extend/capabilities/apps",
"destination": "/developers/extend/apps/getting-started"
},
{
"source": "/developers/extend/mcp",
"destination": "/user-guide/ai/capabilities/mcp"
},
{
"source": "/developers/local-setup",
"destination": "/developers/contribute/capabilities/local-setup"
@@ -8,7 +8,7 @@ title: دليل الأسلوب
لهذا، من الأفضل أن تكون تفصيلًا أكثر قليلاً بدلاً من أن تكون موجزًا للغاية.
دائمًا ضع في اعتبارك أن الناس يقرؤون التعليمات البرمجية أكثر مما يكتبونها، وخاصة في المشاريع مفتوحة المصدر، حيث يمكن لأي شخص المساهمة.
دائمًا ضع في اعتبارك أن الناس يقرؤون التعليمات البرمجية أكثر مما يكتبونها، وخاصة في مشروع مفتوح المصدر، حيث يمكن لأي شخص المساهمة.
هناك العديد من القواعد التي لم يتم تعريفها هنا، ولكن يتم التحقق منها تلقائيًا بواسطة أدوات الفحص.
@@ -6,7 +6,7 @@ description: الدليل للمساهمين (أو المطورين الفضول
## المتطلبات الأساسية
<Tabs>
<Tab title="Linux و MacOS">
<Tab title="Linux و macOS">
قبل أن تتمكن من تثبيت واستخدام Twenty، تأكد من تثبيت الأمور التالية على جهاز الكمبيوتر الخاص بك:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -103,7 +103,7 @@ cd twenty
<Tabs>
<Tab title="Linux">
**الخيار 1 (المفضل):** لتوفير قاعدة بياناتك محليًا:
استخدم الرابط التالي لتثبيت Postgresql على جهاز Linux الخاص بك: [تثبيت Postgresql](https://www.postgresql.org/download/linux/)
استخدم الرابط التالي لتثبيت PostgreSQL على جهاز Linux الخاص بك: [تثبيت PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -129,8 +129,8 @@ cd twenty
brew services list
```
المثبت قد لا ينشئ المستخدم `postgres` افتراضيًا عند التثبيت
عبر Homebrew على MacOS. بدلاً من ذلك، فإنه ينشئ دور PostgreSQL يطابق
قد لا يقوم المُثبِّت بإنشاء المستخدم `postgres` افتراضيًا عند التثبيت
عبر Homebrew على macOS. بدلاً من ذلك، فإنه ينشئ دور PostgreSQL يطابق
اسم المستخدم الخاص بك في MacOS (مثل "john").
للتحقق وإنشاء المستخدم `postgres` إذا لزم الأمر، اتبع هذه الخطوات:
```bash
@@ -173,8 +173,8 @@ cd twenty
<Tab title="ويندوز (WSL)">
يجب أن تُنفذ جميع الخطوات التالية في تيرمينال WSL (داخل جهازك الافتراضي)
**الخيار 1:** لتوفير قاعدة بيانات Postgresql الخاصة بك محليًا:
استخدم الرابط التالي لتثبيت Postgresql على جهاز Linux الافتراضي الخاص بك: [تثبيت Postgresql](https://www.postgresql.org/download/linux/)
**الخيار 1:** لتوفير قاعدة بيانات PostgreSQL الخاصة بك محليًا:
استخدم الرابط التالي لتثبيت PostgreSQL على جهاز Linux الافتراضي الخاص بك: [تثبيت PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -189,11 +189,13 @@ cd twenty
</Tab>
</Tabs>
يمكنك الآن الوصول إلى قاعدة البيانات على [localhost:5432](localhost:5432)، مع المستخدم `postgres` وكلمة المرور `postgres`.
يمكنك الآن الوصول إلى قاعدة البيانات على `localhost:5432`.
إذا استخدمت خيار Docker أعلاه، فإن بيانات الاعتماد الافتراضية هي اسم المستخدم `postgres` وكلمة المرور `postgres`. بالنسبة لتثبيتات PostgreSQL الأصلية، استخدم بيانات الاعتماد والأدوار المُكوَّنة على جهازك.
## الخطوة 4: إعداد قاعدة بيانات Redis (للتخزين المؤقت)
يتطلب Twenty مخزن بيانات Redis لتقديم أفضل أداء
يتطلب Twenty مخزن بيانات Redis لتقديم أفضل أداء.
<Tabs>
<Tab title="Linux">
@@ -210,8 +212,10 @@ cd twenty
```bash
brew install redis
```
ابدأ خادم redis الخاص بك:
`brew services start redis`
ابدأ تشغيل خادم Redis:
```bash
brew services start redis
```
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
@@ -229,11 +233,11 @@ cd twenty
</Tab>
</Tabs>
إذا كنت بحاجة إلى واجهة رسومية للعميل، نوصي بـ [redis insight](https://redis.io/insight/) (يتوفر إصدار مجاني)
إذا كنت بحاجة إلى واجهة رسومية للعميل، نوصي بـ [Redis Insight](https://redis.io/insight/) (يتوفر إصدار مجاني).
## الخطوة 5: إعداد متغيرات البيئة
استخدم متغيرات البيئة أو ملفات `.env` لتكوين مشروعك. المزيد من المعلومات [هنا](/l/ar/developers/self-host/capabilities/setup)
استخدم متغيرات البيئة أو ملفات `.env` لتكوين مشروعك. المزيد من المعلومات [هنا](/l/ar/developers/self-host/capabilities/setup).
انسخ ملفات `.env.example` الموجودة في `/front` و`/server`:
@@ -1263,12 +1263,12 @@ yarn twenty app:build --tarball
1. **يقوم بتحليل ملف البيان والتحقق من صحته** — يقرأ جميع الكيانات `defineX()` من ملفات المصدر لديك ويُتحقّق من بنية ملف البيان.
2. **يُصرِّف دوال المنطق ومكوّنات الواجهة** — يُجمّع مصادر TypeScript إلى ملفات ESM `.mjs` باستخدام esbuild.
3. **يولّد قيم التحقّق** — يحسب تجزئات MD5 لكل ملف مُبنًى، وتُخزَّن في ملف البيان كـ `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **ينشئ عميل API مضبوط الأنواع** — يفحص مخطط GraphQL ويُنشئ عميلَي `CoreApiClient` و`MetadataApiClient` مضبوطي الأنواع.
5. **يشغّل فحص الأنواع لـ TypeScript** — يشغّل `tsc --noEmit` لاكتشاف أخطاء الأنواع قبل النشر.
6. **يعيد البناء باستخدام العميل المُولَّد** — يُجري مرحلة ترجمة ثانية بحيث تُدرَج أنواع العميل المُولَّد.
7. **ينشئ أرشيف tar اختياريًا** — إذا تم تمرير `--tarball`، يشغّل `npm pack` لإنشاء ملف `.tgz` جاهز للتوزيع.
The build output in `.twenty/output/` contains:
مخرجات البناء في `.twenty/output/` تتضمّن:
```text
.twenty/output/
@@ -1282,16 +1282,16 @@ The build output in `.twenty/output/` contains:
└── my-app-1.0.0.tgz # Only with --tarball flag
```
| الخيار | الوصف |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| الخيار | الوصف |
| ----------- | -------------------------------------------------- |
| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) |
| `--tarball` | قم أيضًا بحزم المخرجات في أرشيف `.tgz` |
## Publishing your app
## نشر تطبيقك
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
استخدم `app:publish` لتوزيع تطبيقك — إما إلى سجل npm أو مباشرةً إلى خادم Twenty.
### Publish to npm (default)
### النشر إلى npm (الإعداد الافتراضي)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1301,57 +1301,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
يقوم هذا ببناء التطبيق وتشغيل `npm publish` من دليل `.twenty/output/`. بعد ذلك يمكن تثبيت الحزمة المنشورة من سوق Twenty بواسطة أي مساحة عمل.
### Publish to a Twenty server
### النشر إلى خادم Twenty
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
يقوم هذا ببناء التطبيق مع أرشيف tar، ويرفعه إلى الخادم عبر العملية `uploadAppTarball` في GraphQL، ويبدأ التثبيت في خطوة واحدة. يكون هذا مفيدًا لعمليات النشر الخاصة أو للاختبار مقابل خادم محدّد.
| الخيار | الوصف |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| الخيار | الوصف |
| ----------------- | -------------------------------------------------------- |
| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) |
| `--server <url>` | انشر إلى خادم Twenty بدلًا من npm |
| `--token <token>` | رمز المصادقة للخادم المستهدف |
| `--tag <tag>` | علامة توزيع npm (مثل `beta`، `next`) — للنشر عبر npm فقط |
## Application registration
## تسجيل التطبيق
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
قبل أن يمكن تثبيت تطبيق في مساحة عمل، يجب أن يكون **مسجّلًا**. التسجيل هو سجل بيانات وصفية يوضّح مصدر التطبيق وكيفية مصادقته. يُعالَج هذا تلقائيًا بواسطة CLI في معظم الحالات.
### Source types
### أنواع المصادر
Each registration has a **source type** that determines how the app's files are resolved during installation:
لكل تسجيل **نوع مصدر** يحدّد كيفية تحديد ملفات التطبيق أثناء التثبيت:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| نوع المصدر | كيفية تحديد الملفات | حالة الاستخدام النموذجية |
| ---------- | ------------------------------------------------------------------------- | --------------------------------------- |
| `LOCAL` | تتم مزامنة الملفات في الوقت الفعلي بواسطة مُراقِب CLI — يتم تخطّي التثبيت | التطوير باستخدام `app:dev` |
| `NPM` | تُجلب من سجل npm عبر الحقل `sourcePackage` | تطبيقات منشورة على npm |
| `TARBALL` | تُستخرَج من ملف `.tgz` مرفوع ومخزَّن على الخادم | تطبيقات خاصة منشورة باستخدام `--server` |
### How registration happens
### كيفية إجراء التسجيل
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — ينشئ تلقائيًا تسجيلًا من نوع `LOCAL` في المرة الأولى التي تشغّل فيها وضع التطوير لمساحة عمل.
* **`app:publish --server`** — يرفع أرشيف tar وينشئ (أو يحدّث) تسجيلًا من نوع `TARBALL`، ثم يثبّت التطبيق.
* **سوق npm** — يتم إنشاء تسجيلات `NPM` عند مزامنة التطبيقات من سجل npm إلى كتالوج سوق Twenty.
* **واجهة برمجة تطبيقات GraphQL** — يمكنك أيضًا إنشاء التسجيلات برمجيًا عبر العملية `createApplicationRegistration`.
### Registration vs installation
### التسجيل مقابل التثبيت
**Registration** and **installation** are separate concepts:
**التسجيل** و**التثبيت** مفهومان منفصلان:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* **التسجيل** (`ApplicationRegistration`) هو سجل بيانات وصفية عام يصف التطبيق: اسمه، نوع المصدر، بيانات اعتماد OAuth، وحالة إدراجه في السوق. وهو موجود بشكل مستقل عن أي مساحة عمل.
* **التثبيت** (`Application`) هو مثيل لكل مساحة عمل. عند قيام مستخدم بتثبيت تطبيق، تقوم Twenty بحلّ الحزمة من مصدر التسجيل، وتكتب الملفات المُبنَاة إلى التخزين، وتزامن البيان التعريفي (إنشاء الكائنات والحقول ودوال المنطق، إلخ) في مساحة العمل تلك.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
يمكن تثبيت تسجيل واحد في العديد من مساحات العمل. تحصل كل مساحة عمل على نسختها الخاصة من ملفات التطبيق ونموذج البيانات.
### OAuth credentials
### بيانات اعتماد OAuth
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
يتضمن كل تسجيل بيانات اعتماد OAuth (`oAuthClientId` و`oAuthClientSecret`) يتم إنشاؤها وقت الإنشاء. يستخدمها التطبيق لمصادقة طلبات واجهة برمجة التطبيقات بالنيابة عن المستخدمين. يُعرَض سر العميل مرةً **واحدة** عند الإنشاء — خزّنه بأمان. يمكنك تدويره لاحقًا عبر العملية `rotateApplicationRegistrationClientSecret`.
## إعداد يدوي (بدون المهيئ)
@@ -3,7 +3,7 @@ title: بنقرة واحدة مع Docker Compose
---
<Warning>
الحاويات الخاصة بدوكر مخصصة للاستضافة الإنتاجية أو الاستضافة الذاتية، للتحقيق يرجى التحقق من [الإعداد المحلي](/l/ar/developers/contribute/capabilities/local-setup).
حاويات Docker مخصصة للاستضافة في بيئة الإنتاج أو للاستضافة الذاتية. للمساهمة، يُرجى الاطلاع على [الإعداد المحلي](/l/ar/developers/contribute/capabilities/local-setup).
</Warning>
## نظرة عامة
@@ -12,7 +12,7 @@ title: بنقرة واحدة مع Docker Compose
**مهم:** عدّل الإعدادات المذكورة صراحة في هذا الدليل فقط. قد يؤدي تعديل التكوينات الأخرى إلى مشاكل.
راجع المستندات الخاصة بـ [إعداد متغيرات البيئة](/l/ar/developers/self-host/capabilities/setup) لإعداد متقدم. يجب إعلان جميع متغيرات البيئة في الملف docker-compose.yml على مستوى الخادم و/أو العامل بناءً على المتغير.
راجع [إعداد متغيرات البيئة](/l/ar/developers/self-host/capabilities/setup) لإعداد متقدم. يجب إعلان جميع متغيرات البيئة في ملف `docker-compose.yml` على مستوى الخادم و/أو العامل، اعتمادًا على المتغير.
## متطلبات النظام
@@ -0,0 +1,142 @@
---
title: خادم MCP},{
description: اربط مساعدي الذكاء الاصطناعي بمساحة عمل Twenty الخاصة بك باستخدام بروتوكول سياق النموذج.
---
<Warning>
MCP حاليًا في مرحلة **ألفا** وهو متاح فقط في بعض مساحات العمل. قد لا يكون مفعّلًا لمساحة عملك بعد.
</Warning>
تعرض Twenty خادم [MCP](https://modelcontextprotocol.io/) بحيث تتمكّن مساعدات الذكاء الاصطناعي — Claude Desktop وClaude Code وCursor وChatGPT وغيرها — من قراءة وكتابة بيانات نظام إدارة علاقات العملاء (CRM) لديك باستخدام اللغة الطبيعية.
استخدم **عنوان URL لمساحة العمل** (عنوان URL الذي تستخدمه للوصول إلى Twenty) كنقطة نهاية MCP. على Twenty Cloud، قد يكون عنوان URL لمساحة العمل هو `https://{mycompany}.twenty.com` أو نطاق مخصص. الخادم متاح على:
| البيئة | نقطة نهاية MCP |
| --------------------- | ---------------------------------------------------------------------------------------- |
| **السحابة** | `https://{your-workspace-url}/mcp` (على سبيل المثال: `https://mycompany.twenty.com/mcp`) |
| **الاستضافة الذاتية** | `https://{your-domain}/mcp` |
## طرق المصادقة
لديك طريقتان لمصادقة عميل MCP: **OAuth** (مُوصى بها) أو **مفتاح API**.
### الخيار أ — OAuth (مُوصى به)
باستخدام OAuth، يفتح عميل MCP لديك نافذة متصفح لتسجيل الدخول. لا يتم تخزين أي أسرار في ملفات الإعداد، ويتم تحديث الرموز المميِّزة تلقائيًا.
<Note>
يتطلب OAuth عميل MCP يدعم [مواصفة تفويض MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). تدعمه Claude Desktop وClaude Code وCursor وChatGPT.
</Note>
أضِف ما يلي إلى تهيئة عميل MCP لديك، واستبدِل `{your-workspace-url}` بمضيف مساحة العمل لديك (على سبيل المثال: `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
هذا كل شيء — لا حاجة إلى مفتاح API. عند اتصال العميل للمرة الأولى، سيفعل ما يلي:
1. اكتشاف بيانات التعريف الخاصة بـ OAuth لدى Twenty عبر `/.well-known/oauth-protected-resource` و`/.well-known/oauth-authorization-server`
2. تسجيل نفسه كعميل OAuth عبر التسجيل الديناميكي للعميل (RFC 7591)
3. فتح متصفحك لتفويض الوصول
4. استلام الرموز المميِّزة والاتصال بخادم MCP
تعيد الاتصالات اللاحقة استخدام الرموز المميِّزة المخزنة وتحدِّثها تلقائيًا.
### الخيار ب — مفتاح API
إذا كان عميل MCP لديك لا يدعم OAuth، أو كنت تفضّل بيانات اعتماد ثابتة، فمرِّر مفتاح API في ترويسة `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
يمنح مفتاح API الخاص بك حق الوصول إلى بيانات مساحة العمل. أبعِده عن أنظمة التحكم في الإصدارات وملفات dotfiles المشتركة.
</Warning>
لإنشاء مفتاح API، انتقل إلى **Settings > APIs & Webhooks > + Create key**. راجع [واجهات برمجة التطبيقات](/l/ar/developers/extend/api#create-an-api-key) للتفاصيل.
## البدء السريع
### 1. انسخ الإعداد
انتقل إلى **Settings > AI > More > MCP Server** في Twenty. اختر طريقة المصادقة (OAuth أو مفتاح API)، وانسخ مقطع JSON (سيستخدم بالفعل عنوان URL لمساحة العمل لديك)، ثم الصقه في ملف إعدادات عميل MCP لديك.
| العميل | موقع ملف الإعداد |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) أو `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (المستخدم) أو `.mcp.json` (المشروع) |
| **Cursor** | `.cursor/mcp.json` ضمن مشروعك، أو `~/.cursor/mcp.json` عالميًا |
| **ChatGPT** | فعِّل وضع المطوّر في **Settings > Apps & Connectors > Advanced settings**، ثم استخدم **Create** في **Settings > Apps & Connectors** لإضافة خادم MCP |
### ٢. الاتصال
أعِد تشغيل عميل MCP لديك (أو أعد تحميل ملف الإعداد). إذا كنت تستخدم OAuth فسيتم توجيهك إلى Twenty لتفويض الوصول. إذا كنت تستخدم مفتاح API فسيكون الاتصال فوريًا.
### ٣. ابدأ باستخدامه
اطلب من مساعد الذكاء الاصطناعي التفاعل مع نظام إدارة علاقات العملاء (CRM) لديك:
* *"أرني أحدث 5 شركات تم إنشاؤها"*
* *"أنشئ شخصًا جديدًا باسم Jane Doe في Acme Corp"*
* *"اعثر على جميع الفرص المفتوحة التي تزيد قيمتها عن 10 آلاف دولار"*
## الأدوات المتاحة
بعد الاتصال، يوفّر خادم MCP أدوات تعكس واجهة برمجة تطبيقات Twenty (API). سير العمل الموصى به هو:
1. **`get_tool_catalog`** — اكتشف جميع الأدوات المتاحة
2. **`learn_tools`** — احصل على مخطط الإدخال لأدوات محددة
3. **`execute_tool`** — شغّل أداة
لا تحتاج إلى تذكّر أسماء الأدوات. اسأل مساعد الذكاء الاصطناعي عمّا يمكنه فعله وسيستدعي `get_tool_catalog` تلقائيًا.
## الصلاحيات
ترث اتصالات MCP أذونات المستخدم المُصادَق عليه (OAuth) أو الدور المُعيَّن لمفتاح API. لتقييد ما يمكن لخادم MCP القيام به:
* **OAuth**: ينطبق دور المستخدم في مساحة العمل.
* **API Key**: عيِّن دورًا لمفتاح API ضمن **Settings > Roles**. راجع [الأذونات](/l/ar/user-guide/permissions-access/capabilities/permissions).
## التكوين ذاتي الاستضافة
في حالات الاستضافة الذاتية، استبدِل `{your-workspace-url}` بعنوان URL الخاص بالخادم لديك. تأكّد من أن قيمة `SERVER_URL` في بيئتك تطابق عنوان URL العام لمثيل Twenty لديك — إذ يُستخدَم ذلك لإنشاء بيانات تعريف اكتشاف OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
تُشتق نقطة نهاية MCP ونقاط نهاية OAuth وبيانات تعريف الاكتشاف جميعها من هذه القيمة.
## استكشاف الأخطاء وإصلاحها
**أخطاء "Unauthorized" أو 401**
* OAuth: أعد التفويض عبر مسح الرموز المميِّزة المخزنة في عميل MCP لديك ثم أعد الاتصال.
* API Key: تحقّق من أن المفتاح صالح ولم تنتهِ صلاحيته. أعِد توليده إذا لزم الأمر.
**عملية OAuth لا تفتح متصفحًا**
* تأكّد من أن عميل MCP لديك يدعم تفويض MCP. ارجع إلى طريقة مفتاح API إذا لم يكن كذلك.
**انتهاء مهلة الاتصال**
* تحقّق من إمكانية الوصول إلى عنوان URL لنقطة نهاية MCP من جهازك. بالنسبة لحالات الاستضافة الذاتية، تحقّق من أن الخادم يعمل وأن `SERVER_URL` مُعيَّن بشكل صحيح.
@@ -6,7 +6,7 @@ description: Der Leitfaden für Mitwirkende (oder neugierige Entwickler), die Tw
## Voraussetzungen
<Tabs>
<Tab title="Linux und MacOS">
<Tab title="Linux und macOS">
Bevor Sie Twenty installieren und verwenden können, stellen Sie sicher, dass Sie Folgendes auf Ihrem Computer installiert haben:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -103,7 +103,7 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
<Tabs>
<Tab title="Linux">
**Option 1 (bevorzugt):** Um Ihre Datenbank lokal bereitzustellen:
Verwenden Sie den folgenden Link, um PostgreSQL auf Ihrem Linux-Rechner zu installieren: [Postgresql-Installation](https://www.postgresql.org/download/linux/)
Verwenden Sie den folgenden Link, um PostgreSQL auf Ihrem Linux-Rechner zu installieren: [PostgreSQL-Installation](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -129,8 +129,8 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
brew services list
```
Der Installer erstellt möglicherweise nicht standardmäßig den Benutzer `postgres`, wenn er
über Homebrew auf MacOS installiert wird. Stattdessen wird eine PostgreSQL-Rolle erstellt, die Ihrem macOS
Das Installationsprogramm erstellt den Benutzer `postgres` möglicherweise nicht standardmäßig bei der Installation
über Homebrew auf macOS. Stattdessen wird eine PostgreSQL-Rolle erstellt, die Ihrem macOS
Benutzernamen (z. B. "john") entspricht.
Um zu überprüfen und, falls erforderlich, den Benutzer `postgres` zu erstellen, führen Sie folgende Schritte aus:
```bash
@@ -174,7 +174,7 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
Alle folgenden Schritte sind im WSL-Terminal auszuführen (innerhalb Ihrer virtuellen Maschine)
**Option 1:** Um Ihr PostgreSQL lokal bereitzustellen:
Verwenden Sie den folgenden Link, um PostgreSQL auf Ihrer Linux-VM zu installieren: [Postgresql-Installation](https://www.postgresql.org/download/linux/)
Verwenden Sie den folgenden Link, um PostgreSQL auf Ihrer Linux-VM zu installieren: [PostgreSQL-Installation](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -189,11 +189,13 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
</Tab>
</Tabs>
Sie können jetzt über [localhost:5432](localhost:5432) auf die Datenbank zugreifen, mit dem Benutzer `postgres` und dem Passwort `postgres`.
Sie können nun über `localhost:5432` auf die Datenbank zugreifen.
Wenn Sie die oben genannte Docker-Option verwendet haben, lauten die Standardanmeldedaten Benutzer `postgres` und Passwort `postgres`. Für native PostgreSQL-Installationen verwenden Sie die auf Ihrem Rechner konfigurierten Anmeldedaten und Rollen.
## Schritt 4: Einrichten einer Redis-Datenbank (Cache)
Twenty benötigt einen Redis-Cache, um die beste Leistung zu bieten
Twenty benötigt einen Redis-Cache, um die beste Leistung zu bieten.
<Tabs>
<Tab title="Linux">
@@ -211,7 +213,9 @@ Twenty benötigt einen Redis-Cache, um die beste Leistung zu bieten
brew install redis
```
Starten Sie Ihren Redis-Server:
`brew services start redis`
```bash
brew services start redis
```
**Option 2:** Wenn Sie Docker installiert haben:
```bash
@@ -229,11 +233,11 @@ Twenty benötigt einen Redis-Cache, um die beste Leistung zu bieten
</Tab>
</Tabs>
Wenn Sie eine Client-GUI benötigen, empfehlen wir [redis insight](https://redis.io/insight/) (kostenlose Version verfügbar)
Wenn Sie eine Client-GUI benötigen, empfehlen wir [Redis Insight](https://redis.io/insight/) (kostenlose Version verfügbar).
## Schritt 5: Einrichten von Umgebungsvariablen
Verwenden Sie Umgebungsvariablen oder `.env`-Dateien, um Ihr Projekt zu konfigurieren. Weitere Informationen [hier](/l/de/developers/self-host/capabilities/setup)
Verwenden Sie Umgebungsvariablen oder `.env`-Dateien, um Ihr Projekt zu konfigurieren. Weitere Informationen [hier](/l/de/developers/self-host/capabilities/setup).
Kopieren Sie die `.env.example`-Dateien in `/front` und `/server`:
@@ -1263,12 +1263,12 @@ Der Build-Prozess:
1. **Parst und validiert das Manifest** — liest alle `defineX()`-Entitäten aus Ihren Quelldateien und validiert die Manifeststruktur.
2. **Kompiliert Logikfunktionen und Front-Komponenten** — bündelt TypeScript-Quellcode in ESM `.mjs`-Dateien mit esbuild.
3. **Erzeugt Checksummen** — berechnet MD5-Hashes für jede erstellte Datei, die im Manifest als `builtHandlerChecksum` / `builtComponentChecksum` gespeichert werden.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **Generiert den typisierten API-Client** — führt eine Introspektion des GraphQL-Schemas durch und generiert die typisierten Clients `CoreApiClient` und `MetadataApiClient`.
5. **Führt eine TypeScript-Typprüfung aus** — führt `tsc --noEmit` aus, um Typfehler vor der Veröffentlichung zu erkennen.
6. **Baut mit dem generierten Client neu** — führt einen zweiten Kompiliervorgang durch, damit die generierten Client-Typen enthalten sind.
7. **Erstellt optional einen Tarball** — wenn `--tarball` übergeben wird, wird `npm pack` ausgeführt, um eine `.tgz`-Datei zu erstellen, die für die Verteilung bereit ist.
The build output in `.twenty/output/` contains:
Der Build-Output in `.twenty/output/` enthält:
```text
.twenty/output/
@@ -1282,16 +1282,16 @@ The build output in `.twenty/output/` contains:
└── my-app-1.0.0.tgz # Only with --tarball flag
```
| Option | Beschreibung |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| Option | Beschreibung |
| ----------- | -------------------------------------------------------------- |
| `[appPath]` | Pfad zum App-Verzeichnis (standardmäßig aktuelles Verzeichnis) |
| `--tarball` | Den Output zusätzlich in einen `.tgz`-Tarball packen |
## Publishing your app
## Veröffentlichen Ihrer App
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Verwenden Sie `app:publish`, um Ihre App zu verteilen — entweder zur npm-Registry oder direkt zu einem Twenty-Server.
### Publish to npm (default)
### Bei npm veröffentlichen (Standard)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1301,57 +1301,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Dies baut die App und führt `npm publish` aus dem Verzeichnis `.twenty/output/` aus. Das veröffentlichte Paket kann dann von jedem Arbeitsbereich über den Twenty-Marktplatz installiert werden.
### Publish to a Twenty server
### Auf einem Twenty-Server veröffentlichen
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Dies erstellt beim Build einen Tarball, lädt ihn über die GraphQL-Mutation `uploadAppTarball` auf den Server hoch und stößt die Installation in einem Schritt an. Dies ist nützlich für private Bereitstellungen oder Tests gegen einen bestimmten Server.
| Option | Beschreibung |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Option | Beschreibung |
| ----------------- | ------------------------------------------------------------------ |
| `[appPath]` | Pfad zum App-Verzeichnis (standardmäßig aktuelles Verzeichnis) |
| `--server <url>` | Auf einen Twenty-Server anstelle von npm veröffentlichen |
| `--token <token>` | Authentifizierungstoken für den Zielserver |
| `--tag <tag>` | npm dist-tag (z. B. `beta`, `next`) — nur für npm-Veröffentlichung |
## Application registration
## Anwendungsregistrierung
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Bevor eine App in einem Arbeitsbereich installiert werden kann, muss sie **registriert** werden. Eine Registrierung ist ein Metadatensatz, der beschreibt, woher die App stammt und wie sie authentifiziert wird. Dies wird in den meisten Fällen automatisch durch die CLI erledigt.
### Source types
### Quelltypen
Each registration has a **source type** that determines how the app's files are resolved during installation:
Jede Registrierung hat einen **Quelltyp**, der bestimmt, wie die Dateien der App während der Installation aufgelöst werden:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Quelltyp | Wie Dateien aufgelöst werden | Typischer Anwendungsfall |
| --------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `LOCAL` | Dateien werden in Echtzeit vom CLI-Watcher synchronisiert — die Installation wird übersprungen | Entwicklung mit `app:dev` |
| `NPM` | Über das Feld `sourcePackage` aus der npm-Registry abgerufen | Veröffentlichte Apps auf npm |
| `TARBALL` | Aus einer hochgeladenen, auf dem Server gespeicherten `.tgz`-Datei extrahiert | Private Apps, die mit `--server` veröffentlicht wurden |
### How registration happens
### Wie die Registrierung erfolgt
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — erstellt beim ersten Ausführen des Dev-Modus für einen Arbeitsbereich automatisch eine `LOCAL`-Registrierung.
* **`app:publish --server`** — lädt einen Tarball hoch und erstellt (oder aktualisiert) eine `TARBALL`-Registrierung und installiert anschließend die App.
* **npm-Marktplatz** — `NPM`-Registrierungen werden erstellt, wenn Apps aus der npm-Registry in den Twenty-Marktplatzkatalog synchronisiert werden.
* **GraphQL-API** — Sie können Registrierungen auch programmgesteuert über die Mutation `createApplicationRegistration` erstellen.
### Registration vs installation
### Registrierung vs. Installation
**Registration** and **installation** are separate concepts:
**Registrierung** und **Installation** sind unterschiedliche Konzepte:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* Eine **Registrierung** (`ApplicationRegistration`) ist ein globaler Metadatensatz, der die App beschreibt: ihren Namen, den Quelltyp, die OAuth-Anmeldedaten und den Status der Marktplatzlistung. Sie existiert unabhängig von jedem Arbeitsbereich.
* Eine **Installation** (`Application`) ist eine Instanz pro Arbeitsbereich. Wenn ein Benutzer eine App installiert, ermittelt Twenty das Paket aus der Quelle der Registrierung, schreibt die erstellten Dateien in den Speicher und synchronisiert das Manifest (wobei Objekte, Felder, Logikfunktionen usw. erstellt werden) in diesem Arbeitsbereich.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
Eine Registrierung kann in vielen Arbeitsbereichen installiert werden. Jeder Arbeitsbereich erhält seine eigene Kopie der Dateien und des Datenmodells der App.
### OAuth credentials
### OAuth-Anmeldedaten
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Jede Registrierung enthält OAuth-Anmeldedaten (`oAuthClientId` und `oAuthClientSecret`), die bei der Erstellung generiert werden. Diese werden von der App verwendet, um API-Anfragen im Namen der Benutzer zu authentifizieren. Das Client-Secret wird bei der Erstellung **einmalig** zurückgegeben — bewahren Sie es sicher auf. Sie können es später über die Mutation `rotateApplicationRegistrationClientSecret` rotieren.
## Manuelle Einrichtung (ohne Scaffolder)
@@ -3,7 +3,7 @@ title: 1-Klick mit Docker Compose
---
<Warning>
Docker-Container sind für die Produktion oder das Selbsthosten bestimmt. Für Beiträge siehe bitte das [Lokale Setup](/l/de/developers/contribute/capabilities/local-setup).
Docker-Container sind für produktives Hosting oder Selbsthosting vorgesehen. Zum Mitwirken siehe [Lokale Einrichtung](/l/de/developers/contribute/capabilities/local-setup).
</Warning>
## Überblick
@@ -12,7 +12,7 @@ Diese Anleitung enthält Schritt-für-Schritt-Anweisungen, um die Twenty-Anwendu
**Wichtig:** Ändern Sie nur die in dieser Anleitung explizit erwähnten Einstellungen. Andere Konfigurationen zu ändern, kann zu Problemen führen.
Siehe die Dokumentation [Umgebungsvariablen einrichten](/l/de/developers/self-host/capabilities/setup) zur erweiterten Konfiguration. Alle Umgebungsvariablen müssen in der Datei docker-compose.yml auf Server- und/oder Worker-Ebene deklariert werden, je nach Variable.
Siehe die Dokumentation [Umgebungsvariablen einrichten](/l/de/developers/self-host/capabilities/setup) zur erweiterten Konfiguration. Alle Umgebungsvariablen müssen in der Datei `docker-compose.yml` auf Server- und/oder Worker-Ebene deklariert werden, je nach Variable.
## Systemanforderungen
@@ -0,0 +1,142 @@
---
title: MCP-Server
description: Verbinden Sie KI-Assistenten mit Ihrem Twenty-Workspace über das Model Context Protocol.
---
<Warning>
MCP befindet sich derzeit in **alpha** und ist nur in einigen Workspaces verfügbar. Möglicherweise ist es für Ihren Workspace noch nicht aktiviert.
</Warning>
Twenty stellt einen [MCP](https://modelcontextprotocol.io/)-Server bereit, damit KI-Assistenten — Claude Desktop, Claude Code, Cursor, ChatGPT und andere — Ihre CRM-Daten in natürlicher Sprache lesen und schreiben können.
Verwenden Sie Ihre **Workspace-URL** (die URL, mit der Sie auf Twenty zugreifen) als MCP-Endpunkt. In Twenty Cloud kann Ihre Workspace-URL `https://{mycompany}.twenty.com` oder eine benutzerdefinierte Domain sein. Der Server ist verfügbar unter:
| Umgebung | MCP-Endpunkt |
| ----------------- | ----------------------------------------------------------------------------- |
| **Cloud** | `https://{your-workspace-url}/mcp` (z. B. `https://mycompany.twenty.com/mcp`) |
| **Selbsthosting** | `https://{your-domain}/mcp` |
## Authentifizierungsmethoden
Sie haben zwei Möglichkeiten, Ihren MCP-Client zu authentifizieren: **OAuth** (empfohlen) oder **API-Schlüssel**.
### Option A — OAuth (empfohlen)
Mit OAuth öffnet Ihr MCP-Client ein Browserfenster, damit Sie sich anmelden können. Es werden keine geheimen Informationen in Konfigurationsdateien gespeichert, und Token werden automatisch erneuert.
<Note>
OAuth erfordert einen MCP-Client, der die [MCP-Autorisierungsspezifikation](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) unterstützt. Claude Desktop, Claude Code, Cursor und ChatGPT unterstützen dies.
</Note>
Fügen Sie dies zu Ihrer MCP-Client-Konfiguration hinzu und ersetzen Sie `{your-workspace-url}` durch den Host Ihrer Workspace-URL (z. B. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Das ist alles — kein API-Schlüssel erforderlich. Wenn der Client sich zum ersten Mal verbindet, wird er:
1. Die OAuth-Metadaten von Twenty über `/.well-known/oauth-protected-resource` und `/.well-known/oauth-authorization-server` ermitteln
2. Sich über die dynamische Client-Registrierung (RFC 7591) als OAuth-Client registrieren
3. Ihren Browser öffnen, um den Zugriff zu autorisieren
4. Token empfangen und eine Verbindung zum MCP-Server herstellen
Nachfolgende Verbindungen verwenden die gespeicherten Token erneut und erneuern sie automatisch.
### Option B — API-Schlüssel
Wenn Ihr MCP-Client OAuth nicht unterstützt oder Sie statische Anmeldeinformationen bevorzugen, übergeben Sie einen API-Schlüssel im `Authorization`-Header:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Ihr API-Schlüssel gewährt Zugriff auf Workspace-Daten. Halten Sie es von der Versionskontrolle und von gemeinsam genutzten Dotfiles fern.
</Warning>
Um einen API-Schlüssel zu erstellen, gehen Sie zu **Settings > APIs & Webhooks > + Create key**. Details finden Sie unter [APIs](/l/de/developers/extend/api#create-an-api-key).
## Schnellstart
### 1. Konfiguration kopieren
Gehen Sie in Twenty zu **Settings > AI > More > MCP Server**. Wählen Sie Ihre Authentifizierungsmethode (OAuth oder API-Schlüssel), kopieren Sie das JSON-Snippet (es verwendet bereits Ihre Workspace-URL) und fügen Sie es in die Konfigurationsdatei Ihres MCP-Clients ein.
| Client | Speicherort der Konfigurationsdatei |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) oder `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (Benutzer) oder `.mcp.json` (Projekt) |
| **Cursor** | `.cursor/mcp.json` in Ihrem Projekt oder `~/.cursor/mcp.json` global |
| **ChatGPT** | Aktivieren Sie den Entwicklermodus in **Settings > Apps & Connectors > Advanced settings** und verwenden Sie dann **Create** in **Settings > Apps & Connectors**, um den MCP-Server hinzuzufügen |
### 2. Verbinden
Starten Sie Ihren MCP-Client neu (oder laden Sie die Konfiguration neu). Bei Verwendung von OAuth werden Sie zu Twenty weitergeleitet, um den Zugriff zu autorisieren. Bei Verwendung eines API-Schlüssels wird die Verbindung sofort hergestellt.
### 3. Jetzt loslegen
Bitten Sie Ihren KI-Assistenten, mit Ihrem CRM zu interagieren:
* *"Zeige mir die 5 zuletzt erstellten Unternehmen"*
* *"Erstelle eine neue Person namens Jane Doe bei Acme Corp"*
* *"Finde alle offenen Verkaufschancen mit einem Wert von mehr als $10k"*
## Verfügbare Tools
Nach der Verbindung stellt der MCP-Server Tools bereit, die die Twenty-API widerspiegeln. Der empfohlene Workflow ist:
1. **`get_tool_catalog`** — alle verfügbaren Tools entdecken
2. **`learn_tools`** — das Eingabeschema für bestimmte Tools abrufen
3. **`execute_tool`** — ein Tool ausführen
Sie müssen sich die Tool-Namen nicht merken. Fragen Sie Ihren KI-Assistenten, was er tun kann, und er ruft `get_tool_catalog` automatisch auf.
## Berechtigungen
MCP-Verbindungen erben die Berechtigungen des authentifizierten Benutzers (OAuth) oder die dem API-Schlüssel zugewiesene Rolle. So beschränken Sie, was der MCP-Server tun darf:
* **OAuth**: Es gilt die Workspace-Rolle des Benutzers.
* **API-Schlüssel**: Weisen Sie dem API-Schlüssel unter **Settings > Roles** eine Rolle zu. Siehe [Berechtigungen](/l/de/user-guide/permissions-access/capabilities/permissions).
## Selbstgehostete Konfiguration
Für selbstgehostete Instanzen ersetzen Sie `{your-workspace-url}` durch die URL Ihres Servers. Stellen Sie sicher, dass `SERVER_URL` in Ihrer Umgebung der öffentlichen URL Ihrer Twenty-Instanz entspricht — dieser Wert wird verwendet, um die OAuth-Discovery-Metadaten zu generieren.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Der MCP-Endpunkt, die OAuth-Endpunkte und die Discovery-Metadaten leiten sich alle von diesem Wert ab.
## Fehlerbehebung
**"Unauthorized"- oder 401-Fehler**
* OAuth: Autorisieren Sie erneut, indem Sie die gespeicherten Token in Ihrem MCP-Client löschen und die Verbindung wiederherstellen.
* API-Schlüssel: Überprüfen Sie, ob der Schlüssel gültig ist und nicht abgelaufen ist. Generieren Sie ihn bei Bedarf neu.
**Der OAuth-Flow öffnet keinen Browser**
* Stellen Sie sicher, dass Ihr MCP-Client MCP Authorization unterstützt. Wechseln Sie andernfalls zur API-Schlüssel-Methode.
**Verbindungszeitüberschreitung**
* Stellen Sie sicher, dass die MCP-Endpunkt-URL von Ihrem Rechner aus erreichbar ist. Bei selbstgehosteten Instanzen prüfen Sie, ob der Server läuft und `SERVER_URL` korrekt gesetzt ist.
@@ -6,7 +6,7 @@ description: La guida per i collaboratori (o sviluppatori curiosi) che vogliono
## Prerequisiti
<Tabs>
<Tab title="Linux e MacOS">
<Tab title="Linux e macOS">
Prima di poter installare e usare Twenty, assicurati di installare quanto segue sul tuo computer:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -129,8 +129,8 @@ Dovresti eseguire tutti i comandi nei passaggi successivi dalla radice del proge
brew services list
```
L'installatore potrebbe non creare l'utente `postgres` di default quando si installa
tramite Homebrew su MacOS. Invece, crea un ruolo di PostgreSQL che corrisponde al tuo nome utente macOS
Il programma di installazione potrebbe non creare l'utente `postgres` per impostazione predefinita quando si installa
tramite Homebrew su macOS. Invece, crea un ruolo di PostgreSQL che corrisponde al tuo nome utente macOS
(es., "john").
Per controllare e creare l'utente `postgres` se necessario, segui questi passaggi:
```bash
@@ -174,7 +174,7 @@ Dovresti eseguire tutti i comandi nei passaggi successivi dalla radice del proge
Tutti i passaggi seguenti devono essere eseguiti nel terminale WSL (all'interno della tua macchina virtuale)
**Opzione 1:** Per predisporre PostgreSQL in locale:
Usa il seguente link per installare PostgreSQL nella tua macchina virtuale Linux: [Installazione di PostgreSQL](https://www.postgresql.org/download/linux/)
Usa il seguente link per installare PostgreSQL sulla tua macchina virtuale Linux: [Installazione di PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -189,11 +189,13 @@ Dovresti eseguire tutti i comandi nei passaggi successivi dalla radice del proge
</Tab>
</Tabs>
Puoi ora accedere al database su [localhost:5432](localhost:5432), con utente `postgres` e password `postgres`.
Ora puoi accedere al database all'indirizzo `localhost:5432`.
Se hai utilizzato l'opzione Docker sopra, le credenziali predefinite sono utente `postgres` e password `postgres`. Per le installazioni native di PostgreSQL, usa le credenziali e i ruoli configurati sulla tua macchina.
## Passaggio 4: Configura un database Redis (cache)
Twenty richiede una cache Redis per offrire le migliori prestazioni
Twenty richiede una cache Redis per offrire le migliori prestazioni.
<Tabs>
<Tab title="Linux">
@@ -211,7 +213,9 @@ Twenty richiede una cache Redis per offrire le migliori prestazioni
brew install redis
```
Avvia il tuo server Redis:
`brew services start redis`
```bash
brew services start redis
```
**Opzione 2:** Se hai Docker installato:
```bash
@@ -229,11 +233,11 @@ Twenty richiede una cache Redis per offrire le migliori prestazioni
</Tab>
</Tabs>
Se hai bisogno di una GUI client, ti consigliamo [Redis Insight](https://redis.io/insight/) (versione gratuita disponibile)
Se hai bisogno di una GUI client, ti consigliamo [Redis Insight](https://redis.io/insight/) (versione gratuita disponibile).
## Passaggio 5: Configura le variabili d'ambiente
Usa variabili d'ambiente o file `.env` per configurare il tuo progetto. Maggiori informazioni [qui](/l/it/developers/self-host/capabilities/setup)
Usa variabili d'ambiente o file `.env` per configurare il tuo progetto. Maggiori informazioni [qui](/l/it/developers/self-host/capabilities/setup).
Copia i file `.env.example` in `/front` e `/server`:
@@ -1263,95 +1263,95 @@ Il processo di compilazione:
1. **Analizza e convalida il manifest** — legge tutte le entità `defineX()` dai tuoi file sorgente e convalida la struttura del manifest.
2. **Compila le funzioni di logica e i componenti front-end** — raggruppa i sorgenti TypeScript in file ESM `.mjs` usando esbuild.
3. **Genera i checksum** — calcola gli hash MD5 per ogni file compilato, memorizzati nel manifest come `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **Genera il client API tipizzato** — esegue l'analisi dello schema GraphQL e genera i client tipizzati `CoreApiClient` e `MetadataApiClient`.
5. **Esegue un controllo dei tipi di TypeScript** — esegue `tsc --noEmit` per intercettare gli errori di tipo prima della pubblicazione.
6. **Ricompila con il client generato** — esegue una seconda passata di compilazione in modo da includere i tipi del client generato.
7. **Crea facoltativamente un tarball** — se viene passato `--tarball`, esegue `npm pack` per creare un file `.tgz` pronto per la distribuzione.
The build output in `.twenty/output/` contains:
L'output della build in `.twenty/output/` contiene:
```text
.twenty/output/
├── manifest.json # Manifest with checksums for all built files
├── package.json # Copied from app root
├── yarn.lock # Copied from app root
├── manifest.json # Manifest con checksum per tutti i file compilati
├── package.json # Copiato dalla radice dell'app
├── yarn.lock # Copiato dalla radice dell'app
├── src/
│ ├── logic-functions/ # Compiled .mjs logic function files
│ └── front-components/ # Compiled .mjs front component files
├── public/ # Static assets (if any)
└── my-app-1.0.0.tgz # Only with --tarball flag
│ ├── logic-functions/ # File .mjs compilati delle funzioni logiche
│ └── front-components/ # File .mjs compilati dei componenti front-end
├── public/ # Asset statici (se presenti)
└── my-app-1.0.0.tgz # Solo con il flag --tarball
```
| Opzione | Descrizione |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| Opzione | Descrizione |
| ----------- | ------------------------------------------------------------------- |
| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) |
| `--tarball` | Imballa anche l'output in un tarball `.tgz` |
## Publishing your app
## Pubblicazione della tua app
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Usa `app:publish` per distribuire la tua app — al registro npm oppure direttamente a un server Twenty.
### Publish to npm (default)
### Pubblica su npm (predefinito)
```bash filename="Terminal"
# Publish to npm (requires npm login)
# Pubblica su npm (richiede l'accesso a npm)
yarn twenty app:publish
# Publish with a dist-tag (e.g. beta, next)
# Pubblica con un dist-tag (ad es. beta, next)
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Questo compila l'app ed esegue `npm publish` dalla directory `.twenty/output/`. Il pacchetto pubblicato può quindi essere installato dal marketplace di Twenty da qualsiasi area di lavoro.
### Publish to a Twenty server
### Pubblica su un server Twenty
```bash filename="Terminal"
# Publish directly to a Twenty server
# Pubblica direttamente su un server Twenty
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Questo compila l'app con un tarball, lo carica sul server tramite la mutation GraphQL `uploadAppTarball` e avvia l'installazione in un unico passaggio. Questo è utile per distribuzioni private o per effettuare test su un server specifico.
| Opzione | Descrizione |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Opzione | Descrizione |
| ----------------- | -------------------------------------------------------------------------- |
| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) |
| `--server <url>` | Pubblica su un server Twenty invece di npm |
| `--token <token>` | Token di autenticazione per il server di destinazione |
| `--tag <tag>` | dist-tag di npm (ad es. `beta`, `next`) — solo per la pubblicazione su npm |
## Application registration
## Registrazione dell'applicazione
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Prima che un'app possa essere installata in un'area di lavoro, deve essere **registrata**. Una registrazione è un record di metadati che descrive l'origine dell'app e come autenticarla. Nella maggior parte dei casi questo è gestito automaticamente dalla CLI.
### Source types
### Tipi di origine
Each registration has a **source type** that determines how the app's files are resolved during installation:
Ogni registrazione ha un **tipo di origine** che determina come vengono risolti i file dell'app durante l'installazione:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Tipo di origine | Come vengono risolti i file | Caso d'uso tipico |
| --------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------- |
| `LOCAL` | I file sono sincronizzati in tempo reale dal watcher della CLI — l'installazione viene saltata | Sviluppo con `app:dev` |
| `NPM` | Recuperati dal registro npm tramite il campo `sourcePackage` | App pubblicate su npm |
| `TARBALL` | Estratti da un file `.tgz` caricato e archiviato sul server | App private pubblicate con `--server` |
### How registration happens
### Come avviene la registrazione
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — crea automaticamente una registrazione `LOCAL` la prima volta che esegui la modalità di sviluppo su un'area di lavoro.
* **`app:publish --server`** — carica un tarball e crea (o aggiorna) una registrazione `TARBALL`, quindi installa l'app.
* **Marketplace npm** — le registrazioni `NPM` vengono create quando le app vengono sincronizzate dal registro npm nel catalogo del marketplace di Twenty.
* **GraphQL API** — puoi anche creare registrazioni in modo programmatico tramite la mutation `createApplicationRegistration`.
### Registration vs installation
### Registrazione vs installazione
**Registration** and **installation** are separate concepts:
**Registrazione** e **installazione** sono concetti distinti:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* Una **registrazione** (`ApplicationRegistration`) è un record di metadati globale che descrive l'app: il suo nome, il tipo di origine, le credenziali OAuth e lo stato di pubblicazione nel marketplace. Esiste indipendentemente da qualsiasi area di lavoro.
* Un'**installazione** (`Application`) è un'istanza per area di lavoro. Quando un utente installa un'app, Twenty risolve il pacchetto dalla sorgente della registrazione, scrive i file compilati nell'archiviazione e sincronizza il manifest (creando oggetti, campi, funzioni logiche, ecc.) in quell'area di lavoro.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
Una registrazione può essere installata in molte aree di lavoro. Ogni area di lavoro ottiene la propria copia dei file dell'app e del modello di dati.
### OAuth credentials
### Credenziali OAuth
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Ogni registrazione include credenziali OAuth (`oAuthClientId` e `oAuthClientSecret`) generate al momento della creazione. Queste vengono utilizzate dall'app per autenticare le richieste API per conto degli utenti. Il client secret viene restituito **una sola volta** alla creazioneconservalo in modo sicuro. Puoi ruotarlo in seguito tramite la mutation `rotateApplicationRegistrationClientSecret`.
## Configurazione manuale (senza lo scaffolder)
@@ -3,7 +3,7 @@ title: 1-Click con Docker Compose
---
<Warning>
I container Docker sono per hosting in produzione o auto-hosting, per il contributo consulta il [Setup Locale](/l/it/developers/contribute/capabilities/local-setup).
I container Docker sono destinati all'hosting in produzione o al self-hosting. Per contribuire, consulta [Configurazione locale](/l/it/developers/contribute/capabilities/local-setup).
</Warning>
## Panoramica
@@ -12,7 +12,7 @@ Questa guida fornisce istruzioni passo passo per installare e configurare l'appl
**Importante:** Modifica solo le impostazioni esplicitamente menzionate in questa guida. Modificare altre configurazioni potrebbe portare a problemi.
Consulta i documenti [Configurazione delle Variabili di Ambiente](/l/it/developers/self-host/capabilities/setup) per configurazioni avanzate. Tutte le variabili di ambiente devono essere dichiarate nel file docker-compose.yml a livello di server e/o di worker a seconda della variabile.
Consulta [Configurazione delle variabili di ambiente](/l/it/developers/self-host/capabilities/setup) per configurazioni avanzate. Tutte le variabili di ambiente devono essere dichiarate nel file `docker-compose.yml` a livello di server e/o di worker, a seconda della variabile.
## Requisiti di Sistema
@@ -0,0 +1,142 @@
---
title: Server MCP
description: Collega gli assistenti AI al tuo spazio di lavoro di Twenty utilizzando il Model Context Protocol.
---
<Warning>
MCP è attualmente in **alpha** ed è disponibile solo su alcuni spazi di lavoro. Potrebbe non essere ancora abilitato per il tuo spazio di lavoro.
</Warning>
Twenty espone un server [MCP](https://modelcontextprotocol.io/) affinché gli assistenti AI — Claude Desktop, Claude Code, Cursor, ChatGPT e altri — possano leggere e scrivere i dati del tuo CRM in linguaggio naturale.
Usa l'**URL dello spazio di lavoro** (l'URL che usi per accedere a Twenty) come endpoint MCP. Su Twenty Cloud, l'URL del tuo spazio di lavoro potrebbe essere `https://{mycompany}.twenty.com` oppure un dominio personalizzato. Il server è disponibile all'indirizzo:
| Ambiente | Endpoint MCP |
| ----------------- | ------------------------------------------------------------------------------ |
| **Cloud** | `https://{your-workspace-url}/mcp` (ad es. `https://mycompany.twenty.com/mcp`) |
| **Auto-ospitato** | `https://{your-domain}/mcp` |
## Metodi di autenticazione
Hai due modi per autenticare il tuo client MCP: **OAuth** (consigliato) o **API Key**.
### Opzione A — OAuth (Consigliato)
Con OAuth, il tuo client MCP apre una finestra del browser per effettuare l'accesso. Nessun segreto viene archiviato nei file di configurazione e i token si rinnovano automaticamente.
<Note>
OAuth richiede un client MCP che supporti la [specifica MCP Authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor e ChatGPT lo supportano.
</Note>
Aggiungi questo alla configurazione del tuo client MCP, sostituendo `{your-workspace-url}` con l'host del tuo spazio di lavoro (ad es. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
È tutto — non è necessaria alcuna chiave API. Quando il client si connette per la prima volta, eseguirà:
1. Scoprire i metadati OAuth di Twenty tramite `/.well-known/oauth-protected-resource` e `/.well-known/oauth-authorization-server`
2. Registrarsi come client OAuth tramite registrazione dinamica del client (RFC 7591)
3. Aprire il browser per autorizzare l'accesso
4. Ricevere i token e connettersi al server MCP
Le connessioni successive riutilizzano i token memorizzati e li rinnovano automaticamente.
### Opzione B — Chiave API
Se il tuo client MCP non supporta OAuth, o preferisci credenziali statiche, passa una chiave API nell'header `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
La tua chiave API concede l'accesso ai dati dello spazio di lavoro. Mantienila fuori dal controllo versione e dai dotfile condivisi.
</Warning>
Per creare una chiave API, vai su **Impostazioni > API e Webhook > + Crea chiave**. Vedi [API](/l/it/developers/extend/api#create-an-api-key) per i dettagli.
## Avvio rapido
### 1. Copia la configurazione
Vai su **Impostazioni > AI > Altro > MCP Server** in Twenty. Scegli il metodo di autenticazione (OAuth o Chiave API), copia lo snippet JSON (userà già l'URL del tuo spazio di lavoro) e incollalo nel file di configurazione del tuo client MCP.
| Client | Percorso del file di configurazione |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (utente) o `.mcp.json` (progetto) |
| **Cursor** | `.cursor/mcp.json` nel tuo progetto, oppure `~/.cursor/mcp.json` globalmente |
| **ChatGPT** | Attiva la Modalità sviluppatore in **Impostazioni > App e Connettori > Impostazioni avanzate**, quindi usa **Crea** in **Impostazioni > App e Connettori** per aggiungere il server MCP |
### 2. Connetti
Riavvia il tuo client MCP (o ricarica la configurazione). Se usi OAuth verrai reindirizzato a Twenty per autorizzare l'accesso. Se usi una chiave API la connessione è immediata.
### 3. Inizia a usarlo
Chiedi al tuo assistente AI di interagire con il tuo CRM:
* *"Mostrami le 5 aziende create più di recente"*
* *"Crea una nuova persona di nome Jane Doe presso Acme Corp"*
* *"Trova tutte le opportunità aperte di valore superiore a $10k"*
## Strumenti disponibili
Una volta connesso, il server MCP espone strumenti che rispecchiano l'API di Twenty. Il flusso di lavoro consigliato è:
1. **`get_tool_catalog`** — scoprire tutti gli strumenti disponibili
2. **`learn_tools`** — ottenere lo schema di input per strumenti specifici
3. **`execute_tool`** — eseguire uno strumento
Non è necessario ricordare i nomi degli strumenti. Chiedi al tuo assistente AI cosa può fare e chiamerà `get_tool_catalog` automaticamente.
## Permessi
Le connessioni MCP ereditano le autorizzazioni dell'utente autenticato (OAuth) o il ruolo assegnato alla chiave API. Per limitare ciò che il server MCP può fare:
* **OAuth**: si applica il ruolo dell'utente nello spazio di lavoro.
* **Chiave API**: assegna un ruolo alla chiave API in **Impostazioni > Ruoli**. Vedi [Autorizzazioni](/l/it/user-guide/permissions-access/capabilities/permissions).
## Configurazione self-hosted
Per le istanze self-hosted, sostituisci `{your-workspace-url}` con l'URL del tuo server. Assicurati che `SERVER_URL` nel tuo ambiente corrisponda all'URL pubblico della tua istanza Twenty — viene utilizzato per generare i metadati di discovery OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
L'endpoint MCP, gli endpoint OAuth e i metadati di discovery derivano tutti da questo valore.
## Risoluzione dei problemi
**Errori "Unauthorized" o 401**
* OAuth: esegui nuovamente l'autorizzazione eliminando i token memorizzati nel tuo client MCP e riconnettiti.
* Chiave API: verifica che la chiave sia valida e non sia scaduta. Rigenerala se necessario.
**Il flusso OAuth non apre il browser**
* Assicurati che il tuo client MCP supporti MCP Authorization. In caso contrario, ricorri al metodo con Chiave API.
**Timeout di connessione**
* Verifica che l'URL dell'endpoint MCP sia raggiungibile dalla tua macchina. Per le istanze self-hosted, controlla che il server sia in esecuzione e che `SERVER_URL` sia impostato correttamente.
@@ -6,7 +6,7 @@ description: O guia para contribuidores (ou desenvolvedores curiosos) que deseja
## Pré-requisitos
<Tabs>
<Tab title="Linux e MacOS">
<Tab title="Linux e macOS">
Antes de instalar e usar o Twenty, certifique-se de instalar o seguinte em seu computador:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -30,7 +30,7 @@ wsl --install
```
Você deve agora ver um aviso para reiniciar o computador. Caso contrário, reinicie-o manualmente.
Ao reiniciar, uma janela do powershell será aberta e instalará o Ubuntu. Isso pode levar algum tempo.
Ao reiniciar, uma janela do PowerShell será aberta e instalará o Ubuntu. Isso pode levar algum tempo.
Você verá uma solicitação para criar um nome de usuário e senha para sua instalação do Ubuntu.
2. Instalar e configurar o git
@@ -102,8 +102,8 @@ Você deve executar todos os comandos nas etapas seguintes a partir da raiz do p
<Tabs>
<Tab title="Linux">
**Opção 1 (preferencial):** Para prover seu banco de dados localmente:
Use o seguinte link para instalar o Postgresql na sua máquina Linux: [Instalação do Postgresql](https://www.postgresql.org/download/linux/)
**Opção 1 (preferencial):** Para provisionar seu banco de dados localmente:
Use o seguinte link para instalar o PostgreSQL na sua máquina Linux: [Instalação do PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -130,7 +130,7 @@ Você deve executar todos os comandos nas etapas seguintes a partir da raiz do p
```
O instalador pode não criar o usuário `postgres` por padrão ao instalar
via Homebrew no MacOS. Em vez disso, ele cria uma função PostgreSQL que corresponde ao seu nome de usuário do macOS
via Homebrew no macOS. Em vez disso, ele cria uma função PostgreSQL que corresponde ao seu nome de usuário do macOS
(por exemplo, "john").
Para verificar e criar o usuário `postgres`, se necessário, siga estas etapas:
```bash
@@ -173,8 +173,8 @@ Você deve executar todos os comandos nas etapas seguintes a partir da raiz do p
<Tab title="Windows (WSL)">
Todos os passos a seguir devem ser executados no terminal WSL (dentro da sua máquina virtual)
**Opção 1:** Para provisionar seu Postgresql localmente:
Use o seguinte link para instalar o Postgresql em sua máquina virtual Linux: [Instalação do Postgresql](https://www.postgresql.org/download/linux/)
**Opção 1:** Para provisionar seu PostgreSQL localmente:
Use o seguinte link para instalar o PostgreSQL na sua máquina virtual Linux: [Instalação do PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -189,11 +189,13 @@ Você deve executar todos os comandos nas etapas seguintes a partir da raiz do p
</Tab>
</Tabs>
Você pode agora acessar o banco de dados em [localhost:5432](localhost:5432), com o usuário `postgres` e senha `postgres`.
Agora você pode acessar o banco de dados em `localhost:5432`.
Se você usou a opção do Docker acima, as credenciais padrão são usuário `postgres` e senha `postgres`. Para instalações nativas do PostgreSQL, use as credenciais e os papéis configurados na sua máquina.
## Passo 4: Configurar um Banco de Dados Redis (cache)
O Twenty requer um cache redis para oferecer o melhor desempenho
O Twenty requer um cache Redis para oferecer o melhor desempenho.
<Tabs>
<Tab title="Linux">
@@ -210,8 +212,10 @@ O Twenty requer um cache redis para oferecer o melhor desempenho
```bash
brew install redis
```
Inicie seu servidor redis:
`brew services start redis`
Inicie o servidor Redis:
```bash
brew services start redis
```
**Opção 2:** Se você tem o docker instalado:
```bash
@@ -229,11 +233,11 @@ O Twenty requer um cache redis para oferecer o melhor desempenho
</Tab>
</Tabs>
Se precisar de uma GUI de Cliente, recomendamos o [redis insight](https://redis.io/insight/) (versão gratuita disponível)
Se você precisar de uma GUI de cliente, recomendamos o [Redis Insight](https://redis.io/insight/) (versão gratuita disponível).
## Passo 5: Configurar variáveis de ambiente
Use variáveis de ambiente ou arquivos `.env` para configurar seu projeto. Mais informações [aqui](/l/pt/developers/self-host/capabilities/setup)
Use variáveis de ambiente ou arquivos `.env` para configurar seu projeto. Mais informações [aqui](/l/pt/developers/self-host/capabilities/setup).
Copie os arquivos `.env.example` em `/front` e `/server`:
@@ -1264,35 +1264,35 @@ O processo de build:
1. **Analisa e valida o manifesto** — lê todas as entidades `defineX()` dos seus arquivos de código-fonte e valida a estrutura do manifesto.
2. **Compila funções de lógica e componentes de front-end** — empacota o código-fonte TypeScript em arquivos ESM `.mjs` usando o esbuild.
3. **Gera checksums** — calcula hashes MD5 para cada arquivo gerado, armazenados no manifesto como `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **Gera o cliente de API tipado** — inspeciona o esquema GraphQL e gera clientes tipados `CoreApiClient` e `MetadataApiClient`.
5. **Executa uma verificação de tipos do TypeScript** — executa `tsc --noEmit` para detectar erros de tipo antes da publicação.
6. **Reconstrói com o cliente gerado** — realiza uma segunda passagem de compilação para que os tipos do cliente gerado sejam incluídos.
7. **Opcionalmente cria um tarball** — se `--tarball` for passado, executa `npm pack` para criar um arquivo `.tgz` pronto para distribuição.
The build output in `.twenty/output/` contains:
A saída da compilação em `.twenty/output/` contém:
```text
.twenty/output/
├── manifest.json # Manifest with checksums for all built files
├── package.json # Copied from app root
├── yarn.lock # Copied from app root
├── manifest.json # Manifesto com somas de verificação para todos os arquivos compilados
├── package.json # Copiado da raiz do aplicativo
├── yarn.lock # Copiado da raiz do aplicativo
├── src/
│ ├── logic-functions/ # Compiled .mjs logic function files
│ └── front-components/ # Compiled .mjs front component files
├── public/ # Static assets (if any)
└── my-app-1.0.0.tgz # Only with --tarball flag
│ ├── logic-functions/ # Arquivos .mjs compilados de funções de lógica
│ └── front-components/ # Arquivos .mjs compilados de componentes de front-end
├── public/ # Recursos estáticos (se houver)
└── my-app-1.0.0.tgz # Apenas com a opção --tarball
```
| Opção | Descrição |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| `[appPath]` | Caminho para o diretório do app (padrão: diretório atual) |
| `--tarball` | Também empacota a saída em um tarball `.tgz` |
## Publishing your app
## Publicando seu app
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Use `app:publish` para distribuir seu app — ou para o registro do npm ou diretamente para um servidor Twenty.
### Publish to npm (default)
### Publicar no npm (padrão)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1302,57 +1302,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Isso compila o app e executa `npm publish` a partir do diretório `.twenty/output/`. O pacote publicado pode então ser instalado no marketplace da Twenty por qualquer espaço de trabalho.
### Publish to a Twenty server
### Publicar em um servidor Twenty
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Isso compila o app com um tarball, faz o upload para o servidor via a mutação GraphQL `uploadAppTarball` e aciona a instalação em uma única etapa. Isso é útil para implantações privadas ou para testar em um servidor específico.
| Opção | Descrição |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Opção | Descrição |
| ----------------- | --------------------------------------------------------------------- |
| `[appPath]` | Caminho para o diretório do app (padrão: diretório atual) |
| `--server <url>` | Publicar em um servidor Twenty em vez de no npm |
| `--token <token>` | Token de autenticação para o servidor de destino |
| `--tag <tag>` | dist-tag do npm (ex.: `beta`, `next`) — apenas para publicação no npm |
## Application registration
## Registro de aplicação
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Antes que um app possa ser instalado em um espaço de trabalho, ele precisa ser **registrado**. Um registro é um registro de metadados que descreve de onde o app vem e como autenticá-lo. Isso é tratado automaticamente pela CLI na maioria dos casos.
### Source types
### Tipos de origem
Each registration has a **source type** that determines how the app's files are resolved during installation:
Cada registro tem um **tipo de origem** que determina como os arquivos do app são resolvidos durante a instalação:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Tipo de origem | Como os arquivos são resolvidos | Caso de uso típico |
| -------------- | ------------------------------------------------------------------------------------------- | --------------------------------------- |
| `LOCAL` | Os arquivos são sincronizados em tempo real pelo observador da CLI — a instalação é omitida | Desenvolvimento com `app:dev` |
| `NPM` | Obtidos do registro npm por meio do campo `sourcePackage` | Apps publicados no npm |
| `TARBALL` | Extraídos de um arquivo `.tgz` enviado e armazenado no servidor | Apps privados publicados com `--server` |
### How registration happens
### Como o registro acontece
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — cria automaticamente um registro `LOCAL` na primeira vez que você executa o modo de desenvolvimento em um espaço de trabalho.
* **`app:publish --server`** — faz o upload de um tarball e cria (ou atualiza) um registro `TARBALL`, e em seguida instala o app.
* **marketplace do npm** — registros `NPM` são criados quando apps são sincronizados do registro npm para o catálogo do marketplace da Twenty.
* **API GraphQL** — você também pode criar registros programaticamente por meio da mutação `createApplicationRegistration`.
### Registration vs installation
### Registro vs instalação
**Registration** and **installation** are separate concepts:
**Registro** e **instalação** são conceitos distintos:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* Um **registro** (`ApplicationRegistration`) é um registro global de metadados que descreve o app: seu nome, tipo de origem, credenciais OAuth e status de listagem no marketplace. Ele existe independentemente de qualquer espaço de trabalho.
* Uma **instalação** (`Application`) é uma instância por espaço de trabalho. Quando um usuário instala um app, a Twenty resolve o pacote a partir da origem do registro, grava os arquivos compilados no armazenamento e sincroniza o manifesto (criando objetos, campos, funções de lógica etc.). naquele espaço de trabalho.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
Um registro pode ser instalado em muitos espaços de trabalho. Cada espaço de trabalho recebe sua própria cópia dos arquivos e do modelo de dados do app.
### OAuth credentials
### Credenciais OAuth
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Cada registro inclui credenciais OAuth (`oAuthClientId` e `oAuthClientSecret`) geradas no momento da criação. Elas são usadas pelo app para autenticar requisições de API em nome dos usuários. O segredo do cliente é retornado **uma única vez** na criação — armazene-o com segurança. Você pode rotacioná-lo posteriormente por meio da mutação `rotateApplicationRegistrationClientSecret`.
## Configuração manual (sem o gerador)
@@ -3,7 +3,7 @@ title: 1-Clique c/ Docker Compose
---
<Warning>
Contêineres Docker são para hospedagem de produção ou auto-hospedagem, para contribuições, por favor, verifique o [Setup Local](/l/pt/developers/contribute/capabilities/local-setup).
Os contêineres Docker são para hospedagem em produção ou auto-hospedagem. Para contribuir, consulte a [Configuração local](/l/pt/developers/contribute/capabilities/local-setup).
</Warning>
## Visão geral
@@ -12,7 +12,7 @@ Este guia fornece instruções passo a passo para instalar e configurar o aplica
**Importante:** Modifique apenas as configurações explicitamente mencionadas neste guia. Alterar outras configurações pode levar a problemas.
Veja a documentação [Configurar Variáveis de Ambiente](/l/pt/developers/self-host/capabilities/setup) para configuração avançada. Todas as variáveis de ambiente devem ser declaradas no arquivo docker-compose.yml no nível do servidor e/ou trabalhador, dependendo da variável.
Consulte [Configurar Variáveis de Ambiente](/l/pt/developers/self-host/capabilities/setup) para configuração avançada. Todas as variáveis de ambiente devem ser declaradas no arquivo `docker-compose.yml` no nível do servidor e/ou do trabalhador, dependendo da variável.
## Requisitos do Sistema
@@ -0,0 +1,142 @@
---
title: Servidor MCP
description: Conecte assistentes de IA ao seu espaço de trabalho do Twenty usando o Model Context Protocol.
---
<Warning>
O MCP está atualmente em **alfa** e está disponível apenas em alguns espaços de trabalho. O MCP pode ainda não estar ativado no seu espaço de trabalho.
</Warning>
O Twenty expõe um servidor [MCP](https://modelcontextprotocol.io/) para que assistentes de IA — Claude Desktop, Claude Code, Cursor, ChatGPT e outros — possam ler e escrever seus dados de CRM por meio de linguagem natural.
Use o **URL do espaço de trabalho** (o URL que você usa para acessar o Twenty) como o endpoint do MCP. Na Twenty Cloud, o URL do seu espaço de trabalho pode ser `https://{mycompany}.twenty.com` ou um domínio personalizado. O servidor está disponível em:
| Ambiente | Endpoint do MCP |
| ------------------ | ------------------------------------------------------------------------------------ |
| **Nuvem** | `https://{your-workspace-url}/mcp` (por exemplo, `https://mycompany.twenty.com/mcp`) |
| **Auto-hospedado** | `https://{your-domain}/mcp` |
## Métodos de autenticação
Você tem duas maneiras de autenticar seu cliente MCP: **OAuth** (recomendado) ou **Chave de API**.
### Opção A — OAuth (recomendado)
Com o OAuth, seu cliente MCP abre uma janela do navegador para você fazer login. Nenhum segredo é armazenado em arquivos de configuração, e os tokens são atualizados automaticamente.
<Note>
O OAuth requer um cliente MCP que ofereça suporte à [especificação de Autorização MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor e ChatGPT oferecem suporte.
</Note>
Adicione isto à configuração do seu cliente MCP, substituindo `{your-workspace-url}` pelo host do seu espaço de trabalho (por exemplo, `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
É isso — nenhuma chave de API é necessária. Quando o cliente se conectar pela primeira vez, ele irá:
1. Descobrir os metadados de OAuth do Twenty por meio de `/.well-known/oauth-protected-resource` e `/.well-known/oauth-authorization-server`
2. Registrar-se como um cliente OAuth por meio de registro dinâmico de cliente (RFC 7591)
3. Abrir seu navegador para autorizar o acesso
4. Receber tokens e conectar-se ao servidor MCP
Conexões subsequentes reutilizam os tokens armazenados e os atualizam automaticamente.
### Opção B — Chave de API
Se o seu cliente MCP não oferecer suporte a OAuth, ou se você preferir credenciais estáticas, passe uma chave de API no cabeçalho `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Sua chave de API concede acesso aos dados do espaço de trabalho. Mantenha-a fora do controle de versão e de dotfiles compartilhados.
</Warning>
Para criar uma chave de API, vá em **Settings > APIs & Webhooks > + Create key**. Veja [APIs](/l/pt/developers/extend/api#create-an-api-key) para detalhes.
## Início rápido
### 1. Copie a configuração
Vá até **Settings > AI > More > MCP Server** no Twenty. Escolha seu método de autenticação (OAuth ou Chave de API), copie o trecho de JSON (ele já usará o URL do seu espaço de trabalho) e cole-o no arquivo de configuração do seu cliente MCP.
| Cliente | Local do arquivo de configuração |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) ou `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (usuário) ou `.mcp.json` (projeto) |
| **Cursor** | `.cursor/mcp.json` no seu projeto, ou `~/.cursor/mcp.json` globalmente |
| **ChatGPT** | Ative o Modo Desenvolvedor em **Settings > Apps & Connectors > Advanced settings** e, em seguida, use **Create** em **Settings > Apps & Connectors** para adicionar o servidor MCP |
### 2. Conectar
Reinicie seu cliente MCP (ou recarregue a configuração). Se estiver usando OAuth, você será redirecionado ao Twenty para autorizar o acesso. Se estiver usando uma chave de API, a conexão é imediata.
### 3. Comece a usá-lo
Peça ao seu assistente de IA para interagir com seu CRM:
* *"Mostre-me as 5 empresas criadas mais recentemente"*
* *"Crie uma nova pessoa chamada Jane Doe na Acme Corp"*
* *"Encontre todas as oportunidades em aberto com valor superior a $10k"*
## Ferramentas disponíveis
Depois de conectado, o servidor MCP expõe ferramentas que refletem a API do Twenty. O fluxo de trabalho recomendado é:
1. **`get_tool_catalog`** — descobrir todas as ferramentas disponíveis
2. **`learn_tools`** — obter o esquema de entrada para ferramentas específicas
3. **`execute_tool`** — executar uma ferramenta
Você não precisa se lembrar dos nomes das ferramentas. Pergunte ao seu assistente de IA o que ele pode fazer e ele chamará `get_tool_catalog` automaticamente.
## Permissões
As conexões MCP herdam as permissões do usuário autenticado (OAuth) ou da função atribuída à chave de API. Para restringir o que o servidor MCP pode fazer:
* **OAuth**: Aplica-se à função do espaço de trabalho do usuário.
* **Chave de API**: Atribua uma função à chave de API em **Settings > Roles**. Veja [Permissões](/l/pt/user-guide/permissions-access/capabilities/permissions).
## Configuração auto-hospedada
Para instâncias auto-hospedadas, substitua `{your-workspace-url}` pelo URL do seu servidor. Certifique-se de que `SERVER_URL` no seu ambiente corresponda ao URL público da sua instância do Twenty — isso é usado para gerar os metadados de descoberta do OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
O endpoint do MCP, os endpoints de OAuth e os metadados de descoberta derivam todos desse valor.
## Resolução de Problemas
**Erros "Unauthorized" ou 401**
* OAuth: autorize novamente limpando os tokens armazenados no seu cliente MCP e reconectando.
* Chave de API: verifique se a chave é válida e não expirou. Gere-a novamente, se necessário.
**O fluxo do OAuth não abre um navegador**
* Garanta que seu cliente MCP ofereça suporte à Autorização MCP. Se não oferecer, utilize o método de Chave de API.
**Tempo limite de conexão**
* Confirme que o URL do endpoint MCP é acessível a partir da sua máquina. Para instâncias auto-hospedadas, verifique se o servidor está em execução e se `SERVER_URL` está definido corretamente.
@@ -6,7 +6,7 @@ description: Ghidul pentru contribuitori (sau dezvoltatori curioși) care doresc
## Cerințe
<Tabs>
<Tab title="Linux și MacOS">
<Tab title="Linux și macOS">
Înainte de a instala și utiliza Twenty, asigurați-vă că instalați următoarele pe computerul dvs.:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -129,8 +129,8 @@ Trebuie să rulați toate comenzile în pașii următori de la rădăcina proiec
brew services list
```
Instalatorul s-ar putea să nu creeze implicit utilizatorul `postgres` atunci când instalați
prin Homebrew pe MacOS. În schimb, creează un rol PostgreSQL care se potrivește cu numele de utilizator al
Instalatorul s-ar putea să nu creeze implicit utilizatorul `postgres` la instalarea
prin Homebrew pe macOS. În schimb, creează un rol PostgreSQL care se potrivește cu numele de utilizator al
macOS-ului dvs. (de ex., "john").
Pentru a verifica și crea utilizatorul `postgres` dacă este necesar, urmați acești pași:
```bash
@@ -189,11 +189,13 @@ Trebuie să rulați toate comenzile în pașii următori de la rădăcina proiec
</Tab>
</Tabs>
Acum puteți accesa baza de date la [localhost:5432](localhost:5432), cu utilizator `postgres` și parolă `postgres`.
Acum puteți accesa baza de date la `localhost:5432`.
Dacă ați folosit opțiunea Docker de mai sus, datele implicite de autentificare sunt utilizatorul `postgres` și parola `postgres`. Pentru instalările native PostgreSQL, folosiți datele de autentificare și rolurile configurate pe mașina dvs.
## Pasul 4: Configurați o bază de date Redis (cache)
Twenty necesită un cache Redis pentru a oferi cea mai bună performanță
Twenty necesită un cache Redis pentru a oferi cea mai bună performanță.
<Tabs>
<Tab title="Linux">
@@ -211,7 +213,9 @@ Twenty necesită un cache Redis pentru a oferi cea mai bună performanță
brew install redis
```
Porniți serverul Redis:
`brew services start redis`
```bash
brew services start redis
```
**Opțiunea 2:** Dacă aveți docker instalat:
```bash
@@ -229,11 +233,11 @@ Twenty necesită un cache Redis pentru a oferi cea mai bună performanță
</Tab>
</Tabs>
Dacă aveți nevoie de o interfață grafică pentru client, vă recomandăm [Redis Insight](https://redis.io/insight/) (versiune gratuită disponibilă)
Dacă aveți nevoie de o interfață grafică pentru client, vă recomandăm [Redis Insight](https://redis.io/insight/) (versiune gratuită disponibilă).
## Pasul 5: Configurați variabilele de mediu
Utilizați variabile de mediu sau fișiere `.env` pentru a configura proiectul dvs. Mai multe informații [aici](/l/ro/developers/self-host/capabilities/setup)
Utilizați variabile de mediu sau fișiere `.env` pentru a configura proiectul dvs. Mai multe informații [aici](/l/ro/developers/self-host/capabilities/setup).
Copiați fișierele `.env.example` din `/front` și `/server`:
@@ -1263,95 +1263,95 @@ The build process:
1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure.
2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild.
3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **Generează clientul API tipizat** — examinează schema GraphQL și generează clienți tipizați `CoreApiClient` și `MetadataApiClient`.
5. **Rulează o verificare a tipurilor TypeScript** — rulează `tsc --noEmit` pentru a detecta erorile de tip înainte de publicare.
6. **Reconstruiește cu clientul generat** — efectuează o a doua trecere de compilare astfel încât tipurile clientului generat să fie incluse.
7. **Creează opțional un tarball** — dacă se trece `--tarball`, rulează `npm pack` pentru a crea un fișier `.tgz` gata pentru distribuire.
The build output in `.twenty/output/` contains:
Rezultatul build-ului din `.twenty/output/` conține:
```text
.twenty/output/
├── manifest.json # Manifest with checksums for all built files
├── package.json # Copied from app root
├── yarn.lock # Copied from app root
├── manifest.json # Manifest cu sume de control pentru toate fișierele generate
├── package.json # Copiat din rădăcina aplicației
├── yarn.lock # Copiat din rădăcina aplicației
├── src/
│ ├── logic-functions/ # Compiled .mjs logic function files
│ └── front-components/ # Compiled .mjs front component files
├── public/ # Static assets (if any)
└── my-app-1.0.0.tgz # Only with --tarball flag
│ ├── logic-functions/ # Fișiere .mjs de funcții de logică compilate
│ └── front-components/ # Fișiere .mjs de componente front-end compilate
├── public/ # Resurse statice (dacă există)
└── my-app-1.0.0.tgz # Doar cu opțiunea --tarball
```
| Opțiune | Descriere |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| Opțiune | Descriere |
| ----------- | -------------------------------------------------------------- |
| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) |
| `--tarball` | De asemenea, împachetează rezultatul într-un tarball `.tgz` |
## Publishing your app
## Publicarea aplicației
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Folosește `app:publish` pentru a distribui aplicația — fie în registrul npm, fie direct pe un server Twenty.
### Publish to npm (default)
### Publicare pe npm (implicit)
```bash filename="Terminal"
# Publish to npm (requires npm login)
# Publicare pe npm (necesită autentificare npm)
yarn twenty app:publish
# Publish with a dist-tag (e.g. beta, next)
# Publicare cu un dist-tag (de ex. beta, next)
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Aceasta construiește aplicația și rulează `npm publish` din directorul `.twenty/output/`. Pachetul publicat poate fi apoi instalat din marketplace-ul Twenty de către orice spațiu de lucru.
### Publish to a Twenty server
### Publicare pe un server Twenty
```bash filename="Terminal"
# Publish directly to a Twenty server
# Publicare direct pe un server Twenty
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Aceasta construiește aplicația cu un tarball, o încarcă pe server prin mutația GraphQL `uploadAppTarball` și declanșează instalarea într-un singur pas. Acest lucru este util pentru implementări private sau pentru testare pe un server specific.
| Opțiune | Descriere |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Opțiune | Descriere |
| ----------------- | -------------------------------------------------------------------- |
| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) |
| `--server <url>` | Publică pe un server Twenty în loc de npm |
| `--token <token>` | Jeton de autentificare pentru serverul țintă |
| `--tag <tag>` | npm dist-tag (de ex. `beta`, `next`) — doar pentru publicarea pe npm |
## Application registration
## Înregistrarea aplicației
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Înainte ca o aplicație să poată fi instalată într-un spațiu de lucru, aceasta trebuie să fie **înregistrată**. O înregistrare este o înregistrare de metadate care descrie de unde provine aplicația și cum se autentifică. Acest lucru este gestionat automat de CLI în cele mai multe cazuri.
### Source types
### Tipuri de sursă
Each registration has a **source type** that determines how the app's files are resolved during installation:
Fiecare înregistrare are un **tip de sursă** care determină modul în care fișierele aplicației sunt preluate în timpul instalării:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Tip de sursă | Cum sunt preluate fișierele | Caz de utilizare tipic |
| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------- |
| `LOCAL` | Fișierele sunt sincronizate în timp real de către watcher-ul CLI — instalarea este omisă | Dezvoltare cu `app:dev` |
| `NPM` | Obținute din registrul npm prin câmpul `sourcePackage` | Aplicații publicate pe npm |
| `TARBALL` | Extrase dintr-un fișier `.tgz` încărcat, stocat pe server | Aplicații private publicate cu `--server` |
### How registration happens
### Cum are loc înregistrarea
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — creează automat o înregistrare `LOCAL` prima dată când rulezi modul de dezvoltare pentru un spațiu de lucru.
* **`app:publish --server`** — încarcă un tarball și creează (sau actualizează) o înregistrare `TARBALL`, apoi instalează aplicația.
* **marketplace-ul npm** — înregistrările `NPM` sunt create când aplicațiile sunt sincronizate din registrul npm în catalogul marketplace-ului Twenty.
* **API GraphQL** — poți de asemenea să creezi înregistrări programatic prin mutația `createApplicationRegistration`.
### Registration vs installation
### Înregistrare vs instalare
**Registration** and **installation** are separate concepts:
**Înregistrarea** și **instalarea** sunt concepte separate:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* O **înregistrare** (`ApplicationRegistration`) este o înregistrare globală de metadate care descrie aplicația: numele ei, tipul de sursă, acreditările OAuth și statutul listării în marketplace. Există independent de orice spațiu de lucru.
* O **instalare** (`Application`) este o instanță per spațiu de lucru. Când un utilizator instalează o aplicație, Twenty rezolvă pachetul din sursa înregistrării, scrie fișierele compilate în stocare și sincronizează manifestul (creând obiecte, câmpuri, funcții de logică etc.) în acel spațiu de lucru.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
O singură înregistrare poate fi instalată în multe spații de lucru. Fiecare spațiu de lucru primește propria copie a fișierelor și a modelului de date al aplicației.
### OAuth credentials
### Acreditări OAuth
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Fiecare înregistrare include acreditări OAuth (`oAuthClientId` și `oAuthClientSecret`) generate la momentul creării. Acestea sunt folosite de aplicație pentru a autentifica cererile API în numele utilizatorilor. Secretul clientului este returnat **o singură dată** la crearestrează-l în siguranță. Îl poți roti ulterior prin mutația `rotateApplicationRegistrationClientSecret`.
## Configurare manuală (fără generator)
@@ -3,7 +3,7 @@ title: 1-Click cu Docker Compose
---
<Warning>
Containerele Docker sunt pentru găzduire în producție sau auto-găzduire; pentru a contribui, consultați [Configurare locală](/l/ro/developers/contribute/capabilities/local-setup).
Containerele Docker sunt destinate găzduirii în producție sau auto-găzduirii. Pentru a contribui, consultați [Configurarea locală](/l/ro/developers/contribute/capabilities/local-setup).
</Warning>
## Prezentare generală
@@ -12,7 +12,7 @@ Acest ghid oferă instrucțiuni pas cu pas pentru a instala și configura aplica
**Important:** Modificați numai setările menționate explicit în acest ghid. Modificarea altor configurații poate duce la probleme.
Consultați documentația [Setup Environment Variables](/l/ro/developers/self-host/capabilities/setup) pentru configurare avansată. Toate variabilele de mediu trebuie declarate în fișierul docker-compose.yml la nivel de server și/sau de lucru, în funcție de variabilă.
Consultați [Setup Environment Variables](/l/ro/developers/self-host/capabilities/setup) pentru configurare avansată. Toate variabilele de mediu trebuie declarate în fișierul `docker-compose.yml` la nivel de server și/sau de lucru, în funcție de variabilă.
## Cerințe de Sistem
@@ -0,0 +1,142 @@
---
title: Server MCP
description: Conectați asistenți AI la spațiul dvs. de lucru Twenty folosind Model Context Protocol.
---
<Warning>
MCP este în prezent în **alpha** și este disponibil doar în unele spații de lucru. Este posibil să nu fie activat încă pentru spațiul dvs. de lucru.
</Warning>
Twenty expune un server [MCP](https://modelcontextprotocol.io/) astfel încât asistenții AI — Claude Desktop, Claude Code, Cursor, ChatGPT și alții — să poată citi și scrie datele tale din CRM prin limbaj natural.
Folosește **URL-ul spațiului de lucru** (URL-ul pe care îl folosești pentru a accesa Twenty) drept punct final MCP. Pe Twenty Cloud, URL-ul spațiului tău de lucru poate fi `https://{mycompany}.twenty.com` sau un domeniu personalizat. Serverul este disponibil la:
| Mediu | Punct final MCP |
| -------------------- | ------------------------------------------------------------------------------ |
| **Cloud** | `https://{your-workspace-url}/mcp` (de ex. `https://mycompany.twenty.com/mcp`) |
| **Găzduire proprie** | `https://{your-domain}/mcp` |
## Metode de autentificare
Ai două moduri de a-ți autentifica clientul MCP: **OAuth** (recomandat) sau **Cheie API**.
### Opțiunea A — OAuth (Recomandat)
Cu OAuth, clientul tău MCP deschide o fereastră de browser pentru a te autentifica. Nicio informație secretă nu este stocată în fișierele de configurare, iar tokenurile se reîmprospătează automat.
<Note>
OAuth necesită un client MCP care suportă [specificația MCP Authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor și ChatGPT o suportă.
</Note>
Adaugă asta în configurația clientului tău MCP, înlocuind `{your-workspace-url}` cu gazda spațiului tău de lucru (de ex. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Atât — nu este necesară nicio cheie API. Când clientul se conectează pentru prima dată, acesta va:
1. Va descoperi metadatele OAuth ale Twenty prin `/.well-known/oauth-protected-resource` și `/.well-known/oauth-authorization-server`
2. Se va înregistra ca un client OAuth prin înregistrare dinamică a clientului (RFC 7591)
3. Îți va deschide browserul pentru a autoriza accesul
4. Va primi tokenurile și se va conecta la serverul MCP
Conexiunile ulterioare reutilizează tokenurile stocate și le reîmprospătează automat.
### Opțiunea B — Cheie API
Dacă clientul tău MCP nu suportă OAuth sau preferi acreditări statice, furnizează o cheie API în antetul `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Cheia ta API oferă acces la datele spațiului de lucru. Ține-o în afara controlului versiunilor și a dotfiles partajate.
</Warning>
Pentru a crea o cheie API, mergi la **Settings > APIs & Webhooks > + Create key**. Vezi [API-uri](/l/ro/developers/extend/api#create-an-api-key) pentru detalii.
## Pornire rapidă
### 1. Copiază configurația
Mergi la **Settings > AI > More > MCP Server** în Twenty. Alege metoda de autentificare (OAuth sau Cheie API), copiază fragmentul JSON (va folosi deja URL-ul spațiului tău de lucru) și lipește-l în fișierul de configurare al clientului tău MCP.
| Client | Locația fișierului de configurare |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) sau `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (utilizator) sau `.mcp.json` (proiect) |
| **Cursor** | `.cursor/mcp.json` în proiectul tău sau `~/.cursor/mcp.json` global |
| **ChatGPT** | Activează Modul pentru dezvoltatori în **Settings > Apps & Connectors > Advanced settings**, apoi folosește **Create** în **Settings > Apps & Connectors** pentru a adăuga serverul MCP |
### 2. Conectează-te
Repornește clientul tău MCP (sau reîncarcă configurația). Dacă folosești OAuth, vei fi redirecționat către Twenty pentru a autoriza accesul. Dacă folosești o cheie API, conexiunea este imediată.
### 3. Începe să-l folosești
Roagă-ți asistentul AI să interacționeze cu CRM-ul tău:
* *"Arată-mi cele 5 companii create cel mai recent"*
* *"Creează o persoană nouă numită Jane Doe la Acme Corp"*
* *"Găsește toate oportunitățile deschise cu o valoare mai mare de $10k"*
## Instrumente disponibile
După conectare, serverul MCP expune instrumente care oglindesc API-ul Twenty. Fluxul de lucru recomandat este:
1. **`get_tool_catalog`** — descoperă toate instrumentele disponibile
2. **`learn_tools`** — obține schema de intrare pentru instrumente specifice
3. **`execute_tool`** — rulează un instrument
Nu este nevoie să reții numele instrumentelor. Întreabă-ți asistentul AI ce poate face și va apela `get_tool_catalog` automat.
## Permisiuni
Conexiunile MCP moștenesc permisiunile utilizatorului autentificat (OAuth) sau rolul atribuit cheii API. Pentru a restricționa ce poate face serverul MCP:
* **OAuth**: Se aplică rolul utilizatorului din spațiul de lucru.
* **Cheie API**: Atribuie un rol cheii API în **Settings > Roles**. Vezi [Permisiuni](/l/ro/user-guide/permissions-access/capabilities/permissions).
## Configurație pentru auto-găzduire
Pentru instanțele auto-găzduite, înlocuiește `{your-workspace-url}` cu URL-ul serverului tău. Asigură-te că `SERVER_URL` din mediul tău corespunde URL-ului public al instanței tale Twenty — acesta este folosit pentru a genera metadatele de descoperire OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Punctul final MCP, punctele finale OAuth și metadatele de descoperire derivă toate din această valoare.
## Depanare
**Erori "Unauthorized" sau 401**
* OAuth: reautorizează ștergând tokenurile stocate din clientul tău MCP și reconectează-te.
* Cheie API: verifică dacă cheia este validă și nu a expirat. Regenereaz-o dacă este necesar.
**Fluxul OAuth nu deschide un browser**
* Asigură-te că clientul tău MCP suportă MCP Authorization. Dacă nu, revino la metoda cu Cheie API.
**Timeout de conexiune**
* Confirmă că URL-ul punctului final MCP este accesibil de pe calculatorul tău. Pentru instanțele auto-găzduite, verifică faptul că serverul rulează și că `SERVER_URL` este setat corect.
@@ -8,7 +8,7 @@ title: Руководство по стилю
Для этого лучше быть немного более многословными, чем слишком краткими.
Всегда держите в голове, что код читают чаще, чем пишут, особенно в проекте с открытым исходным кодом, где к нему может присоединиться кто угодно.
Всегда помните, что код читают чаще, чем пишут, особенно в проекте с открытым исходным кодом, в который может внести вклад кто угодно.
Существует много правил, которые здесь не описаны, но автоматически проверяются линтерами.
@@ -150,7 +150,7 @@ type MyType = {
### Используйте строковые литералы вместо перечислений.
[Строковые литералы](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) - это основной способ обработки значений, напоминающих перечисление, в TypeScript. Они легче расширяются с помощью Pick и Omit и обеспечивают лучшее взаимодействие с разработчиком, особенно с автозаполнением кода.
[Строковые литералы](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) - это основной способ обработки значений, напоминающих перечисление, в TypeScript. Их проще расширять с помощью Pick и Omit, и они обеспечивают более удобную работу разработчика, особенно благодаря автодополнению кода.
Вы можете увидеть, почему TypeScript рекомендует избегать перечислений [здесь](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
@@ -6,7 +6,7 @@ description: Руководство для участников (или любо
## Требования
<Tabs>
<Tab title="Linux и MacOS">
<Tab title="Linux и macOS">
Прежде чем установить и использовать Twenty, убедитесь, что у вас установлено следующее:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -30,7 +30,7 @@ wsl --install
```
Теперь должно появиться приглашение на перезагрузку компьютера. Если нет, перезагрузите его вручную.
После перезагрузки откроется окно PowerShell и установит Ubuntu. Это может занять некоторое время.
После перезагрузки откроется окно PowerShell и начнётся установка Ubuntu. Это может занять некоторое время.
Появится запрос на создание имени пользователя и пароля для вашей установки Ubuntu.
2. Установите и настройте git
@@ -102,8 +102,8 @@ cd twenty
<Tabs>
<Tab title="Linux">
**Опция 1 (предпочтительно):** Чтобы настроить вашу базу данных локально:
Используйте следующую ссылку для установки Postgresql на вашу Linux машину: [Установка Postgresql](https://www.postgresql.org/download/linux/)
**Опция 1 (предпочтительно):** Чтобы настроить базу данных локально:
Используйте следующую ссылку для установки PostgreSQL на компьютер с Linux: [Установка PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -130,7 +130,7 @@ cd twenty
```
Установщик может не создать пользователя `postgres` по умолчанию при установке
через Homebrew на MacOS. Вместо этого он создает роль PostgreSQL, которая совпадает с вашим именем пользователя в MacOS
через Homebrew на macOS. Вместо этого он создает роль PostgreSQL, которая совпадает с вашим именем пользователя в MacOS
например, "john".
Чтобы проверить и создать пользователя `postgres`, при необходимости выполните следующие шаги:
```bash
@@ -173,8 +173,8 @@ cd twenty
<Tab title="Windows (WSL)">
Все последующие шаги следует выполнять в терминале WSL (внутри вашей виртуальной машины)
**Опция 1:** Чтобы настроить вашу базу данных Postgresql локально:
Используйте следующую ссылку для установки Postgresql на вашу Linux виртуальную машину: [Установка Postgresql](https://www.postgresql.org/download/linux/)
**Опция 1:** Чтобы настроить PostgreSQL локально:
Используйте следующую ссылку для установки PostgreSQL на виртуальную машину с Linux: [Установка PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -189,11 +189,13 @@ cd twenty
</Tab>
</Tabs>
Теперь вы можете получить доступ к базе данных по адресу [localhost:5432](localhost:5432), с пользователем `postgres` и паролем `postgres`.
Теперь вы можете получить доступ к базе данных по адресу `localhost:5432`.
Если вы использовали вариант с Docker выше, учетные данные по умолчанию: пользователь `postgres` и пароль `postgres`. Для нативных установок PostgreSQL используйте учетные данные и роли, настроенные на вашей машине.
## Шаг 4: Настройка базы данных Redis (кэш)
Twenty требует кэша Redis для обеспечения наилучшей производительности
Twenty требует кэша Redis для обеспечения наилучшей производительности.
<Tabs>
<Tab title="Linux">
@@ -210,8 +212,10 @@ Twenty требует кэша Redis для обеспечения наилуч
```bash
brew install redis
```
Запустите сервер redis:
`brew services start redis`
Запустите сервер Redis:
```bash
brew services start redis
```
**Опция 2:** Если у вас установлен docker:
```bash
@@ -229,11 +233,11 @@ Twenty требует кэша Redis для обеспечения наилуч
</Tab>
</Tabs>
Если вам нужен графический интерфейс клиента, мы рекомендуем [redis insight](https://redis.io/insight/) (доступна бесплатная версия)
Если вам нужен графический интерфейс клиента, мы рекомендуем [Redis Insight](https://redis.io/insight/) (доступна бесплатная версия).
## Шаг 5: Настройка переменных окружения
Используйте переменные окружения или файлы `.env` для настройки вашего проекта. Подробнее [здесь](/l/ru/developers/self-host/capabilities/setup)
Используйте переменные окружения или файлы `.env` для настройки вашего проекта. Подробнее [здесь](/l/ru/developers/self-host/capabilities/setup).
Скопируйте `.env.example` файлы в `/front` и `/server`:
@@ -1263,12 +1263,12 @@ yarn twenty app:build --tarball
1. **Разбирает и проверяет манифест** — читает все сущности `defineX()` из ваших исходных файлов и проверяет структуру манифеста.
2. **Компилирует логические функции и фронтенд-компоненты** — упаковывает исходники TypeScript в ESM-файлы `.mjs` с помощью esbuild.
3. **Генерирует контрольные суммы** — вычисляет хэши MD5 для каждого собранного файла, сохраняемые в манифесте как `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **Генерирует типизированный клиент API** — проводит интроспекцию схемы GraphQL и генерирует типизированные клиенты `CoreApiClient` и `MetadataApiClient`.
5. **Запускает проверку типов TypeScript** — выполняет `tsc --noEmit`, чтобы обнаружить ошибки типов перед публикацией.
6. **Пересобирает со сгенерированным клиентом** — выполняет второй проход компиляции, чтобы включить сгенерированные типы клиента.
7. **Опционально создаёт tar-архив** — если передан `--tarball`, выполняет `npm pack` для создания файла `.tgz`, готового к распространению.
The build output in `.twenty/output/` contains:
Результат сборки в `.twenty/output/` содержит:
```text
.twenty/output/
@@ -1282,16 +1282,16 @@ The build output in `.twenty/output/` contains:
└── my-app-1.0.0.tgz # Only with --tarball flag
```
| Вариант | Описание |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| Вариант | Описание |
| ----------- | ----------------------------------------------------------- |
| `[appPath]` | Путь к каталогу приложения (по умолчанию — текущий каталог) |
| `--tarball` | Также упаковать результат в tar-архив `.tgz` |
## Publishing your app
## Публикация вашего приложения
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Используйте `app:publish` для распространения вашего приложения — либо в реестр npm, либо напрямую на сервер Twenty.
### Publish to npm (default)
### Публикация в npm (по умолчанию)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1301,57 +1301,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Это собирает приложение и выполняет `npm publish` из каталога `.twenty/output/`. Опубликованный пакет затем может быть установлен из маркетплейса Twenty любым рабочим пространством.
### Publish to a Twenty server
### Публикация на сервер Twenty
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Это собирает приложение с tar-архивом, загружает его на сервер через мутацию GraphQL `uploadAppTarball` и запускает установку в один шаг. Это полезно для приватных развёртываний или тестирования на конкретном сервере.
| Вариант | Описание |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Вариант | Описание |
| ----------------- | --------------------------------------------------------------------- |
| `[appPath]` | Путь к каталогу приложения (по умолчанию — текущий каталог) |
| `--server <url>` | Публиковать на сервер Twenty вместо npm |
| `--token <token>` | Токен аутентификации для целевого сервера |
| `--tag <tag>` | dist-тег npm (например, `beta`, `next`) — только для публикации в npm |
## Application registration
## Регистрация приложения
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Прежде чем приложение можно будет установить в рабочем пространстве, его необходимо **зарегистрировать**. Регистрация — это запись метаданных, описывающая, откуда берётся приложение и как его аутентифицировать. В большинстве случаев это делает CLI автоматически.
### Source types
### Типы источников
Each registration has a **source type** that determines how the app's files are resolved during installation:
У каждой регистрации есть **тип источника**, который определяет, как файлы приложения будут получены при установке:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Тип источника | Как получаются файлы | Типичный сценарий использования |
| ------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------- |
| `LOCAL` | Файлы синхронизируются в реальном времени наблюдателем CLI — установка пропускается | Разработка с `app:dev` |
| `NPM` | Получается из реестра npm через поле `sourcePackage` | Опубликованные приложения в npm |
| `TARBALL` | Извлекается из загруженного файла `.tgz`, хранящегося на сервере | Приватные приложения, опубликованные с `--server` |
### How registration happens
### Как происходит регистрация
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — автоматически создаёт регистрацию `LOCAL` при первом запуске режима разработки для рабочего пространства.
* **`app:publish --server`** — загружает tar-архив и создаёт (или обновляет) регистрацию `TARBALL`, затем устанавливает приложение.
* **маркетплейс npm** — регистрации `NPM` создаются, когда приложения синхронизируются из реестра npm в каталог маркетплейса Twenty.
* **GraphQL API** — вы также можете создавать регистрации программно через мутацию `createApplicationRegistration`.
### Registration vs installation
### Регистрация и установка
**Registration** and **installation** are separate concepts:
**Регистрация** и **установка** — это разные понятия:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* **Регистрация** (`ApplicationRegistration`) — это глобальная запись метаданных, описывающая приложение: его имя, тип источника, учётные данные OAuth и статус публикации в маркетплейсе. Она существует независимо от какого-либо рабочего пространства.
* **Установка** (`Application`) — это экземпляр для каждого рабочего пространства. Когда пользователь устанавливает приложение, Twenty получает пакет из источника, указанного в регистрации, записывает собранные файлы в хранилище и синхронизирует манифест (создавая объекты, поля, логические функции и т. д.) в этом рабочем пространстве.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
Одну и ту же регистрацию можно установить во многих рабочих пространствах. Каждое рабочее пространство получает свою собственную копию файлов приложения и модели данных.
### OAuth credentials
### Учётные данные OAuth
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Каждая регистрация включает учётные данные OAuth (`oAuthClientId` и `oAuthClientSecret`), сгенерированные при создании. Они используются приложением для аутентификации запросов к API от имени пользователей. Секрет клиента возвращается **один раз** при создании — храните его в надёжном месте. Позже вы можете сменить его через мутацию `rotateApplicationRegistrationClientSecret`.
## Ручная настройка (без генератора)
@@ -3,7 +3,7 @@ title: В один клик с Docker Compose
---
<Warning>
Контейнеры Docker предназначены для продакшен-размещения или самостоятельного хостинга; для участия в разработке ознакомьтесь с разделом [Локальная установка](/l/ru/developers/contribute/capabilities/local-setup).
Контейнеры Docker предназначены для продакшен-хостинга или саморазмещения. Для участия в разработке см. [Локальная настройка](/l/ru/developers/contribute/capabilities/local-setup).
</Warning>
## Обзор
@@ -12,7 +12,7 @@ title: В один клик с Docker Compose
**Важно:** изменяйте только те настройки, которые явно упоминаются в этом руководстве. Изменение других конфигураций может привести к проблемам.
См. документацию [Настройка переменных окружения](/l/ru/developers/self-host/capabilities/setup) для расширенной конфигурации. Все переменные окружения должны быть задекларированы в файле docker-compose.yml на уровне сервера и / или рабочего потока в зависимости от переменной.
См. [Настройка переменных окружения](/l/ru/developers/self-host/capabilities/setup) для расширенной конфигурации. Все переменные окружения должны быть задекларированы в файле `docker-compose.yml` на уровне сервера и/или воркера, в зависимости от переменной.
## Системные требования
@@ -0,0 +1,142 @@
---
title: Сервер MCP
description: Подключайте ИИ-ассистентов к вашему рабочему пространству Twenty с помощью протокола Model Context Protocol.
---
<Warning>
В настоящее время MCP находится на стадии **alpha** и доступен только в некоторых рабочих пространствах. В вашем рабочем пространстве он может быть ещё не включён.
</Warning>
Twenty предоставляет сервер [MCP](https://modelcontextprotocol.io/), чтобы ИИ-ассистенты — Claude Desktop, Claude Code, Cursor, ChatGPT и другие — могли читать и записывать ваши данные CRM на естественном языке.
Используйте **URL рабочей области** (URL, который вы используете для доступа к Twenty) в качестве конечной точки MCP. В Twenty Cloud URL вашей рабочей области может быть `https://{mycompany}.twenty.com` или собственный домен. Сервер доступен по адресу:
| Среда | Конечная точка MCP |
| --------------------------- | --------------------------------------------------------------------------------- |
| **Облако** | `https://{your-workspace-url}/mcp` (например, `https://mycompany.twenty.com/mcp`) |
| **Самостоятельный хостинг** | `https://{your-domain}/mcp` |
## Методы аутентификации
Есть два способа аутентифицировать ваш MCP-клиент: **OAuth** (рекомендуется) или **API Key**.
### Вариант A — OAuth (рекомендуется)
При использовании OAuth ваш MCP-клиент откроет окно браузера для входа в систему. Секреты не хранятся в файлах конфигурации, а токены обновляются автоматически.
<Note>
Для OAuth требуется MCP-клиент, поддерживающий [спецификацию авторизации MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Её поддерживают Claude Desktop, Claude Code, Cursor и ChatGPT.
</Note>
Добавьте это в конфигурацию вашего MCP-клиента, заменив `{your-workspace-url}` на хост вашей рабочей области (например, `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Вот и всё — ключ API не требуется. При первом подключении клиент выполнит:
1. Обнаружит метаданные OAuth Twenty через `/.well-known/oauth-protected-resource` и `/.well-known/oauth-authorization-server`
2. Зарегистрирует себя как OAuth-клиент через динамическую регистрацию клиентов (RFC 7591)
3. Откроет браузер для авторизации доступа
4. Получит токены и подключится к серверу MCP
При последующих подключениях используются сохранённые токены, которые автоматически обновляются.
### Вариант B — ключ API
Если ваш MCP-клиент не поддерживает OAuth или вы предпочитаете статические учётные данные, передайте ключ API в заголовке `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Ваш ключ API предоставляет доступ к данным рабочей области. Не храните его в системе контроля версий и общих dotfiles.
</Warning>
Чтобы создать ключ API, перейдите в **Settings > APIs & Webhooks > + Create key**. См. [API](/l/ru/developers/extend/api#create-an-api-key) для подробностей.
## Быстрый старт
### 1. Скопируйте конфигурацию
Перейдите в **Settings > AI > More > MCP Server** в Twenty. Выберите метод аутентификации (OAuth или ключ API), скопируйте фрагмент JSON (в нём уже будет использован URL вашей рабочей области) и вставьте его в файл конфигурации вашего MCP-клиента.
| Клиент | Расположение файла конфигурации |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) или `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (пользователь) или `.mcp.json` (проект) |
| **Cursor** | `.cursor/mcp.json` в вашем проекте или `~/.cursor/mcp.json` глобально |
| **ChatGPT** | Включите режим разработчика в **Settings > Apps & Connectors > Advanced settings**, затем используйте **Create** в **Settings > Apps & Connectors**, чтобы добавить сервер MCP |
### 2. Подключение
Перезапустите ваш MCP-клиент (или перезагрузите конфигурацию). Если используется OAuth, вы будете перенаправлены в Twenty для авторизации доступа. Если используется ключ API, подключение произойдёт сразу.
### 3. Начните использовать
Попросите вашего ИИ-ассистента взаимодействовать с вашей CRM:
* *"Покажи мне 5 последних созданных компаний"*
* *"Создай новый контакт по имени Jane Doe в компании Acme Corp"*
* *"Найди все открытые сделки стоимостью более $10 000"*
## Доступные инструменты
После подключения сервер MCP предоставляет инструменты, отражающие API Twenty. Рекомендуемый рабочий процесс:
1. **`get_tool_catalog`** — получить список всех доступных инструментов
2. **`learn_tools`** — получить схему входных данных для конкретных инструментов
3. **`execute_tool`** — запустить инструмент
Вам не нужно запоминать названия инструментов. Спросите у вашего ИИ-ассистента, что он умеет, и он автоматически вызовет `get_tool_catalog`.
## Разрешения
Подключения MCP наследуют разрешения аутентифицированного пользователя (OAuth) или роль, назначенную ключу API. Чтобы ограничить действия, доступные серверу MCP:
* **OAuth**: Применяется роль пользователя в рабочей области.
* **API Key**: Назначьте ключу API роль в **Settings > Roles**. См. [Разрешения](/l/ru/user-guide/permissions-access/capabilities/permissions).
## Конфигурация для самостоятельного хостинга
Для экземпляров с самостоятельным хостингом замените `{your-workspace-url}` на URL вашего сервера. Убедитесь, что значение `SERVER_URL` в вашей среде соответствует публичному URL экземпляра Twenty — оно используется для генерации метаданных обнаружения OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Конечная точка MCP, конечные точки OAuth и метаданные обнаружения формируются на основе этого значения.
## Устранение неполадок
**Ошибки "Unauthorized" или 401**
* OAuth: повторно авторизуйтесь, очистив сохранённые токены в вашем MCP-клиенте и переподключившись.
* API Key: убедитесь, что ключ действителен и не истёк. При необходимости сгенерируйте его заново.
**Процесс OAuth не открывает браузер**
* Убедитесь, что ваш MCP-клиент поддерживает MCP Authorization. Если нет — используйте метод с ключом API.
**Тайм-аут подключения**
* Убедитесь, что URL конечной точки MCP доступен с вашего компьютера. Для экземпляров с самостоятельным хостингом проверьте, что сервер запущен и значение `SERVER_URL` установлено корректно.
@@ -149,7 +149,7 @@ type MyType = {
### enum'lar yerine string literal'leri kullanın
[String literalleri](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), TypeScript'te enum benzeri değerleri yönetmek için en iyi yöntemdir. Pick ve Omit ile genişletilmesi daha kolay olur ve özellikle kod tamamlama ile daha iyi bir geliştirici deneyimi sunarlar.
[String literalleri](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), TypeScript'te enum benzeri değerleri yönetmek için en iyi yöntemdir. Pick ve Omit ile genişletilmeleri daha kolaydır ve özellikle kod tamamlama ile daha iyi bir geliştirici deneyimi sunarlar.
TypeScript, enum'ların neden kaçınılması gereken bir seçenek olduğunu [burada](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums) açıklamaktadır.
@@ -6,7 +6,7 @@ description: Twenty'i yerel olarak çalıştırmak isteyen katkıda bulunanlar (
## Ön Gereksinimler
<Tabs>
<Tab title="Linux ve MacOS">
<Tab title="Linux ve macOS">
Twenty'i yüklemeden ve kullanmadan önce bilgisayarınıza aşağıdakileri yüklediğinizden emin olun:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -30,7 +30,7 @@ wsl --install
```
Şimdi bilgisayarınızı yeniden başlatmanız gerektiğine dair bir uyarı göreceksiniz. Eğer görmüyorsanız, manuel olarak yeniden başlatın.
Yeniden başladıktan sonra bir powershell penceresi açılacak ve Ubuntu yüklenecek. Bu biraz zaman alabilir.
Yeniden başlatıldığında bir PowerShell penceresi açılacak ve Ubuntu yüklenecek. Bu biraz zaman alabilir.
Ubuntu kurulumunuz için bir kullanıcı adı ve şifre oluşturmanız gerektiğine dair bir uyarı göreceksiniz.
2. git'i Yükleyin ve Yapılandırın
@@ -102,8 +102,8 @@ Sonraki adımlardaki tüm komutları projenin kök dizininden çalıştırmalıs
<Tabs>
<Tab title="Linux">
**Seçenek 1 (tercih edilen):** Veritabanınızı yerel olarak kurmak için:
Linux makinenize Postgresql yüklemek için şu bağlantıyı kullanın: [Postgresql Kurulumu](https://www.postgresql.org/download/linux/)
**Seçenek 1 (tercih edilen):** Veritabanınızı yerel olarak hazırlamak için:
Linux makinenize PostgreSQL yüklemek için aşağıdaki bağlantıyı kullanın: [PostgreSQL Kurulumu](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -129,7 +129,8 @@ Sonraki adımlardaki tüm komutları projenin kök dizininden çalıştırmalıs
brew services list
```
Yükleyici, MacOS'ta Homebrew ile yüklenirken varsayılan olarak `postgres` kullanıcısını oluşturmayabilir. Bunun yerine, macOS kullanıcı adınıza (ör. "john") uygun bir PostgreSQL rolü oluşturur.
Yükleyici, macOS'ta Homebrew aracılığıyla yüklenirken
varsayılan olarak `postgres` kullanıcısını oluşturmayabilir. Bunun yerine, macOS kullanıcı adınıza (ör. "john") uygun bir PostgreSQL rolü oluşturur.
Gerekiyorsa `postgres` kullanıcısını kontrol etmek ve oluşturtmak için şu adımları izleyin:
```bash
# PostgreSQL'e Bağlan
@@ -171,8 +172,8 @@ Sonraki adımlardaki tüm komutları projenin kök dizininden çalıştırmalıs
<Tab title="Windows (WSL)">
Aşağıdaki tüm adımlar WSL terminalinde (sanallaştırma makineniz içinde) çalıştırılmalıdır.
**Seçenek 1:** Postgresql'i yerel olarak sağlamak için:
Linux sanal makinenize Postgresql yüklemek için şu bağlantıyı kullanın: [Postgresql Kurulumu](https://www.postgresql.org/download/linux/)
**Seçenek 1:** PostgreSQL'i yerel olarak hazırlamak için:
Linux sanal makinenize PostgreSQL yüklemek için aşağıdaki bağlantıyı kullanın: [PostgreSQL Kurulumu](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -187,11 +188,13 @@ Sonraki adımlardaki tüm komutları projenin kök dizininden çalıştırmalıs
</Tab>
</Tabs>
Veritabanına [localhost:5432](localhost:5432) adresinden, kullanıcı `postgres` ve şifre `postgres` ile şimdi erişebilirsiniz.
Artık veritabanına `localhost:5432` üzerinden erişebilirsiniz.
Yukarıdaki Docker seçeneğini kullandıysanız, varsayılan kimlik bilgileri kullanıcı adı `postgres` ve parola `postgres` şeklindedir. Yerel PostgreSQL kurulumları için, makinenizde yapılandırılmış kimlik bilgilerini ve rolleri kullanın.
## Adım 4: Redis Veritabanı (önbellek) Kurun
Twenty, en iyi performansı sağlamak için bir redis önbelleğe ihtiyaç duyar
Twenty, en iyi performansı sağlamak için bir Redis önbelleği gerektirir.
<Tabs>
<Tab title="Linux">
@@ -209,7 +212,9 @@ Twenty, en iyi performansı sağlamak için bir redis önbelleğe ihtiyaç duyar
brew install redis
```
Redis sunucunuzu başlatın:
`brew services start redis`
```bash
brew services start redis
```
**Seçenek 2:** Eğer docker yüklüyse:
```bash
@@ -227,11 +232,11 @@ Twenty, en iyi performansı sağlamak için bir redis önbelleğe ihtiyaç duyar
</Tab>
</Tabs>
Bir İstemci GUI'ye ihtiyacınız varsa, [redis insight](https://redis.io/insight/) (ücretsiz sürüm mevcut) öneriyoruz.
Bir istemci GUI'ye ihtiyacınız varsa, [Redis Insight](https://redis.io/insight/) (ücretsiz sürüm mevcut) öneriyoruz.
## Adım 5: Çevresel değişkenleri ayarlayın
## Adım 5: Ortam değişkenlerini ayarlayın
Projenizi yapılandırmak için çevresel değişkenler veya `.env` dosyaları kullanın. Daha fazla bilgi [burada](/l/tr/developers/self-host/capabilities/setup)
Projenizi yapılandırmak için çevresel değişkenler veya `.env` dosyaları kullanın. Daha fazla bilgi [burada](/l/tr/developers/self-host/capabilities/setup).
`.env.example` dosyalarını `/front` ve `/server` içine kopyalayın:
@@ -1246,9 +1246,9 @@ uploadFile(
Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve birden çok tetikleyiciyi gösteren minimal, uçtan uca bir örneği [buradan](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world) inceleyin:
## Building your app
## Uygulamanızı derleme
Once you've developed your app with `app:dev`, use `app:build` to compile it into a distributable package.
Uygulamanızı `app:dev` ile geliştirdikten sonra, `app:build` kullanarak onu dağıtılabilir bir pakete derleyin.
```bash filename="Terminal"
# Build the app (output goes to .twenty/output/)
@@ -1258,17 +1258,17 @@ yarn twenty app:build
yarn twenty app:build --tarball
```
The build process:
Derleme süreci:
1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure.
2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild.
3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
1. **Manifesti ayrıştırır ve doğrular** — kaynak dosyalarınızdaki tüm `defineX()` varlıklarını okur ve manifest yapısını doğrular.
2. **Mantık işlevlerini ve ön bileşenleri derler** — TypeScript kaynaklarını esbuild kullanarak ESM `.mjs` dosyalarına paketler.
3. **Sağlama toplamları üretir** — her bir oluşturulan dosya için MD5 karmalarını hesaplar ve manifestte `builtHandlerChecksum` / `builtComponentChecksum` olarak saklar.
4. **Tipli API istemcisini oluşturur** — GraphQL şemasını inceleyip tipli `CoreApiClient` ve `MetadataApiClient` istemcilerini üretir.
5. **TypeScript tip denetimi çalıştırır** — yayımlamadan önce tip hatalarını yakalamak için `tsc --noEmit` çalıştırır.
6. **Oluşturulan istemciyle yeniden derler** — oluşturulan istemci tiplerinin dahil edilmesi için ikinci bir derleme geçişi yapar.
7. **İsteğe bağlı olarak bir tarball oluşturur** — `--tarball` iletilirse, dağıtıma hazır bir `.tgz` dosyası oluşturmak için `npm pack` çalıştırır.
The build output in `.twenty/output/` contains:
`.twenty/output/` içindeki derleme çıktısı şunları içerir:
```text
.twenty/output/
@@ -1284,14 +1284,14 @@ The build output in `.twenty/output/` contains:
| Seçenek | Açıklama |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| `[appPath]` | Uygulama dizininin yolu (varsayılan olarak geçerli dizin) |
| `--tarball` | Çıktıyı ayrıca bir `.tgz` tarball olarak paketler |
## Publishing your app
## Uygulamanızı yayımlama
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
Uygulamanızı dağıtmak için `app:publish` komutunu kullanın — npm kayıt defterine ya da doğrudan bir Twenty sunucusuna yayımlayın.
### Publish to npm (default)
### npm'ye yayımlama (varsayılan)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1301,57 +1301,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
Bu, uygulamayı derler ve `.twenty/output/` dizininden `npm publish` çalıştırır. Yayımlanan paket daha sonra Twenty pazar yerinden herhangi bir çalışma alanı tarafından kurulabilir.
### Publish to a Twenty server
### Bir Twenty sunucusuna yayımlama
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
Bu, uygulamayı bir tarball ile derler, `uploadAppTarball` GraphQL mutasyonu aracılığıyla sunucuya yükler ve tek adımda kurulumu tetikler. Bu, özel dağıtımlar veya belirli bir sunucuya karşı test yapmak için kullanışlıdır.
| Seçenek | Açıklama |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| Seçenek | Açıklama |
| ----------------- | ---------------------------------------------------------------- |
| `[appPath]` | Uygulama dizininin yolu (varsayılan olarak geçerli dizin) |
| `--server <url>` | npm yerine bir Twenty sunucusuna yayımlar |
| `--token <token>` | Hedef sunucu için kimlik doğrulama belirteci |
| `--tag <tag>` | npm dist-tag (örn. `beta`, `next`) — yalnızca npm yayımlama için |
## Application registration
## Uygulama kaydı
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
Bir uygulama bir çalışma alanına kurulmadan önce kaydedilmelidir. Kayıt, uygulamanın nereden geldiğini ve nasıl kimlik doğrulanacağını açıklayan bir meta veri kaydıdır. Bu, çoğu durumda CLI tarafından otomatik olarak gerçekleştirilir.
### Source types
### Kaynak türleri
Each registration has a **source type** that determines how the app's files are resolved during installation:
Her kaydın, kurulum sırasında uygulamanın dosyalarının nasıl çözümleneceğini belirleyen bir kaynak türü vardır:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| Kaynak türü | Dosyaların nasıl çözümlendiği | Tipik kullanım durumu |
| ----------- | ----------------------------------------------------------------------------------- | ------------------------------------------ |
| `LOCAL` | Dosyalar, CLI izleyici tarafından gerçek zamanlı olarak eşitlenir — kurulum atlanır | `app:dev` ile geliştirme |
| `NPM` | `sourcePackage` alanı aracılığıyla npm kayıt defterinden alınır | npm'de yayımlanan uygulamalar |
| `TARBALL` | Sunucuda depolanan, yüklenmiş bir `.tgz` dosyasından çıkarılır | `--server` ile yayımlanan özel uygulamalar |
### How registration happens
### Kayıt nasıl gerçekleşir
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — bir çalışma alanına karşı geliştirme modunu ilk kez çalıştırdığınızda otomatik olarak bir `LOCAL` kaydı oluşturur.
* **`app:publish --server`** — bir tarball yükler ve bir `TARBALL` kaydı oluşturur (veya günceller), ardından uygulamayı kurar.
* **npm pazar yeri** — uygulamalar npm kayıt defterinden Twenty pazar yeri kataloğuna eşitlendiğinde `NPM` kayıtları oluşturulur.
* **GraphQL API** — `createApplicationRegistration` mutasyonu aracılığıyla programatik olarak da kayıtlar oluşturabilirsiniz.
### Registration vs installation
### Kayıt ve kurulum
**Registration** and **installation** are separate concepts:
**Kayıt** ve **kurulum** ayrı kavramlardır:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* Bir kayıt (`ApplicationRegistration`), uygulamayı tanımlayan genel bir meta veri kaydıdır: adı, kaynak türü, OAuth kimlik bilgileri ve pazar yeri listeleme durumu. Herhangi bir çalışma alanından bağımsız olarak var olur.
* Bir kurulum (`Application`), çalışma alanı başına bir örnektir. Bir kullanıcı bir uygulamayı kurduğunda, Twenty paketi kaydın kaynağından çözümler, derlenen dosyaları depolamaya yazar ve manifesti (nesneler, alanlar, mantık işlevleri vb. oluşturarak) eşitler o çalışma alanında.
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
Bir kayıt birçok çalışma alanına kurulabilir. Her çalışma alanı, uygulamanın dosyalarının ve veri modelinin kendi kopyasını alır.
### OAuth credentials
### OAuth kimlik bilgileri
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
Her kayıt, oluşturma sırasında üretilen OAuth kimlik bilgilerini (`oAuthClientId` ve `oAuthClientSecret`) içerir. Bunlar, kullanıcılar adına API isteklerini kimlik doğrulamak için uygulama tarafından kullanılır. İstemci gizli anahtarı oluşturma sırasında yalnızca bir kez sağlanır — onu güvenli bir şekilde saklayın. Bunu daha sonra `rotateApplicationRegistrationClientSecret` mutasyonu aracılığıyla yenileyebilirsiniz.
## Manuel kurulum (scaffolder olmadan)
@@ -3,7 +3,7 @@ title: 1-Tıklama ile Docker Compose
---
<Warning>
Docker konteynerleri, üretim ortamında veya kendi sunucunuzda barındırma içindir; katkıda bulunmak için [Yerel Kurulum](/l/tr/developers/contribute/capabilities/local-setup) sayfasına bakın.
Docker kapsayıcıları, üretim ortamında barındırma veya kendi kendine barındırma içindir. Katkıda bulunmak için lütfen [Yerel Kurulum](/l/tr/developers/contribute/capabilities/local-setup) bölümüne bakın.
</Warning>
## Genel Bakış
@@ -12,7 +12,7 @@ Bu kılavuz, Docker Compose kullanarak Twenty uygulamasını kurmak ve yapıland
**Önemli:** Yalnızca bu kılavuzda açıkça belirtilen ayarları değiştirin. Diğer yapılandırmaları değiştirmek sorunlara yol açabilir.
İleri düzey yapılandırma için belgelerdeki [Ortam Değişkenlerini Ayarlama](/l/tr/developers/self-host/capabilities/setup) bölümüne bakın. Tüm ortam değişkenleri, sunucu ve / veya işçi düzeyine bağlı olarak docker-compose.yml dosyasında ilan edilmelidir.
İleri düzey yapılandırma için [Ortam Değişkenlerini Ayarlama](/l/tr/developers/self-host/capabilities/setup) bölümüne bakın. Tüm ortam değişkenleri, değişkene bağlı olarak sunucu ve/veya işçi düzeyinde `docker-compose.yml` dosyasında tanımlanmalıdır.
## Sistem Gereksinimleri
@@ -0,0 +1,142 @@
---
title: MCP Sunucusu
description: Model Context Protocol kullanarak yapay zeka asistanlarını Twenty çalışma alanınıza bağlayın.
---
<Warning>
MCP şu anda **alfa** aşamasındadır ve yalnızca bazı çalışma alanlarında kullanılabilir. Çalışma alanınızda henüz etkinleştirilmemiş olabilir.
</Warning>
Twenty, yapay zeka asistanlarının — Claude Desktop, Claude Code, Cursor, ChatGPT ve diğerlerinin — doğal dil aracılığıyla CRM verilerinizi okuması ve yazması için bir [MCP](https://modelcontextprotocol.io/) sunucusu sunar.
MCP uç noktası olarak **çalışma alanı URL'nizi** (Twenty'ye erişmek için kullandığınız URL) kullanın. Twenty Cloud'da, çalışma alanı URL'niz `https://{mycompany}.twenty.com` ya da özel bir alan adı olabilir. Sunucu şu adreste kullanılabilir:
| Ortam | MCP Uç Noktası |
| ---------------------------- | ---------------------------------------------------------------------------- |
| **Bulut** | `https://{your-workspace-url}/mcp` (örn. `https://mycompany.twenty.com/mcp`) |
| **Kendi Kendine Barındırma** | `https://{your-domain}/mcp` |
## Kimlik Doğrulama Yöntemleri
MCP istemcinizi kimlik doğrulamak için iki yolunuz var: **OAuth** (önerilir) veya **API Anahtarı**.
### Seçenek A — OAuth (Önerilir)
OAuth ile, MCP istemciniz oturum açmanız için bir tarayıcı penceresi açar. Gizli bilgiler yapılandırma dosyalarında saklanmaz ve belirteçler otomatik olarak yenilenir.
<Note>
OAuth, [MCP Yetkilendirme belirtimi](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)ni destekleyen bir MCP istemcisi gerektirir. Claude Desktop, Claude Code, Cursor ve ChatGPT bunu destekler.
</Note>
Bunu MCP istemci yapılandırmanıza ekleyin; `{your-workspace-url}` öğesini çalışma alanınızın ana makine adıyla değiştirin (örn. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Hepsi bu — API anahtarı gerekmez. İstemci ilk kez bağlandığında şunları yapacaktır:
1. Twenty'nin OAuth üst verilerini `/.well-known/oauth-protected-resource` ve `/.well-known/oauth-authorization-server` üzerinden keşfeder
2. Dinamik istemci kaydı (RFC 7591) yoluyla kendisini bir OAuth istemcisi olarak kaydeder
3. Erişimi yetkilendirmek için tarayıcınızı açar
4. Belirteçleri alır ve MCP sunucusuna bağlanır
Sonraki bağlantılar, depolanan belirteçleri yeniden kullanır ve bunları otomatik olarak yeniler.
### Seçenek B — API Anahtarı
MCP istemciniz OAuth'u desteklemiyorsa veya statik kimlik bilgilerini tercih ediyorsanız, `Authorization` üstbilgisinde bir API anahtarı gönderin:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
API anahtarınız çalışma alanı verilerine erişim sağlar. Sürüm kontrolünün ve paylaşılan dotfile'ların dışında tutun.
</Warning>
Bir API anahtarı oluşturmak için **Settings > APIs & Webhooks > + Create key** bölümüne gidin. Ayrıntılar için [API'ler](/l/tr/developers/extend/api#create-an-api-key) bölümüne bakın.
## Hızlı Başlangıç
### 1. Yapılandırmayı kopyalayın
Twenty içinde **Settings > AI > More > MCP Server** bölümüne gidin. Kimlik doğrulama yöntemini (OAuth veya API Anahtarı) seçin, JSON parçacığını kopyalayın (çalışma alanı URL'nizi zaten kullanacaktır) ve MCP istemcinizin yapılandırma dosyasına yapıştırın.
| İstemci | Yapılandırma dosyasının konumu |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) veya `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (kullanıcı) veya `.mcp.json` (proje) |
| **Cursor** | Projenizde `.cursor/mcp.json` veya genel olarak `~/.cursor/mcp.json` |
| **ChatGPT** | Developer Mode'u **Settings > Apps & Connectors > Advanced settings** içinde açın, ardından MCP sunucusunu eklemek için **Settings > Apps & Connectors** içindeki **Create** seçeneğini kullanın |
### 2. Bağlan
MCP istemcinizi yeniden başlatın (veya yapılandırmayı yeniden yükleyin). OAuth kullanıyorsanız, erişimi yetkilendirmek için Twenty'ye yönlendirileceksiniz. API anahtarı kullanıyorsanız, bağlantı anında kurulur.
### 3. Kullanmaya başlayın
Yapay zeka asistanınızdan CRM'inizle etkileşime geçmesini isteyin:
* *"Bana en son oluşturulan 5 şirketi göster"*
* *"Acme Corp'ta Jane Doe adlı yeni bir kişi oluştur"*
* *"10.000 $'dan fazla değere sahip tüm açık fırsatları bul"*
## Kullanılabilir Araçlar
Bağlandıktan sonra, MCP sunucusu Twenty API'sini yansıtan araçlar sunar. Önerilen iş akışı:
1. **`get_tool_catalog`** — mevcut tüm araçları keşfedin
2. **`learn_tools`** — belirli araçların girdi şemasını alın
3. **`execute_tool`** — bir aracı çalıştırın
Araç adlarını hatırlamanıza gerek yok. Yapay zeka asistanınıza neler yapabileceğini sorun; `get_tool_catalog` çağrısını otomatik olarak yapacaktır.
## İzinler
MCP bağlantıları, kimliği doğrulanmış kullanıcının (OAuth) izinlerini veya API anahtarına atanan rolü devralır. MCP sunucusunun neler yapabileceğini kısıtlamak için:
* **OAuth**: Kullanıcının çalışma alanı rolü geçerlidir.
* **API Anahtarı**: **Settings > Roles** altında API anahtarına bir rol atayın. Bkz. [İzinler](/l/tr/user-guide/permissions-access/capabilities/permissions).
## Öz Barındırmalı Yapılandırma
Öz barındırmalı kurulumlarda `{your-workspace-url}` değerini sunucunuzun URL'siyle değiştirin. Ortamınızdaki `SERVER_URL` değerinin Twenty örneğinizin genel URL'siyle eşleştiğinden emin olun — bu, OAuth keşif üst verilerini oluşturmak için kullanılır.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
MCP uç noktası, OAuth uç noktaları ve keşif üst verilerinin tümü bu değerden türetilir.
## Sorun Giderme
**"Unauthorized" veya 401 hataları**
* OAuth: MCP istemcinizde depolanan belirteçleri temizleyip yeniden bağlanarak yeniden yetkilendirin.
* API Anahtarı: anahtarın geçerli olduğunu ve süresinin dolmadığını doğrulayın. Gerekirse yeniden oluşturun.
**OAuth akışı bir tarayıcı açmıyor**
* MCP istemcinizin MCP Yetkilendirmeyi desteklediğinden emin olun. Desteklemiyorsa API Anahtarı yöntemine geri dönün.
**Bağlantı zaman aşımı**
* MCP uç noktası URL'sine makinenizden erişilebildiğini doğrulayın. Öz barındırmalı kurulumlar için, sunucunun çalıştığını ve `SERVER_URL` değerinin doğru ayarlandığını kontrol edin.
@@ -6,7 +6,7 @@ description: 本指南适用于希望在本地运行 Twenty 的贡献者或好
## 先决条件
<Tabs>
<Tab title="Linux 和 MacOS">
<Tab title="Linux 和 macOS">
在安装和使用 Twenty 之前,请确保在您的计算机上安装以下内容:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
@@ -30,7 +30,7 @@ wsl --install
```
现在您应该看到一个提示,要求您重启计算机。 如果没有,请手动重启。
重启后,一个 powershell 窗口将打开并安装 Ubuntu。 这可能需要一些时间。
重启后,将打开一个 PowerShell 窗口并安装 Ubuntu。 这可能需要一些时间。
您将看到一个提示,要求为您的 Ubuntu 安装创建用户名和密码。
2. 安装和配置 git
@@ -102,8 +102,8 @@ cd twenty
<Tabs>
<Tab title="Linux">
\*\*选项 1推荐):\*\*在本地供应您的数据库:
使用以下链接在 Linux 机器上安装 Postgresql[Postgresql 安装](https://www.postgresql.org/download/linux/)
\*\*选项 1首选):\*\*在本地预配您的数据库:
使用以下链接在 Linux 机器上安装 PostgreSQL[PostgreSQL 安装](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -129,7 +129,8 @@ cd twenty
brew services list
```
安装器在通过 Homebrew 安装时可能不会默认创建 `postgres` 用户。 相反,它会创建一个与您的 macOS 用户名(例如,“john”)匹配的 PostgreSQL 角色。
macOS 上通过 Homebrew 安装时
安装程序可能不会默认创建 `postgres` 用户。 相反,它会创建一个与您的 macOS 用户名(例如,“john”)匹配的 PostgreSQL 角色。
按照以下步骤检查并在必要时创建 `postgres` 用户:
```bash
# Connect to PostgreSQL
@@ -171,8 +172,8 @@ cd twenty
<Tab title="Windows (WSL)">
以下所有步骤应在 WSL 终端(在您的虚拟机内)中运行
\*\*选项 1\*\*在本地提供您的 Postgresql
使用以下链接在 Linux 虚拟机上安装 Postgresql[Postgresql 安装](https://www.postgresql.org/download/linux/)
\*\*选项 1\*\*在本地预配您的 PostgreSQL
使用以下链接在 Linux 虚拟机上安装 PostgreSQL[PostgreSQL 安装](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
@@ -187,11 +188,13 @@ cd twenty
</Tab>
</Tabs>
您现在可以在 [localhost:5432](localhost:5432) 访问数据库,用户名 `postgres`,密码 `postgres`
您现在可以通过 `localhost:5432` 访问数据库。
如果您使用了上面的 Docker 选项,默认凭据为用户 `postgres`、密码 `postgres`。 对于原生 PostgreSQL 安装,请使用在您的机器上已配置的凭据和角色。
## 步骤 4:设置 Redis 数据库(缓存)
Twenty 需要 Redis 缓存来提供最佳性能
Twenty 需要 Redis 缓存来提供最佳性能
<Tabs>
<Tab title="Linux">
@@ -208,8 +211,10 @@ Twenty 需要 Redis 缓存来提供最佳性能
```bash
brew install redis
```
启动您的 redis server
`brew services start redis`
启动您的 Redis 服务器
```bash
brew services start redis
```
\*\*选项 2\*\*如果您已安装 docker
```bash
@@ -227,11 +232,11 @@ Twenty 需要 Redis 缓存来提供最佳性能
</Tab>
</Tabs>
如果您需要客户端 GUI,我们推荐 [redis insight](https://redis.io/insight/)(提供免费版
如果您需要客户端 GUI,我们推荐 [Redis Insight](https://redis.io/insight/)(提供免费版本)。
## 步骤 5:设置环境变量
使用环境变量或 `.env` 文件配置您的项目。 更多信息请参见 [此处](/l/zh/developers/self-host/capabilities/setup)
使用环境变量或 `.env` 文件配置您的项目。 更多信息请参见 [此处](/l/zh/developers/self-host/capabilities/setup).
复制 `/front` 和 `/server` 目录中的 `.env.example` 文件:
@@ -1263,12 +1263,12 @@ The build process:
1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure.
2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild.
3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`.
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
4. **生成类型化的 API 客户端** — 对 GraphQL 架构进行自省,并生成带类型的 `CoreApiClient` `MetadataApiClient` 客户端。
5. **运行 TypeScript 类型检查** — 运行 `tsc --noEmit` 以在发布前捕获类型错误。
6. **使用生成的客户端重新构建** — 执行第二次编译,以便包含生成的客户端类型。
7. **可选地创建一个 tar 包** — 如果传入 `--tarball`,则运行 `npm pack` 以创建用于分发的 `.tgz` 文件。
The build output in `.twenty/output/` contains:
`.twenty/output/` 中的构建产物包含:
```text
.twenty/output/
@@ -1282,16 +1282,16 @@ The build output in `.twenty/output/` contains:
└── my-app-1.0.0.tgz # Only with --tarball flag
```
| 选项 | 描述 |
| ----------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--tarball` | Also pack the output into a `.tgz` tarball |
| 选项 | 描述 |
| ----------- | ----------------------- |
| `[appPath]` | 应用目录的路径(默认为当前目录) |
| `--tarball` | 同时将输出打包为一个 `.tgz` tar 包 |
## Publishing your app
## 发布你的应用
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
使用 `app:publish` 分发你的应用 — 可以发布到 npm 注册表,或直接发布到 Twenty 服务器。
### Publish to npm (default)
### 发布到 npm(默认)
```bash filename="Terminal"
# Publish to npm (requires npm login)
@@ -1301,57 +1301,57 @@ yarn twenty app:publish
yarn twenty app:publish --tag beta
```
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
这会构建应用,并在 `.twenty/output/` 目录下运行 `npm publish`。 发布后的软件包可由任何工作区从 Twenty 市场进行安装。
### Publish to a Twenty server
### 发布到 Twenty 服务器
```bash filename="Terminal"
# Publish directly to a Twenty server
yarn twenty app:publish --server https://app.twenty.com
```
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
这会以 tar 包方式构建应用,通过 `uploadAppTarball` GraphQL 变更将其上传到服务器,并在一步中触发安装。 这对于私有部署或针对特定服务器进行测试非常有用。
| 选项 | 描述 |
| ----------------- | --------------------------------------------------------- |
| `[appPath]` | Path to the app directory (defaults to current directory) |
| `--server <url>` | Publish to a Twenty server instead of npm |
| `--token <token>` | Authentication token for the target server |
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
| 选项 | 描述 |
| ----------------- | ------------------------------------------ |
| `[appPath]` | 应用目录的路径(默认为当前目录) |
| `--server <url>` | 发布到 Twenty 服务器(而非 npm) |
| `--token <token>` | 目标服务器的身份验证令牌 |
| `--tag <tag>` | npm dist-tag(例如 `beta``next`)— 仅用于发布到 npm |
## Application registration
## 应用注册
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
在应用安装到工作区之前,必须先进行**注册**。 注册是一条元数据记录,用于描述应用的来源以及如何对其进行身份验证。 在大多数情况下,CLI 会自动处理这一流程。
### Source types
### 来源类型
Each registration has a **source type** that determines how the app's files are resolved during installation:
每个注册都有一个**来源类型**,用于决定安装期间如何解析应用的文件:
| Source type | How files are resolved | Typical use case |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------- |
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
| 来源类型 | 文件的解析方式 | 典型用例 |
| --------- | -------------------------------- | --------------------- |
| `LOCAL` | 文件由 CLI 监听器实时同步——跳过安装步骤 | 使用 `app:dev` 进行开发 |
| `NPM` | 通过 `sourcePackage` 字段从 npm 注册表获取 | 在 npm 上发布的应用 |
| `TARBALL` | 从存储在服务器上的已上传 `.tgz` 文件中解压获得 | 使用 `--server` 发布的私有应用 |
### How registration happens
### 注册如何进行
* **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
* **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
* **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
* **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
* **`app:dev`** — 第一次针对某个工作区运行开发模式时,会自动创建一个 `LOCAL` 注册。
* **`app:publish --server`** — 上传一个 tar 包并创建(或更新)一个 `TARBALL` 注册,然后安装应用。
* **npm 市场** — 当应用从 npm 注册表同步到 Twenty 市场目录时,会创建 `NPM` 注册。
* **GraphQL API** — 你也可以通过 `createApplicationRegistration` 变更以编程方式创建注册。
### Registration vs installation
### 注册与安装
**Registration** and **installation** are separate concepts:
**注册** 与 **安装** 是两个独立的概念:
* A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
* An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
* **注册**`ApplicationRegistration`)是一条全局元数据记录,用于描述应用:其名称、来源类型、OAuth 凭据以及在市场中的上架状态。 它独立于任何工作区存在。
* **安装**`Application`)是一个按工作区划分的实例。 当用户安装一个应用时,Twenty 会根据注册的来源解析软件包,将构建生成的文件写入存储,并同步清单(创建对象、字段、逻辑函数等) 到该工作区中。
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
一个注册可以安装到多个工作区。 每个工作区都会获得应用文件和数据模型的独立副本。
### OAuth credentials
### OAuth 凭据
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
每个注册都包含在创建时生成的 OAuth 凭据(`oAuthClientId` `oAuthClientSecret`)。 应用使用这些凭据代表用户对 API 请求进行身份验证。 客户端密钥在创建时只会返回**一次**——请妥善保管。 你可以稍后通过 `rotateApplicationRegistrationClientSecret` 变更来轮换它。
## 手动设置(不使用脚手架)
@@ -3,7 +3,7 @@ title: 1-点击使用Docker Compose
---
<Warning>
Docker容器用于生产托管或自托管,关于贡献,请查看[本地设置](/l/zh/developers/contribute/capabilities/local-setup)。
Docker 容器用于生产环境托管或自托管。 如需参与贡献,请查看[本地设置](/l/zh/developers/contribute/capabilities/local-setup)。
</Warning>
## 概览
@@ -12,7 +12,7 @@ Docker容器用于生产托管或自托管,关于贡献,请查看[本地设
**重要:** 仅修改本指南中明确提到的设置。 更改其他配置可能会导致问题。
请参阅文档[设置环境变量](/l/zh/developers/self-host/capabilities/setup)获取高级配置。 所有环境变量必须在docker-compose.yml文件中根据变量声明在服务器和/或工作器级别
有关高级配置,请参阅[设置环境变量](/l/zh/developers/self-host/capabilities/setup)。 所有环境变量必须在 `docker-compose.yml` 文件中声明,具体在服务器和/或 worker 层级,取决于变量
## 系统要求
@@ -0,0 +1,142 @@
---
title: MCP服务器
description: 使用 Model Context Protocol 将 AI 助手连接到你的 Twenty 工作区。
---
<Warning>
MCP 目前处于**alpha**阶段,并且仅在部分工作区可用。 它可能尚未在你的工作区启用。
</Warning>
Twenty 提供一个 [MCP](https://modelcontextprotocol.io/) 服务器,使 AI 助手——Claude Desktop、Claude Code、Cursor、ChatGPT 等——能够通过自然语言读取和写入您的 CRM 数据。
将您的**工作区 URL**(用于访问 Twenty 的 URL)用作 MCP 端点。 在 Twenty Cloud 上,您的工作区 URL 可能是 `https://{mycompany}.twenty.com`,也可能是自定义域名。 服务器可通过以下地址访问:
| 环境 | MCP 端点 |
| ------- | -------------------------------------------------------------------------- |
| **云端** | `https://{your-workspace-url}/mcp` (例如 `https://mycompany.twenty.com/mcp`) |
| **自托管** | `https://{your-domain}/mcp` |
## 身份验证方法
您可以通过两种方式对 MCP 客户端进行身份验证:**OAuth**(推荐)或 **API 密钥**。
### 选项 A — OAuth(推荐)
使用 OAuth 时,您的 MCP 客户端会打开浏览器窗口供您登录。 不会在配置文件中存储任何机密,令牌会自动刷新。
<Note>
OAuth 需要支持[MCP 授权规范](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)的 MCP 客户端。 Claude Desktop、Claude Code、Cursor 和 ChatGPT 均支持它。
</Note>
将以下内容添加到 MCP 客户端配置中,并将 `{your-workspace-url}` 替换为您的工作区主机(例如 `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
就这样——无需 API 密钥。 客户端首次连接时将:
1. 通过 `/.well-known/oauth-protected-resource` 和 `/.well-known/oauth-authorization-server` 发现 Twenty 的 OAuth 元数据
2. 通过动态客户端注册(RFC 7591)将自身注册为 OAuth 客户端
3. 打开您的浏览器以授权访问
4. 接收令牌并连接到 MCP 服务器
后续连接会重用已存储的令牌并自动刷新。
### 选项 B — API 密钥
如果您的 MCP 客户端不支持 OAuth,或您更喜欢静态凭据,请在 `Authorization` 请求头中传递 API 密钥:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
您的 API 密钥可访问工作区数据。 请不要将其纳入版本控制,也不要放入共享的 dotfiles。
</Warning>
要创建 API 密钥,请前往 **Settings > APIs & Webhooks > + Create key**。 详见[API](/l/zh/developers/extend/api#create-an-api-key)。
## 快速开始
### 1. 复制配置
在 Twenty 中前往 **Settings > AI > More > MCP Server**。 选择您的身份验证方式(OAuth 或 API 密钥),复制 JSON 片段(其中已包含您的工作区 URL),并将其粘贴到 MCP 客户端的配置文件中。
| 客户端 | 配置文件位置 |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) 或 `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (用户) 或 `.mcp.json` (项目) |
| **Cursor** | 在您的项目中为 `.cursor/mcp.json`,或全局为 `~/.cursor/mcp.json` |
| **ChatGPT** | 在 **Settings > Apps & Connectors > Advanced settings** 中开启开发者模式,然后在 **Settings > Apps & Connectors** 中使用 **Create** 添加 MCP 服务器 |
### 2. 连接
重启您的 MCP 客户端(或重新加载配置)。 如果使用 OAuth,您将被重定向到 Twenty 以授权访问。 如果使用 API 密钥,连接会立即建立。
### 3. 开始使用
让您的 AI 助手与您的 CRM 交互:
* *"给我展示最近创建的 5 家公司"*
* *"在 Acme Corp 新建一位名为 Jane Doe 的联系人"*
* *"查找所有价值超过 $10k 的未关闭商机"*
## 可用工具
连接后,MCP 服务器会提供与 Twenty API 一一对应的工具。 推荐的工作流程是:
1. **`get_tool_catalog`** — 发现所有可用工具
2. **`learn_tools`** — 获取特定工具的输入模式
3. **`execute_tool`** — 运行工具
您无需记住工具名称。 询问您的 AI 助手它能做什么,它会自动调用 `get_tool_catalog`。
## 权限
MCP 连接将继承已通过身份验证的用户(OAuth)的权限,或继承分配给 API 密钥的角色。 要限制 MCP 服务器的操作范围:
* **OAuth**:生效的是该用户在工作区中的角色。
* **API 密钥**:在 **Settings > Roles** 下为该 API 密钥分配角色。 参见[权限](/l/zh/user-guide/permissions-access/capabilities/permissions)。
## 自托管配置
对于自托管实例,请将 `{your-workspace-url}` 替换为您的服务器 URL。 请确保您环境中的 `SERVER_URL` 与 Twenty 实例的公共 URL 一致——该值用于生成 OAuth 发现元数据。
```bash
SERVER_URL=https://twenty.yourcompany.com
```
MCP 端点、OAuth 端点以及发现元数据都基于该值生成。
## 故障排除
**"Unauthorized" 或 401 错误**
* OAuth:清除 MCP 客户端中存储的令牌并重新连接以重新授权。
* API 密钥:验证该密钥有效且未过期。 如有需要,请重新生成。
**OAuth 流程未打开浏览器**
* 确保您的 MCP 客户端支持 MCP 授权。 如果不支持,请改用 API 密钥方法。
**连接超时**
* 确认从您的机器可以访问 MCP 端点 URL。 对于自托管实例,请检查服务器是否正在运行,并确认 `SERVER_URL` 设置正确。
@@ -0,0 +1,139 @@
---
title: MCP Server
description: Connect AI assistants to your Twenty workspace using the Model Context Protocol.
---
<Warning>
MCP is currently in **alpha** and is only available on some workspaces. It may not be enabled for your workspace yet.
</Warning>
Twenty exposes an [MCP](https://modelcontextprotocol.io/) server so that AI assistants — Claude Desktop, Claude Code, Cursor, ChatGPT, and others — can read and write your CRM data through natural language.
Use your **workspace URL** (the URL you use to access Twenty) as the MCP endpoint. On Twenty Cloud, your workspace URL might be `https://{mycompany}.twenty.com` or a custom domain. The server is available at:
| Environment | MCP Endpoint |
|-------------|-------------|
| **Cloud** | `https://{your-workspace-url}/mcp` (e.g. `https://mycompany.twenty.com/mcp`) |
| **Self-Hosted** | `https://{your-domain}/mcp` |
## Authentication Methods
You have two ways to authenticate your MCP client: **OAuth** (recommended) or **API Key**.
### Option A — OAuth (Recommended)
With OAuth, your MCP client opens a browser window for you to log in. No secrets are stored in config files, and tokens refresh automatically.
<Note>
OAuth requires an MCP client that supports the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor, and ChatGPT support it.
</Note>
Add this to your MCP client configuration, replacing `{your-workspace-url}` with your workspace host (e.g. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
That's it — no API key needed. When the client connects for the first time it will:
1. Discover Twenty's OAuth metadata via `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`
2. Register itself as an OAuth client via dynamic client registration (RFC 7591)
3. Open your browser to authorize access
4. Receive tokens and connect to the MCP server
Subsequent connections reuse the stored tokens and refresh them automatically.
### Option B — API Key
If your MCP client does not support OAuth, or you prefer static credentials, pass an API key in the `Authorization` header:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Your API key grants access to workspace data. Keep it out of version control and shared dotfiles.
</Warning>
To create an API key, go to **Settings > APIs & Webhooks > + Create key**. See [APIs](/developers/extend/api#create-an-api-key) for details.
## Quick Start
### 1. Copy the config
Go to **Settings > AI > More > MCP Server** in Twenty. Choose your authentication method (OAuth or API Key), copy the JSON snippet (it will already use your workspace URL), and paste it into your MCP client's config file.
| Client | Config file location |
|--------|---------------------|
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (user) or `.mcp.json` (project) |
| **Cursor** | `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` globally |
| **ChatGPT** | Turn on Developer Mode in **Settings > Apps & Connectors > Advanced settings**, then use **Create** in **Settings > Apps & Connectors** to add the MCP server |
### 2. Connect
Restart your MCP client (or reload the config). If using OAuth you will be redirected to Twenty to authorize access. If using an API key the connection is immediate.
### 3. Start using it
Ask your AI assistant to interact with your CRM:
- *"Show me the 5 most recently created companies"*
- *"Create a new person named Jane Doe at Acme Corp"*
- *"Find all open opportunities worth more than $10k"*
## Available Tools
Once connected, the MCP server exposes tools that mirror the Twenty API. The recommended workflow is:
1. **`get_tool_catalog`** — discover all available tools
2. **`learn_tools`** — get the input schema for specific tools
3. **`execute_tool`** — run a tool
You don't need to remember tool names. Ask your AI assistant what it can do and it will call `get_tool_catalog` automatically.
## Permissions
MCP connections inherit the permissions of the authenticated user (OAuth) or the role assigned to the API key. To restrict what the MCP server can do:
- **OAuth**: The user's workspace role applies.
- **API Key**: Assign a role to the API key under **Settings > Roles**. See [Permissions](/user-guide/permissions-access/capabilities/permissions).
## Self-Hosted Configuration
For self-hosted instances, replace `{your-workspace-url}` with your server URL. Make sure `SERVER_URL` in your environment matches the public URL of your Twenty instance — this is used to generate the OAuth discovery metadata.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
The MCP endpoint, OAuth endpoints, and discovery metadata all derive from this value.
## Troubleshooting
**"Unauthorized" or 401 errors**
- OAuth: re-authorize by clearing the stored tokens in your MCP client and reconnecting.
- API Key: verify the key is valid and hasn't expired. Regenerate it if needed.
**OAuth flow doesn't open a browser**
- Ensure your MCP client supports MCP Authorization. Fall back to the API Key method if it doesn't.
**Connection timeout**
- Confirm the MCP endpoint URL is reachable from your machine. For self-hosted instances, check that the server is running and `SERVER_URL` is set correctly.
+2 -2
View File
@@ -61,8 +61,8 @@ const jestConfig = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
coverageThreshold: {
global: {
statements: 49,
lines: 47.6,
statements: 48.5,
lines: 47.0,
functions: 39.5,
},
},
@@ -1,19 +1,23 @@
/* oxlint-disable no-console */
import { generateApiKeys } from './mock-data/generate-api-keys.js';
import { generateBillingPlans } from './mock-data/generate-billing-plans.js';
import { generateMetadata } from './mock-data/generate-metadata.js';
import { generateObjectMetadata } from './mock-data/generate-object-metadata.js';
import { generateMinimalMetadata } from './mock-data/generate-minimal-metadata.js';
import { generateRecordData } from './mock-data/generate-record-data.js';
import { generateRoles } from './mock-data/generate-roles.js';
import { generateNavigationMenuItems } from './mock-data/generate-navigation-menu-items.js';
import { generateViews } from './mock-data/generate-views.js';
import { authenticate } from './mock-data/utils.js';
const main = async () => {
const token = await authenticate();
const metadata = await generateMetadata(token);
const metadata = await generateObjectMetadata(token);
await generateMinimalMetadata(token);
await generateRecordData(token, metadata);
await generateRoles(token);
await generateViews(token);
await generateNavigationMenuItems(token);
try {
await generateBillingPlans(token);
@@ -0,0 +1,51 @@
/* oxlint-disable no-console */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const MINIMAL_METADATA_QUERY = `
query FindMinimalMetadata {
minimalMetadata {
objectMetadataItems {
id
nameSingular
namePlural
labelSingular
labelPlural
icon
isCustom
isActive
isSystem
isRemote
}
views {
id
type
key
objectMetadataId
}
collectionHashes {
collectionName
hash
}
}
}
`;
export const generateMinimalMetadata = async (token: string) => {
console.log('Fetching minimal metadata from /metadata ...');
const data = (await graphqlRequest(
'/metadata',
MINIMAL_METADATA_QUERY,
token,
)) as {
minimalMetadata: Record<string, unknown>;
};
writeGeneratedFile(
'metadata/minimal/mock-minimal-metadata.ts',
'mockedMinimalMetadata',
'MinimalMetadata',
"import { type MinimalMetadata } from '@/metadata-store/types/MinimalMetadata';",
data.minimalMetadata,
);
};
@@ -0,0 +1,50 @@
/* oxlint-disable no-console, lingui/no-unlocalized-strings */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY = `
query FindManyNavigationMenuItems {
navigationMenuItems {
id
userWorkspaceId
targetRecordId
targetObjectMetadataId
viewId
folderId
name
link
icon
color
position
applicationId
createdAt
updatedAt
targetRecordIdentifier {
id
labelIdentifier
imageIdentifier
}
}
}
`;
export const generateNavigationMenuItems = async (token: string) => {
console.log('Fetching navigation menu items from /metadata ...');
const data = (await graphqlRequest(
'/metadata',
FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY,
token,
)) as {
navigationMenuItems: Record<string, unknown>[];
};
console.log(` Got ${data.navigationMenuItems.length} navigation menu items.`);
writeGeneratedFile(
'metadata/navigation-menu-items/mock-navigation-menu-items-data.ts',
'mockedNavigationMenuItems',
'NavigationMenuItem[]',
"import { type NavigationMenuItem } from '~/generated-metadata/graphql';",
data.navigationMenuItems,
);
};
@@ -141,7 +141,7 @@ const METADATA_QUERY = `
}
`;
export const generateMetadata = async (token: string) => {
export const generateObjectMetadata = async (token: string) => {
console.log('Fetching object metadata from /metadata ...');
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
@@ -1,9 +1,9 @@
/* oxlint-disable no-console, lingui/no-unlocalized-strings */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const FIND_ALL_CORE_VIEWS_QUERY = `
query FindAllCoreViews {
getCoreViews {
const FIND_ALL_VIEWS_QUERY = `
query FindAllViews {
getViews {
id
name
objectMetadataId
@@ -101,19 +101,19 @@ export const generateViews = async (token: string) => {
const data = (await graphqlRequest(
'/metadata',
FIND_ALL_CORE_VIEWS_QUERY,
FIND_ALL_VIEWS_QUERY,
token,
)) as {
getCoreViews: Record<string, unknown>[];
getViews: Record<string, unknown>[];
};
console.log(` Got ${data.getCoreViews.length} views.`);
console.log(` Got ${data.getViews.length} views.`);
writeGeneratedFile(
'metadata/views/mock-views-data.ts',
'mockedCoreViews',
'CoreViewWithRelations[]',
"import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';",
data.getCoreViews,
'mockedViews',
'ViewWithRelations[]',
"import { type ViewWithRelations } from '@/views/types/ViewWithRelations';",
data.getViews,
);
};
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
@@ -39,9 +39,9 @@ const setupMockIsMatchingLocation = (pathname: string) => {
);
};
jest.mock('@/auth/hooks/useIsLogged');
const setupMockIsLogged = (isLogged: boolean) => {
jest.mocked(useIsLogged).mockReturnValueOnce(isLogged);
jest.mock('@/auth/hooks/useHasAccessTokenPair');
const setupMockHasAccessTokenPair = (hasAccessTokenPair: boolean) => {
jest.mocked(useHasAccessTokenPair).mockReturnValueOnce(hasAccessTokenPair);
};
const defaultHomePagePath = '/objects/companies';
@@ -83,7 +83,7 @@ const setupMockState = (
// prettier-ignore
const testCases: {
loc: AppPath;
isLoggedIn: boolean;
hasAccessTokenPair: boolean;
isWorkspaceSuspended: boolean;
onboardingStatus: OnboardingStatus | undefined;
res: string | undefined;
@@ -93,248 +93,248 @@ const testCases: {
verifyEmailRedirectPath?: string;
returnToPath?: string;
}[] = [
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Verify, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Verify, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SignInUp, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SignInUp, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Invite, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Invite, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.ResetPassword, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateWorkspace, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateProfile, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SyncEmails, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.InviteTeam, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCallDecision, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCall, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCall, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequired, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Index, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Index, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.TasksPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.TasksPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordIndexPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordShowPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Authorize, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Authorize, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFound, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFound, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
// returnToPath: should redirect to saved path instead of defaultHomePagePath
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' },
// isOnAWorkspace:false — on default domain, don't redirect to returnToPath or defaultHomePagePath from auth pages
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
];
describe('usePageChangeEffectNavigateLocation', () => {
@@ -344,7 +344,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
loc,
onboardingStatus,
isWorkspaceSuspended,
isLoggedIn,
hasAccessTokenPair,
isOnAWorkspace,
objectNamePluralFromParams,
objectNamePluralFromMetadata,
@@ -355,7 +355,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
setupMockIsMatchingLocation(loc);
setupMockOnboardingStatus(onboardingStatus);
setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended);
setupMockIsLogged(isLoggedIn);
setupMockHasAccessTokenPair(hasAccessTokenPair);
setupMockIsOnAWorkspace(isOnAWorkspace ?? true);
setupMockUseParams(objectNamePluralFromParams);
setupMockState(
@@ -1,7 +1,7 @@
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
import { ONBOARDING_PATHS } from '@/auth/constants/OnboardingPaths';
import { ONGOING_USER_CREATION_PATHS } from '@/auth/constants/OngoingUserCreationPaths';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { returnToPathState } from '@/auth/states/returnToPathState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
@@ -26,7 +26,7 @@ const readReturnToPathFromUrlSearchParams = (): string | null => {
};
export const usePageChangeEffectNavigateLocation = () => {
const isLoggedIn = useIsLogged();
const hasAccessTokenPair = useHasAccessTokenPair();
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
const onboardingStatus = useOnboardingStatus();
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
@@ -54,7 +54,7 @@ export const usePageChangeEffectNavigateLocation = () => {
: readReturnToPathFromUrlSearchParams();
if (
(!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) &&
(!hasAccessTokenPair || (hasAccessTokenPair && !isOnAWorkspace)) &&
!someMatchingLocationOf([
...ONGOING_USER_CREATION_PATHS,
AppPath.ResetPassword,
@@ -140,13 +140,13 @@ export const usePageChangeEffectNavigateLocation = () => {
...ONGOING_USER_CREATION_PATHS,
]) &&
!isMatchingLocation(location, AppPath.ResetPassword) &&
isLoggedIn &&
hasAccessTokenPair &&
isOnAWorkspace
) {
return resolvedReturnToPath ?? defaultHomePagePath;
}
if (isMatchingLocation(location, AppPath.Index) && isLoggedIn) {
if (isMatchingLocation(location, AppPath.Index) && hasAccessTokenPair) {
return resolvedReturnToPath ?? defaultHomePagePath;
}
@@ -6,11 +6,11 @@ import {
PageDecorator,
type PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { PrefetchLoadingDecorator } from '~/testing/decorators/PrefetchLoadingDecorator';
import { LoadingDecorator } from '~/testing/decorators/LoadingDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
const meta: Meta<PageDecoratorArgs> = {
title: 'App/Loading/PrefetchLoading',
title: 'App/Loading',
component: RecordIndexPage,
args: {
routePath: '/objects/:objectNamePlural',
@@ -20,7 +20,7 @@ const meta: Meta<PageDecoratorArgs> = {
},
parameters: {
msw: graphqlMocks,
prefetchLoadingSetDelay: 1000,
loadingSetDelay: 1000,
},
tags: ['no-tests'],
};
@@ -32,7 +32,7 @@ export type Story = StoryObj<typeof RecordIndexPage>;
export const Default: Story = {
// oxlint-disable-next-line @typescripttypescript/ban-ts-comment
// @ts-ignore
decorators: [PrefetchLoadingDecorator, PageDecorator],
decorators: [LoadingDecorator, PageDecorator],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Oor hierdie werkruimte"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Kry toegang tot jou werksruimte data vanaf jou gunsteling MCP-kliënt soos Claude Desktop, Windsurf of Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Voeg filterreël by"
msgid "Add first filter"
msgstr "Voeg eerste filter by"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Voeg vouer by"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Voeg by tot gunstelinge"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webtuistes"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-sleutel"
@@ -2137,6 +2132,11 @@ msgstr "Magtiging"
msgid "Authentication failed"
msgstr "Magtiging het misluk"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "verwyder"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Verwyder veld"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Verwyder vouer"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Gunstelinge"
@@ -9422,7 +9419,6 @@ msgstr "Geen Lêers Nie"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Geen vouer"
@@ -9431,11 +9427,6 @@ msgstr "Geen vouer"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Geen vouers gevind nie"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Afstand"
msgid "Remove"
msgstr "Verwyder"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Verwyder {favoriteCount} gunsteling?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Verwyder {favoriteCount} gunstelinge?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Verwyder Gedeleteerde filter"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Verwyder veranderlike"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Hernoem"
@@ -13501,16 +13486,6 @@ msgstr "Hierdie aksie kan nie ongedaan gemaak word nie. Dit sal permanent jou li
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Hierdie aksie kan nie ongedaan gemaak word nie. Dit sal jou twee-faktor-verifikasiemetode permanent terugstel. <0/> Tik asseblief jou e-pos in om te bevestig."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Hierdie aksie sal hierdie gunstelingvouer en al {favoriteCount} gunstelinge daarin uitvee. Wil jy voortgaan?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Hierdie aksie sal hierdie gunstelingvouer en die gunsteling daarin uitvee. Wil jy voortgaan?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "Tipe"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15128,7 +15103,6 @@ msgstr "Werksvloeie"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Werkruimte"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "عن مساحة العمل هذه"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "الوصول إلى بيانات مساحة العمل الخاصة بك من عميل MCP المفضل لديك مثل Claude Desktop و Windsurf أو Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "إضافة قاعدة تصفية"
msgid "Add first filter"
msgstr "أضف أول فلتر"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "إضافة مجلد"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "إضافة إلى المفضلة"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "واجهة برمجة التطبيقات والويب هوك"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "مفتاح واجهة برمجة التطبيقات"
@@ -2137,6 +2132,11 @@ msgstr "المصادقة"
msgid "Authentication failed"
msgstr "فشل المصادقة"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "حذف"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "حذف الحقل"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "حذف المجلد"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "المفضلة"
@@ -9422,7 +9419,6 @@ msgstr "لا توجد ملفات"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "لا يوجد مجلد"
@@ -9431,11 +9427,6 @@ msgstr "لا يوجد مجلد"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "لم يتم العثور على مجلدات"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "عن بعد"
msgid "Remove"
msgstr "\\\\"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "إزالة {favoriteCount} من المفضلات؟"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "إزالة {favoriteCount} من المفضلات؟"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "إزالة الفلتر المحذوف"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "إزالة المتغير"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "إعادة التسمية"
@@ -13501,16 +13486,6 @@ msgstr "لا يمكن التراجع عن هذا الإجراء. سيؤدي ذل
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "لا يمكن التراجع عن هذا الإجراء. سيؤدي ذلك إلى إعادة تعيين طريقة المصادقة الثنائية الخاصة بك بشكل دائم. <0/> الرجاء إدخال بريدك الإلكتروني للتأكيد."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "سيؤدي هذا الإجراء إلى حذف مجلد المفضلات هذا وجميع المفضلات البالغ عددها {favoriteCount} بداخله. هل تريد المتابعة؟"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "سيؤدي هذا الإجراء إلى حذف مجلد المفضلات هذا والمفضلة الموجودة بداخله. هل تريد المتابعة؟"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "النوع"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15126,7 +15101,6 @@ msgstr "سير العمل"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "مساحة العمل"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Sobre aquest espai de treball"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accediu a les dades del vostre espai de treball des del vostre client MCP preferit, com Claude Desktop, Windsurf o Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Afegeix una regla de filtre"
msgid "Add first filter"
msgstr "Afegeix el primer filtre"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Afegeix una carpeta"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Afegeix a les preferides"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API i Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clau d'API"
@@ -2137,6 +2132,11 @@ msgstr "Autenticació"
msgid "Authentication failed"
msgstr "Error en l'autenticació"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "elimina"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Esborra camp"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Elimina la carpeta"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Preferits"
@@ -9422,7 +9419,6 @@ msgstr "Sense fitxers"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Sense carpeta"
@@ -9431,11 +9427,6 @@ msgstr "Sense carpeta"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "No s'ha trobat cap carpeta"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Remote"
msgid "Remove"
msgstr "Elimina"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Vols eliminar {favoriteCount} preferit?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Vols eliminar {favoriteCount} preferits?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Elimina filtre Eliminat"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Elimina la variable"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Canvia el nom"
@@ -13501,16 +13486,6 @@ msgstr "Aquesta acció no es pot desfer. Això eliminarà permanentment la teva
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Aquesta acció no es pot desfer. Això restablirà permanentment el teu mètode d'autenticació de dos factors. <0/> Si us plau, escriu el teu correu electrònic per confirmar."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Aquesta acció suprimirà aquesta carpeta de preferits i tots els {favoriteCount} preferits que conté. Vols continuar?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Aquesta acció suprimirà aquesta carpeta de preferits i el preferit que conté. Vols continuar?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "Tipus"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15128,7 +15103,6 @@ msgstr "Fluxos de treball"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Espai de treball"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "O tomto pracovním prostoru"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Přistupujte ke svým pracovním datům z vašeho oblíbeného MCP klienta, jako je Claude Desktop, Windsurf nebo Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Přidat pravidlo filtru"
msgid "Add first filter"
msgstr "Přidat první filtr"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Přidat složku"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Přidat do oblíbených"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API klíč"
@@ -2137,6 +2132,11 @@ msgstr "Ověření"
msgid "Authentication failed"
msgstr "Ověření selhalo"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "smazat"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Smazat pole"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Smazat složku"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Oblíbené"
@@ -9422,7 +9419,6 @@ msgstr "Žádné soubory"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Žádná složka"
@@ -9431,11 +9427,6 @@ msgstr "Žádná složka"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Žádné složky nenalezeny"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Vzdálený"
msgid "Remove"
msgstr "Odstranit"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Odebrat {favoriteCount} oblíbenou položku?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Odebrat {favoriteCount} oblíbené položky?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Odstranit smazaný filtr"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Odstranit proměnnou"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Přejmenovat"
@@ -13501,16 +13486,6 @@ msgstr "Tato akce je nevratná. Tímto bude vaše členství v tomto pracovním
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Tuto akci nelze vrátit zpět. Tímto dojde k trvalému resetování vaší metody dvoufaktorového ověřování. <0/> Pro potvrzení zadejte svůj e-mail."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Tato akce smaže tuto složku oblíbených a všech {favoriteCount} oblíbených položek uvnitř. Chcete pokračovat?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Tato akce smaže tuto složku oblíbených a oblíbenou položku uvnitř. Chcete pokračovat?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "Typ"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15128,7 +15103,6 @@ msgstr "Pracovní postupy"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Pracovní prostor"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Om dette arbejdsområde"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Adgang til dine arbejdsdata fra din foretrukne MCP-klient som Claude Desktop, Windsurf eller Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Tilføj filterregel"
msgid "Add first filter"
msgstr "Tilføj første filter"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Tilføj mappe"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Tilføj til Favorit"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-nøgle"
@@ -2137,6 +2132,11 @@ msgstr "Godkendelse"
msgid "Authentication failed"
msgstr "Godkendelse mislykkedes"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "slet"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Slet felt"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Slet mappe"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Favoritter"
@@ -9422,7 +9419,6 @@ msgstr "Ingen filer"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Ingen mappe"
@@ -9431,11 +9427,6 @@ msgstr "Ingen mappe"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Ingen mapper fundet"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Fjernforbindelse"
msgid "Remove"
msgstr "Fjern"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Fjern {favoriteCount} favorit?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Fjern {favoriteCount} favoritter?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Fjern slettet filter"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Fjern variabel"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Omdøb"
@@ -13503,16 +13488,6 @@ msgstr "Denne handling kan ikke fortrydes. Dette vil permanent fjerne dit medlem
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Denne handling kan ikke fortrydes. Dette vil permanent nulstille din tofaktorgodkendelsesmetode. <0/> Indtast venligst din e-mail for at bekræfte."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Denne handling vil slette denne favoritmappe og alle de {favoriteCount} favoritter i den. Vil du fortsætte?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Denne handling vil slette denne favoritmappe og favoritten i den. Vil du fortsætte?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14035,7 +14010,7 @@ msgid "Type"
msgstr "Type"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15130,7 +15105,6 @@ msgstr "Arbejdsgange"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Arbejdsområde"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Über diesen Arbeitsbereich"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Greifen Sie auf Ihre Workspace-Daten von Ihrem bevorzugten MCP-Client wie Claude Desktop, Windsurf oder Cursor zu."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Filterregel hinzufügen"
msgid "Add first filter"
msgstr "Ersten Filter hinzufügen"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Ordner hinzufügen"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Zu Favoriten hinzufügen"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-Schlüssel"
@@ -2137,6 +2132,11 @@ msgstr "Authentifizierung"
msgid "Authentication failed"
msgstr "Authentifizierung fehlgeschlagen"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "löschen"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Feld löschen"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Ordner löschen"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Favoriten"
@@ -9422,7 +9419,6 @@ msgstr "Keine Dateien"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Kein Ordner"
@@ -9431,11 +9427,6 @@ msgstr "Kein Ordner"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Keine Ordner gefunden"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Remote"
msgid "Remove"
msgstr "Entfernen"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "{favoriteCount} Favorit entfernen?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "{favoriteCount} Favoriten entfernen?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Gelöschten Filter entfernen"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Variable entfernen"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Umbenennen"
@@ -13501,16 +13486,6 @@ msgstr "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch wird Ihre M
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch wird Ihre Zwei-Faktor-Authentifizierung dauerhaft zurückgesetzt. <0/> Bitte geben Sie zur Bestätigung Ihre E-Mail ein."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Diese Aktion löscht diesen Favoritenordner und alle {favoriteCount} enthaltenen Favoriten. Möchten Sie fortfahren?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Diese Aktion löscht diesen Favoritenordner und den enthaltenen Favoriten. Möchten Sie fortfahren?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "Typ"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15128,7 +15103,6 @@ msgstr "Workflows"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Arbeitsbereich"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Σχετικά με αυτό το χώρο εργασίας"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Πρόσβαση στα δεδομένα του χώρου εργασίας σας από τον αγαπημένο σας πελάτη MCP, όπως το Claude Desktop, το Windsurf ή το Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Προσθήκη κανόνα φίλτρου"
msgid "Add first filter"
msgstr "Προσθήκη πρώτου φίλτρου"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Προσθήκη φακέλου"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Προσθήκη στα Αγαπημένα"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API Key"
@@ -2137,6 +2132,11 @@ msgstr "Ταυτοποίηση"
msgid "Authentication failed"
msgstr "Αποτυχία ταυτοποίησης"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "διαγραφή"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Διαγραφή πεδίου"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Διαγραφή φακέλου"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Αγαπημένα"
@@ -9422,7 +9419,6 @@ msgstr "Χωρίς αρχεία"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Κανένας φάκελος"
@@ -9431,11 +9427,6 @@ msgstr "Κανένας φάκελος"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Δεν βρέθηκαν φάκελοι"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Απομακρυσμένα"
msgid "Remove"
msgstr "Αφαίρεση"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Αφαίρεση {favoriteCount} αγαπημένου;"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Αφαίρεση {favoriteCount} αγαπημένων;"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Αφαίρεση φίλτρου διαγραμμένων"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Αφαίρεση μεταβλητής"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Μετονομασία"
@@ -13505,16 +13490,6 @@ msgstr "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Αυτό θα επαναφέρει μόνιμα τη μέθοδο ελέγχου ταυτότητας δύο παραγόντων σας. <0/> Παρακαλώ πληκτρολογήστε το email σας για επιβεβαίωση."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Αυτή η ενέργεια θα διαγράψει αυτόν τον φάκελο αγαπημένων και όλα τα {favoriteCount} αγαπημένα μέσα. Θέλετε να συνεχίσετε;"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Αυτή η ενέργεια θα διαγράψει αυτόν τον φάκελο αγαπημένων και το αγαπημένο μέσα. Θέλετε να συνεχίσετε;"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14037,7 +14012,7 @@ msgid "Type"
msgstr "Τύπος"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15132,7 +15107,6 @@ msgstr "Ροές Εργασίας"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Περιοχή Εργασίας"
+16 -42
View File
@@ -658,10 +658,10 @@ msgstr "About this workspace"
msgid "Access workspace data"
msgstr "Access workspace data"
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -925,11 +925,6 @@ msgstr "Add filter rule"
msgid "Add first filter"
msgstr "Add first filter"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Add folder"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1095,7 +1090,6 @@ msgid "Add to Favorite"
msgstr "Add to Favorite"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1682,6 +1676,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API Key"
@@ -2132,6 +2127,11 @@ msgstr "Authentication"
msgid "Authentication failed"
msgstr "Authentication failed"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr "Authentication method"
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4301,7 +4301,6 @@ msgstr "delete"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4389,7 +4388,6 @@ msgstr "Delete field"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Delete Folder"
@@ -6306,7 +6304,6 @@ msgstr "Fast Model"
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Favorites"
@@ -9417,7 +9414,6 @@ msgstr "No Files"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "No folder"
@@ -9426,11 +9422,6 @@ msgstr "No folder"
msgid "No folders available"
msgstr "No folders available"
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "No folders found"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9808,6 +9799,7 @@ msgstr "Numbered list"
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr "OAuth"
@@ -9816,6 +9808,11 @@ msgstr "OAuth"
msgid "OAuth Credentials"
msgstr "OAuth Credentials"
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr "OAuth or API Key"
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11132,16 +11129,6 @@ msgstr "Remote"
msgid "Remove"
msgstr "Remove"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Remove {favoriteCount} favorite?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Remove {favoriteCount} favorites?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11173,7 +11160,6 @@ msgid "Remove Deleted filter"
msgstr "Remove Deleted filter"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11228,7 +11214,6 @@ msgstr "Remove variable"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Rename"
@@ -13498,16 +13483,6 @@ msgstr "This action cannot be undone. This will permanently remove your membersh
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14030,7 +14005,7 @@ msgid "Type"
msgstr "Type"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr "Type '/' for commands, '@' for mentions"
@@ -15125,7 +15100,6 @@ msgstr "Workflows"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Workspace"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Sobre este espacio de trabajo"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accede a los datos de tu espacio de trabajo desde tu cliente MCP favorito, como Claude Desktop, Windsurf o Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Agregar regla de filtro"
msgid "Add first filter"
msgstr "Añadir primer filtro"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Agregar carpeta"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Añadir a favoritos"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API y Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clave API"
@@ -2137,6 +2132,11 @@ msgstr "Autenticación"
msgid "Authentication failed"
msgstr "Autenticación fallida"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "eliminar"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Eliminar campo"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Eliminar carpeta"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Favoritos"
@@ -9422,7 +9419,6 @@ msgstr "Sin archivos"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Sin carpeta"
@@ -9431,11 +9427,6 @@ msgstr "Sin carpeta"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "No se encontraron carpetas"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr "Lista numerada"
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Remoto"
msgid "Remove"
msgstr "Eliminar"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "¿Eliminar {favoriteCount} favorito?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "¿Eliminar {favoriteCount} favoritos?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Eliminar filtro Eliminado"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Eliminar variable"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Renombrar"
@@ -13503,16 +13488,6 @@ msgstr "Esta acción no se puede deshacer. Esto eliminará permanentemente su me
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Esta acción no se puede deshacer. Esto restablecerá permanentemente tu método de autenticación de dos factores. <0/> Escribe tu correo electrónico para confirmar."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Esta acción eliminará esta carpeta de favoritos y los {favoriteCount} favoritos que contiene. ¿Deseas continuar?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Esta acción eliminará esta carpeta de favoritos y el favorito que contiene. ¿Deseas continuar?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14035,7 +14010,7 @@ msgid "Type"
msgstr "Tipo"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15130,7 +15105,6 @@ msgstr "Workflows"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Espacio de trabajo"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "Tietoa tästä työtilasta"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Käytä työtilan tietoja suosikki MCP-asiakkaasi, kuten Claude Desktop, Windsurf tai Cursor, kautta."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Lisää suodatinsääntö"
msgid "Add first filter"
msgstr "Lisää ensimmäinen suodatin"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Lisää kansio"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Lisää suosikkilistalle"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-avain"
@@ -2137,6 +2132,11 @@ msgstr "Todennus"
msgid "Authentication failed"
msgstr "Todennus epäonnistui"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "poista"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Poista kenttä"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Poista kansio"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Suosikit"
@@ -9422,7 +9419,6 @@ msgstr "Ei tiedostoja"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Ei kansiota"
@@ -9431,11 +9427,6 @@ msgstr "Ei kansiota"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Kansioita ei löytynyt"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "Etä"
msgid "Remove"
msgstr "Poista"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Poistetaanko {favoriteCount} suosikki?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Poistetaanko {favoriteCount} suosikkia?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Poista Poistetut-suodatin"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Poista muuttuja"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Nimeä uudelleen"
@@ -13501,16 +13486,6 @@ msgstr "Tätä toimintoa ei voi peruuttaa. Tämä poistaa jäsenyytesi pysyväst
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Tätä toimintoa ei voi peruuttaa. Tämä nollaa pysyvästi kaksivaiheisen todennustapasi. <0/> Vahvista kirjoittamalla sähköpostiosoitteesi."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Tämä toiminto poistaa tämän suosikkikansion ja kaikki sen sisällä olevat {favoriteCount} suosikkia. Haluatko jatkaa?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Tämä toiminto poistaa tämän suosikkikansion ja sen sisällä olevan suosikin. Haluatko jatkaa?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14033,7 +14008,7 @@ msgid "Type"
msgstr "Tyyppi"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15128,7 +15103,6 @@ msgstr "Työnkulut"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Työtila"
+16 -42
View File
@@ -663,10 +663,10 @@ msgstr "À propos de cet espace de travail"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accédez à vos données de l'espace de travail depuis votre client MCP préféré comme Claude Desktop, Windsurf ou Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -930,11 +930,6 @@ msgstr "Ajouter une règle de filtre"
msgid "Add first filter"
msgstr "Ajouter le premier filtre"
#. js-lingui-id: //rVZt
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
msgid "Add folder"
msgstr "Ajouter un dossier"
#. js-lingui-id: 7XzAKI
#: src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder.tsx
msgid "Add inputs to your form"
@@ -1100,7 +1095,6 @@ msgid "Add to Favorite"
msgstr "Ajouter aux favoris"
#. js-lingui-id: pBsoKL
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1687,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clé API"
@@ -2137,6 +2132,11 @@ msgstr "Authentification"
msgid "Authentication failed"
msgstr "Authentification échouée"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -4306,7 +4306,6 @@ msgstr "supprimer"
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
@@ -4394,7 +4393,6 @@ msgstr "Supprimer le champ"
#. js-lingui-id: 97QUV6
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Delete Folder"
msgstr "Supprimer le dossier"
@@ -6311,7 +6309,6 @@ msgstr ""
#. js-lingui-id: X9kySA
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx
#: src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx
msgid "Favorites"
msgstr "Favoris"
@@ -9422,7 +9419,6 @@ msgstr "Aucun fichier"
#. js-lingui-id: pYblOw
#: src/modules/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubPage.tsx
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folder"
msgstr "Aucun dossier"
@@ -9431,11 +9427,6 @@ msgstr "Aucun dossier"
msgid "No folders available"
msgstr ""
#. js-lingui-id: aywdyd
#: src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerList.tsx
msgid "No folders found"
msgstr "Aucun dossier trouvé"
#. js-lingui-id: 1jgsYC
#: src/modules/settings/accounts/components/message-folders/SettingsMessageFoldersEmptyStateCard.tsx
msgid "No folders found for this account"
@@ -9813,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9821,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -11137,16 +11134,6 @@ msgstr "À distance"
msgid "Remove"
msgstr "Retirer"
#. js-lingui-id: gleHgw
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorite?"
msgstr "Supprimer {favoriteCount} favori ?"
#. js-lingui-id: 1VQkMD
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "Remove {favoriteCount} favorites?"
msgstr "Supprimer {favoriteCount} favoris ?"
#. js-lingui-id: 0Urj9q
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "Remove {navigationMenuItemCount} navigation menu item?"
@@ -11178,7 +11165,6 @@ msgid "Remove Deleted filter"
msgstr "Supprimer le filtre supprimé"
#. js-lingui-id: T/pF0Z
#: src/modules/favorites/components/PageFavoriteButton.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
#: src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
msgid "Remove from favorites"
@@ -11233,7 +11219,6 @@ msgstr "Supprimer la variable"
#. js-lingui-id: 2wxgft
#: src/modules/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown.tsx
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
#: src/modules/favorites/components/FavoriteFolderNavigationDrawerItemDropdown.tsx
#: src/modules/activities/files/components/AttachmentDropdown.tsx
msgid "Rename"
msgstr "Renommer"
@@ -13503,16 +13488,6 @@ msgstr "Cette action est irréversible. Cela supprimera définitivement votre ap
msgid "This action cannot be undone. This will permanently reset your two factor authentication method. <0/> Please type in your email to confirm."
msgstr "Cette action est irréversible. Elle réinitialisera définitivement votre méthode d'authentification à deux facteurs. <0/> Veuillez saisir votre adresse e-mail pour confirmer."
#. js-lingui-id: XdFYs2
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and all {favoriteCount} favorites inside. Do you want to continue?"
msgstr "Cette action supprimera ce dossier de favoris et les {favoriteCount} favoris qu'il contient. Voulez-vous continuer ?"
#. js-lingui-id: pFXbgz
#: src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx
msgid "This action will delete this favorite folder and the favorite inside. Do you want to continue?"
msgstr "Cette action supprimera ce dossier de favoris ainsi que le favori qu'il contient. Voulez-vous continuer ?"
#. js-lingui-id: lAHIY2
#: src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx
msgid "This action will delete this folder and all {navigationMenuItemCount} navigation menu items inside. Do you want to continue?"
@@ -14035,7 +14010,7 @@ msgid "Type"
msgstr "Type"
#. js-lingui-id: SKD2e4
#: src/modules/activities/components/ActivityRichTextEditor.tsx
#: src/modules/object-record/record-field/ui/meta-types/input/components/RichTextFieldEditor.tsx
msgid "Type '/' for commands, '@' for mentions"
msgstr ""
@@ -15130,7 +15105,6 @@ msgstr "Workflows"
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownVisibilityContent.tsx
#: src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
#: src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx
#: src/modules/favorites/components/WorkspaceFavorites.tsx
msgid "Workspace"
msgstr "Espace de travail"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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