Compare commits

...
Author SHA1 Message Date
Charles BochetandGitHub 09daccc3f9 fix(server): add subFieldName column early in upgrade sequence (#20584)
## Summary

Cross-version upgrades from pre-2.3 still fail after #20581 / #20583 —
different column, structurally similar problem:

```
column ViewSortEntity.subFieldName does not exist
  at WorkspaceFlatViewSortMapCacheService.computeForCache (...flat-view-sort/services/workspace-flat-view-sort-map-cache.service.js:40)
  ... triggered indirectly by DropMessageDirectionFieldCommand (2.3 workspace command)
```

(see
https://github.com/twentyhq/twenty-infra/actions/runs/25862573418/job/75997337604)

### Why narrowing the `select` doesn't fit here

In the previous two PRs the offender was a bare `findOne` on
`WorkspaceEntity` — easy to narrow. Here the chain is:

1. The 2.3 `DropMessageDirectionFieldCommand` builds a workspace
migration that deletes a `fieldMetadata` (the `direction` field).
2. `WorkspaceMigrationRunnerService.run` walks the metadata cascade
graph (`getMetadataRelatedMetadataNames`) and pulls `viewSort` into the
dependency set because `viewSort` is the inverse one-to-many of
`fieldMetadata` (deleting a field cascades to view sorts that reference
it).
3. That maps to cache keys → `flatViewSortMaps` gets requested →
`WorkspaceFlatViewSortMapCacheService.computeForCache` runs.
4. `computeForCache` does `viewSortRepository.find({ where: {
workspaceId }, withDeleted: true })` with no `select`, so TypeORM emits
a SELECT that includes `subFieldName` — the column doesn't exist in DB
yet (added by a 2.5 instance command much later in the sequence). 💥

Narrowing the cache provider's select would silently drop `subFieldName`
from the cache for runtime use too, until something invalidates it.
Brittle, and would re-break the next time anyone adds a `viewSort`
column.

### Structural fix

Ensure the column exists in DB before any 2.3 workspace command can
trigger that cascade. Within a version, the upgrade runner sorts: fast
instance → slow instance → workspace, so a new 2.3 fast instance command
lands before `DropMessageDirectionFieldCommand`.

- **Add**
`2-3/2-3-instance-command-fast-1747234200000-add-sub-field-name-to-view-sort.ts`
— `ALTER TABLE ... ADD COLUMN IF NOT EXISTS "subFieldName"`. Comment in
the file explains the cascade and why this lives in 2.3 instead of 2.5.
- **Make idempotent** the existing
`2-5/...-add-sub-field-name-to-view-sort.ts` — switched to `ADD COLUMN
IF NOT EXISTS` / `DROP COLUMN IF EXISTS` so it's a no-op on
cross-upgrade paths while still creating the column on fresh-from-2.5
installs.
- Register the new command in `instance-commands.constant.ts`.

The 2.5 command body change is semantically preserving (idempotent), and
v2.5.0 hasn't shipped to any production DB yet — so this doesn't violate
the "never rewrite committed instance commands" rule in spirit.

### Note on the previous two PRs

#20581 and #20583 narrowed `select` on `WorkspaceEntity` for
`isInternalMessagesImportEnabled`. That's a band-aid that works because
there's a small, enumerable set of bare `workspaceRepository.findOne`
call sites. It could in principle be replaced with the same pattern as
this PR (early 2.x instance command that adds the workspace column). Not
doing that here to keep the diff tight, but happy to follow up if
preferred.

## Test plan

- [ ] Re-run twenty-infra cross-version-upgrade CI and confirm 2.3
workspace commands complete
- [ ] Verify the new 2.3 instance command and the modified 2.5 instance
command are both idempotent (running upgrade twice should not error)
- [ ] Verify a fresh install path still ends with `subFieldName` present
on `core.viewSort`
2026-05-14 16:02:53 +02:00
Charles BochetandGitHub 484037c179 fix(server): scope workspace findOne in ApplicationService (#20583)
## Summary

Cross-version upgrade still fails after #20581:

```
column WorkspaceEntity.isInternalMessagesImportEnabled does not exist
  at ApplicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow (application.service.ts:84)
  at UpdateGlobalObjectContextCommandMenuItemsCommand.runOnWorkspace (1-23-…)
  at BackfillRecordPageLayoutsCommand.runOnWorkspace (1-23-…)
```

(see
https://github.com/twentyhq/twenty-infra/actions/runs/25861366732/job/75993012161)

### Root cause

Same class of bug as #20581, different location.
`ApplicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow`
does:

```ts
await this.workspaceRepository.findOne({
  where: { id: workspaceId },
  withDeleted: true,
});
```

No `select`, so TypeORM emits a SELECT for every column declared on
`WorkspaceEntity`. PR #20457 added `isInternalMessagesImportEnabled` to
the entity; its DB column is only created by the 2-5 fast instance
command `1778525104406-add-is-internal-messages-import-enabled`. Many
workspace commands across versions 1-21 → 2-3 call this service (notably
the 1-23 commands shown in the stack), and they all run before the 2-5
instance command — so the bare findOne hits a column that doesn't exist
yet and the upgrade aborts.

### Fix

The function only reads `workspace.id` (passed to cache) and
`workspace.workspaceCustomApplicationId`. Narrow the select to just
those.

The `workspace: WorkspaceEntity` input variant of the function is
unchanged — only the path where we fetch the workspace ourselves is
narrowed. Callers don't see the workspace entity (the function only
returns `{ twentyStandardFlatApplication, workspaceCustomFlatApplication
}`).

### Why not edit the committed 1-23 workspace commands

Same reasoning as #20581: the fix lives in the service that does the
read, so future column additions to `WorkspaceEntity` don't risk
re-breaking every caller. Per `CLAUDE.md`, instance command `up`/`down`
is immutable; this isn't an instance command.

## Test plan

- [ ] Re-run the failing cross-version-upgrade job and confirm it gets
past 1-23
- [ ] Verify the function still resolves the standard + custom
applications correctly for a workspace (no behavior change in returned
shape)
2026-05-14 15:24:59 +02:00
Charles BochetandGitHub a5982b644c fix(server): scope workspace findOne in 1-21 backfill-datasource command (#20581)
## Summary

Cross-version upgrades from pre-1-21 instances currently fail with:

```
error: column WorkspaceEntity.isInternalMessagesImportEnabled does not exist
```

(see
https://github.com/twentyhq/twenty-infra/actions/runs/25857499266/job/75979993686)

### Root cause

The 1-21 workspace command `backfill-datasource-to-workspace` does:

```ts
const workspace = await this.workspaceRepository.findOne({
  where: { id: workspaceId },
});
```

No `select`, so TypeORM emits a SELECT for every column declared on
`WorkspaceEntity`. PR #20457 added `isInternalMessagesImportEnabled` to
the entity, but its DB column is only created by the 2-5 fast instance
command `1778525104406-add-is-internal-messages-import-enabled`. On a
fresh cross-version upgrade, the runner reaches the 1-21 workspace
segment before that 2-5 instance command runs, the bare `findOne` issues
SELECT on a column that doesn't exist yet, and the upgrade aborts.

### Fix

Narrow the select to just the columns this command actually reads (`id`,
`databaseSchema`). The query now ignores entity columns added later in
the upgrade sequence.

### Why edit a committed workspace command

Per `CLAUDE.md`, committed *instance* command `up`/`down` logic is
immutable. Workspace commands are idempotent backfills — adding a
`select` narrows the read but doesn't change behavior, so it's safe.

### Audit

Verified this is the only unguarded `workspaceRepository.find*` across
the entire upgrade subtree:
- `WorkspaceIteratorService.iterate` uses `select: ['databaseSchema']`
- `WorkspaceVersionService.getActiveOrSuspendedWorkspaceIds` uses
`select: ['id']`
- `UpgradeStatusService.loadActiveOrSuspendedWorkspaces` uses `select:
['id', 'displayName']`

## Test plan

- [ ] Re-run the failing cross-version upgrade job and confirm it gets
past 1-21
- [ ] Verify the 1-21 backfill still correctly skips workspaces with a
non-empty `databaseSchema` and backfills those without
2026-05-14 14:57:26 +02:00
4054ede5bb i18n - translations (#20582)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-14 14:52:45 +02:00
af4765effe feat(twenty-server): one-hop relation filters in GraphQL API (#20527)
## Summary

Adds support for filtering records by fields on a related MANY_TO_ONE
object via the GraphQL API. Backend only — no frontend, no REST, no
view-filter persistence yet.

```graphql
{
  people(filter: { company: { name: { like: "%Airbnb%" } } }) {
    edges { node { id } }
  }
}
```

### Where the work lands

- **Schema** — `relation-field-metadata-gql-type.generator.ts` now emits
`{relationName}: TargetFilterInput` alongside the existing
`{joinColumnName}: UUIDFilter` for MANY_TO_ONE relations. Mirrors the
order-by generator that already does this for sort. Lazy thunks in
`object-metadata-filter-gql-input-type.generator.ts` handle the cycle
between filter inputs.
- **Arg processor** — `FilterArgProcessorService` no longer hard-rejects
accessing a relation by its name. When the value is a nested object on a
MANY_TO_ONE field, it recurses into the target object's metadata so each
leaf still gets validated and coerced. Depth-capped at 1.
- **Query parser** — new `parseRelationSubFilter` branch in
`graphql-query-filter-field.parser.ts`. When triggered: looks up the
target object metadata, calls `ensureRelationJoin` against the outer
query builder, and recurses via a child
`GraphqlQueryFilterConditionParser` scoped to the target.
`and`/`or`/`not` inside the relation filter keep working because the
child dispatches through the same `parseKeyFilter`.
- **Shared join utility** — `ensureRelationJoin.util.ts` is a single
function that inspects `queryBuilder.expressionMap.joinAttributes` for
the alias before adding a `LEFT JOIN`. Rewired the existing inline
`qb.leftJoin` calls in the order parser and group-by service to use it,
so filter-driven joins no longer collide with sort-driven joins on the
same relation.

### Out of scope (explicit)

- ONE_TO_MANY reverse traversal (needs EXISTS subqueries)
- Aggregates (`company.people.count > 5` — needs HAVING)
- View-filter storage (no `relationPath` column on `ViewFilterEntity`)
- REST DSL changes
- Frontend filter-picker UX
- Nesting deeper than one hop (parser and arg-processor both reject)

### Open question for review

Permissions. The order-by-on-relation code path already lets users sort
People by Company.name without a Company read-permission check, and this
PR matches that behavior for filters — felt wrong to add a stricter gate
only on the filter side. If we want object-permission gating on the
relation target, it should be a follow-up that covers both paths
consistently. The only attack surface today is existence inference via
timing, identical to what sort already exposes.

## Test plan

- [x] `tsc --noEmit` — clean for changed files (5 unrelated pre-existing
errors on main untouched)
- [x] `oxlint --type-aware` + `prettier --check` — 0 errors on all 17
changed/new files
- [x] `jest filter-arg-processor.service.spec` — 229 tests pass (the new
optional `flatObjectMetadataMaps` arg is backwards-compatible)
- [x] Integration test (`filter-by-relation-field.integration-spec.ts`,
6 cases) — needs to be verified against a seeded test DB. Could not
exercise the happy path in my isolated worktree; depth-2 rejection
passed there.
- [ ] EXPLAIN ANALYZE on the integration test query to confirm the FK on
`person.companyId` is indexed for both standard and custom MANY_TO_ONE
relations.

### Integration test cases

1. Filter People by `company.name = "Airbnb"` (exact match)
2. Filter People by `company.name like "%irbnb%"`
3. Non-matching filter returns empty
4. Combined with a scalar filter at root via `and`
5. **Combined with `orderBy` on the same relation** — proves the
join-dedupe works (without `ensureRelationJoin`, TypeORM throws
"duplicate alias")
6. Depth-2 nesting (`company.accountOwner.name`) returns
`INVALID_ARGS_FILTER`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
2026-05-14 14:45:32 +02:00
Charles BochetandGitHub a941f6fe01 feat(server): migrate TOTP secret encryption to SecretEncryptionService (#20577)
## Summary

Removes the last `APP_SECRET`-derived at-rest encryption site by
migrating `core.twoFactorAuthenticationMethod.secret` from
`SimpleSecretEncryptionUtil` (AES-256-CBC with key derived from
`sha256(APP_SECRET + userId + workspaceId + 'otp-secret' +
'KEY_ENCRYPTION_KEY')`) to the versioned `enc:v2:` envelope
(ENCRYPTION_KEY → HKDF-SHA256 bound to `workspaceId` → AES-256-GCM).

- New `decrypt-legacy-aes-cbc.util.ts` faithfully reproduces the
pre-migration CBC derivation byte-for-byte;
`SecretEncryptionService.decryptVersioned` dispatches to it when callers
pass `legacyAesCbcPurpose`, with a dedicated one-shot WARN log family.
- `TwoFactorAuthenticationService` now uses `encryptVersioned` /
`decryptVersioned` (passing the legacy purpose so existing rows still
decrypt). `SimpleSecretEncryptionUtil` and its spec are deleted;
`TwoFactorAuthenticationModule` imports `SecretEncryptionModule` in
their place.
- `TwoFactorAuthenticationMethodEntity` gets a `@Check` decorator
(`CHK_twoFactorAuthenticationMethod_secret_encrypted`) restricting
`secret` to the `enc:v2:` envelope; the matching 2.5 slow instance
command (`1798000009000-encrypt-totp-secrets`) cursor-paginates `JOIN`ed
`userWorkspace` rows to recover the legacy `userId`, re-encrypts to
`enc:v2`, and applies the CHECK constraint in `up()`.

### Deviation note

The plan suggested wiring a workspace-only legacy derivation directly
into `decryptVersioned`. In practice the production rows are
user-and-workspace-scoped (the legacy purpose is
`\${userId}\${workspaceId}otp-secret`), so a workspace-only derivation
could not recover them. The PR keeps the public `decryptVersioned` API
intact and adds an optional `legacyAesCbcPurpose` so callers that can
reconstruct the legacy context (the 2FA service and the slow command)
opt in.

### Final state of remaining `APP_SECRET` usages

- HS256 JWT verify (read-only, self-retiring once asymmetric migration
completes).
- Express-session cookie signing.
- Approved-access-domain HMAC (signing root, not at-rest).
- Zero-friction fallback in `resolveEncryptionKeysOrThrow`
(intentional).

No production at-rest data is encrypted with `APP_SECRET`-derived keys
anymore.

## Test plan

- [x] `npx jest src/engine/core-modules/secret-encryption
src/engine/core-modules/two-factor-authentication` — 170 unit tests
pass, including new unit tests for the legacy CBC util and the new
`SecretEncryptionService` fallback branch.
- [x] `npx jest --config ./jest-integration.config.ts
test/integration/upgrade/suites/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets.integration-spec.ts`
— 4 integration tests cover legacy-CBC seed → slow command → `enc:v2`
round-trip, idempotency, CHECK constraint enforcement on `up()`, and
rollback via `down()`.
- [x] `npx oxlint --type-aware` and `npx prettier --check` clean on all
touched files.
- [ ] CI on this PR (server validation, tests, lint, typecheck).
2026-05-14 13:24:06 +02:00
Abdul RahmanandGitHub fc53f18a9f Twenty discord integration (#20530)
<img width="1290" height="777" alt="Screenshot 2026-05-14 at 8 55 02 AM"
src="https://github.com/user-attachments/assets/37cde89b-b4c3-438a-8ccf-39621f9799b4"
/>



https://github.com/user-attachments/assets/cd8af6f9-f2e5-47cf-bf1f-57590bb358d1



https://github.com/user-attachments/assets/ff53c6b7-ee1a-42db-bbc5-d7df5b3fa6b0



https://github.com/user-attachments/assets/ddca2c16-774d-4672-aa61-05e878d8b2b7
2026-05-14 10:57:53 +00:00
Anish PaudelandGitHub ddaba26abe chore(.vscode): add remaining packages to VSCode workspace (#20570)
## Context
This PR extends the multi-root VSCode workspace configuration introduced
in #2937 by adding the remaining packages from the `packages/*`
directories to the `twenty.code-workspace` folders array.

## Problem
Previously, some packages were not added to the multi-root workspace
configuration. As a result, when opening the repository as a vscode
workspace, those packages were hidden from the VSCode Explorer because
they were not part of the configured workspace folders.

## Benefits

- Prevents packages from being hidden when the repository is opened as a
vscode workspace.
- Improves consistency and navigation across the monorepo workspace
experience.

## Related PR
- #2937
2026-05-14 10:38:18 +00:00
neo773andGitHub 42975a4168 fix(server): decouple SDK client generation from workspace activation (#20514)
`activateWorkspace` enqueues SDK gen job inside
`WorkspaceManagerService.init()` introduced by
https://github.com/twentyhq/twenty/pull/19271

But if enqueue call fails it crashes cuz it doesn't have try catch so
created workspace is in corrupted state

<img width="636" height="812" alt="image"
src="https://github.com/user-attachments/assets/09acd042-46d0-4225-adc0-c74ea770785d"
/>


FIx:
Move SDK enqueue out of `init()` Call after
`activateAndInitializeUpgradeState` succeeds, wrap in try catch. Mirror
preInstalledAppsService.installOnWorkspace pattern.

Assuming enqueue failure if Redis is unavailable we fallback to
`SdkClientArchiveService.downloadArchiveBufferOrGenerate` which
generates it on the fly

Around 19 workspaces in prod affected with status `ONGOING_CREATION`
2026-05-14 10:25:24 +00:00
Félix MalfaitandGitHub dbc033b29b fix(auth): exclude /authorize from MinimalMetadataGater loading gate (#20572)
## Summary

Fixes the blank `/authorize` page reported when a user reopens the OAuth
consent screen on `app.twenty.com` after a prior multi-workspace SSO
sign-in.

### Reproduction

1. ChatGPT (or any MCP client) opens
`https://app.twenty.com/authorize?client_id=…` while signed out.
2. User picks "Continue with Google", lands in workspace selection,
picks a workspace, authorizes the app. Works.
3. Some time later, ChatGPT re-opens
`https://app.twenty.com/authorize?client_id=…`.
4. **Observed:** fully blank page, no console errors. Deleting the
`tokenPair` cookie unblocks it.

### Root cause

The multi-workspace social-SSO branch
([auth.service.ts:988-1011](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts#L988-L1011))
lands the user on `app.twenty.com/welcome?tokenPair=…` with a
workspace-agnostic token. `SignInUpGlobalScopeFormEffect` writes that
into the host-scoped `tokenPair` cookie on `app.twenty.com`, and nothing
clears it after the user proceeds to a workspace subdomain. On the next
visit to `app.twenty.com/authorize?…`, `MinimalMetadataGater` sees
`hasAccessTokenPair === true` and renders `<UserOrMetadataLoader />`
instead of `<Authorize />`. The loader never goes away because:

- `IsMinimalMetadataReadyEffect` waits for `metadataStore.status ===
'up-to-date'`.
- `MinimalMetadataLoadEffect` only loads metadata when
`hasAccessTokenPair && isActiveWorkspace`, and the default domain has no
workspace context.

Skeleton loader stays forever → user perceives "blank page".

Some users get rescued by `WorkspaceProviderEffect` auto-redirecting
them to their last-authenticated workspace subdomain, but that cookie is
set with `domain: .twenty.com` and silently fails to persist for users
on custom domains — so the bug is most visible there.

### Fix

`/authorize` only issues `findApplicationRegistrationByClientId`, which
is a `PublicEndpointGuard` query. It doesn't need workspace metadata.
Add it to the gater's excluded-paths list alongside the existing
pre-auth pages (`SignInUp`, `Verify`, `Invite`, …) so the page renders
immediately regardless of token state.

This is a one-line, minimum-blast-radius fix. Two related cleanups are
separate concerns and not addressed here:
- Clearing the workspace-agnostic `tokenPair` cookie on `app.twenty.com`
after workspace selection.
- Fixing `lastAuthenticatedWorkspaceDomain` propagation for
custom-domain users.

## Test plan

- [x] `npx oxlint` + `prettier --check` on the touched file — clean.
- [x] `npx nx typecheck twenty-front` — clean.
- [ ] Manual: with a stale `tokenPair` cookie set on `app.twenty.com`,
open `https://app.twenty.com/authorize?client_id=<valid>&…` — consent
screen renders, no blank.
- [ ] Manual: signed-out → open the same URL — still redirects to
`/welcome` and back through the flow.
- [ ] Manual: signed-in on a workspace subdomain → open
`https://app.twenty.com/authorize?…` — `WorkspaceProviderEffect`
auto-redirect still kicks in (unchanged behavior).
2026-05-14 10:17:17 +00:00
Abdullah.andGitHub 72f857fc10 [Website] Refine feature card scroll entrance to a subtle opacity fade (#20574)
Thomas mentioned the animations need to be subtle. Therefore, this PR
removes transform-based slide/rotate animations in favor of a clean 0.6s
opacity fade for a polished, professional appearance.

Before:


https://github.com/user-attachments/assets/bede627f-d4b3-4126-8eb0-c1d5a9b4d16a

After:


https://github.com/user-attachments/assets/85cb604b-a8c9-4730-85d3-8edb8ef0fc71
2026-05-14 10:01:07 +00:00
a47e1e0e5e Fix time consuming search ilike fallback (#20544)
## Context
When the tsvector full-text search returns 0 hits on the first page,
SearchService falls back to ILIKE '%word%' over searchVector::text. The
leading wildcard makes the GIN index unusable, so it seq-scans the
table.
On large searchable custom objects (e.g. a workspace with ~500k rows in
_logs) a single fallback can take 2–3s, multiplied across all searchable
objects in one request.

## Implementation
Wrap the fallback query in a tiny TypeORM transaction and apply a
Postgres per-statement timeout via set_config('statement_timeout', ms,
true) (= SET LOCAL). On timeout, Postgres throws 57014 (QUERY_CANCELED);
we catch it, warn-log with workspace/object context, and return [] for
that object

## Note
This PR bounds the slow fallback and doesn't make it fast. The right
structural fix is to let the fallback use an index. Since tsvector does
not work with certain language (which is the reason why the ILIKE
fallback was implemented in the first place), we should probably use the
pg_trgm extension instead (@FelixMalfait)

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-05-14 10:00:33 +00:00
Charles BochetandGitHub 0d5617d446 chore(server): drop unused postgresCredentials feature (#20573)
## Summary

Drops the `postgresCredentials` legacy feature: a never-finished
"postgres proxy" that would have let users query their workspace data
over a standard Postgres connection. Nothing — frontend, e2e, Zapier,
docs, other server code — calls these mutations/query.

## History

- **Introduced** June 2024 (#5767, Thomas Trompette) as "first step for
creating credentials for database proxy", alongside the Postgres FDW /
remote-server work and the custom `twenty-postgres-spilo` image. Planned
follow-ups (provisioning a DB on the proxy, mapping users, exposing it
as a remote server) never landed.
- **Abandoned** January 2026 (#17001, Weiko) when the sibling "remote
integration" feature was removed as a BREAKING CHANGE — "not maintained
for more than a year and never officially launched". The spilo image was
then replaced with vanilla `postgres:16` (#19182, March 2026), retiring
the FDW infrastructure entirely.
- This PR finishes the cleanup: removes the orphaned module, the
`allPostgresCredentials` relation, `JwtTokenTypeEnum.POSTGRES_PROXY` +
payload, the reserved metadata keywords, and adds a 2.5.0 fast instance
command that drops `core.postgresCredentials` (reversible `down`).
Regenerated frontend GraphQL types + SDK metadata client.

## Test plan

- [x] `tsgo --noEmit` clean on twenty-server + twenty-front; lint +
prettier clean on touched files.
- [x] `database:migrate:generate` reports no pending schema diff; server
boots and serves the new schema.
2026-05-14 12:04:09 +02:00
Abdullah.andGitHub 61683d8bda [Website] Replace product page hero visual with interactive CRM depicting AI chat in action. (#20566)
Before:

<img width="1439" height="518" alt="image"
src="https://github.com/user-attachments/assets/4d294a1b-c5a0-43b2-9895-61a8ee19da62"
/>

After:


https://github.com/user-attachments/assets/c019586f-ef9f-4ae0-8afe-14f08e8cb057
2026-05-14 07:48:14 +00:00
ab705b14d7 i18n - translations (#20569)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-14 09:38:09 +02:00
90537b3b88 i18n - translations (#20567)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-14 09:34:18 +02:00
Paul RastoinandGitHub a754a95d38 Regrant id token write to claude for oidc swap (#20564) 2026-05-14 07:19:08 +00:00
8dd0aa46e3 Update OAuth consent modal design (#20540)
## Summary

Updates the OAuth consent screen to match the provided modal treatment,
including the header artwork, app-to-Twenty logo layout, scope icons,
and a content-hugging title that stays on one line until its max width.

Also preserves `returnToPath` through Google and Microsoft social SSO so
users who sign in from `/authorize` return to the OAuth consent flow
with the original OAuth query parameters intact.

## Reference

![OAuth consent modal
reference](https://raw.githubusercontent.com/twentyhq/twenty/046e02d9b3916e286c6bb4ef1c8045b0df53c074/oauth-modal-reference.png)

## Validation

- `yarn nx test twenty-front
--testFile=src/modules/auth/hooks/__tests__/useAuth.test.tsx
--coverage=false`
- `yarn nx test twenty-server
--testFile=src/engine/core-modules/auth/services/auth.service.spec.ts
--coverage=false`
- `yarn nx typecheck twenty-front`
- `yarn nx typecheck twenty-server`
- `git diff --check`

## Notes

The reference screenshot is linked from a separate branch-hosted image
commit so it renders in the PR body without adding that screenshot to
the product diff.

Co-authored-by: Charles Bochet <[email protected]>
2026-05-14 07:15:11 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
91b2390ee8 chore(deps-dev): bump vite-plugin-svgr from 4.3.0 to 4.5.0 (#20561)
Bumps [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr)
from 4.3.0 to 4.5.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pd4d10/vite-plugin-svgr/releases">vite-plugin-svgr's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.0</h2>
<p><em>No significant changes</em></p>
<h5>    <a
href="https://github.com/pd4d10/vite-plugin-svgr/compare/v4.4.0...v4.5.0">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/d1f8887202a6edd119053c665f0679419df70025"><code>d1f8887</code></a>
4.5.0</li>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/b326999d5a2546227bb4b2d2cdcf6092392f4d11"><code>b326999</code></a>
ci: fix npm publish</li>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/79300b869297ab2063a730f33ec6f7984b8acbe8"><code>79300b8</code></a>
4.4.0</li>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/b7d3c36dd658a6b8344b82307f36dc3acb0d1479"><code>b7d3c36</code></a>
chore: update deps</li>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/6f9d9af421ec891c1594b5511d4865cea6c8f79a"><code>6f9d9af</code></a>
feat: support rolldown-vite via <code>transformWithOxc</code> (<a
href="https://redirect.github.com/pd4d10/vite-plugin-svgr/issues/130">#130</a>)</li>
<li><a
href="https://github.com/pd4d10/vite-plugin-svgr/commit/cef5adead59da0abfc0b81edf4b2022b193e4607"><code>cef5ade</code></a>
ci: fix release note</li>
<li>See full diff in <a
href="https://github.com/pd4d10/vite-plugin-svgr/compare/v4.3.0...v4.5.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=vite-plugin-svgr&package-manager=npm_and_yarn&previous-version=4.3.0&new-version=4.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 07:04:39 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b36345a699 chore(deps-dev): bump @mui/material from 7.3.8 to 7.3.11 (#20562)
Bumps
[@mui/material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-material)
from 7.3.8 to 7.3.11.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/releases">@​mui/material's
releases</a>.</em></p>
<blockquote>
<h2>v7.3.11</h2>
<p>A big thanks to the 5 contributors who made this release
possible.</p>
<h3><code>@mui/[email protected]</code></h3>
<ul>
<li>[autocomplete] Fix highlight sync and scroll preservation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48350">#48350</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[autocomplete] Fix popper rendering issues (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48343">#48343</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[autocomplete] Improve highlight tracking and selection state (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48318">#48318</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[button] Fix <code>startIcon</code> alignment (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48339">#48339</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[button] Remove duplicated className entries (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48284">#48284</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[checkbox] Set <code>aria-checked=mixed</code> when indeterminate
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48286">#48286</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[dialog][drawer][focus trap] Fix initial focus target (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48324">#48324</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[drawer] Fix transition jump (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48340">#48340</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[input] Fix layout shift with display: flex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48359">#48359</a>)
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a></li>
<li>[inputs] Fix autofocus in SSR environment (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48307">#48307</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[popper] Persist positioning styles when popperOptions changes
reference (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48302">#48302</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[switch] Fix incorrect <code>role</code> with
<code>slotProps.input</code> (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48472">#48472</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[utils] Add shadow dom utils (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48309">#48309</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
</ul>
<h3>Docs</h3>
<ul>
<li>[docs] Update banner to announce v9 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48299">#48299</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[docs] Add v9 in the versions select in v7.mui.com (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48233">#48233</a>)
<a
href="https://github.com/alexfauquette"><code>@​alexfauquette</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[internal] Update some host-reference entries (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48225">#48225</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/alexfauquette"><code>@​alexfauquette</code></a>,
<a href="https://github.com/mj12albert"><code>@​mj12albert</code></a>,
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a>,
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></p>
<h2>v7.3.10</h2>
<p>A big thanks to the 15 contributors who made this release possible. A
few highlights :</p>
<ul>
<li>📖 Added the Menubar component page to the docs.</li>
</ul>
<h3><code>@mui/[email protected]</code></h3>
<ul>
<li>[alert][dialog] Accessibility improvements (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48161">#48161</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[autocomplete] Add <code>root</code> slot (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47916">#47916</a>)
<a href="https://github.com/GerardasB"><code>@​GerardasB</code></a></li>
<li>[autocomplete] Fix helper text focusing input when clicked (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48162">#48162</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[autocomplete] Fix popup reopening on window focus regain with
openOnFocus (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47914">#47914</a>)
<a href="https://github.com/aman44444"><code>@​aman44444</code></a></li>
<li>[autocomplete] Optimize selected option lookup (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48027">#48027</a>)
<a href="https://github.com/anchmelev"><code>@​anchmelev</code></a></li>
<li>[autocomplete] Support full slots for clearIndicator and
popupIndicator (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47913">#47913</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[button-base] Fix native button detection (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47994">#47994</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[input] Fix high contrast cutoff on first character (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48160">#48160</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[list item text][card header] Revert cleanup of duplicated CSS rules
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47938">#47938</a>)
<a href="https://github.com/sai6855"><code>@​sai6855</code></a></li>
<li>[popper] Add missing classes export (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48033">#48033</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[select] Fix focus visible always set on menu item (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48022">#48022</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[slider] Accept readonly array for the value prop (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47961">#47961</a>)
<a href="https://github.com/pcorpet"><code>@​pcorpet</code></a></li>
<li>[switch] Add border to make it visible in high contrast mode (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48210">#48210</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/blob/v7.3.11/CHANGELOG.md">@​mui/material's
changelog</a>.</em></p>
<blockquote>
<h2>7.3.11</h2>
<!-- raw HTML omitted -->
<p><em>May 6, 2026</em></p>
<p>A big thanks to the 5 contributors who made this release
possible.</p>
<h3><code>@mui/[email protected]</code></h3>
<ul>
<li>[autocomplete] Fix highlight sync and scroll preservation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48350">#48350</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[autocomplete] Fix popper rendering issues (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48343">#48343</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[autocomplete] Improve highlight tracking and selection state (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48318">#48318</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[button] Fix <code>startIcon</code> alignment (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48339">#48339</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[button] Remove duplicated className entries (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48284">#48284</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[checkbox] Set <code>aria-checked=mixed</code> when indeterminate
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48286">#48286</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[dialog][drawer][focus trap] Fix initial focus target (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48324">#48324</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[drawer] Fix transition jump (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48340">#48340</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[input] Fix layout shift with display: flex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48359">#48359</a>)
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a></li>
<li>[inputs] Fix autofocus in SSR environment (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48307">#48307</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[popper] Persist positioning styles when popperOptions changes
reference (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48302">#48302</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[switch] Fix incorrect <code>role</code> with
<code>slotProps.input</code> (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48472">#48472</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
<li>[utils] Add shadow dom utils (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48309">#48309</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
</ul>
<h3>Docs</h3>
<ul>
<li>[docs] Update banner to announce v9 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48299">#48299</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[docs] Add v9 in the versions select in v7.mui.com (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48233">#48233</a>)
<a
href="https://github.com/alexfauquette"><code>@​alexfauquette</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[internal] Update some host-reference entries (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48225">#48225</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/alexfauquette"><code>@​alexfauquette</code></a>,
<a href="https://github.com/mj12albert"><code>@​mj12albert</code></a>,
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a>,
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></p>
<h2>7.3.10</h2>
<!-- raw HTML omitted -->
<p><em>Apr 8, 2026</em></p>
<p>A big thanks to the 15 contributors who made this release possible. A
few highlights :</p>
<ul>
<li>📖 Added the Menubar component page to the docs.</li>
</ul>
<h3><code>@mui/[email protected]</code></h3>
<ul>
<li>[alert][dialog] Accessibility improvements (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48161">#48161</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[autocomplete] Add <code>root</code> slot (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/47916">#47916</a>)
<a href="https://github.com/GerardasB"><code>@​GerardasB</code></a></li>
<li>[autocomplete] Fix helper text focusing input when clicked (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48162">#48162</a>)
<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mui/material-ui/commit/6ddda377e979d1783b2cc00226098fde4509bb2e"><code>6ddda37</code></a>
[release] v7.3.11 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48478">#48478</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/a2c7fa0eaeec48767ef7728a1d418609e0bf44ea"><code>a2c7fa0</code></a>
[switch] Fix incorrect <code>role</code> with
<code>slotProps.input</code> (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48472">#48472</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/c3628bd4930e9089fdf41cba78fc2b5798a2bc76"><code>c3628bd</code></a>
[input] Fix layout shift with display: flex (<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a>)
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48359">#48359</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/290c89694eae625cf4ad20f874098f0b8889ceb4"><code>290c896</code></a>
[autocomplete] Fix highlight sync and scroll preservation (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48350">#48350</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/6e172b760815dc3dc8254f58e0dcd793fe690979"><code>6e172b7</code></a>
[autocomplete] Fix popper rendering issues (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48343">#48343</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/3f2323ce5e780134dcbe75802cf5ab5035822dd4"><code>3f2323c</code></a>
[drawer] Fix transition jump (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48340">#48340</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/1afbdfebd521eb96c2bd4d43ec1f9b2f48960a00"><code>1afbdfe</code></a>
[button] Fix <code>startIcon</code> alignment (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48339">#48339</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/dcef24179ffd7a08f47e4c6b656cd58181c34fdc"><code>dcef241</code></a>
[dialog][drawer][focus trap] Fix initial focus target (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>) (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48324">#48324</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/4b0275ea82d1807324eb04034db50501eac1f802"><code>4b0275e</code></a>
[autocomplete] Improve highlight tracking and selection state (<a
href="https://github.com/mj12albert"><code>@​mj12albert</code></a>)
(...</li>
<li><a
href="https://github.com/mui/material-ui/commit/47e41decac18a891f329f854b0d22e2585fa95be"><code>47e41de</code></a>
[internal] Update some host-reference entries (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48225">#48225</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/mui/material-ui/commits/v7.3.11/packages/mui-material">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@mui/material&package-manager=npm_and_yarn&previous-version=7.3.8&new-version=7.3.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 07:04:10 +00:00
Félix MalfaitandGitHub 34d9fcaba1 chore(auth): drop unused workspacePersonalInviteToken from SSO state (#20557)
## Summary

Pure dead-code removal. The Google and Microsoft SSO strategies have
been packing `workspacePersonalInviteToken` into the OAuth `state` blob
and re-emitting it on `validate()`, but
[`signInUpWithSocialSSO`](packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts)
never destructures or reads it from the user object. The SSO flow
resolves invitations by the IdP-verified email instead:

```ts
const invitation =
  currentWorkspace && email
    ? await this.findInvitationForSignInUp({
        currentWorkspace,
        email,   // ← matched against appToken.context.email
      })
    : undefined;
```

So the strategy plumbing is write-only and confusing for readers.

Removed from:
-
[`SocialSSOState`](packages/twenty-server/src/engine/core-modules/auth/types/social-sso-state.type.ts)
- `GoogleRequest['user']` and `MicrosoftRequest['user']`
- The `state` JSON in both strategies' `authenticate()`
- The user object in both strategies' `validate()`

No frontend change needed — `useAuth.buildRedirectUrl` still sets the
`inviteToken` query param when a personal invite token is present (used
by other paths), and nothing on the SSO server side was reading it.

The token-based invitation lookup is preserved for the password signup
flow via `auth.resolver.signUp` → `findInvitationForSignInUp({
currentWorkspace, workspacePersonalInviteToken })`. Unrelated,
untouched.

## Test plan

- [x] `npx jest engine/core-modules/auth` (twenty-server) — 26 suites /
178 tests pass.
- [x] `tsgo -p tsconfig.json --noEmit` — no new errors on the touched
files (pre-existing `IS_REST_METADATA_API_NEW_FORMAT_DIRECT` errors on
main are unrelated).
- [x] `oxlint` + `prettier --check` on touched files — clean.
- [ ] Manual smoke: Google sign-in still works (workspace selection /
verify flow unaffected since `workspaceInviteHash`, `workspaceId`,
`action`, `locale`, `billingCheckoutSessionState`, `returnToPath` still
flow correctly).
2026-05-14 06:15:53 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8edc76d3d0 chore(deps): bump react-dropzone from 14.2.3 to 14.4.1 (#20560)
Bumps [react-dropzone](https://github.com/react-dropzone/react-dropzone)
from 14.2.3 to 14.4.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/react-dropzone/react-dropzone/releases">react-dropzone's
releases</a>.</em></p>
<blockquote>
<h2>v14.4.1</h2>
<h2><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.4.0...v14.4.1">14.4.1</a>
(2026-02-10)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>prevent tarball from being included in published package (<a
href="https://github.com/react-dropzone/react-dropzone/commit/7919a235412ee5a224e76b9e9bef25fdb5e8ff0b">7919a23</a>)</li>
</ul>
<h2>v14.4.0</h2>
<h1><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.3.8...v14.4.0">14.4.0</a>
(2026-01-29)</h1>
<h3>Bug Fixes</h3>
<ul>
<li>accept files with empty type during drag events (<a
href="https://github.com/react-dropzone/react-dropzone/commit/eaa8ba54963480afbba50415b1dd792514fefac1">eaa8ba5</a>)</li>
<li>correct dragLeave filter logic and add dragend test (<a
href="https://github.com/react-dropzone/react-dropzone/commit/273aff4a151aba05ddd473cbc49dc3db59132f2f">273aff4</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>add isDragGlobal state for document-level drag detection (<a
href="https://github.com/react-dropzone/react-dropzone/commit/f0874b0ad8e94dbf662b16bc82d0aa2b082ec8ee">f0874b0</a>)</li>
</ul>
<h2>v14.3.8</h2>
<h2><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.3.7...v14.3.8">14.3.8</a>
(2025-02-24)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>event_type:</strong> 🎨 Update drop event type to include
FileSystemFileHandle (<a
href="https://github.com/react-dropzone/react-dropzone/commit/d6911c991e077151e302b599b92269432ab0472b">d6911c9</a>)</li>
</ul>
<h2>v14.3.7</h2>
<h2><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.3.6...v14.3.7">14.3.7</a>
(2025-02-24)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>make ESM build compatible with native Node.js (<a
href="https://github.com/react-dropzone/react-dropzone/commit/201687900724b45ec98d26cde3626a1c6687c9e1">2016879</a>)</li>
</ul>
<h2>v14.3.6</h2>
<h2><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.3.5...v14.3.6">14.3.6</a>
(2025-02-23)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>types:</strong> fix React 19 incompatible JSX type import
(<a
href="https://github.com/react-dropzone/react-dropzone/commit/356d9d5cb604c47e393f332f3dfe0e8d12c58d95">356d9d5</a>)</li>
</ul>
<h2>v14.3.5</h2>
<h2><a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.3.4...v14.3.5">14.3.5</a>
(2024-11-04)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/9247d71a4dd6d9f36f177fee31c90a327a34d297"><code>9247d71</code></a>
chore: add *.tgz to .gitignore</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/7919a235412ee5a224e76b9e9bef25fdb5e8ff0b"><code>7919a23</code></a>
fix: prevent tarball from being included in published package</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/e2ae36d0c910e64ea7e269e741f26c909f758000"><code>e2ae36d</code></a>
build: remove <code>@​semantic-release/git</code></li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/84ff2d6671f95c925cb3e5c4b785fabd1a13c9fc"><code>84ff2d6</code></a>
build: update deps for semantic-release and node version</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/65095ea5c15b34724793ad38248bf10cd3fef147"><code>65095ea</code></a>
build: add --ignore-engines flag to test workflow for Node 18
compatibility</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/d70ea9632b70770b8d5b68d214f9532842120005"><code>d70ea96</code></a>
build: enable passwordless publishing with NPM trusted publishers</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/2c2c56ee46aefaaa3e81778d822dca4eb2e3c7f7"><code>2c2c56e</code></a>
build: add full git history fetch and build step to release
workflow</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/a9e97413c8a7684d5495ba4c869a8c629e210c59"><code>a9e9741</code></a>
build: add semantic-release configuration with trusted publishing
support</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/1455f4cc0a3c389a056532027017e92874d41fba"><code>1455f4c</code></a>
build: Update checkout action to version 4 in release workflow</li>
<li><a
href="https://github.com/react-dropzone/react-dropzone/commit/f4e67064ebc9310ac0e6e04d6f04cc7c4b4da057"><code>f4e6706</code></a>
build: Update Node.js setup action to version 4</li>
<li>Additional commits viewable in <a
href="https://github.com/react-dropzone/react-dropzone/compare/v14.2.3...v14.4.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for react-dropzone since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-dropzone&package-manager=npm_and_yarn&previous-version=14.2.3&new-version=14.4.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 06:13:10 +00:00
Charles BochetandGitHub 7fa136f305 feat(twenty-server): migrate remaining at-rest encryption sites to versioned envelope (#20550)
## Summary

Second PR in the encryption key rotation series. The previous PR
(#20528) introduced `ENCRYPTION_KEY` + the versioned
`enc:v2:<keyId>:<base64>` envelope inside `SecretEncryptionService` and
migrated `ConnectedAccountTokenEncryptionService` as the first consumer.
This PR routes every remaining at-rest encryption site through the
versioned envelope so that `ENCRYPTION_KEY` (and the future
`FALLBACK_ENCRYPTION_KEY`) actually covers them. The legacy unprefixed
CTR ciphertext remains readable as a fallback during the rollout window
— every migrated read site uses `decryptVersioned`, which transparently
delegates to the legacy CTR decrypt when it sees an unprefixed payload.

### Service migrations
- **`ApplicationVariableEntityService` (#8)** — workspace-scoped. HKDF
info is bound to each row's `workspaceId`. A new
`decryptAndMaskVersioned` helper lands on `SecretEncryptionService` for
the resolver display path.
- **`ApplicationRegistrationVariableService` (#7)** + consumers —
**instance-scoped**. Registration variables are server-level config
readable by every workspace that installs the application, so HKDF info
is `instance`. Updated consumers:
  - `LogicFunctionExecutorService.buildServerVariableEnvMap`
  - `ConnectionProviderService.getClientCredentials`
- **`LogicFunctionExecutorService.buildEnvVar` (#9)** —
workspace-scoped. Each variable's `workspaceId` is threaded into
`decryptVersioned`, so per-workspace HKDF contexts are honoured at
execution time.
- **`UpdateApplicationVariableActionHandlerService`**
(workspace-migration runner) — threads `workspaceId` through the
secret/non-secret toggle.
- **`JwtKeyManagerService` (#3)** — instance-scoped. Signing keys are
shared across the JWKS.
- **`ConfigStorageService` (#6)** — instance-scoped sensitive STRING
config variables.

### Slow instance commands (2.5.0)

Each migrated site has a paired backfill that re-encrypts existing rows
into the v2 envelope before the column is constrained:

| timestamp | command | scope | CHECK constraint |
|---|---|---|---|
| `1798000005000` | encrypt-application-variable | workspaceId |
`"isSecret" = false OR value = '' OR value LIKE 'enc:v2:%'` |
| `1798000006000` | encrypt-application-registration-variable | instance
| `"encryptedValue" = '' OR value LIKE 'enc:v2:%'` |
| `1798000007000` | encrypt-signing-key-private-keys | instance |
`"privateKey" IS NULL OR value LIKE 'enc:v2:%'` |
| `1798000008000` | encrypt-sensitive-config-storage | instance | _none_
— heterogeneous jsonb column |

All backfills are idempotent (the SELECT filter skips rows already in v2
form) and run before their respective `up()` adds the CHECK constraint.
Every `down()` deliberately stops at dropping the CHECK constraint —
they intentionally do not re-introduce plaintext on rollback.

### Tests

- Unit specs for each new slow command cover the v2 upgrade path, the
idempotency invariant, and the instance vs workspace HKDF scope.
- New `JwtKeyManagerService` spec asserts
`decryptVersioned`/`encryptVersioned` are called without `workspaceId`
(instance scope).
- Updated existing specs for `ApplicationVariableEntityService`,
`ConfigStorageService`, and `buildEnvVar` to assert the versioned API
and the workspace HKDF context plumbing.
- New `SecretEncryptionService.decryptAndMaskVersioned` cases in the
service spec.
- Updated the `applicationRegistrationVariable` integration spec to
assert the column now stores `enc:v2:<keyId>:<base64>` instead of raw
legacy CTR.

### Out of scope (future PRs)
- `PostgresCredentialsService` — bespoke
`jwtWrapperService.generateAppSecret`–derived key +
`encryptText`/`decryptText` from `auth.util.ts`; deserves its own
migration.
- `SimpleSecretEncryptionUtil` (TOTP) — entirely different `aes-256-cbc`
`iv:enc` format; deserves its own migration.

## Test plan

- [x] `npx nx typecheck twenty-server`
- [x] `npx nx lint:diff-with-main twenty-server` (oxlint + prettier)
- [x] Local jest run for `secret-encryption | connected-account-token |
application-variable | application-registration-variable | build-env-var
| jwt-key-manager | config-storage | encrypt-application-variable |
encrypt-application-registration-variable | encrypt-signing-key |
encrypt-sensitive-config-storage` — 17 suites, 106 tests pass.
- [x] Local jest run for `upgrade | instance-command` — 12 suites, 86
tests pass.
- [ ] CI green
- [ ] Manual review of CHECK constraint shapes by a server reviewer
(each one matches `enc:v2:%` rather than `enc:v_:%` since none of the
migrated columns can legitimately hold `enc:v1:` ciphertext).
2026-05-14 06:10:52 +00:00
Charles BochetandGitHub 04eb913551 chore(page-layout): remove IS_RECORD_PAGE_LAYOUT_* feature flags (#20556)
## Summary

- Both \`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED\` and
\`IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED\` are force-enabled on
every existing workspace by the 1.23.0 upgrade command
\`BackfillRecordPageLayoutsCommand\` and seeded enabled for new
workspaces via \`DEFAULT_FEATURE_FLAGS\` +
\`seed-feature-flags.util.ts\`. They are no longer load-bearing.
- Unwrap all \`if (flag) { … }\` conditionals to their enabled branch on
both server and front.
- Delete legacy fallback files that only the disabled branch reached:
\`PageLayoutRelationWidgetsSyncEffect\`,
\`usePageLayoutWithRelationWidgets\`,
\`reInjectDynamicRelationWidgetsFromDraft\`,
\`injectRelationWidgetsIntoLayout\`, \`isDynamicRelationWidget\` (and
their tests).
- Strip the two \`enableFeatureFlags\` calls from the 1.23 upgrade
command — the page-layout backfill data logic itself is kept intact
since old workspaces upgrading from < 1.23 still need it.
- No DB cleanup migration: stale \`featureFlag\` rows are left in place,
matching the precedent set by #20531 and #20460.

Net diff: 37 files, +106 / -1727.

## Test plan

- [x] \`npx nx typecheck twenty-shared twenty-server twenty-front\` —
all pass
- [x] \`npx nx lint:diff-with-main twenty-server twenty-front\` — all
pass
- [x] \`cd packages/twenty-front && npx jest page-layout\` — 1240 tests,
all pass
- [x] \`cd packages/twenty-server && npx jest
workspace-entity-manager.spec\` — pass
- [ ] Manual smoke: open a record page, verify tabs render and \"Edit
Layout\" command-menu action is available
- [ ] Manual smoke: Settings → Data model → object → Layout tab is
visible (and hidden for remote / Dashboard objects)
- [ ] Manual smoke: edit a tab title, save, reload — confirm persistence
2026-05-14 08:12:00 +02:00
Charles BochetandGitHub 6dd1e8a471 feat(upgrade): expose twenty_upgrade_workspaces_up_to_date_total (#20555)
## Summary
Adds a fourth gauge alongside the existing
`twenty_upgrade_workspaces_behind_total` /
`twenty_upgrade_workspaces_failed_total` so dashboards can show how many
workspaces are currently healthy, not just the ones that need attention.

- New gauge: `twenty_upgrade_workspaces_up_to_date_total`
- New count is computed during
`UpgradeStatusService.refreshInstanceAndAllWorkspacesStatus` (cheap — we
already iterate over every workspace), persisted in the existing
`UpgradeStatusCacheService` so the cache-hit path stays a single round
trip, and surfaced via `InstanceAndAllWorkspacesUpgradeStatusDTO` for
the admin panel.

## Files
-
`packages/twenty-server/src/engine/core-modules/upgrade/upgrade-gauge.service.ts`
— register the new ObservableGauge
-
`packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status.service.ts`
— count UP_TO_DATE workspaces during refresh, propagate through cached
path
-
`packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status-cache.service.ts`
— persist `upToDateWorkspaceCount` next to behind/failed sets
-
`packages/twenty-server/src/engine/core-modules/upgrade/dtos/instance-and-all-workspaces-upgrade-status.dto.ts`
— `Int` field on the admin DTO
- Tests: extended `upgrade-status.service.spec.ts` (14/14 green) —
cached and refresh paths both assert on `upToDateWorkspaceCount`

## Follow-up
A companion `twenty-infra` PR adds the new tile + line on the
upgrade-status Grafana dashboard.
2026-05-13 23:45:56 +02:00
martmullandGitHub d81756f2e8 Add default value to apiKey for authentication method (#20552)
- fix authentication method default value when docker instance to apiKey
2026-05-13 21:17:44 +00:00
Félix MalfaitandGitHub 49b9660420 fix(auth): preserve returnToPath across Google/Microsoft SSO redirects (#20537)
## Summary

Fixes the consent-modal-not-reopening half of
[#20535](https://github.com/twentyhq/twenty/issues/20535): when a
signed-out user opens an OAuth `/authorize?...` URL (e.g. ChatGPT
connecting to `api.twenty.com/mcp`) and signs in with **Google or
Microsoft**, the original `/authorize` request was lost and the consent
screen never reopened.

### Root cause

`PageChangeEffect` already saves the deep link as `returnToPath` (Jotai
atom) before navigating to `/welcome`. That atom is in-memory: it
survives SPA navigation, and the cross-subdomain workspace hop is
handled by `useBuildSearchParamsFromUrlSyncedStates` round-tripping the
value through the URL.

But the social-SSO path leaves `app.twenty.com` entirely —
`app.twenty.com/welcome` → `api.twenty.com/auth/google` → Google →
`api.twenty.com/auth/google/redirect` → frontend — so the atom is wiped.
None of the existing code paths plumbed `returnToPath` through that hop:
- `useAuth.buildRedirectUrl` packed `workspaceInviteHash`/`action`/etc.
but not `returnToPath`.
- `SocialSSOState` / the Google + Microsoft strategies didn't carry it
through the OAuth `state` blob.
- `signInUpWithSocialSSO` + `computeRedirectURI` didn't re-emit it on
the redirect back to the frontend.

The email path worked because all transitions stay on the default
frontend domain, so the atom survives until `SignInUpGlobalScopeForm`
bakes it into the workspace URL.

### What changed

Plumb `returnToPath` through the SSO state the same way
`workspaceInviteHash` and `action` already flow:

- **Frontend** (`useAuth.buildRedirectUrl`): read `returnToPath` from
the Jotai store and append it to `/auth/google` / `/auth/microsoft` when
set and structurally valid.
- **Server types** (`SocialSSOState`, `GoogleRequest['user']`,
`MicrosoftRequest['user']`): add optional `returnToPath`.
- **Strategies** (`google.auth.strategy.ts`,
`microsoft.auth.strategy.ts`): include `returnToPath:
req.query.returnToPath` in the JSON `state` and read it back in
`validate`.
- **auth.service.ts** (`signInUpWithSocialSSO`, `computeRedirectURI`):
forward `returnToPath` on both branches — the multi-workspace redirect
to `AppPath.SignInUp?tokenPair=...` and the single-workspace redirect to
`<workspace>/verify?loginToken=...`. Validated via a new
`isValidReturnToPath` helper so a tampered query value can't become an
open-redirect vector.

After the round-trip, `useInitializeQueryParamState` rehydrates the atom
from the URL and `usePageChangeEffectNavigateLocation` resolves it as
the post-auth destination — same mechanism the email path already relied
on.

Out of scope: the OAuth `resource` parameter handling tracked in
[#20296](https://github.com/twentyhq/twenty/issues/20296) is independent
and not addressed here.

## Test plan

- [x] `npx jest src/engine/core-modules/auth` (twenty-server) — 27
suites / 183 tests pass, including new
`is-valid-return-to-path.util.spec.ts`.
- [x] `npx jest src/modules/auth` (twenty-front) — 13 suites / 52 tests
pass, including two new cases in `useAuth.test.tsx` covering the happy
path and the protocol-relative open-redirect guard.
- [x] `npx nx typecheck twenty-server` / `twenty-front` — clean.
- [x] `npx oxlint` + `prettier --check` on touched files — clean.
- [ ] Manual: signed-out user opens
`https://app.twenty.com/authorize?client_id=...` → Continue with Google
→ completes Google → selects workspace → consent screen renders.
- [ ] Manual: same flow, single workspace — lands on consent screen
directly after Verify.
- [ ] Manual: email path still works (regression).
- [ ] Manual: tamper `returnToPath=//evil.com` on the `/auth/google` URL
→ server validation rejects, user lands at default home, not at
`evil.com`.

E2E note: existing `return-to-path.spec.ts` already covers deep links
with query params through the email path. A mock OAuth provider would be
needed to cover the SSO path end-to-end; unit coverage stands in for
now.
2026-05-13 21:54:54 +02:00
Charles BochetandGitHub 2a9fef2341 feat(upgrade): emit structured logfmt logs for upgrade flow (#20539)
## Summary

Adds a small helper that lets every log line in the upgrade flow stay
human-readable while emitting a structured tail that Loki / the
upgrade-status Grafana dashboard can filter on.

Output shape per `logger.log()` call:

```
<humanMessage as-is, may span multiple lines>
[upgrade] event=<event> key=value …    ← always single line
```

Same call produces **one** structured Loki event regardless of how
chatty the human-readable part gets — the dashboard's `|= "[upgrade]"`
filter only matches the trailing line.

## Helper API

```ts
formatUpgradeLog({
  humanMessage: string,                  // free-form, multi-line OK, for engineers scrolling raw pod logs
  event: string,                         // required anchor for Loki filtering / dashboards
  logFields?: Record<string,             // short structured key=value tail
    string | number | boolean | null | undefined
  >,
});
```

- `humanMessage` is preserved as-is. A thrown `new Error('line one\nline
two')` surfacing through `humanMessage` stays human-readable across
multiple lines.
- `logFields` values are logfmt-escaped: whitespace / `=` / `"` get
quoted, embedded `\` / `"` / `\n` / `\r` / `\t` are escaped, `null` /
`undefined` emit literally (`key=null`, `key=undefined`) instead of
being silently dropped — caught via `isDefined` from
`twenty-shared/utils`.
- `event` itself runs through the same escape so an event name with
whitespace or `=` can't break parsing.

## Example output

```
Initialized upgrade sequence: 8 step(s)
[upgrade] event=sequence.initialized stepCount=8 dryRun=false

Upgrading workspace abc-123 1/10
[upgrade] event=workspace.start workspaceId=abc-123 index=1 total=10 dryRun=false

Upgrade for workspace abc-123 completed.
[upgrade] event=workspace.success workspaceId=abc-123 executedByVersion=1.4.0 dryRun=false

Upgrade summary: 42 workspace(s) succeeded, 1 workspace(s) failed
[upgrade] event=summary totalSuccesses=42 totalFailures=1 dryRun=false

Upgrade failed: Workspace migration runner failed:
  - Option id is required
  - Option id is invalid
[upgrade] event=aborted totalSuccesses=41 totalFailures=2 dryRun=false
```

Loki query for the dashboard: `{namespace="twenty"} |= "[upgrade]" |
logfmt event, workspaceId, command, executedByVersion`

## Scope

Only the **upgrade-specific** call sites carry the tag:

- `upgrade.command.ts` — `sequence.initialized`, `sequence.step`
(verbose), `summary`, `aborted`
- `upgrade-sequence-runner.service.ts` — `sequence.stopped`,
`sequence.aborted`
- `workspace-command-runner.service.ts` — `workspace.start`,
`workspace.success`, `cache.invalidate.failed`

`instance-command-runner.service.ts` is intentionally **not** tagged —
`runFastInstanceCommand` / `runSlowInstanceCommand` are also invoked
from `RunInstanceCommandsCommand` (DB init / `run-instance-commands`),
so an `[upgrade]` tag there would mislead at init time. Those lines stay
plain-text; stacks still flow on their own via NestJS
`logger.error(message, error.stack)`.

`chalk` is dropped from `upgrade.command.ts` — ANSI escapes break log
parsers and chalk is a no-op without a TTY anyway.

## Tests

9 inline-snapshot tests in `format-upgrade-log.util.spec.ts` surface the
actual output of every interesting shape (summary call site, multi-line
humanMessage, quoted / escaped / control-character logField values,
null/undefined fields, event name escaping). Snapshots double as
documentation of what a real upgrade log line looks like.

## Test plan

- [x] Unit tests green (`jest format-upgrade-log`)
- [x] oxlint + prettier clean
- [x] tsgo typecheck clean on the upgrade module
- [x] CI green
- [ ] Smoke test on staging: run `upgrade` command, confirm `[upgrade]`
structured lines surface in Loki and `| logfmt` extracts fields
2026-05-13 19:31:22 +00:00
0dc8426727 i18n - translations (#20554)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-13 21:33:01 +02:00
Félix MalfaitGitHubClaude Opus 4.7claude[bot] <41898282+claude[bot]@users.noreply.github.com>
e16977f97b [breaking: deploy server before front] feat(view-sort): pick sort sub-field inline on the chip (#20445)
## Summary

Lets users choose which sub-field of a composite column to sort by —
directly from the sort chip — by clicking the sub-field label and
picking from a dropdown. Persists per view via a new nullable
\`subFieldName\` column on \`ViewSort\`.

Replaces #20438, which proposed a field-settings (admin) configuration
for the same problem. The chip-level approach is more discoverable (the
option lives where the user is looking) and per-view, so different views
on the same object can sort by different sub-fields.

### What changes for users

- **FullName columns**: previously sorted by \`firstName\` and
\`lastName\` together as a stable dual-key sort. Now the user can pick
which sub-field is primary (the other is the tie-breaker). Default
remains \`firstName\` primary, \`lastName\` tie-breaker.
- **Address columns**: previously not sortable at all (not in
\`SORTABLE_FIELD_METADATA_TYPES\`). Now sortable, with a chip dropdown
listing each enabled sub-field. Default is \`addressCity\` if enabled,
else the first enabled sub-field. Disabling a sub-field at the
field-metadata level (existing setting) removes it from the dropdown.
- **Other composite types** (Currency, Phones, Emails, Links, Actor) and
scalar fields keep their existing single-key sort behavior.

### UX

```
┌─────────────────────────┐    ┌─────────────────────────┐
│ ↑ Name · Last name  ✕ │    │ ↑ Address · City  ✕ │
└────────┬────────────────┘    └────────┬────────────────┘
         ▼ (click sub-field)            ▼
   ┌────────────┐                ┌────────────┐
   │ First name │                │ Address 1  │
   │ Last name ✓│                │ Address 2  │
   └────────────┘                │ City      ✓│
                                 │ State      │
                                 │ Postcode   │
                                 │ Country    │
                                 └────────────┘
```

The chip body still toggles direction on click — the \`Dropdown\`'s
internal wrapper calls \`stopPropagation\` so the sub-field click
doesn't bubble to the chip's onClick.

## What changed

**Backend:**
- \`ViewSortEntity\` — new nullable \`subFieldName: varchar\` column
- \`ViewSortDTO\`, \`CreateViewSortInput\`,
\`UpdateViewSortInputUpdates\` — new \`@Field(() => String, { nullable:
true })\`
- \`FLAT_VIEW_SORT_EDITABLE_PROPERTIES\` — \`'subFieldName'\` added so
the property flows through the update merge path
- \`ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME.viewSort\` —
new \`subFieldName\` entry with \`toCompare: true\` so cache diffs
notice it
- \`fromCreateViewSortInputToFlatViewSortToCreate\` — threads
\`subFieldName\` through
- Instance command migration (\`add-sub-field-name-to-view-sort\`) —
single \`ALTER TABLE core.viewSort ADD subFieldName varchar\` / \`DROP\`

**Frontend:**
- \`RecordSort\` and \`ViewSort\` types — \`subFieldName?: string |
null\`
- \`VIEW_SORT_FRAGMENT\` — adds \`subFieldName\` so the field
round-trips
- \`mapRecordSortToViewSort\` + \`areViewSortsEqual\` — carry the new
field through, include it in the diff so the usual
\`useSaveRecordSortsToViewSorts\` create/update flow fires when it
changes
- \`useSaveRecordSortsToViewSorts\` — passes \`subFieldName\` in both
\`CreateViewSortInput\` and \`UpdateViewSortInputUpdates\`
- \`getOrderByForFieldMetadataType(field, direction, subFieldName?)\` —
new optional third arg. \`turnSortsIntoOrderBy\` threads
\`sort.subFieldName\` into it.
- \`Address\` added to \`SORTABLE_FIELD_METADATA_TYPES\`
- New helpers: \`getEnabledAddressSubFields\` (filters by the field's
\`subFields\` setting, falls back to the 6 default visible address
sub-fields), \`getDefaultSortSubFieldForAddress\`,
\`getDefaultSortSubFieldForFullName\`
- New shared types/constants: \`AllowedFullNameSubField\`,
\`ALLOWED_FULL_NAME_SUBFIELDS\`, \`DEFAULT_VISIBLE_ADDRESS_SUBFIELDS\`
- \`SortOrFilterChip\` — new \`labelSubField?: ReactNode\` slot; renders
as \` · {sub-field}\` with subdued weight after the main label
- \`EditableSortChip\` — builds options from field metadata
(\`ALLOWED_FULL_NAME_SUBFIELDS\` for FullName,
\`getEnabledAddressSubFields\` for Address), uses i18n-wrapped labels,
persists picks via \`upsertRecordSort\`

## Test plan

- [x] \`npx nx typecheck\` passes for twenty-shared, twenty-front,
twenty-server
- [x] \`oxlint --type-aware\` on all 19 frontend + 9 server changed
files: 0 errors
- [x] \`prettier --check\`: clean
- [x] 16 unit tests pass — \`getOrderByForFieldMetadataType\` covers the
new \`subFieldName\` override branch for FULL_NAME and ADDRESS;
\`getDefaultSortSubFieldForAddress\` covers the city/first-enabled
fallback path; \`getDefaultSortSubFieldForFullName\` exercises its
constant
- [ ] Manual: sort a People view by Full Name → click the chip's
sub-field label → switch between First name and Last name → reload page
→ choice is preserved
- [ ] Manual: sort a Company view by Address → confirm dropdown lists
only enabled sub-fields → disable Address \`addressCity\` in field
settings → confirm dropdown options update and runtime falls back to the
first enabled sub-field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-05-13 21:25:15 +02:00
8bc21ec434 i18n - docs translations (#20553)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-05-13 21:00:52 +02:00
915ce37d20 i18n - translations (#20551)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-05-13 19:32:03 +02:00
martmullandGitHub 0cc2194399 Simplify create-twenty-app command (#20512)
## Simplify `create-twenty-app` for zero-interaction use

Makes `npx create-twenty-app@latest my-app` a fully non-interactive,
single-command experience suitable for automated environments (Codex,
Claude plugins).

  ### Changes

- **Remove all interactive prompts** — app name, display name,
description, and scaffold confirmation are now derived from CLI args
with sensible defaults. `inquirer` dependency removed
   entirely.
- **Replace OAuth with API key auth** — use the seeded dev API key
(`DEV_API_KEY`) to authenticate against the Docker instance as
`[email protected]`, eliminating the browser-based OAuth
  flow.
- **Docker-first with early validation** — check Docker is installed
before scaffolding; if missing, print the install URL and exit. Detect
alternative runtimes (Podman, nerdctl).
- **Parallel image pull** — `docker pull` runs in the background during
scaffold + dependency install, saving 10-30s on typical runs.
- **Always pull latest image** — ensures the dev server is up-to-date on
every run.
- **Stop detecting port 3000** — only check port 2020 (Docker instance).
- **Update CLI flags** — remove `--skip-local-instance` and `--yes`; add
`--skip-docker`.
- **Update CI workflows and docs** — align e2e workflows, package
README, and template README/cd.yml with the new flow.
2026-05-13 16:44:27 +00:00
70a3b25680 i18n - docs translations (#20549)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-05-13 19:07:39 +02:00
6a9509ec01 i18n - translations (#20548)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-13 19:00:36 +02:00
EtienneandGitHub fcd2d586ee chore(billing) - remove feature flag (#20531)
- remove feature flag
- remove old enforce cap usage logic
2026-05-13 18:52:29 +02:00
EtienneandGitHub 695a374efd fix - nav drawer expansion (#20545)
[PR](https://github.com/twentyhq/twenty/pull/20505), I merge, does not
fix the whole issue

Closes https://github.com/twentyhq/twenty/issues/20502
2026-05-13 16:30:37 +00:00
martmullandGitHub dea1f89904 Inject none secret env variables into front components (#20511)
## Summary
- Inject non-secret application variables (`isSecret: false`) into front
component `process.env` via the existing Web Worker `setWorkerEnv`
mechanism
- Filter secret variables server-side in the resolver so they never
reach the browser
- Set application variables before system variables (`TWENTY_API_URL`,
`TWENTY_APP_ACCESS_TOKEN`) to prevent override
- Wire up environment variable keys in the logic function code editor
for TypeScript autocomplete

  ## Test plan
  - [x] Unit tests for `buildNonSecretEnvVar` (6 passing)
  - [x] Typecheck passes for `twenty-front` and `twenty-server`
- [x] Install an app with both `isSecret: false` and `isSecret: true`
variables, open a front component, verify only non-secret vars appear in
`process.env`
- [x] Open a logic function editor, verify autocomplete suggests
declared variable keys
2026-05-13 16:27:56 +00:00
59b993bdb3 i18n - translations (#20547)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-05-13 18:32:28 +02:00
Charles BochetandGitHub e0b4c9918b feat(twenty-server): introduce ENCRYPTION_KEY env var with versioned envelope (#20528)
## Summary

- Adds `ENCRYPTION_KEY` (primary) and `FALLBACK_ENCRYPTION_KEY`
(decrypt-only fallback for rotation) env vars to twenty-server, with
backward-compatible fallback to `APP_SECRET` when `ENCRYPTION_KEY` is
unset.
- Introduces a versioned ciphertext envelope `enc:v2:<keyId>:<base64>`
using AES-256-GCM with HKDF-SHA256 derived per-context keys. The 8-hex
`keyId` fingerprint lets every row identify which physical key encrypted
it, so rotation routes directly to primary or fallback without trial
decryption; GCM's auth tag gives true integrity (legacy CTR has none).
- Migrates `ConnectedAccountTokenEncryptionService` to the new envelope
and plumbs `workspaceId` through every caller, so per-workspace HKDF
context binds each row to its tenant.

The remaining encryption sites (`jwt-key-manager`, `config-storage`,
`postgres-credentials`, `application-variable`, TOTP) stay on the legacy
unprefixed CTR path and will be migrated in follow-up PRs. The
operator-facing rotation runbook is out of scope here.

### Format details

`enc:v{N}:{keyId}:{base64}` — `N=2` is the only version produced by new
writes (`v1` exists for backward-compatible decryption of existing
connected-account rows). `keyId =
sha256(rawKey).slice(0,4).toString('hex')`. The CHECK constraint on
`core.connectedAccount.{accessToken,refreshToken}` is relaxed from `LIKE
'enc:v1:%'` to `LIKE 'enc:v_:%'` so both versions pass.

### Key resolution

| `ENCRYPTION_KEY` | `FALLBACK_ENCRYPTION_KEY` | `APP_SECRET` | Encrypt
with | Decrypt try order |
|---|---|---|---|---|
| set | set | (any) | `ENCRYPTION_KEY` | match `keyId` → primary →
fallback |
| set | unset | (any) | `ENCRYPTION_KEY` | match `keyId` → primary |
| unset | set | set | `APP_SECRET` | match `keyId` → `APP_SECRET` →
fallback |
| unset | unset | set | `APP_SECRET` | match `keyId` → `APP_SECRET` |
| unset | unset | unset | startup error | n/a |

## Test plan

- [x] `npx nx typecheck twenty-server` — clean
- [x] `npx jest
'secret-encryption|connected-account-token-encryption|connected-account-refresh-tokens|encrypt-connected-account-tokens|connection-provider-oauth-flow'`
— 87 tests pass
- [x] New `secret-encryption.service.versioned.spec.ts` covers: key
resolution table (no-key error, APP_SECRET fallback, ENCRYPTION_KEY
precedence), v2 round-trip with/without workspaceId, GCM tamper
rejection, workspaceId-mismatch rejection, keyId-based primary→fallback
routing, missing-key error names the fingerprint, v1 legacy decryption,
no-prefix legacy decryption, malformed envelope rejection.
- [x] Updated `connected-account-token-encryption.service.spec.ts`
covers workspaceId binding and HKDF context isolation.
- [x] Updated slow instance command spec verifies workspaceId is
threaded through encryption and the relaxed `enc:v_:%` LIKE pattern
matches both v1 and v2.
- [ ] Manual E2E: connect a Gmail account on a freshly deployed instance
with `APP_SECRET` only → confirm `core.connectedAccount.accessToken` is
`enc:v2:<keyId>:<base64>`.
- [ ] Manual E2E: rotate — set `ENCRYPTION_KEY=<new>` and
`FALLBACK_ENCRYPTION_KEY=<old APP_SECRET>`, restart, confirm
pre-rotation rows still decrypt and new rows carry the new `keyId`.
- [ ] Manual E2E: missing key — set `ENCRYPTION_KEY=<new>` without the
fallback, confirm decrypt error names the old `keyId` so the operator
can identify the missing key.
2026-05-13 16:15:54 +00:00
aec2e01662 fix(server): handle ImapFlow socket errors instead of crashing the process (#20510)
## Summary

`ImapFlow` is an `EventEmitter`; per Node.js semantics, an emitted
`'error'` event with no listener becomes an uncaught exception that
exits the process. Both ImapFlow construction sites in `twenty-server`
(`ImapClientProvider` used by all messaging flows, and
`testImapConnection` in the connection-wizard validator) currently build
the client without attaching a permanent `'error'` listener, so a
transient socket condition (idle timeout, network blip, server-side
disconnect) crashes `twenty-server` and triggers a container restart
with a ~1 min HTTP 502 window for end users.

This patch attaches an `'error'` listener at each call site that logs
the error and lets `imapflow`'s internal reconnect handle recovery. Same
shape / same precedent as #20143 (Redis session-store client) which
fixed #20144.

Closes #20509.

## What changed

-
`packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts`:
`ImapClientProvider.createConnection` now attaches `client.on('error',
...)` between construction and `connect()`.
-
`packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts`:
`testImapConnection` does the same on its short-lived test client.

Both listeners log via the existing `Logger` instance (matching the
resolver-level logging already in `ImapClientProvider.getClient`) and
surface `error.stack` so transient socket conditions are observable but
no longer fatal.

## Crash this fixes (real production stack)

```
node:events:487
      throw er; // Unhandled 'error' event
      ^

Error: Socket timeout
    at TLSSocket.<anonymous> (/app/node_modules/imapflow/lib/imap-flow.js:795:29)
    at TLSSocket.emit (node:events:509:28)
    at Socket._onTimeout (node:net:610:8)
    ...
Emitted 'error' event on ImapFlow instance at:
    at ImapFlow.emitError (/app/node_modules/imapflow/lib/imap-flow.js:397:14)
  code: 'ETIMEOUT',
```

End-user impact: server process exits cleanly (code 0), Docker / k8s
restarts it; the DB, worker, redis, and caddy containers are unaffected
— only the API server dies, taking the GraphQL/REST surface offline for
a ~1 min health-check warmup.

## Test plan

- [x] `npx nx typecheck twenty-server` (planned — relying on CI for
verification)
- [x] `npx nx lint:diff-with-main twenty-server` (planned — relying on
CI for verification)
- [x] Manually reproduced the crash on `v2.2` by hitting an
IMAP/SMTP/CalDAV outbound flow with Gmail; with the patch applied
locally to the running container (verified the listener fires and logs
without process exit), the server stays up across the same trigger
sequence.
- [ ] Unit-level coverage: behavior is "listener exists, doesn't throw"
— not easily covered without a contrived socket-mock test. Existing call
sites have no unit tests today; happy to add one if a reviewer prefers,
otherwise mirroring the convention from #20143 which merged without a
new test.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Co-authored-by: neo773 <[email protected]>
2026-05-13 15:13:55 +00:00
EtienneandGitHub aecfe699f4 feat(ai-chat) - Stop ai thinking if credits exhausted (#20526)
Billing is now decremented per-step, not per-turn. The onStepFinish
callback in chat-execution.service.ts calls a new
decrementAndCheckAvailableCredits method on each model step, so Redis is
debited incrementally as the agent runs rather than all at once at the
end.

Credit exhaustion stops the stream mid-run. When a step depletes the
remaining credits, a hasNoMoreAvailableCredits flag is set and passed
into the stopWhen predicate of streamText, causing the agent to halt
before starting the next step.

A new credits-exhausted event is introduced. After the stream drains and
the response is persisted, if credits ran out the job publishes a
dedicated credits-exhausted event to the frontend instead of the normal
message-persisted event.

The frontend handles this new event. useAgentChatSubscription has a new
credits-exhausted case that sets a BILLING_CREDITS_EXHAUSTED-coded error
on the atom, closes the writer, and stops the streaming state —
triggering the existing AiChatCreditsExhaustedMessage UI.
2026-05-13 15:09:08 +00:00
gusfromspaceandGitHub 068fc9363d fix(navigation): settings drawer should never appear collapsed (#20505)
## Summary

Fixes #20502

/claim #20502

The navigation drawer's collapsed state is persisted to \`localStorage\`
via \`isNavigationDrawerExpandedState\`. When a user collapses the
drawer in the main app and then opens settings via a **direct URL,
refresh, or new tab**, the settings layout renders in collapsed mode —
no \`useOpenSettingsMenu\` call is made in those paths to force
expansion.

\`StyledAnimatedContainer\` (which controls the outer drawer width) used
raw \`isNavigationDrawerExpanded\` with no settings-route override.
Inner components (\`NavigationDrawerItemsCollapsableContainer\`) already
guard with \`isExpanded = isNavigationDrawerExpanded || isSettingsPage\`
— the outer container simply needed the same treatment.

**Fix:** derive \`isExpanded = isSettingsDrawer ||
isNavigationDrawerExpanded\` using the already-in-scope
\`useIsSettingsDrawer()\` result and pass it to both
\`StyledAnimatedContainer\` and \`StyledContainer\`. 1 derived variable,
2 prop changes, no new hooks or state.

**Changed files:**
-
\`packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx\`

## Test plan

Verified via code review and CI. The fix is structurally identical to
the existing \`isSettingsPage\` guard already used in
\`NavigationDrawerItemsCollapsableContainer\` — the outer container
simply lacked the same treatment. No new hooks, no side effects, no
state mutations.

Manual UI verification (collapse → navigate to settings → confirm
expanded) was not performed against a running instance. If the
maintainers want to verify, the logic path is the same as the inner
component guard that already ships in production.

> **Note (2026-05-12):** PR #20508 was submitted after this PR with a
\`useEffect\`-based approach. That approach has already received a
review comment from a team member noting that \`useEffect\` should be a
last resort per the project's own React guidelines. This fix uses no
effects or imperative state — only a derived variable.

---

> [!NOTE]
> **AI-Assisted Contribution**
> This patch was generated by
[Mesopredator](https://github.com/GusFromSpace), an autonomous code
intelligence system.
> Static analysis located the bug, the fix was written and verified with
\`tsc --noEmit\` + \`node\` test suite, and reviewed by a human before
submission.
> Please treat this as a community contribution and request changes if
needed.
2026-05-13 14:19:25 +00:00
99a5a038bc fix(server): add Apple seed workspace as fallback for single-workspace mode (#20498)
## Issue
As per the developer docs, the local setup uses the `npx nx
database:reset twenty-server` command, which seeds 4 workspaces. This
works correctly for multi-workspace mode
(`IS_MULTIWORKSPACE_ENABLED=true`) and integration tests but causes
issues in single-workspace mode (`IS_MULTIWORKSPACE_ENABLED=false`) or
when switching from multi-workspace mode back to single-workspace mode.

Also, the default mode is single-workspace but 4 workspaces are already
seeded in the database. As a result,
`WorkspaceDomainsService.getDefaultWorkspace()` selects the newest
workspace (Empty4), which is intended only for integration testing and
contains no user data.


There is also an existing warning log mentioning fallback to the Apple
seed workspace when multiple workspaces are found in single-workspace
mode i.e `IS_MULTIWORKSPACE_ENABLED=true`, but it was never implemented.

```
 if (workspaces.length > 1) {
      Logger.warn(
        ` ${workspaces.length} workspaces found in database. In single-workspace mode, there should be only one workspace. Apple seed workspace will be used as fallback if it found.`,
      );
    }
```

Although we could replace with `"nx command-no-deps --
workspace:seed:dev --light"` in `project.json` for `database:reset`,
which will only seed one workspace but it wont resolve issue when
switching from multi-workspace mode back to single-workspace mode or for
integration test `with-db-reset`.


## Changes

- Implement fallback behavior already hinted by existing warning logs.
- Ensure the Apple seed workspace is used as the fallback in
single-workspace mode when multiple workspaces exist.

This improves:
- Local developer onboarding experience.
- Switching between multi-workspace and single-workspace development
modes.
- Consistency during local development and integration testing.

## Related PR
- #19822 
- #20464 

These PRs only resolves the issue for docker environments but not for
local development setup.

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-05-13 14:06:10 +00:00
EtienneandGitHub 3d90b3882b fix(ai-agent-node) - agent node execution error (#20534)
**Root cause:** getWorkflowRunContext(stepInfos) builds a Record<string,
unknown> from the previous steps' results. There is no workspaceId key
in it, so context.workspaceId as string silently evaluated to undefined.
That undefined was then passed all the way down to
WorkspaceCacheService.getOrRecompute, **which correctly throws** when
workspaceId is not a valid UUID.

Before : 
<img width="525" height="130" alt="Screenshot 2026-05-13 at 14 58 54"
src="https://github.com/user-attachments/assets/0549b4dc-7063-44e5-95a1-00a460a6d7f1"
/>

Introduced with billing v2 yesterday, since then, workspaceId is needed
to bill credit usage
2026-05-13 13:20:27 +00:00
Paul RastoinandGitHub 643ec121a7 [twenty-server] no-misused-promise lint (#20529)
# Introduction
Adding `no-miused-promise` lint rule to the twenty-server
In order to flag such pattern
```ts
//  Flagged — forEach doesn't await the callback
items.forEach(async (item) => {
  await process(item);
});
```

## What happened
- Refactored the code-interpreter driver to have a async onResult (
which is also expected by e2b )
- Workspace manager still dirty solution including force cast
- Basic fixes
2026-05-13 13:14:06 +00:00
27fd124c2e Dedicated REST controllers for object & field metadata (#20364)
## Summary
- Replace the dynamic `RestApiMetadataController` (which parsed
`/rest/metadata/*path` and proxied to internal GraphQL) with two
dedicated controllers: `ObjectMetadataController` and
  `FieldMetadataController`.
- Drop the GraphQL hop: reads hit Postgres directly via TypeORM
repositories; writes call the existing
`{create,update,delete}One{Object,Field}` service methods.
- Introduce a new clean response shape behind a workspace feature flag
(`IS_REST_METADATA_API_NEW_FORMAT_DIRECT`) — see grace period below.
- Update the OpenAPI spec so the REST playground reflects the (default)
legacy shape during the grace period.

  ## Why
The legacy metadata controller was over-complex: it routed every method
through a path parser, a set of GraphQL query-builder factories, an
internal GraphQL call, and a
`cleanGraphQLResponse` post-processor. Operation names from GraphQL
(`createOneObject`, `updateOneField`, …) leaked straight into REST
responses. The internal-GraphQL hop also gave us
nothing on metadata reads — pagination, filtering, and serialization all
happen against the same Postgres tables either way.

  ## Feature flag & grace period
  `IS_REST_METADATA_API_NEW_FORMAT_DIRECT` (workspace-scoped):
- **Existing workspaces:** flag absent → resolves to `false` → **legacy
response shape** (no behavior change).
- **Newly created workspaces:** flag seeded to `true` via
`DEFAULT_FEATURE_FLAGS` → **new response shape** from day one.
- **Toggle:** support-assisted (no frontend); customers contact us to
opt into the new shape early.
- **Removal:** the flag, the legacy adapter utils
(`to-legacy-{object,field}-metadata-response.util.ts`), and the
parametrized test wrapper get deleted after the grace window. New shape
becomes the only shape; OpenAPI flips to new shape; POST loses the
conditional and reverts to a declarative response.

  ## Response shapes

| Operation | Legacy (flag OFF, default for existing) | New (flag ON) |
|-----------|-----------------------------------------|---------------|
| `GET /rest/metadata/objects` | `{ data: { objects: [...] }, pageInfo,
totalCount }` | `{ data: [...], pageInfo, totalCount }` |
| `GET /rest/metadata/objects/:id` | `{ data: { object: {...} } }` | `{
... }` |
| `POST /rest/metadata/objects` | `201 { data: { createOneObject: {...}
} }` | `201 { ... }` |
| `PATCH/PUT /rest/metadata/objects/:id` | `{ data: { updateOneObject:
{...} } }` | `{ ... }` |
| `DELETE /rest/metadata/objects/:id` | `{ data: { deleteOneObject: {
... } } }` | `{ ... }` |

Same matrix for `/rest/metadata/fields`. Cursor params
(`starting_after`, `ending_before`, `limit`) and `totalCount` are
preserved across both shapes. POST returns `201` in both (old
  controller already did — the doc on main saying `200` was wrong).

  ## Implementation notes
- Reads go straight to Postgres with TypeORM cursor pagination
(`paginateByIdCursor` util, mutually-exclusive `starting_after` /
`ending_before`). No cache on this path — caching +
  filterable pagination didn't combine cleanly.
- Object endpoints inline `fields[]` via a single follow-up `WHERE
objectMetadataId IN (...)` query.
- Controllers read the flag via `FeatureFlagService.isFeatureEnabled`
and conditionally pass the result through a legacy-shape adapter util
before returning.
- Per-domain REST exception filters
(`{Object,Field}MetadataRestApiExceptionFilter`); the `exceptionCode →
httpStatus` switch is extracted to a util so it can be merged with the
  existing GraphQL handler later.
- New controllers live inside the metadata domain modules
(`metadata-modules/{object,field}-metadata/controllers/`) to match
existing precedent (view-field, view, page-layout, …).
- Removes: `RestApiMetadataController`, `RestApiMetadataService`,
`metadata/query-builder/`, `clean-graphql-response.utils.ts`.
- Integration tests are parametrized over both flag values via
`describe.each` — both shapes are asserted in CI.
- OpenAPI fixes inherited from the migration (kept as-is): documents
flat `fields: [...]` rather than the obsolete `{edges:{node:[...]}}`
wrapping; always emits `totalCount`; POST
status `201`. These match what customers actually receive on both
shapes.

Note: Next goal is to implement something similar for graphql and remove
nestjs-query dependency for those 2 entities, then generalise it.
Note2: We have the same issue with Core Rest API such as
```json
{
  "data": {
    "createCompany": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "createdAt": "2026-05-07T12:14:52.769Z",
      "updatedAt": "2026-05-07T12:14:52.769Z",
      "deletedAt": "2026-05-07T12:14:52.769Z",
   ...
```
with "createCompany" here which is odd compared to REST standards (FYI
@etiennejouan @charlesBochet)

## Before (Without feature flag)
<img width="1346" height="712" alt="Screenshot 2026-05-12 at 20 50 38"
src="https://github.com/user-attachments/assets/316ce225-1045-4aac-97a9-60fd537eb1ec"
/>
<img width="1378" height="729" alt="Screenshot 2026-05-12 at 20 52 24"
src="https://github.com/user-attachments/assets/a621ab6f-e4f8-44d5-817c-1efd25d33c30"
/>

## After (With feature flag)
<img width="1376" height="728" alt="Screenshot 2026-05-12 at 20 50 46"
src="https://github.com/user-attachments/assets/2424d9c5-e4ed-497c-8e5c-6b54d78675e4"
/>
<img width="1375" height="727" alt="Screenshot 2026-05-12 at 20 51 47"
src="https://github.com/user-attachments/assets/101d957f-38ed-45d9-ab7b-f4f4eb983397"
/>

---------

Co-authored-by: prastoin <[email protected]>
2026-05-13 12:31:16 +00:00
bbc55193f5 Fix phone unique constraints (#20261)
## Summary

Closes #20195

Fix phone field unique constraints so phone numbers are considered
unique by both `primaryPhoneNumber` and `primaryPhoneCallingCode`.

- Include `primaryPhoneCallingCode` in the shared phone composite unique
constraint metadata
- Align the frontend settings composite field config with the backend
metadata
- Return all included unique composite subfields when building
create-many conflict fields
- Match composite unique conflict fields as a group during create-many
upserts

## Root Cause

Phone composite metadata only marked `primaryPhoneNumber` as part of the
unique constraint. That made different international phone numbers with
the same national number conflict, for example `+1 123456789` and `+32
123456789`.

## Test Plan

- `yarn workspace twenty-shared build`
- `jest --runTestsByPath <index action handler and create-many utility
specs>`
- `prettier --check <touched files>`
- `oxlint --type-aware <touched files>`
- `nx run twenty-shared:typecheck`
- `nx run twenty-server:typecheck`
- `nx run twenty-front:typecheck`

---------

Co-authored-by: mkdev11 <[email protected]>
Co-authored-by: Etienne <[email protected]>
Co-authored-by: prastoin <[email protected]>
2026-05-13 11:57:19 +00:00
Charles BochetandGitHub ac653182b2 feat(server): migrate all remaining JWT token types to ES256 (#20513)
## Summary

Extends the asymmetric signing work from #20467 to cover **every
remaining `JwtTokenTypeEnum` value**: `LOGIN`, `WORKSPACE_AGNOSTIC`,
`FILE`, `API_KEY`, `APPLICATION_ACCESS`, `APPLICATION_REFRESH`,
`APP_OAUTH_STATE`, plus the ACCESS-shaped session token issued by the
code interpreter tool.

After this PR, every JWT the server signs is ES256 with a `kid` pointing
at the current `core."signingKey"` row, while legacy HS256 tokens (no
`kid` header) remain verifiable indefinitely through the existing
fallback in `JwtWrapperService.resolveVerificationKey`. No new entity /
migration / config: this is a pure routing change on top of the
infrastructure that already shipped.

## Why

`#20467` only flipped `ACCESS` and `REFRESH` to ES256. Every other JWT
type was still HS256-signed against the global `APP_SECRET`, which kept
the original blast radius (a leaked `APP_SECRET` invalidates *every* JWT
type forever). Migrating the rest unifies the sign path on rotatable
per-server private keys without forcing any token reissue.

## Mechanical changes

### Sign side (8 services)
- `LoginTokenService.generateLoginToken`
- `TransientTokenService.generateTransientToken`
- `WorkspaceAgnosticTokenService.generateWorkspaceAgnosticToken`
- `ApplicationTokenService.signApplicationToken` (`APPLICATION_ACCESS` +
`APPLICATION_REFRESH`)
- `ApiKeyService.generateApiKeyToken`
- `FileUrlService.signFileByIdUrl` / `signWorkspaceLogoUrl`
- `ConnectionProviderOAuthFlowService.signState` (`APP_OAUTH_STATE`)
- `CodeInterpreterTool.generateSessionToken`

Each call site swaps `jwtWrapperService.sign(payload, { secret:
generateAppSecret(...), ... })` for `await
jwtWrapperService.signAsync(payload, { expiresIn, [jwtid] })`. The
`generateAppSecret` calls on the sign side are dropped (verifier-side
`generateAppSecret` stays in `resolveVerificationKey` for the HS256
fallback).

### Verifier side
- `WorkspaceAgnosticTokenService.validateToken` now goes through
`verifyJwtToken` instead of the bespoke `verify({ secret })` path, so
new ES256 tokens are accepted while the legacy HS256 fallback inside
`resolveVerificationKey` still serves the old shape.
- `JwtWrapperService.sign()` is kept (legacy compat / tests) but is now
strictly deprecated — there are no remaining production callers.

### Async ripple (`signFileByIdUrl` was synchronous)
- `FileUrlService.signFileByIdUrl` and `signWorkspaceLogoUrl` are now
`async`; the `signUrl` callback used by `getRecordImageIdentifier` is
widened to accept `Promise<string | null>`.
- Every direct/indirect caller is updated: admin panel (user lookup +
statistics + top workspaces), search service
(`computeSearchObjectResults`, `getImageIdentifierValue`), workspace
resolver (`logo` resolver, public workspace by domain/id),
`WorkspaceMemberTranspiler` (now `async toWorkspaceMemberDto[s]` /
`toDeletedWorkspaceMemberDto[s]` / `generateSignedAvatarUrl`),
`UserService.loadSignedAvatarUrlsByUserId`,
`UserWorkspaceService.castWorkspaceToAvailableWorkspace`,
workspace-invitation, approved-access-domain, agent-chat-streaming,
agent-message-part resolver, navigation-menu-item record identifier,
file-ai-chat / file-core-picture / file-email-attachment / file-workflow
/ files-field services, rich-text & files-field query result getters,
and the code-interpreter tool.

## Backward compatibility

- **Legacy HS256 tokens (no `kid`)** keep verifying via
`resolveVerificationKey` → `extractAppSecretBody` → `generateAppSecret`
for both `workspaceId`-bearing and `userId`-bearing payloads.
- The `API_KEY` HS256-via-ACCESS-secret fallback (#16504) still kicks in
inside `verifyJwtToken` for pre-2025-12-12 API keys.
- No payload shape changes, no DB writes, no env var changes — old
tokens issued by `main` continue to authenticate.

## Tests

### Unit (all green locally — 63/63)
Updated specs for every migrated service to mock `signAsync` instead of
`sign` and assert the new option shape:
- `login-token.service.spec.ts`, `transient-token.service.spec.ts`,
`workspace-agnostic-token.service.spec.ts`,
`application-token.service.spec.ts`, `api-key.service.spec.ts`,
`connection-provider-oauth-flow.service.spec.ts`.

### Integration (`jwt-key-rotation.integration-spec.ts`)
- Existing ACCESS coverage (current key, legacy HS256 fallback,
rotated-out key, revoked key, unknown kid) is preserved.
- New `it.each` assertion: `REFRESH`, `WORKSPACE_AGNOSTIC`, and `LOGIN`
tokens emitted by the real signUp → signUpInNewWorkspace →
getAuthTokensFromLoginToken pipeline are ES256 with a `kid` matching the
current signing key — proves end-to-end that the migration didn't
regress those flows.

## Open question (separate decision)

This PR keeps the legacy HS256 verification fallback **forever**. We may
eventually want to sunset it for `API_KEY` once telemetry shows
pre-migration tokens are gone, but that's a separate product/security
decision and not part of this change.

## Test plan

- [ ] CI green
- [ ] `npx nx lint:diff-with-main twenty-server` passes
- [ ] `npx nx typecheck twenty-server` passes
- [ ] `jwt-key-rotation` integration suite passes (new + existing
assertions)
- [ ] Manually verify: signing in issues an ES256 ACCESS / REFRESH
token, generating an API key issues an ES256 token with `kid`, signed
file URL JWT is ES256 with `kid`
- [ ] Pre-existing HS256 tokens still authenticate (covered by
integration test, but worth a manual check with a token from `main`)
2026-05-13 10:53:45 +00:00
Paul RastoinandGitHub a159a68e2c [twenty-server] no floating promises lint rule (#20499)
## Introduction

That's an audit + RFC

## Fire-and-forget (`void`) -- Intentional, correct

These are telemetry, metrics, and audit logging in hot paths or
non-critical contexts. `void` is the right choice.

| File | What was voided |
|---|---|
| `sign-in-up.service.ts` | `metricsService.incrementCounter` (sign-up
metric) + `auditService.insertWorkspaceEvent` (workspace created) |
| `use-graphql-error-handler.hook.ts` | 5x
`metricsService.incrementCounter` (GraphQL operation metrics) |
| `bullmq.driver.ts` | 2x `metricsService.incrementCounter` (job
completed/failed metrics) |
| `call-webhook.job.ts` | 2x `auditService.insertWorkspaceEvent` + 1x
`metricsService.incrementCounter` |
| `custom-domain-manager.service.ts` | `analytics.insertWorkspaceEvent`
(domain activation event) |
| `logic-function-executor.service.ts` |
`auditService.insertWorkspaceEvent` (function execution) |
| `workflow-runner.workspace-service.ts` |
`metricsService.incrementCounter` (throttle metric) |
| `cleaner.workspace-service.ts` | `metricsService.incrementCounter`
(deleted workspace metric) |
| `stream-agent-chat.job.ts` | Detached IIFE for streaming chunks
(intentional concurrent pipeline) |
| `workspace-auth-context.middleware.ts` |
`withWorkspaceAuthContext(...)` (AsyncLocalStorage, returns void anyway)
|

## Top-level script entry points (`void bootstrap()`)

These are module-level calls where the promise has no consumer. `void`
makes the lint rule happy and documents the intent.

| File | What changed |
|---|---|
| `main.ts` | `void bootstrap()` |
| `command.ts` | `void bootstrap()` |
| `queue-worker.ts` | `void bootstrap()` |
| `truncate-db.ts` | `void dropSchemasSequentially()` |
| `codegen/index.ts` | `void generateTests(forceArg)` |

## Now properly awaited -- Real bug fixes

These were floating promises that could silently fail, lose data, or
cause race conditions.

| File | What was fixed |
|---|---|
| `billing-sync-plans-data.command.ts` | `meters.map(async ...)` wrapped
in `Promise.all` -- was returning before upserts finished |
| `cache-storage.service.ts` | `setAdd` and `setPop` had `.then()`
chains that weren't returned/awaited |
| `create-audit-log-from-internal-event.ts` | 4x
`auditService.createObjectEvent` now awaited inside a job |
| `cleaner.workspace-service.ts` | 2x `emailService.send(...)` now
awaited -- emails could silently fail |
| `agent-async-executor.service.ts` | `calculateAndBillUsage` +
`billNativeWebSearchUsage` in `finally` block now awaited |
| `repair-tool-call.util.ts` | `calculateAndBillUsage` now awaited |
| `agent-title-generation.service.ts` | `calculateAndBillUsage` now
awaited |
| `chat-execution.service.ts` | `billNativeWebSearchUsage` now awaited |
| `ai-generate-text.controller.ts` | `calculateAndBillUsage` in
`finally` block now awaited |
| `agent-turn.resolver.ts` | `messageQueueService.add(...)` now awaited
|
| `command.ts` | `app.close()` now awaited (was exiting before graceful
shutdown) |
| `i18n.service.ts` | `loadTranslations()` in `onModuleInit` now awaited
|
| `workspace-query-hook.explorer.ts` | `explore()` in `onModuleInit` now
awaited |
| `message-queue.explorer.ts` | `handleProcessorGroupCollection` in
`onModuleInit` now awaited |
| `ai-billing.service.spec.ts` | Test now properly `await`s the async
call |
| `messaging-messages-import.service.spec.ts` | `expect(...)` now
properly `await`ed for async assertion |
| `archive.finalize()` (3 files) | Voided -- promise resolution already
handled by `pipeline()` / `on('end')` |

## Impersonation & security audit trail -- Upgraded from `void` to
`await`

These were previously fire-and-forget but are
security/compliance-critical events that must be reliably persisted.

| File | What was fixed |
|---|---|
| `impersonation.service.ts` | 4x `auditService.insertWorkspaceEvent`
now awaited (impersonation attempt, token generation
attempt/success/failure) |
| `auth.resolver.ts` | 5x `auditService.insertWorkspaceEvent` now
awaited (impersonation token exchange attempt/success/failure at server
and workspace levels) |
| `auth.service.ts` | 2x `analytics.insertWorkspaceEvent` now awaited
(impersonation attempted/issued) |

## Billing audit -- Upgraded from `void` to `await`

Payment events should be reliably persisted for financial/compliance
reporting.

| File | What was fixed |
|---|---|
| `billing-webhook-invoice.service.ts` |
`auditService.insertWorkspaceEvent(PAYMENT_RECEIVED_EVENT)` now awaited
inside Stripe webhook handler |

## Fire-and-forget with proper error handling -- Upgraded from bare
`void`

These remain non-blocking but now catch and log errors instead of
risking unhandled rejections.

| File | What was fixed |
|---|---|
| `logic-function-executor.service.ts` |
`applicationLogsService.writeLogs` now uses `.catch()` instead of bare
`void` -- user-facing logs should surface errors |

## Systemic infrastructure fixes

| File | What was fixed |
|---|---|
| `metrics.service.ts` | `incrementCounter`: Redis cache write
(`metricsCacheService.updateCounter`) now uses `.catch()` internally
instead of raw `await` -- prevents unhandled rejections across all `void
metricsService.incrementCounter(...)` call sites when Redis is unhealthy
|
| `audit.service.ts` | `preventIfDisabled`: made properly `async` with
`await` and consistent `Promise<{ success: boolean }>` return type.
Removed broken `catch` that returned an `AuditException` as a value
(wrong constructor args, unreachable dead code). Removed unused
`AuditException` import |

## Fixed in this session (beyond original PR)

| File | What changed |
|---|---|
| `telemetry.listener.ts` | Removed misleading `Promise.all` + `void`
combo; replaced with simple `for...of` + `void` |
| `message-queue.explorer.ts` | Changed from `void` to `await` so
startup crashes on registration failure |
2026-05-13 10:08:42 +00:00
38d9eacff8 chore: sync AI model catalog from models.dev (#20523)
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).

This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.

**Please review before merging** — verify no critical models were
incorrectly deprecated.

Co-authored-by: FelixMalfait <[email protected]>
2026-05-13 08:56:41 +02:00
765 changed files with 24219 additions and 12828 deletions
@@ -109,7 +109,7 @@ jobs:
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --example hello-world --display-name "Test hello-world app" --description "E2E test hello-world app" --skip-local-instance
create-twenty-app test-app --example hello-world --display-name "Test hello-world app" --description "E2E test hello-world app" --api-url http://localhost:3000
- name: Install scaffolded app dependencies
run: |
@@ -105,7 +105,7 @@ jobs:
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --skip-local-instance --yes
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --api-url http://localhost:3000
- name: Install scaffolded app dependencies
run: |
@@ -107,7 +107,7 @@ jobs:
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --example postcard --display-name "Test postcard app" --description "E2E test postcard app" --skip-local-instance
create-twenty-app test-app --example postcard --display-name "Test postcard app" --description "E2E test postcard app" --api-url http://localhost:3000
- name: Install scaffolded app dependencies
run: |
+2
View File
@@ -51,6 +51,7 @@ jobs:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
@@ -123,6 +124,7 @@ jobs:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
+40
View File
@@ -43,6 +43,46 @@
{
"name": "packages/twenty-e2e-testing",
"path": "../packages/twenty-e2e-testing"
},
{
"name": "packages/twenty-docs",
"path": "../packages/twenty-docs"
},
{
"name": "packages/create-twenty-app",
"path": "../packages/create-twenty-app"
},
{
"name": "packages/twenty-apps",
"path": "../packages/twenty-apps"
},
{
"name": "packages/twenty-claude-skills",
"path": "../packages/twenty-claude-skills"
},
{
"name": "packages/twenty-cli",
"path": "../packages/twenty-cli"
},
{
"name": "packages/twenty-client-sdk",
"path": "../packages/twenty-client-sdk"
},
{
"name": "packages/twenty-companion",
"path": "../packages/twenty-companion"
},
{
"name": "packages/twenty-front-component-renderer",
"path": "../packages/twenty-front-component-renderer"
},
{
"name": "packages/twenty-sdk",
"path": "../packages/twenty-sdk"
},
{
"name": "packages/twenty-website-new",
"path": "../packages/twenty-website-new"
}
],
"settings": {
+3 -1
View File
@@ -4,8 +4,10 @@ enableHardenedMode: true
enableInlineHunks: true
enableScripts: false
nodeLinker: node-modules
npmMinimalAgeGate: "3d"
npmMinimalAgeGate: 3d
yarnPath: .yarn/releases/yarn-4.13.0.cjs
+11 -10
View File
@@ -25,18 +25,19 @@ yarn twenty dev
The scaffolder will:
1. Create a new project with TypeScript, linting, tests, and a preconfigured `twenty` CLI
2. Optionally start a local Twenty server (Docker)
3. Open the browser for OAuth authentication
2. Start a local Twenty server via Docker (pulls the latest image automatically)
3. Authenticate with the development API key
## Options
| Flag | Description |
| ------------------------------ | --------------------------------------- |
| `--example <name>` | Initialize from an example |
| `--name <name>` | Set the app name (skips the prompt) |
| `--display-name <displayName>` | Set the display name (skips the prompt) |
| `--description <description>` | Set the description (skips the prompt) |
| `--skip-local-instance` | Skip the local server setup prompt |
| Flag | Description |
| ---------------------------------- | --------------------------------------------------------------------- |
| `--example <name>` | Initialize from an example |
| `--name <name>` | Set the app name |
| `--display-name <displayName>` | Set the display name |
| `--description <description>` | Set the description |
| `--api-url <url>` | Twenty instance URL (default: `http://localhost:2020`) |
| `--authentication-method <method>` | `oauth` or `apiKey` (default: `apiKey` for local, `oauth` for remote) |
By default (no flags), a minimal app is generated with core files and an integration test. Use `--example` to start from a richer example:
@@ -57,7 +58,7 @@ Full documentation is available at **[docs.twenty.com/developers/extend/apps](ht
## Troubleshooting
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
- Auth not working: make sure you are logged in to Twenty in the browser, then run `yarn twenty remote add`.
- Auth not working: run `yarn twenty remote add --local` to re-authenticate.
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
## Contributing
+1 -3
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "2.4.0",
"version": "2.4.2",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -32,7 +32,6 @@
"chalk": "^5.3.0",
"commander": "^12.0.0",
"fs-extra": "^11.2.0",
"inquirer": "^10.0.0",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
@@ -43,7 +42,6 @@
"@swc/core": "^1.15.11",
"@swc/jest": "^0.2.39",
"@types/fs-extra": "^11.0.0",
"@types/inquirer": "^9.0.0",
"@types/jest": "^30.0.0",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.kebabcase": "^4.1.7",
+27 -15
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env node
import chalk from 'chalk';
import { Command, CommanderError } from 'commander';
import { CreateAppCommand } from '@/create-app.command';
import {
type AuthenticationMethod,
CreateAppCommand,
} from '@/create-app.command';
import packageJson from '../package.json';
const program = new Command(packageJson.name)
@@ -13,20 +16,17 @@ const program = new Command(packageJson.name)
)
.argument('[directory]')
.option('--example <name>', 'Initialize from an example')
.option('-n, --name <name>', 'Application name (skips prompt)')
.option('-n, --name <name>', 'Application name')
.option('-d, --display-name <displayName>', 'Application display name')
.option('--description <description>', 'Application description')
.option(
'-d, --display-name <displayName>',
'Application display name (skips prompt)',
'--api-url <apiUrl>',
'Twenty instance URL (default: http://localhost:2020)',
)
.option(
'--description <description>',
'Application description (skips prompt)',
'--authentication-method <method>',
'Authentication method: oauth or apiKey (default: apiKey for local, oauth for remote)',
)
.option(
'--skip-local-instance',
'Skip the local Twenty instance setup prompt',
)
.option('-y, --yes', 'Auto-confirm prompts (e.g. start existing container)')
.helpOption('-h, --help', 'Display this help message.')
.action(
async (
@@ -36,8 +36,8 @@ const program = new Command(packageJson.name)
name?: string;
displayName?: string;
description?: string;
skipLocalInstance?: boolean;
yes?: boolean;
apiUrl?: string;
authenticationMethod?: AuthenticationMethod;
},
) => {
if (directory && !/^[a-z0-9-]+$/.test(directory)) {
@@ -54,14 +54,26 @@ const program = new Command(packageJson.name)
process.exit(1);
}
if (
options?.authenticationMethod &&
!['oauth', 'apiKey'].includes(options.authenticationMethod)
) {
console.error(
chalk.red(
'Error: --authentication-method must be "oauth" or "apiKey".',
),
);
process.exit(1);
}
await new CreateAppCommand().execute({
directory,
example: options?.example,
name: options?.name,
displayName: options?.displayName,
description: options?.description,
skipLocalInstance: options?.skipLocalInstance,
yes: options?.yes,
apiUrl: options?.apiUrl,
authenticationMethod: options?.authenticationMethod,
});
},
);
@@ -2,8 +2,19 @@ This is a [Twenty](https://twenty.com) application bootstrapped with [`create-tw
## Getting Started
This app was scaffolded with a local Twenty server running at [http://localhost:2020](http://localhost:2020).
Login with the default development credentials: `[email protected]` / `[email protected]`.
Run `yarn twenty help` to list all available commands.
## Useful Commands
- `yarn twenty dev` - Start the development server and sync your app
- `yarn twenty server status` - Check the local Twenty server status
- `yarn twenty server start` - Start the local Twenty server
- `yarn test` - Run integration tests
## Learn More
- [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start)
@@ -11,7 +11,7 @@ permissions:
contents: read
env:
TWENTY_DEPLOY_URL: http://localhost:3000
TWENTY_DEPLOY_URL: http://localhost:2020
concurrency:
group: cd-${{ github.ref }}
@@ -5,21 +5,29 @@ import { install } from '@/utils/install';
import { tryGitInit } from '@/utils/try-git-init';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import kebabCase from 'lodash.kebabcase';
import * as path from 'path';
import { basename } from 'path';
import { spawn } from 'node:child_process';
import {
authLogin,
authLoginOAuth,
checkDockerRunning,
ConfigService,
containerExists,
detectLocalServer,
DEV_API_KEY,
DEV_API_URL,
serverStart,
} from 'twenty-sdk/cli';
import { isDefined } from 'twenty-shared/utils';
import {
getDockerInstallInstructions,
isDockerInstalled,
} from '@/utils/docker-install';
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
const IMAGE = 'twentycrm/twenty-app-dev:latest';
export type AuthenticationMethod = 'oauth' | 'apiKey';
type CreateAppOptions = {
directory?: string;
@@ -27,40 +35,62 @@ type CreateAppOptions = {
name?: string;
displayName?: string;
description?: string;
skipLocalInstance?: boolean;
yes?: boolean;
apiUrl?: string;
authenticationMethod?: AuthenticationMethod;
};
export class CreateAppCommand {
private static TOTAL_STEPS = 4;
private stepCounter = 0;
private totalSteps = 0;
async execute(options: CreateAppOptions = {}): Promise<void> {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(options);
this.getAppInfos(options);
const apiUrl = options.apiUrl ?? DEV_API_URL;
const skipLocalInstance = apiUrl !== DEV_API_URL;
if (!skipLocalInstance && !isDockerInstalled()) {
console.log(chalk.yellow('\n' + getDockerInstallInstructions() + '\n'));
process.exit(1);
}
if (skipLocalInstance && options.authenticationMethod === 'apiKey') {
console.log(
chalk.yellow(
'API key authentication is only supported on a local Docker instance. Ignoring and switching to OAuth authentication.',
),
);
}
const authenticationMethod = skipLocalInstance
? 'oauth'
: (options.authenticationMethod ?? 'apiKey');
try {
await this.validateDirectory(appDirectory);
const confirmed = await this.promptScaffoldConfirmation({
appName,
appDisplayName,
appDescription,
appDirectory,
autoConfirm: options.yes,
this.totalSteps = this.computeTotalSteps({
skipLocalInstance,
});
if (!confirmed) {
console.log(chalk.gray('\nScaffolding cancelled.'));
process.exit(0);
}
this.stepCounter = 0;
console.log('');
const dockerPullPromise =
!skipLocalInstance && checkDockerRunning()
? this.pullImageInBackground()
: Promise.resolve(false);
this.logPlan({ appName, appDisplayName, appDescription, appDirectory });
this.logNextStep('Creating project directory');
this.logStep(1, 'Creating project directory');
await fs.ensureDir(appDirectory);
this.logDetail(appDirectory);
this.logStep(2, 'Scaffolding project files');
this.logNextStep('Scaffolding project files');
if (options.example) {
const exampleSucceeded = await this.tryDownloadExample(
@@ -87,10 +117,12 @@ export class CreateAppCommand {
});
}
this.logStep(3, 'Installing dependencies');
this.logNextStep('Installing dependencies');
await install(appDirectory, (message) => this.logDetail(message));
this.logStep(4, 'Initializing Git repository');
this.logNextStep('Initializing Git repository');
const gitInitialized = await tryGitInit(appDirectory);
if (gitInitialized) {
@@ -104,38 +136,29 @@ export class CreateAppCommand {
console.log('');
let hasLocalServer = false;
let authSucceeded = false;
let resolvedApiUrl = apiUrl;
let serverReady = skipLocalInstance;
if (!options.skipLocalInstance) {
const existingServerUrl = await detectLocalServer();
if (!skipLocalInstance) {
this.logNextStep('Starting Twenty server');
const serverResult = await this.ensureDockerServer(dockerPullPromise);
if (existingServerUrl) {
hasLocalServer = true;
authSucceeded = await this.promptConnectToLocal(existingServerUrl);
} else {
const shouldStart = await this.shouldStartServer(options.yes);
if (shouldStart) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
if (startResult.success) {
hasLocalServer = true;
authSucceeded = await this.promptConnectToLocal(
startResult.data.url,
);
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
}
} else {
this.logServerSkipped();
}
if (isDefined(serverResult.url)) {
resolvedApiUrl = serverResult.url;
serverReady = true;
}
}
this.logSuccess(appDirectory, hasLocalServer, authSucceeded);
if (serverReady && authenticationMethod === 'oauth') {
this.logNextStep('Authenticating via OAuth');
authSucceeded = await this.authenticateWithOAuth(resolvedApiUrl);
} else if (serverReady && authenticationMethod === 'apiKey') {
this.logNextStep('Authenticating via API key');
authSucceeded = await this.authenticateWithDevKey(resolvedApiUrl);
}
this.logSuccess(appDirectory, resolvedApiUrl, authSucceeded);
} catch (error) {
console.error(
chalk.red('\nCreate application failed:'),
@@ -145,66 +168,42 @@ export class CreateAppCommand {
}
}
private async getAppInfos(options: CreateAppOptions): Promise<{
private computeTotalSteps({
skipLocalInstance,
}: {
skipLocalInstance: boolean;
}): number {
let steps = 4; // directory, scaffold, install, git
if (!skipLocalInstance) {
steps += 1; // start server
}
steps += 1; // authenticate (oauth or apiKey)
return steps;
}
private getAppInfos(options: CreateAppOptions): {
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
}> {
const { directory } = options;
const hasName = isDefined(options.name) || isDefined(directory);
const hasDisplayName = isDefined(options.displayName);
const hasDescription = isDefined(options.description);
const hasExample = isDefined(options.example);
const { name, displayName, description } = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Application name:',
when: () => !hasName && !hasExample,
default: 'my-twenty-app',
validate: (input) => {
if (input.length === 0) return 'Application name is required';
return true;
},
},
{
type: 'input',
name: 'displayName',
message: 'Application display name:',
when: () => !hasDisplayName && !hasExample,
default: (answers: { name?: string }) => {
return convertToLabel(
answers?.name ?? options.name ?? directory ?? '',
);
},
},
{
type: 'input',
name: 'description',
message: 'Application description (optional):',
when: () => !hasDescription && !hasExample,
default: '',
},
]);
} {
const appName = (
options.name ??
name ??
directory ??
options.directory ??
options.example ??
'my-twenty-app'
).trim();
const appDisplayName =
(options.displayName ?? displayName)?.trim() || convertToLabel(appName);
options.displayName?.trim() || convertToLabel(appName);
const appDescription = (options.description ?? description ?? '').trim();
const appDescription = (options.description ?? '').trim();
const appDirectory = directory
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
const appDirectory = options.directory
? path.join(CURRENT_EXECUTION_DIRECTORY, options.directory)
: path.join(CURRENT_EXECUTION_DIRECTORY, kebabCase(appName));
return { appName, appDisplayName, appDirectory, appDescription };
@@ -232,45 +231,30 @@ export class CreateAppCommand {
return true;
} catch (error) {
console.error(
chalk.red(
console.log(
chalk.yellow(
`\n${error instanceof Error ? error.message : 'Failed to download example.'}`,
),
);
this.logDetail('Falling back to default template...');
const { useTemplate } = await inquirer.prompt([
{
type: 'confirm',
name: 'useTemplate',
message: 'Would you like to create a default template app instead?',
default: true,
},
]);
if (!useTemplate) {
process.exit(1);
}
// Clean up any partial files from the failed download
await fs.emptyDir(appDirectory);
return false;
}
}
private async promptScaffoldConfirmation({
private logPlan({
appName,
appDisplayName,
appDescription,
appDirectory,
autoConfirm,
}: {
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
autoConfirm?: boolean;
}): Promise<boolean> {
}): void {
console.log(chalk.blue('\nCreating Twenty Application\n'));
console.log(chalk.white(` Name: ${appName}`));
console.log(chalk.white(` Display name: ${appDisplayName}`));
@@ -280,43 +264,13 @@ export class CreateAppCommand {
}
console.log(chalk.white(` Directory: ${appDirectory}`));
console.log(chalk.white('\nThe following steps will be performed:\n'));
console.log(chalk.gray(' 1. Create project directory'));
console.log(
chalk.gray(
' 2. Scaffold project files from base template\n' +
' - Copy template files\n' +
' - Configure dotfiles (.gitignore, .github)\n' +
' - Generate unique application identifiers\n' +
' - Update package.json with app name and SDK versions',
),
);
console.log(chalk.gray(' 3. Install dependencies (yarn)'));
console.log(
chalk.gray(' 4. Initialize Git repository with initial commit'),
);
console.log('');
if (autoConfirm) {
return true;
}
const { proceed } = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Proceed?',
default: true,
},
]);
return proceed;
}
private logStep(step: number, title: string): void {
private logNextStep(title: string): void {
this.stepCounter++;
console.log(
chalk.blue(`\n[${step}/${CreateAppCommand.TOTAL_STEPS}]`) +
chalk.blue(`\n[${this.stepCounter}/${this.totalSteps}]`) +
chalk.white(` ${title}...`),
);
}
@@ -325,100 +279,57 @@ export class CreateAppCommand {
console.log(chalk.gray(`${message}`));
}
private async shouldStartServer(autoConfirm?: boolean): Promise<boolean> {
console.log(
chalk.white(
'\n A local Twenty instance is required for app development.\n' +
' It provides the API and schema your application connects to.\n',
),
);
private pullImageInBackground(): Promise<boolean> {
return new Promise((resolve) => {
const child = spawn('docker', ['pull', IMAGE], { stdio: 'ignore' });
if (checkDockerRunning() && containerExists()) {
if (autoConfirm) {
return true;
}
const { startExisting } = await inquirer.prompt([
{
type: 'confirm',
name: 'startExisting',
message:
'An existing Twenty server container was found. Would you like to start it?',
default: true,
},
]);
return startExisting;
}
if (autoConfirm) {
return true;
}
const { startDocker } = await inquirer.prompt([
{
type: 'confirm',
name: 'startDocker',
message:
'No running Twenty instance found. Would you like to start one using Docker?',
default: true,
},
]);
return startDocker;
child.on('close', (code) => resolve(code === 0));
child.on('error', () => resolve(false));
});
}
private logServerSkipped(): void {
console.log(
chalk.gray(
'\n To start a Twenty instance later:\n' +
' yarn twenty server start\n\n' +
' To connect to a remote instance instead:\n' +
' yarn twenty remote add\n',
),
);
}
private async promptConnectToLocal(serverUrl: string): Promise<boolean> {
console.log(
chalk.white(
'\n Authentication links your app to a Twenty instance so you can\n' +
' sync custom objects, fields, and roles during development.\n' +
' This will open a browser window to complete the OAuth flow.\n',
),
);
const { shouldAuthenticate } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldAuthenticate',
message: `Authenticate to the local Twenty instance (${serverUrl})?`,
default: true,
},
]);
if (!shouldAuthenticate) {
private async ensureDockerServer(
dockerPullPromise: Promise<boolean>,
): Promise<{ url?: string }> {
if (!checkDockerRunning()) {
console.log(
chalk.gray(
'\n Authentication skipped. To authenticate later:\n' +
` yarn twenty remote add --local\n`,
chalk.yellow(
'\n Docker is installed but not running.\n' +
' Please start Docker and run this command again.\n',
),
);
return false;
return {};
}
await inquirer.prompt([
{
type: 'input',
name: 'confirm',
message: 'Press Enter to open the browser for authentication...',
},
]);
this.logDetail('Ensuring latest Twenty server image...');
const pullSucceeded = await dockerPullPromise;
if (!pullSucceeded) {
this.logDetail(
'Image pull failed, continuing with cached image if available...',
);
}
const startResult = await serverStart({
onProgress: (message: string) => this.logDetail(message),
});
if (startResult.success) {
return { url: startResult.data.url };
}
console.log(chalk.yellow(`\n ${startResult.error.message}`));
return {};
}
private async authenticateWithDevKey(apiUrl: string): Promise<boolean> {
try {
const result = await authLoginOAuth({
apiUrl: serverUrl,
const result = await authLogin({
apiKey: DEV_API_KEY,
apiUrl,
remote: 'local',
});
@@ -426,21 +337,68 @@ export class CreateAppCommand {
const configService = new ConfigService();
await configService.setDefaultRemote('local');
this.logDetail('Authenticated as [email protected] (development API key)');
return true;
} else {
console.log(
chalk.yellow(
'Authentication failed. Run `yarn twenty remote add --local` manually.',
),
);
return false;
}
console.log(
chalk.yellow(
' Authentication failed. Run `yarn twenty remote add --local` manually.',
),
);
return false;
} catch {
console.log(
chalk.yellow(
'Authentication failed. Run `yarn twenty remote add` manually.',
' Authentication failed. Run `yarn twenty remote add --local` manually.',
),
);
return false;
}
}
private deriveRemoteName(url: string): string {
try {
return new URL(url).hostname.replace(/\./g, '-');
} catch {
return 'remote';
}
}
private async authenticateWithOAuth(apiUrl: string): Promise<boolean> {
try {
const remoteName = this.deriveRemoteName(apiUrl);
ConfigService.setActiveRemote(remoteName);
this.logDetail('Opening browser for OAuth...');
const result = await authLoginOAuth({ apiUrl });
if (result.success) {
const configService = new ConfigService();
await configService.setDefaultRemote(remoteName);
this.logDetail(`Authenticated via OAuth to ${apiUrl}`);
return true;
}
console.log(
chalk.yellow(
` OAuth failed: ${result.error.message}\n` +
` Run \`yarn twenty remote add --api-url ${apiUrl}\` manually.`,
),
);
return false;
} catch {
console.log(
chalk.yellow(
` Authentication failed. Run \`yarn twenty remote add --api-url ${apiUrl}\` manually.`,
),
);
@@ -450,12 +408,13 @@ export class CreateAppCommand {
private logSuccess(
appDirectory: string,
hasLocalServer: boolean,
apiUrl: string,
authSucceeded: boolean,
): void {
const dirName = basename(appDirectory);
console.log(chalk.green('\n✔ Application created successfully!\n'));
console.log(chalk.white(' Next steps:\n'));
let stepNumber = 1;
@@ -465,17 +424,21 @@ export class CreateAppCommand {
stepNumber++;
if (!authSucceeded) {
const remoteCommand = hasLocalServer
? 'yarn twenty remote add --local'
: 'yarn twenty remote add';
console.log(chalk.white(` ${stepNumber}. Connect to a Twenty instance`));
console.log(chalk.cyan(` ${remoteCommand}\n`));
console.log(
chalk.cyan(
' yarn twenty remote add --api-url <your-instance-url>\n',
),
);
stepNumber++;
}
console.log(chalk.white(` ${stepNumber}. Start developing`));
console.log(chalk.cyan(' yarn twenty dev\n'));
stepNumber++;
console.log(chalk.white(` ${stepNumber}. Open your twenty instance`));
console.log(chalk.cyan(` ${apiUrl}\n`));
console.log(
chalk.gray(
@@ -0,0 +1,33 @@
import { execSync } from 'node:child_process';
const DOCKER_DOWNLOAD_URLS: Record<string, string> = {
darwin: 'https://docs.docker.com/desktop/setup/install/mac-install/',
win32: 'https://docs.docker.com/desktop/setup/install/windows-install/',
linux: 'https://docs.docker.com/engine/install/',
};
export const isDockerInstalled = (): boolean => {
try {
execSync('docker --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
};
export const getDockerInstallInstructions = (): string => {
const url =
DOCKER_DOWNLOAD_URLS[process.platform] ?? DOCKER_DOWNLOAD_URLS.linux;
return [
' Docker is required but not installed.',
'',
` Install Docker: ${url}`,
'',
' Then run this command again.',
'',
' Alternatively, connect to an existing Twenty instance:',
' npx create-twenty-app@latest my-twenty-app --api-url <your-instance-url>',
].join('\n');
};
@@ -0,0 +1,18 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -0,0 +1,173 @@
# Discord for Twenty
Connect Discord to your Twenty workflows. Post messages, edit them, react,
delete, and browse channels — all from the workflow builder or the AI chat.
## What you can do
Once installed and the admin has provided the bot token, five tools become
available in workflows and the AI chat:
- **Send Discord message** — *"post 'New deal closed 🎉' to #general"*. As a
workflow step: provide `channelId` + `messageText`. Optional
`replyToMessageId` turns it into a reply to a previous message.
- **Update Discord message** — edit a message the bot already sent. Useful
for status messages that change as a workflow progresses.
- **Delete Discord message** — remove a bot message (for example after a
mistake or when a workflow is cancelled).
- **Add Discord reaction** — react with a unicode emoji (`👍`, `✅`, `🎉`)
or a custom server emoji (`partyparrot:643452342342342342`). Great for
signalling status at a glance.
- **List Discord channels** — discovers the text-postable channels in a
Discord server, useful as the first step in a workflow that picks a
channel dynamically. **Leave `guildId` blank if your bot is in only one
server — it auto-picks.** If the bot is in multiple servers, the error
response lists each server's name and ID so you can copy the right one.
## Installing
1. Open **Settings → Applications** in your Twenty workspace.
2. Find **Discord** in the available apps and click **Install**.
3. Use any of the tools above in a workflow step or via the AI chat.
> **Heads up:** if you see *"Discord is not configured"* on the first run,
> your Twenty admin needs to follow the [Self-hosting setup](#self-hosting-setup-admin-only)
> section below — they need to create a Discord application and provide the
> bot token before the tools work.
## Finding channel and message IDs
Discord IDs are long numeric strings (e.g. `1234567890123456789`). To copy
them from the Discord client:
1. **Enable Developer Mode** once: **User Settings → Advanced → Developer
Mode** (toggle on). Discord then shows extra "Copy ID" options on
right-click.
2. **Channel ID** — right-click any channel → *Copy Channel ID*.
3. **Message ID** — right-click any message → *Copy Message ID*.
4. **Server (guild) ID** — right-click the server icon → *Copy Server ID*.
Only needed for `discord-list-channels` when your bot is in multiple
servers (otherwise leave it blank for auto-pick).
> **Tip:** when chaining steps, pass `messageId` from a previous *Send
> Discord message* step directly into a follow-up *Update / Delete / Add
> Reaction* step. No copy/paste needed.
## Limitations
What this connector intentionally does **not** support in v1:
- **Direct messages to users.** Discord's DM API needs a different identity
flow.
- **Threads.** Replying inside a thread or starting a new one isn't exposed.
- **Webhooks.** Uses the bot REST API only.
- **Slash commands / interactions.** The bot doesn't register or respond to
`/commands`.
- **Per-workspace identity.** All Twenty workspaces in the same Twenty
deployment share the same Discord bot — see
[Why bot token instead of OAuth?](#why-bot-token-instead-of-oauth) below.
- **2000-character message limit.** Discord rejects longer payloads with
HTTP 400.
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| `Discord is not configured` | `DISCORD_BOT_TOKEN` not set | Admin: paste the bot token in Settings → Applications → Discord → **Settings tab** |
| `401 Unauthorized` | Token wrong, copied with whitespace, or rotated | Reset the token in the Discord Developer Portal and re-paste |
| `Missing Access (50001)` | Bot wasn't invited to that server | Re-run the OAuth invite URL for that server (see admin setup) |
| `Missing Permissions (50013)` | Bot is in the server but lacks Send / Manage / React permissions | In Discord: server settings → roles → grant the bot's role the missing permission |
| `Unknown Channel (10003)` | `channelId` is wrong, or bot can't see that channel | Verify the ID; check the bot has *View Channel* permission |
| `Unknown Message (10008)` | `messageId` is wrong, or message was deleted | Verify the ID is from a still-existing message |
| `Cannot edit a message authored by another user (50005)` | Trying to update / delete a message the bot didn't send | The bot can only edit its own messages; it can delete others' messages only with the *Manage Messages* permission |
| `Bot is in N Discord servers — please specify which one` (from list-channels) | Bot is in multiple servers and `guildId` was left blank | Copy the server ID from the error message and paste it into the `guildId` field |
| `Discord API responded with 429` | Hit Discord's rate limit | Reduce workflow concurrency; this connector doesn't yet auto-retry |
---
## Self-hosting setup (admin-only)
This section is for Twenty server admins. If you're on Twenty Cloud, skip
this — the bot credentials are already configured.
### 1. Create a Discord application
1. Visit https://discord.com/developers/applications and click
**New Application**.
2. Name it (e.g. *"Twenty Bot"*) and create. Use a dedicated app — don't
reuse one configured for other bots.
3. Sidebar → **Bot** tab → **Reset Token** → copy the token immediately
(it's only shown once; if you lose it, reset again).
4. *Recommended:* toggle **Public Bot** OFF so randoms can't invite the
bot to their servers.
### 2. Generate the invite URL and add the bot to your server(s)
1. In the Discord Developer Portal: sidebar → **OAuth2****URL Generator**.
2. **Scopes**: tick `bot`.
3. **Bot Permissions**: tick `Send Messages`, `Manage Messages`,
`Add Reactions`, `Read Message History`.
4. Copy the generated URL.
5. Open the URL in your browser, pick the Discord server, authorize.
Repeat for each server you want workflows to post in.
### 3. Wire the bot token into Twenty
1. In Twenty: **Settings → Applications → Discord → Settings tab**
(gear icon).
2. Paste the bot token into the `DISCORD_BOT_TOKEN` row. Saves on blur.
Workspace users can now use the Discord workflow tools immediately — no
further per-user configuration needed (which is unique vs Linear / Slack
where each user connects their own account).
---
## Why bot token instead of OAuth?
Slack and Linear use OAuth-per-workspace
(`defineConnectionProvider({ type: 'oauth' })`) so each Twenty workspace
stores its own access token. Discord works differently:
- Discord's `bot` scope **does** have an OAuth flow, but the `access_token`
it returns is a *user* bearer token — useless for bot actions like posting
messages. To actually send messages as the bot you need the static
**bot token** from the Developer Portal.
- That bot token is global to the Discord application (and therefore to
the Twenty deployment). Discord deprecated per-install bot tokens years
ago.
- Webhooks are a separate auth model but only support posting — no edit,
delete, or reactions — so they don't cover Slack/Linear parity.
The result: this connector skips `defineConnectionProvider` entirely and
reads `DISCORD_BOT_TOKEN` from an `applicationVariable` set once at
deployment scope. See
[Discord's OAuth2 docs](https://discord.com/developers/docs/topics/oauth2#bot-users)
for the underlying reason bot users authenticate via static tokens.
---
## Developers only
If you're working on this app rather than installing the published version:
```bash
cd packages/twenty-apps/internal/twenty-discord
# Day-to-day development (publish + install + watch in one):
yarn twenty dev
# Run unit tests:
yarn test
# Lint:
yarn lint
```
`twenty dev` is recommended for iteration — it publishes to your local
Twenty server, installs the app, and watches for changes in one command.
The Discord REST API (v10) is called directly via `fetch` — no `discord.js`
or other SDK dependency. See
`src/logic-functions/utils/discord-api-request.ts` for the auth and
error-handling wrapper that all handlers go through.
@@ -0,0 +1,31 @@
{
"name": "twenty-discord",
"version": "0.1.0",
"description": "Discord workflow connector for Twenty",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"keywords": [
"twenty-app"
],
"packageManager": "[email protected]",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json .",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"twenty-sdk": "2.4.0"
},
"devDependencies": {
"@types/node": "^24.7.2",
"oxlint": "^0.16.0",
"typescript": "^5.9.3",
"vitest": "^3.1.1"
}
}
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 127 127"
fill="none"
role="img"
>
<title>Discord</title>
<path
fill="#5865F2"
d="M107.7 26.4A104.6 104.6 0 0 0 81.6 18a0.4 0.4 0 0 0-0.4 0.2 73 73 0 0 0-3.2 6.6 96.6 96.6 0 0 0-29 0A66.8 66.8 0 0 0 45.6 18.2a0.4 0.4 0 0 0-0.4-0.2 104.2 104.2 0 0 0-26.1 8.4 0.4 0.4 0 0 0-0.2 0.2C2.3 51.4-2.2 75.7 0 99.7a0.4 0.4 0 0 0 0.2 0.3 105.2 105.2 0 0 0 31.6 16 0.4 0.4 0 0 0 0.5-0.1 75.6 75.6 0 0 0 6.5-10.6 0.4 0.4 0 0 0-0.2-0.5 69.4 69.4 0 0 1-9.9-4.7 0.4 0.4 0 0 1 0-0.7 54.6 54.6 0 0 0 2-1.6 0.4 0.4 0 0 1 0.4-0.1 75 75 0 0 0 63.9 0 0.4 0.4 0 0 1 0.4 0.1c0.6 0.6 1.3 1.1 2 1.6a0.4 0.4 0 0 1 0 0.7 65.2 65.2 0 0 1-9.9 4.7 0.4 0.4 0 0 0-0.2 0.5 84.8 84.8 0 0 0 6.5 10.6 0.4 0.4 0 0 0 0.5 0.1 104.8 104.8 0 0 0 31.6-16 0.4 0.4 0 0 0 0.2-0.3c2.7-27.8-4.5-51.9-18.9-73.1a0.4 0.4 0 0 0-0.2-0.2zM42.5 85a12.3 12.3 0 0 1-11.5-12.9c0-7.1 5-12.9 11.5-12.9 6.6 0 11.7 5.8 11.6 12.9 0 7-5.1 12.9-11.6 12.9zm42.8 0a12.3 12.3 0 0 1-11.5-12.9c0-7.1 5-12.9 11.5-12.9 6.6 0 11.6 5.8 11.5 12.9 0 7-5 12.9-11.5 12.9z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,30 @@
import { defineApplication } from 'twenty-sdk/define';
import {
APPLICATION_UNIVERSAL_IDENTIFIER,
DISCORD_BOT_TOKEN_VARIABLE_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Twenty Discord',
description:
'Connect Discord to Twenty. Workflow steps post, update, and delete bot messages and add reactions using a Discord bot token shared across the deployment.',
logoUrl: 'public/twenty-discord.svg',
author: 'Twenty',
category: 'Communication',
aboutDescription:
'Official Discord connector for Twenty CRM. Create a Discord application at https://discord.com/developers/applications, copy its bot token into the DISCORD_BOT_TOKEN application variable, then invite the bot to each server you want workflows to post in. Use workflow actions to post, update, or delete bot messages and add reactions.',
websiteUrl: 'https://docs.twenty.com/developers/extend/apps/getting-started',
termsUrl: 'https://www.twenty.com/terms',
emailSupport: '[email protected]',
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
applicationVariables: {
DISCORD_BOT_TOKEN: {
universalIdentifier: DISCORD_BOT_TOKEN_VARIABLE_UNIVERSAL_IDENTIFIER,
description:
'Bot token from your Discord application (Developer Portal → Bot tab → Reset Token). Used with the `Bot` auth prefix to call the Discord REST API. The same token authenticates the bot across every guild it has been invited to.',
isSecret: true,
},
},
});
@@ -0,0 +1,17 @@
import { defineCommandMenuItem } from 'twenty-sdk/define';
import {
SEND_DISCORD_MESSAGE_COMMAND_UNIVERSAL_IDENTIFIER,
SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineCommandMenuItem({
universalIdentifier: SEND_DISCORD_MESSAGE_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Send Discord message',
shortLabel: 'Discord message',
icon: 'IconBrandDiscord',
isPinned: false,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier:
SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
@@ -0,0 +1,670 @@
import { useCallback, useEffect, useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
import {
closeSidePanel,
enqueueSnackbar,
unmountFrontComponent,
} from 'twenty-sdk/front-component';
import { SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
type DiscordGuild = {
id: string;
name: string;
};
type DiscordChannel = {
id: string;
name: string;
type: number;
parentId: string | null;
position: number;
};
const DISCORD_MESSAGE_MAX_LENGTH = 2000;
// The front component sandbox dispatches events with a non-standard shape.
// Values may live on e.detail.value, e.value, or e.target.value.
const readSerializedValue = (
e: React.SyntheticEvent<HTMLElement>,
): string | undefined => {
const obj = e as {
detail?: { value?: string };
value?: string;
target?: { value?: string };
};
if (typeof obj.detail?.value === 'string') return obj.detail.value;
if (typeof obj.value === 'string') return obj.value;
if (typeof obj.target?.value === 'string') return obj.target.value;
return undefined;
};
const onValueChange =
(fn: (value: string) => void) => (e: React.SyntheticEvent<HTMLElement>) => {
const v = readSerializedValue(e);
if (typeof v === 'string') fn(v);
};
const callAppRoute = async (
path: string,
method: 'GET' | 'POST',
body?: Record<string, unknown>,
) => {
const apiBaseUrl = process.env.TWENTY_API_URL;
const token =
process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
if (!apiBaseUrl || !token) {
throw new Error('API configuration missing');
}
const response = await fetch(`${apiBaseUrl}/s${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
...(body ? { body: JSON.stringify(body) } : {}),
});
if (!response.ok) {
const text = await response.text().catch(() => '');
throw new Error(
`Request failed (${response.status}): ${text.slice(0, 200)}`,
);
}
return response.json();
};
const COLOR = {
bg: '#f1f1f1',
card: '#ffffff',
surface: '#fcfcfc',
border: '#ebebeb',
borderStrong: '#d6d6d6',
text: '#333333',
textSecondary: '#666666',
textTertiary: '#999999',
placeholder: '#cccccc',
accent: '#5865f2',
error: '#e05252',
};
const INLINE_SELECT: React.CSSProperties = {
appearance: 'none',
WebkitAppearance: 'none' as const,
border: 'none',
background: 'transparent',
color: 'inherit',
font: 'inherit',
cursor: 'pointer',
outline: 'none',
padding: 0,
};
const STYLES = {
outer: {
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
fontSize: '13px',
backgroundColor: COLOR.bg,
padding: '12px',
height: '100%',
boxSizing: 'border-box' as const,
},
container: {
backgroundColor: COLOR.card,
borderRadius: '8px',
border: `1px solid ${COLOR.border}`,
display: 'flex',
flexDirection: 'column' as const,
height: '100%',
boxSizing: 'border-box' as const,
color: COLOR.text,
},
topBar: {
display: 'flex',
alignItems: 'center',
padding: '8px 12px',
borderBottom: `1px solid ${COLOR.border}`,
flexShrink: 0,
},
guildSelect: {
appearance: 'none' as const,
WebkitAppearance: 'none' as const,
background: COLOR.surface,
border: `1px solid ${COLOR.border}`,
borderRadius: '6px',
padding: '3px 8px',
color: COLOR.text,
fontSize: '12px',
fontFamily: 'inherit',
cursor: 'pointer',
outline: 'none',
},
channelPicker: {
flexShrink: 0,
padding: '12px',
display: 'flex',
alignItems: 'center',
gap: '6px',
borderBottom: `1px solid ${COLOR.border}`,
},
channelChip: {
appearance: 'none' as const,
WebkitAppearance: 'none' as const,
background: 'transparent',
border: `1px solid ${COLOR.border}`,
borderRadius: '20px',
padding: '3px 9px',
color: COLOR.textSecondary,
fontSize: '12px',
fontFamily: 'inherit',
cursor: 'pointer',
outline: 'none',
display: 'inline-flex' as const,
alignItems: 'center',
gap: '4px',
whiteSpace: 'nowrap' as const,
lineHeight: '16px',
},
chipIcon: {
fontSize: '12px',
lineHeight: 1,
flexShrink: 0,
color: COLOR.textTertiary,
},
optionsLoadingIndicator: {
marginLeft: 'auto',
display: 'flex',
alignItems: 'center',
},
spinner: {
width: '14px',
height: '14px',
border: `2px solid ${COLOR.border}`,
borderTopColor: COLOR.textSecondary,
borderRadius: '50%',
animation: 'spin 0.6s linear infinite',
},
body: {
flex: 1,
display: 'flex',
flexDirection: 'column' as const,
padding: '24px',
overflow: 'auto',
minHeight: 0,
gap: '6px',
},
messageInput: {
background: 'transparent',
border: 'none',
outline: 'none',
color: COLOR.text,
fontSize: '14px',
fontFamily: 'inherit',
width: '100%',
padding: 0,
resize: 'none' as const,
flex: 1,
minHeight: '120px',
},
charCount: {
fontSize: '11px',
textAlign: 'right' as const,
color: COLOR.textTertiary,
flexShrink: 0,
},
charCountOverLimit: {
color: COLOR.error,
},
actionBar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
gap: '6px',
padding: '6px 12px',
borderTop: `1px solid ${COLOR.border}`,
flexShrink: 0,
},
cancelButton: {
background: 'transparent',
border: 'none',
color: COLOR.textSecondary,
fontSize: '12px',
fontFamily: 'inherit',
cursor: 'pointer',
padding: '5px 10px',
borderRadius: '6px',
},
sendButton: {
background: COLOR.accent,
border: 'none',
color: '#ffffff',
fontSize: '12px',
fontWeight: 500,
fontFamily: 'inherit',
cursor: 'pointer',
padding: '5px 14px',
borderRadius: '6px',
},
sendButtonDisabled: {
background: COLOR.borderStrong,
color: COLOR.textTertiary,
cursor: 'not-allowed',
},
successOuter: {
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
backgroundColor: COLOR.bg,
padding: '12px',
height: '100%',
boxSizing: 'border-box' as const,
},
successContainer: {
display: 'flex',
alignItems: 'center',
gap: '10px',
padding: '10px 14px',
boxSizing: 'border-box' as const,
backgroundColor: COLOR.card,
borderRadius: '8px',
border: `1px solid ${COLOR.border}`,
color: COLOR.text,
},
successCheck: {
width: '20px',
height: '20px',
borderRadius: '50%',
backgroundColor: '#2ea043',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '11px',
color: '#fff',
flexShrink: 0,
},
successText: {
color: COLOR.text,
fontWeight: 500,
fontSize: '13px',
flex: 1,
overflow: 'hidden' as const,
textOverflow: 'ellipsis' as const,
whiteSpace: 'nowrap' as const,
},
closeButton: {
background: 'transparent',
border: `1px solid ${COLOR.border}`,
color: COLOR.textSecondary,
fontSize: '12px',
fontFamily: 'inherit',
cursor: 'pointer',
padding: '5px 14px',
borderRadius: '6px',
flexShrink: 0,
},
loadingOuter: {
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
backgroundColor: COLOR.bg,
padding: '12px',
height: '100%',
boxSizing: 'border-box' as const,
},
loadingContainer: {
color: COLOR.textSecondary,
fontSize: '13px',
textAlign: 'center' as const,
backgroundColor: COLOR.card,
borderRadius: '8px',
border: `1px solid ${COLOR.border}`,
padding: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
errorOuter: {
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
backgroundColor: COLOR.bg,
padding: '12px',
height: '100%',
boxSizing: 'border-box' as const,
},
errorContainer: {
backgroundColor: COLOR.card,
borderRadius: '8px',
border: `1px solid ${COLOR.border}`,
padding: '24px',
display: 'flex',
flexDirection: 'column' as const,
alignItems: 'center',
justifyContent: 'center',
gap: '10px',
},
errorText: {
color: COLOR.error,
fontSize: '12px',
textAlign: 'center' as const,
},
retryButton: {
background: COLOR.surface,
border: `1px solid ${COLOR.border}`,
color: COLOR.text,
fontSize: '12px',
fontFamily: 'inherit',
cursor: 'pointer',
padding: '5px 12px',
borderRadius: '6px',
},
};
const SendMessageForm = () => {
const [guilds, setGuilds] = useState<DiscordGuild[]>([]);
const [guildsLoading, setGuildsLoading] = useState(true);
const [guildsError, setGuildsError] = useState<string | null>(null);
const [channels, setChannels] = useState<DiscordChannel[]>([]);
const [channelsLoading, setChannelsLoading] = useState(false);
const [selectedGuildId, setSelectedGuildId] = useState('');
const [selectedChannelId, setSelectedChannelId] = useState('');
const [messageText, setMessageText] = useState('');
const [sending, setSending] = useState(false);
const [sentChannelName, setSentChannelName] = useState<string | null>(null);
const [closing, setClosing] = useState(false);
const fetchChannels = useCallback(async (guildId: string) => {
if (!guildId) {
setChannels([]);
setSelectedChannelId('');
return;
}
setChannelsLoading(true);
try {
const result = await callAppRoute(
`/discord/channels?guildId=${encodeURIComponent(guildId)}`,
'GET',
);
if (!result.success) {
setChannels([]);
await enqueueSnackbar({
message: result.error ?? 'Failed to load channels',
variant: 'error',
});
return;
}
setChannels(result.channels ?? []);
} catch (error) {
setChannels([]);
await enqueueSnackbar({
message:
error instanceof Error ? error.message : 'Failed to load channels',
variant: 'error',
});
} finally {
setChannelsLoading(false);
}
}, []);
const handleGuildChange = useCallback(
(newGuildId: string) => {
setSelectedGuildId(newGuildId);
setSelectedChannelId('');
fetchChannels(newGuildId);
},
[fetchChannels],
);
const fetchGuilds = useCallback(async () => {
try {
setGuildsError(null);
const result = await callAppRoute('/discord/guilds', 'GET');
if (!result.success) {
setGuildsError(result.error ?? 'Failed to load Discord servers');
return;
}
const fetchedGuilds: DiscordGuild[] = result.guilds ?? [];
setGuilds(fetchedGuilds);
// Auto-select when the bot is in exactly one server to skip a needless step.
if (fetchedGuilds.length === 1) {
setSelectedGuildId(fetchedGuilds[0].id);
fetchChannels(fetchedGuilds[0].id);
}
} catch (error) {
setGuildsError(
error instanceof Error
? error.message
: 'Failed to load Discord servers',
);
} finally {
setGuildsLoading(false);
}
}, [fetchChannels]);
useEffect(() => {
fetchGuilds();
}, [fetchGuilds]);
const handleSubmit = async () => {
const trimmedMessage = (messageText ?? '').trim();
if (
!selectedGuildId ||
!selectedChannelId ||
trimmedMessage.length === 0 ||
trimmedMessage.length > DISCORD_MESSAGE_MAX_LENGTH
) {
return;
}
setSending(true);
try {
const result = await callAppRoute('/discord/messages', 'POST', {
channelId: selectedChannelId,
messageText: trimmedMessage,
});
if (!result.success) {
await enqueueSnackbar({
message: result.error ?? 'Failed to send message',
variant: 'error',
});
setSending(false);
return;
}
const channelName =
channels.find((channel) => channel.id === selectedChannelId)?.name ??
'channel';
setSentChannelName(channelName);
await enqueueSnackbar({
message: `Message sent to #${channelName}`,
variant: 'success',
});
} catch (error) {
await enqueueSnackbar({
message:
error instanceof Error ? error.message : 'Failed to send message',
variant: 'error',
});
setSending(false);
}
};
const handleCancel = () => {
setClosing(true);
unmountFrontComponent();
closeSidePanel();
};
if (closing) {
return null;
}
if (sentChannelName) {
return (
<div style={STYLES.successOuter}>
<div style={STYLES.successContainer}>
<div style={STYLES.successCheck}>{'✓'}</div>
<span style={STYLES.successText}>
Message sent to #{sentChannelName}
</span>
<button
type="button"
style={STYLES.closeButton}
onClick={handleCancel}
>
Close
</button>
</div>
</div>
);
}
if (guildsLoading) {
return (
<div style={STYLES.loadingOuter}>
<div style={STYLES.loadingContainer}>Loading...</div>
</div>
);
}
if (guildsError) {
return (
<div style={STYLES.errorOuter}>
<div style={STYLES.errorContainer}>
<p style={STYLES.errorText}>{guildsError}</p>
<button
type="button"
style={STYLES.retryButton}
onClick={fetchGuilds}
>
Retry
</button>
</div>
</div>
);
}
const trimmedLength = (messageText ?? '').trim().length;
const isOverLimit = (messageText ?? '').length > DISCORD_MESSAGE_MAX_LENGTH;
const canSubmit =
!!selectedGuildId &&
!!selectedChannelId &&
trimmedLength > 0 &&
!isOverLimit &&
!sending;
return (
<div style={STYLES.outer}>
<style>{'@keyframes spin{to{transform:rotate(360deg)}}'}</style>
<div style={STYLES.container}>
<div style={STYLES.topBar}>
<select
value={selectedGuildId}
onChange={onValueChange(handleGuildChange)}
style={STYLES.guildSelect}
>
<option value="">Select server...</option>
{guilds.map((guild) => (
<option key={guild.id} value={guild.id}>
{guild.name}
</option>
))}
</select>
{channelsLoading && (
<div style={STYLES.optionsLoadingIndicator}>
<div style={STYLES.spinner} />
</div>
)}
</div>
<div style={STYLES.channelPicker}>
<label style={STYLES.channelChip}>
<span style={STYLES.chipIcon}>{'#'}</span>
<select
value={selectedChannelId}
onChange={onValueChange(setSelectedChannelId)}
style={INLINE_SELECT}
disabled={!selectedGuildId || channelsLoading}
>
<option value="">
{channels.length === 0 ? 'No channels' : 'Select channel'}
</option>
{channels.map((channel) => (
<option key={channel.id} value={channel.id}>
{channel.name}
</option>
))}
</select>
</label>
</div>
<div style={STYLES.body}>
<textarea
value={messageText}
onInput={onValueChange(setMessageText)}
onChange={onValueChange(setMessageText)}
style={STYLES.messageInput}
placeholder="Type a message..."
/>
<div
style={{
...STYLES.charCount,
...(isOverLimit ? STYLES.charCountOverLimit : {}),
}}
>
{(messageText ?? '').length} / {DISCORD_MESSAGE_MAX_LENGTH}
</div>
</div>
<div style={STYLES.actionBar}>
<button
type="button"
style={STYLES.cancelButton}
onClick={handleCancel}
>
Cancel
</button>
<button
type="button"
style={{
...STYLES.sendButton,
...(!canSubmit ? STYLES.sendButtonDisabled : {}),
}}
disabled={!canSubmit}
onClick={handleSubmit}
>
{sending ? 'Sending...' : 'Send'}
</button>
</div>
</div>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
name: 'send-discord-message-form',
description:
'Form to send a message to a Discord channel: pick the server, pick the channel, type the message, send.',
component: SendMessageForm,
});
@@ -0,0 +1,41 @@
export const APPLICATION_UNIVERSAL_IDENTIFIER =
'9b1b7628-ac98-4e44-8e1e-91b401378a5f';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'6aa50eb2-e8c2-470f-8c4d-09debbf6e051';
export const DISCORD_BOT_TOKEN_VARIABLE_UNIVERSAL_IDENTIFIER =
'ac986a3b-19a5-4cee-a87a-2f8d3885ad84';
export const DISCORD_POST_MESSAGE_UNIVERSAL_IDENTIFIER =
'6c03d456-c1fa-4acc-9928-d97ced0925fe';
export const DISCORD_UPDATE_MESSAGE_UNIVERSAL_IDENTIFIER =
'b37285d9-cb11-4fe5-963d-0434780122ba';
export const DISCORD_DELETE_MESSAGE_UNIVERSAL_IDENTIFIER =
'a2efc27c-eae0-4713-b327-bdb1aaeff95f';
export const DISCORD_ADD_REACTION_UNIVERSAL_IDENTIFIER =
'32c77c8a-ebd0-4308-aa1b-d21bb94422ac';
export const DISCORD_LIST_CHANNELS_UNIVERSAL_IDENTIFIER =
'ef2296f6-1b48-4edd-81ac-cafa4fe73f99';
export const DISCORD_LIST_CHANNELS_ROUTE_UNIVERSAL_IDENTIFIER =
'550cd993-af6e-46ae-b697-f838f34cfe39';
export const DISCORD_LIST_GUILDS_UNIVERSAL_IDENTIFIER =
'0aaaca4f-3156-4b7c-a9f5-ee6a2e17cbf1';
export const DISCORD_LIST_GUILDS_ROUTE_UNIVERSAL_IDENTIFIER =
'3cfa36d9-3c04-495c-bc09-f8019ad85dce';
export const DISCORD_POST_MESSAGE_ROUTE_UNIVERSAL_IDENTIFIER =
'eda60c95-450b-4a86-b5a0-6b85f488ae81';
export const SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
'ee8d8efa-2f9d-4d2c-b759-bb3cafc7797d';
export const SEND_DISCORD_MESSAGE_COMMAND_UNIVERSAL_IDENTIFIER =
'8c80b9d8-2f18-4b6c-8933-30d170790685';
@@ -0,0 +1,35 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_ADD_REACTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordAddReactionHandler } from 'src/logic-functions/handlers/discord-add-reaction-handler';
import { discordAddReactionInputSchema } from './schemas/discord-add-reaction-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_ADD_REACTION_UNIVERSAL_IDENTIFIER,
name: 'discord-add-reaction',
description:
'Add an emoji reaction to a Discord message (for example a checkmark ✅) so the channel can see status at a glance.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordAddReactionInputSchema,
},
workflowActionTriggerSettings: {
label: 'Add Discord Reaction',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordAddReactionInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
message: { type: 'string' },
error: { type: 'string' },
messageId: { type: 'string' },
channelId: { type: 'string' },
},
},
],
},
handler: discordAddReactionHandler,
});
@@ -0,0 +1,35 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_DELETE_MESSAGE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordDeleteMessageHandler } from 'src/logic-functions/handlers/discord-delete-message-handler';
import { discordDeleteMessageInputSchema } from './schemas/discord-delete-message-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_DELETE_MESSAGE_UNIVERSAL_IDENTIFIER,
name: 'discord-delete-message',
description:
'Remove a Discord message this bot sent (for example after a mistake or when a workflow is cancelled).',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordDeleteMessageInputSchema,
},
workflowActionTriggerSettings: {
label: 'Delete Discord Message',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordDeleteMessageInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
message: { type: 'string' },
error: { type: 'string' },
messageId: { type: 'string' },
channelId: { type: 'string' },
},
},
],
},
handler: discordDeleteMessageHandler,
});
@@ -0,0 +1,27 @@
import type { RoutePayload } from 'twenty-sdk/define';
import { defineLogicFunction } from 'twenty-sdk/define';
import { DISCORD_LIST_CHANNELS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordListChannelsHandler } from 'src/logic-functions/handlers/discord-list-channels-handler';
const handler = async (event: RoutePayload) => {
const params = event.queryStringParameters ?? {};
const guildId =
typeof params.guildId === 'string' && params.guildId.length > 0
? params.guildId
: undefined;
return discordListChannelsHandler({ guildId });
};
export default defineLogicFunction({
universalIdentifier: DISCORD_LIST_CHANNELS_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'discord-list-channels-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/discord/channels',
httpMethod: 'GET',
isAuthRequired: true,
},
});
@@ -0,0 +1,46 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_LIST_CHANNELS_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordListChannelsHandler } from 'src/logic-functions/handlers/discord-list-channels-handler';
import { discordListChannelsInputSchema } from './schemas/discord-list-channels-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_LIST_CHANNELS_UNIVERSAL_IDENTIFIER,
name: 'discord-list-channels',
description:
'List the text-postable channels (types `GUILD_TEXT` and `GUILD_ANNOUNCEMENT`) in a Discord server, sorted by Discord position. Each entry exposes `id`, `name`, `type`, `parentId` (category), and `position`. Pass the server (guild) ID to scope the listing; if omitted and the bot is in exactly one server, that server is auto-selected. If the bot is in multiple servers, the error response lists each server name and ID for explicit selection. Voice channels, threads, forums, and categories are excluded.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordListChannelsInputSchema,
},
workflowActionTriggerSettings: {
label: 'List Discord Channels',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordListChannelsInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
channels: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
type: { type: 'number' },
parentId: { type: 'string' },
position: { type: 'number' },
},
},
},
count: { type: 'number' },
error: { type: 'string' },
},
},
],
},
handler: discordListChannelsHandler,
});
@@ -0,0 +1,21 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/define';
import { DISCORD_LIST_GUILDS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordListGuildsHandler } from 'src/logic-functions/handlers/discord-list-guilds-handler';
const handler = async (_event: RoutePayload) => {
return discordListGuildsHandler();
};
export default defineLogicFunction({
universalIdentifier: DISCORD_LIST_GUILDS_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'discord-list-guilds-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/discord/guilds',
httpMethod: 'GET',
isAuthRequired: true,
},
});
@@ -0,0 +1,43 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_LIST_GUILDS_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordListGuildsHandler } from 'src/logic-functions/handlers/discord-list-guilds-handler';
import { discordListGuildsInputSchema } from './schemas/discord-list-guilds-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_LIST_GUILDS_UNIVERSAL_IDENTIFIER,
name: 'discord-list-guilds',
description:
'List the Discord servers (guilds) the bot has been invited to. Each entry exposes `id` and `name`. Use this to discover which servers the bot can post in, then pass the chosen server ID to `discord-list-channels` or other server-scoped actions.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordListGuildsInputSchema,
},
workflowActionTriggerSettings: {
label: 'List Discord Servers',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordListGuildsInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
guilds: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
},
},
count: { type: 'number' },
error: { type: 'string' },
},
},
],
},
handler: discordListGuildsHandler,
});
@@ -0,0 +1,45 @@
import { isNonEmptyString } from '@sniptt/guards';
import type { RoutePayload } from 'twenty-sdk/define';
import { defineLogicFunction } from 'twenty-sdk/define';
import { DISCORD_POST_MESSAGE_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordPostMessageHandler } from 'src/logic-functions/handlers/discord-post-message-handler';
const handler = async (event: RoutePayload) => {
const body = event.body as Record<string, unknown> | null;
const rawChannelId = body?.channelId;
const rawMessageText = body?.messageText;
if (typeof rawChannelId !== 'string' || typeof rawMessageText !== 'string') {
return {
success: false,
message: 'Failed to post Discord message',
error: '`channelId` and `messageText` must be strings.',
};
}
const channelId = rawChannelId.trim();
const messageText = rawMessageText.trim();
if (!isNonEmptyString(channelId) || !isNonEmptyString(messageText)) {
return {
success: false,
message: 'Failed to post Discord message',
error: '`channelId` and `messageText` are required.',
};
}
return discordPostMessageHandler({ channelId, messageText });
};
export default defineLogicFunction({
universalIdentifier: DISCORD_POST_MESSAGE_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'discord-post-message-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/discord/messages',
httpMethod: 'POST',
isAuthRequired: true,
},
});
@@ -0,0 +1,35 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_POST_MESSAGE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordPostMessageHandler } from 'src/logic-functions/handlers/discord-post-message-handler';
import { discordPostMessageInputSchema } from './schemas/discord-post-message-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_POST_MESSAGE_UNIVERSAL_IDENTIFIER,
name: 'discord-post-message',
description:
'Send a message to a Discord channel as the bot. Optionally reply to an existing message using its message ID from a previous step.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordPostMessageInputSchema,
},
workflowActionTriggerSettings: {
label: 'Send Discord Message',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordPostMessageInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
message: { type: 'string' },
error: { type: 'string' },
messageId: { type: 'string' },
channelId: { type: 'string' },
},
},
],
},
handler: discordPostMessageHandler,
});
@@ -0,0 +1,35 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-shared/logic-function';
import { DISCORD_UPDATE_MESSAGE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { discordUpdateMessageHandler } from 'src/logic-functions/handlers/discord-update-message-handler';
import { discordUpdateMessageInputSchema } from './schemas/discord-update-message-input.schema';
export default defineLogicFunction({
universalIdentifier: DISCORD_UPDATE_MESSAGE_UNIVERSAL_IDENTIFIER,
name: 'discord-update-message',
description:
'Change the text of a Discord message this bot already sent. You need the channel ID and the message ID from when it was posted.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: discordUpdateMessageInputSchema,
},
workflowActionTriggerSettings: {
label: 'Update Discord Message',
icon: 'IconBrandDiscord',
inputSchema: jsonSchemaToInputSchema(discordUpdateMessageInputSchema),
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
message: { type: 'string' },
error: { type: 'string' },
messageId: { type: 'string' },
channelId: { type: 'string' },
},
},
],
},
handler: discordUpdateMessageHandler,
});
@@ -0,0 +1,52 @@
import { type DiscordAddReactionInput } from 'src/logic-functions/types/discord-add-reaction-input.type';
import { type DiscordToolResult } from 'src/logic-functions/types/discord-tool-result.type';
import { buildDiscordFailureResult } from 'src/logic-functions/utils/build-discord-failure-result';
import { discordApiRequest } from 'src/logic-functions/utils/discord-api-request';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
export const discordAddReactionHandler = async (
parameters: DiscordAddReactionInput,
): Promise<DiscordToolResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return {
success: false,
message: 'Discord is not configured',
error: tokenResult.error,
};
}
const channelId = parameters.channelId.trim();
const messageId = parameters.messageId.trim();
const emoji = parameters.emoji.trim();
const encodedChannelId = encodeURIComponent(channelId);
const encodedMessageId = encodeURIComponent(messageId);
const encodedEmoji = encodeURIComponent(emoji);
try {
const result = await discordApiRequest({
botToken: tokenResult.botToken,
method: 'PUT',
path: `/channels/${encodedChannelId}/messages/${encodedMessageId}/reactions/${encodedEmoji}/@me`,
});
if (!result.ok) {
return {
success: false,
message: 'Failed to add Discord reaction',
error: result.errorMessage,
};
}
return {
success: true,
message: `Reaction "${emoji}" added to the message.`,
messageId,
channelId,
};
} catch (error) {
return buildDiscordFailureResult('Failed to add Discord reaction', error);
}
};
@@ -0,0 +1,46 @@
import { type DiscordDeleteMessageInput } from 'src/logic-functions/types/discord-delete-message-input.type';
import { type DiscordToolResult } from 'src/logic-functions/types/discord-tool-result.type';
import { buildDiscordFailureResult } from 'src/logic-functions/utils/build-discord-failure-result';
import { discordApiRequest } from 'src/logic-functions/utils/discord-api-request';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
export const discordDeleteMessageHandler = async (
parameters: DiscordDeleteMessageInput,
): Promise<DiscordToolResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return {
success: false,
message: 'Discord is not configured',
error: tokenResult.error,
};
}
try {
const result = await discordApiRequest({
botToken: tokenResult.botToken,
method: 'DELETE',
path: `/channels/${encodeURIComponent(
parameters.channelId,
)}/messages/${encodeURIComponent(parameters.messageId)}`,
});
if (!result.ok) {
return {
success: false,
message: 'Failed to delete Discord message',
error: result.errorMessage,
};
}
return {
success: true,
message: 'Discord message deleted.',
messageId: parameters.messageId,
channelId: parameters.channelId,
};
} catch (error) {
return buildDiscordFailureResult('Failed to delete Discord message', error);
}
};
@@ -0,0 +1,125 @@
import { isDefined } from 'twenty-shared/utils';
import { type DiscordListChannelsInput } from 'src/logic-functions/types/discord-list-channels-input.type';
import {
type DiscordListChannelsResult,
type DiscordListChannelsResultChannel,
} from 'src/logic-functions/types/discord-list-channels-output.type';
import { discordApiRequest } from 'src/logic-functions/utils/discord-api-request';
import { fetchDiscordGuilds } from 'src/logic-functions/utils/fetch-discord-guilds';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
const GUILD_TEXT_CHANNEL_TYPE = 0;
const GUILD_ANNOUNCEMENT_CHANNEL_TYPE = 5;
const POSTABLE_CHANNEL_TYPES = new Set<number>([
GUILD_TEXT_CHANNEL_TYPE,
GUILD_ANNOUNCEMENT_CHANNEL_TYPE,
]);
type DiscordChannelResponse = {
id: string;
name: string | null;
type: number;
parent_id: string | null;
position: number;
};
const buildFailureResult = (error: string): DiscordListChannelsResult => ({
success: false,
channels: [],
count: 0,
error,
});
const resolveGuildId = async ({
botToken,
providedGuildId,
}: {
botToken: string;
providedGuildId: string | undefined;
}): Promise<{ ok: true; guildId: string } | { ok: false; error: string }> => {
const trimmed = providedGuildId?.trim();
if (isDefined(trimmed) && trimmed.length > 0) {
return { ok: true, guildId: trimmed };
}
const guildsResult = await fetchDiscordGuilds(botToken);
if (!guildsResult.ok) {
return { ok: false, error: guildsResult.errorMessage };
}
const guilds = guildsResult.data;
if (guilds.length === 0) {
return {
ok: false,
error:
'Bot has not been invited to any Discord server yet. Use the OAuth2 → URL Generator in the Discord Developer Portal (scope `bot`) to invite it to a server, then retry.',
};
}
if (guilds.length > 1) {
const guildList = guilds
.map((guild) => `"${guild.name}" (${guild.id})`)
.join(', ');
return {
ok: false,
error: `Bot is in ${guilds.length} Discord servers — please specify which one via the guildId input. Available: ${guildList}.`,
};
}
return { ok: true, guildId: guilds[0].id };
};
export const discordListChannelsHandler = async (
parameters: DiscordListChannelsInput,
): Promise<DiscordListChannelsResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return buildFailureResult(tokenResult.error);
}
const guildIdResult = await resolveGuildId({
botToken: tokenResult.botToken,
providedGuildId: parameters.guildId,
});
if (!guildIdResult.ok) {
return buildFailureResult(guildIdResult.error);
}
const result = await discordApiRequest<DiscordChannelResponse[]>({
botToken: tokenResult.botToken,
method: 'GET',
path: `/guilds/${encodeURIComponent(guildIdResult.guildId)}/channels`,
});
if (!result.ok) {
return buildFailureResult(result.errorMessage);
}
const channels: DiscordListChannelsResultChannel[] = result.data
.filter(
(channel) =>
POSTABLE_CHANNEL_TYPES.has(channel.type) && isDefined(channel.name),
)
.map((channel) => ({
id: channel.id,
name: channel.name as string,
type: channel.type,
parentId: channel.parent_id,
position: channel.position,
}))
.sort((a, b) => a.position - b.position);
return {
success: true,
channels,
count: channels.length,
};
};
@@ -0,0 +1,36 @@
import { type DiscordListGuildsResult } from 'src/logic-functions/types/discord-list-guilds-output.type';
import { fetchDiscordGuilds } from 'src/logic-functions/utils/fetch-discord-guilds';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
const buildFailureResult = (error: string): DiscordListGuildsResult => ({
success: false,
guilds: [],
count: 0,
error,
});
export const discordListGuildsHandler =
async (): Promise<DiscordListGuildsResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return buildFailureResult(tokenResult.error);
}
const result = await fetchDiscordGuilds(tokenResult.botToken);
if (!result.ok) {
return buildFailureResult(result.errorMessage);
}
const guilds = result.data.map((guild) => ({
id: guild.id,
name: guild.name,
}));
return {
success: true,
guilds,
count: guilds.length,
};
};
@@ -0,0 +1,61 @@
import { isDefined } from 'twenty-shared/utils';
import { type DiscordPostMessageInput } from 'src/logic-functions/types/discord-post-message-input.type';
import { type DiscordToolResult } from 'src/logic-functions/types/discord-tool-result.type';
import { buildDiscordFailureResult } from 'src/logic-functions/utils/build-discord-failure-result';
import { discordApiRequest } from 'src/logic-functions/utils/discord-api-request';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
type DiscordMessageResponse = {
id: string;
channel_id: string;
};
export const discordPostMessageHandler = async (
parameters: DiscordPostMessageInput,
): Promise<DiscordToolResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return {
success: false,
message: 'Discord is not configured',
error: tokenResult.error,
};
}
const replyToMessageId = parameters.replyToMessageId?.trim();
const body: Record<string, unknown> = {
content: parameters.messageText,
};
if (isDefined(replyToMessageId) && replyToMessageId.length > 0) {
body.message_reference = { message_id: replyToMessageId };
}
try {
const result = await discordApiRequest<DiscordMessageResponse>({
botToken: tokenResult.botToken,
method: 'POST',
path: `/channels/${encodeURIComponent(parameters.channelId)}/messages`,
body,
});
if (!result.ok) {
return {
success: false,
message: 'Failed to post Discord message',
error: result.errorMessage,
};
}
return {
success: true,
message: `Message posted to Discord (id=${result.data.id}).`,
messageId: result.data.id,
channelId: result.data.channel_id,
};
} catch (error) {
return buildDiscordFailureResult('Failed to post Discord message', error);
}
};
@@ -0,0 +1,52 @@
import { type DiscordToolResult } from 'src/logic-functions/types/discord-tool-result.type';
import { type DiscordUpdateMessageInput } from 'src/logic-functions/types/discord-update-message-input.type';
import { buildDiscordFailureResult } from 'src/logic-functions/utils/build-discord-failure-result';
import { discordApiRequest } from 'src/logic-functions/utils/discord-api-request';
import { getDiscordBotToken } from 'src/logic-functions/utils/get-discord-bot-token';
type DiscordMessageResponse = {
id: string;
channel_id: string;
};
export const discordUpdateMessageHandler = async (
parameters: DiscordUpdateMessageInput,
): Promise<DiscordToolResult> => {
const tokenResult = getDiscordBotToken();
if (!tokenResult.success) {
return {
success: false,
message: 'Discord is not configured',
error: tokenResult.error,
};
}
try {
const result = await discordApiRequest<DiscordMessageResponse>({
botToken: tokenResult.botToken,
method: 'PATCH',
path: `/channels/${encodeURIComponent(
parameters.channelId,
)}/messages/${encodeURIComponent(parameters.messageId)}`,
body: { content: parameters.newMessageText },
});
if (!result.ok) {
return {
success: false,
message: 'Failed to update Discord message',
error: result.errorMessage,
};
}
return {
success: true,
message: 'Discord message updated.',
messageId: result.data.id,
channelId: result.data.channel_id,
};
} catch (error) {
return buildDiscordFailureResult('Failed to update Discord message', error);
}
};
@@ -0,0 +1,26 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordAddReactionInputSchema: InputJsonSchema = {
type: 'object',
properties: {
channelId: {
type: 'string',
label: 'Discord channel ID',
description: 'Channel where the message lives (Discord channel ID).',
},
messageId: {
type: 'string',
label: 'Message ID',
description:
'The message to react to: its **message ID** (same value as `messageId` returned when the bot posted it, or right-click the message → "Copy Message ID").',
},
emoji: {
type: 'string',
label: 'Emoji',
description:
'Emoji to add. For a standard unicode emoji, paste the emoji directly (e.g. `👍`, `✅`, `🎉`). For a custom server emoji, use the format `name:id` (e.g. `partyparrot:643452342342342342`). The bot must have **Add Reactions** permission in the channel.',
},
},
required: ['channelId', 'messageId', 'emoji'],
additionalProperties: false,
};
@@ -0,0 +1,20 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordDeleteMessageInputSchema: InputJsonSchema = {
type: 'object',
properties: {
channelId: {
type: 'string',
label: 'Discord channel ID',
description: 'Channel that contains the message (Discord channel ID).',
},
messageId: {
type: 'string',
label: 'Message ID',
description:
'Which message to remove: its **message ID** (same value as `messageId` returned when the bot posted it). The bot can delete its own messages without extra permissions; deleting others\' messages requires the **Manage Messages** permission.',
},
},
required: ['channelId', 'messageId'],
additionalProperties: false,
};
@@ -0,0 +1,15 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordListChannelsInputSchema: InputJsonSchema = {
type: 'object',
properties: {
guildId: {
type: 'string',
label: 'Discord server (guild) ID',
description:
'Optional. The Discord server ID to list channels for (a long number like `1234567890123456789`). In Discord with Developer Mode on: right-click the server icon → "Copy Server ID". If omitted and the bot is in exactly one server, that server is used automatically; if the bot is in multiple servers, the error response lists each server name and ID so you can pick one.',
},
},
required: [],
additionalProperties: false,
};
@@ -0,0 +1,8 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordListGuildsInputSchema: InputJsonSchema = {
type: 'object',
properties: {},
required: [],
additionalProperties: false,
};
@@ -0,0 +1,28 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordPostMessageInputSchema: InputJsonSchema = {
type: 'object',
properties: {
channelId: {
type: 'string',
label: 'Discord channel ID',
description:
'Where to post: the Discord channel ID (a long number like `1234567890123456789`). In Discord with Developer Mode on: right-click the channel → "Copy Channel ID". The bot must be a member of the server and have permission to send messages in this channel.',
},
messageText: {
type: 'string',
label: 'Message',
multiline: true,
description:
'The message text. Supports Discord-flavoured markdown (**bold**, *italics*, ```code blocks```, lists, links). Max 2000 characters — Discord rejects longer payloads.',
},
replyToMessageId: {
type: 'string',
label: 'Reply to message ID',
description:
'Optional. To reply to an existing message, paste its ID (from a previous step\'s `messageId` output, or right-click the message → "Copy Message ID"). Leave empty for a normal new message.',
},
},
required: ['channelId', 'messageText'],
additionalProperties: false,
};
@@ -0,0 +1,27 @@
import { type InputJsonSchema } from 'twenty-sdk/logic-function';
export const discordUpdateMessageInputSchema: InputJsonSchema = {
type: 'object',
properties: {
channelId: {
type: 'string',
label: 'Discord channel ID',
description: 'Channel that contains the message (Discord channel ID).',
},
messageId: {
type: 'string',
label: 'Message ID',
description:
'Which message to edit: its **message ID** (same value as `messageId` returned when the bot posted the message). You can only edit messages this bot sent.',
},
newMessageText: {
type: 'string',
label: 'New message',
multiline: true,
description:
'Replacement text shown in Discord instead of the old content. Supports Discord-flavoured markdown. Max 2000 characters.',
},
},
required: ['channelId', 'messageId', 'newMessageText'],
additionalProperties: false,
};
@@ -0,0 +1,5 @@
export type DiscordAddReactionInput = {
channelId: string;
messageId: string;
emoji: string;
};
@@ -0,0 +1,4 @@
export type DiscordDeleteMessageInput = {
channelId: string;
messageId: string;
};
@@ -0,0 +1,3 @@
export type DiscordListChannelsInput = {
guildId?: string;
};
@@ -0,0 +1,14 @@
export type DiscordListChannelsResultChannel = {
id: string;
name: string;
type: number;
parentId: string | null;
position: number;
};
export type DiscordListChannelsResult = {
success: boolean;
channels: DiscordListChannelsResultChannel[];
count: number;
error?: string;
};
@@ -0,0 +1,11 @@
export type DiscordListGuildsResultGuild = {
id: string;
name: string;
};
export type DiscordListGuildsResult = {
success: boolean;
guilds: DiscordListGuildsResultGuild[];
count: number;
error?: string;
};
@@ -0,0 +1,5 @@
export type DiscordPostMessageInput = {
channelId: string;
messageText: string;
replyToMessageId?: string;
};
@@ -0,0 +1,7 @@
export type DiscordToolResult = {
success: boolean;
message: string;
error?: string;
messageId?: string;
channelId?: string;
};
@@ -0,0 +1,5 @@
export type DiscordUpdateMessageInput = {
channelId: string;
messageId: string;
newMessageText: string;
};
@@ -0,0 +1,10 @@
import { type DiscordToolResult } from 'src/logic-functions/types/discord-tool-result.type';
export const buildDiscordFailureResult = (
message: string,
error: unknown,
): DiscordToolResult => ({
success: false,
message,
error: error instanceof Error ? error.message : 'Discord request failed',
});
@@ -0,0 +1,108 @@
import { isDefined } from 'twenty-shared/utils';
const DISCORD_API_BASE_URL = 'https://discord.com/api/v10';
type DiscordApiSuccess<TData> = {
ok: true;
status: number;
data: TData;
};
type DiscordApiFailure = {
ok: false;
status: number;
errorMessage: string;
};
export type DiscordApiResult<TData> =
| DiscordApiSuccess<TData>
| DiscordApiFailure;
type DiscordErrorBody = {
code?: number;
message?: string;
};
type DiscordApiRequestParams = {
botToken: string;
path: string;
method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
body?: unknown;
};
export const discordApiRequest = async <TData = unknown>({
botToken,
path,
method,
body,
}: DiscordApiRequestParams): Promise<DiscordApiResult<TData>> => {
const url = `${DISCORD_API_BASE_URL}${path}`;
const hasBody = isDefined(body);
let response: Response;
try {
response = await fetch(url, {
method,
headers: {
Authorization: `Bot ${botToken}`,
...(hasBody ? { 'Content-Type': 'application/json' } : {}),
},
...(hasBody ? { body: JSON.stringify(body) } : {}),
});
} catch (error) {
return {
ok: false,
status: 0,
errorMessage: `Discord API request failed: ${(error as Error).message}`,
};
}
if (response.status === 204) {
return { ok: true, status: 204, data: undefined as TData };
}
if (!response.ok) {
const errorMessage = await extractDiscordErrorMessage(response);
return {
ok: false,
status: response.status,
errorMessage,
};
}
try {
const data = (await response.json()) as TData;
return { ok: true, status: response.status, data };
} catch (error) {
return {
ok: false,
status: response.status,
errorMessage: `Discord API returned a non-JSON response: ${
(error as Error).message
}`,
};
}
};
const extractDiscordErrorMessage = async (
response: Response,
): Promise<string> => {
const fallback = `Discord API responded with ${response.status}`;
try {
const errorBody = (await response.json()) as DiscordErrorBody;
if (isDefined(errorBody.message)) {
return isDefined(errorBody.code)
? `${errorBody.message} (Discord error code ${errorBody.code})`
: errorBody.message;
}
return fallback;
} catch {
return fallback;
}
};
@@ -0,0 +1,18 @@
import {
discordApiRequest,
type DiscordApiResult,
} from 'src/logic-functions/utils/discord-api-request';
export type DiscordGuildResponse = {
id: string;
name: string;
};
export const fetchDiscordGuilds = (
botToken: string,
): Promise<DiscordApiResult<DiscordGuildResponse[]>> =>
discordApiRequest<DiscordGuildResponse[]>({
botToken,
method: 'GET',
path: '/users/@me/guilds',
});
@@ -0,0 +1,19 @@
import { isDefined } from 'twenty-shared/utils';
export const DISCORD_BOT_TOKEN_ENV_VAR = 'DISCORD_BOT_TOKEN';
export const getDiscordBotToken = ():
| { success: true; botToken: string }
| { success: false; error: string } => {
const botToken = process.env[DISCORD_BOT_TOKEN_ENV_VAR];
if (!isDefined(botToken) || botToken.length === 0) {
return {
success: false,
error:
'Discord is not configured. Open the Twenty Discord app settings and set the DISCORD_BOT_TOKEN application variable (Developer Portal → Bot tab → Reset Token).',
};
}
return { success: true, botToken };
};
@@ -0,0 +1,21 @@
import { defineApplicationRole } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Twenty Discord tools role',
description:
'No CRM data access — tools only forward requests to Discord using the configured bot token.',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
});
@@ -0,0 +1,31 @@
{
"compileOnSave": false,
"compilerOptions": {
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"jsx": "react-jsx",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"allowUnreachableCode": false,
"strict": true,
"alwaysStrict": true,
"noImplicitAny": true,
"strictBindCallApply": false,
"target": "es2020",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
"paths": {
"src/*": ["./src/*"],
"~/*": ["./*"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
},
});
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "2.4.0",
"version": "2.4.2",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -323,6 +323,7 @@ type FrontComponent {
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
applicationVariables: JSON
}
type CommandMenuItem {
@@ -812,6 +813,7 @@ type ViewSort {
id: UUID!
fieldMetadataId: UUID!
direction: ViewSortDirection!
subFieldName: String
viewId: UUID!
workspaceId: UUID!
createdAt: DateTime!
@@ -1486,7 +1488,6 @@ enum BillingUsageType {
enum BillingProductKey {
BASE_PRODUCT
RESOURCE_CREDIT
WORKFLOW_NODE_EXECUTION
}
type BillingPriceLicensed {
@@ -1580,7 +1581,7 @@ type BillingEndTrialPeriod {
billingPortalUrl: String
}
type BillingMeteredProductUsage {
type BillingResourceCreditUsage {
productKey: BillingProductKey!
periodStart: DateTime!
periodEnd: DateTime!
@@ -1749,13 +1750,11 @@ enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_MARKETPLACE_SETTING_TAB_VISIBLE
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_EMAIL_GROUP_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_BILLING_V2_ENABLED
IS_REST_METADATA_API_NEW_FORMAT_DIRECT
}
type WorkspaceUrls {
@@ -2444,13 +2443,6 @@ type ImapSmtpCaldavConnectionSuccess {
connectedAccountId: String!
}
type PostgresCredentials {
id: UUID!
user: String!
password: String!
workspaceId: UUID!
}
type ToolIndexEntry {
name: String!
description: String!
@@ -2961,7 +2953,7 @@ type Query {
enterpriseSubscriptionStatus: EnterpriseSubscriptionStatusDTO
billingPortalSession(returnUrlPath: String): BillingSession!
listPlans: [BillingPlan!]!
getMeteredProductsUsage: [BillingMeteredProductUsage!]!
getResourceCreditUsage: [BillingResourceCreditUsage!]!
findWorkspaceInvitations: [WorkspaceInvitation!]!
getApprovedAccessDomains: [ApprovedAccessDomain!]!
getPageLayoutTabs(pageLayoutId: String!): [PageLayoutTab!]!
@@ -3063,7 +3055,6 @@ type Query {
getAutoCompleteAddress(address: String!, token: String!, country: String, isFieldCity: Boolean): [AutocompleteResult!]!
getAddressDetails(placeId: String!, token: String!): PlaceDetailsResult!
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
getPostgresCredentials: PostgresCredentials
findManyPublicDomains: [PublicDomain!]!
getEmailingDomains: [EmailingDomain!]!
findManyMarketplaceApps: [MarketplaceApp!]!
@@ -3201,9 +3192,9 @@ type Mutation {
switchBillingPlan: BillingUpdate!
cancelSwitchBillingPlan: BillingUpdate!
cancelSwitchBillingInterval: BillingUpdate!
setMeteredSubscriptionPrice(priceId: String!): BillingUpdate!
setResourceCreditSubscriptionPrice(priceId: String!): BillingUpdate!
endSubscriptionTrialPeriod: BillingEndTrialPeriod!
cancelSwitchMeteredPrice: BillingUpdate!
cancelSwitchResourceCreditPrice: BillingUpdate!
deleteWorkspaceInvitation(appTokenId: String!): String!
resendWorkspaceInvitation(appTokenId: String!): SendInvitations!
sendInvitations(emails: [String!]!, roleId: UUID): SendInvitations!
@@ -3335,8 +3326,6 @@ type Mutation {
startChannelSync(connectedAccountId: UUID!): ChannelSyncSuccess!
saveImapSmtpCaldavAccount(accountOwnerId: UUID!, handle: String!, connectionParameters: EmailAccountConnectionParameters!, id: UUID): ImapSmtpCaldavConnectionSuccess!
updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag!
enablePostgresProxy: PostgresCredentials!
disablePostgresProxy: PostgresCredentials!
createPublicDomain(domain: String!, applicationId: String): PublicDomain!
updatePublicDomain(domain: String!, applicationId: String): PublicDomain!
deletePublicDomain(domain: String!): Boolean!
@@ -3552,6 +3541,7 @@ input CreateViewSortInput {
id: UUID
fieldMetadataId: UUID!
direction: ViewSortDirection = ASC
subFieldName: String
viewId: UUID!
}
@@ -3565,6 +3555,7 @@ input UpdateViewSortInput {
input UpdateViewSortInputUpdates {
direction: ViewSortDirection
subFieldName: String
}
input DeleteViewSortInput {
@@ -276,6 +276,7 @@ export interface FrontComponent {
isHeadless: Scalars['Boolean']
usesSdkClient: Scalars['Boolean']
applicationTokenPair?: ApplicationTokenPair
applicationVariables?: Scalars['JSON']
__typename: 'FrontComponent'
}
@@ -549,6 +550,7 @@ export interface ViewSort {
id: Scalars['UUID']
fieldMetadataId: Scalars['UUID']
direction: ViewSortDirection
subFieldName?: Scalars['String']
viewId: Scalars['UUID']
workspaceId: Scalars['UUID']
createdAt: Scalars['DateTime']
@@ -1146,7 +1148,7 @@ export type BillingUsageType = 'METERED' | 'LICENSED'
/** The different billing products available */
export type BillingProductKey = 'BASE_PRODUCT' | 'RESOURCE_CREDIT' | 'WORKFLOW_NODE_EXECUTION'
export type BillingProductKey = 'BASE_PRODUCT' | 'RESOURCE_CREDIT'
export interface BillingPriceLicensed {
recurringInterval: SubscriptionInterval
@@ -1232,7 +1234,7 @@ export interface BillingEndTrialPeriod {
__typename: 'BillingEndTrialPeriod'
}
export interface BillingMeteredProductUsage {
export interface BillingResourceCreditUsage {
productKey: BillingProductKey
periodStart: Scalars['DateTime']
periodEnd: Scalars['DateTime']
@@ -1241,7 +1243,7 @@ export interface BillingMeteredProductUsage {
rolloverCredits: Scalars['Float']
totalGrantedCredits: Scalars['Float']
unitPriceCents: Scalars['Float']
__typename: 'BillingMeteredProductUsage'
__typename: 'BillingResourceCreditUsage'
}
export interface BillingPlan {
@@ -1389,7 +1391,7 @@ export interface FeatureFlag {
__typename: 'FeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT'
export interface WorkspaceUrls {
customUrl?: Scalars['String']
@@ -2122,14 +2124,6 @@ export interface ImapSmtpCaldavConnectionSuccess {
__typename: 'ImapSmtpCaldavConnectionSuccess'
}
export interface PostgresCredentials {
id: Scalars['UUID']
user: Scalars['String']
password: Scalars['String']
workspaceId: Scalars['UUID']
__typename: 'PostgresCredentials'
}
export interface ToolIndexEntry {
name: Scalars['String']
description: Scalars['String']
@@ -2588,7 +2582,7 @@ export interface Query {
enterpriseSubscriptionStatus?: EnterpriseSubscriptionStatusDTO
billingPortalSession: BillingSession
listPlans: BillingPlan[]
getMeteredProductsUsage: BillingMeteredProductUsage[]
getResourceCreditUsage: BillingResourceCreditUsage[]
findWorkspaceInvitations: WorkspaceInvitation[]
getApprovedAccessDomains: ApprovedAccessDomain[]
getPageLayoutTabs: PageLayoutTab[]
@@ -2663,7 +2657,6 @@ export interface Query {
getAutoCompleteAddress: AutocompleteResult[]
getAddressDetails: PlaceDetailsResult
getUsageAnalytics: UsageAnalytics
getPostgresCredentials?: PostgresCredentials
findManyPublicDomains: PublicDomain[]
getEmailingDomains: EmailingDomain[]
findManyMarketplaceApps: MarketplaceApp[]
@@ -2734,9 +2727,9 @@ export interface Mutation {
switchBillingPlan: BillingUpdate
cancelSwitchBillingPlan: BillingUpdate
cancelSwitchBillingInterval: BillingUpdate
setMeteredSubscriptionPrice: BillingUpdate
setResourceCreditSubscriptionPrice: BillingUpdate
endSubscriptionTrialPeriod: BillingEndTrialPeriod
cancelSwitchMeteredPrice: BillingUpdate
cancelSwitchResourceCreditPrice: BillingUpdate
deleteWorkspaceInvitation: Scalars['String']
resendWorkspaceInvitation: SendInvitations
sendInvitations: SendInvitations
@@ -2868,8 +2861,6 @@ export interface Mutation {
startChannelSync: ChannelSyncSuccess
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess
updateLabPublicFeatureFlag: FeatureFlag
enablePostgresProxy: PostgresCredentials
disablePostgresProxy: PostgresCredentials
createPublicDomain: PublicDomain
updatePublicDomain: PublicDomain
deletePublicDomain: Scalars['Boolean']
@@ -3173,6 +3164,7 @@ export interface FrontComponentGenqlSelection{
isHeadless?: boolean | number
usesSdkClient?: boolean | number
applicationTokenPair?: ApplicationTokenPairGenqlSelection
applicationVariables?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -3486,6 +3478,7 @@ export interface ViewSortGenqlSelection{
id?: boolean | number
fieldMetadataId?: boolean | number
direction?: boolean | number
subFieldName?: boolean | number
viewId?: boolean | number
workspaceId?: boolean | number
createdAt?: boolean | number
@@ -4192,7 +4185,7 @@ export interface BillingEndTrialPeriodGenqlSelection{
__scalar?: boolean | number
}
export interface BillingMeteredProductUsageGenqlSelection{
export interface BillingResourceCreditUsageGenqlSelection{
productKey?: boolean | number
periodStart?: boolean | number
periodEnd?: boolean | number
@@ -5154,15 +5147,6 @@ export interface ImapSmtpCaldavConnectionSuccessGenqlSelection{
__scalar?: boolean | number
}
export interface PostgresCredentialsGenqlSelection{
id?: boolean | number
user?: boolean | number
password?: boolean | number
workspaceId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ToolIndexEntryGenqlSelection{
name?: boolean | number
description?: boolean | number
@@ -5633,7 +5617,7 @@ export interface QueryGenqlSelection{
enterpriseSubscriptionStatus?: EnterpriseSubscriptionStatusDTOGenqlSelection
billingPortalSession?: (BillingSessionGenqlSelection & { __args?: {returnUrlPath?: (Scalars['String'] | null)} })
listPlans?: BillingPlanGenqlSelection
getMeteredProductsUsage?: BillingMeteredProductUsageGenqlSelection
getResourceCreditUsage?: BillingResourceCreditUsageGenqlSelection
findWorkspaceInvitations?: WorkspaceInvitationGenqlSelection
getApprovedAccessDomains?: ApprovedAccessDomainGenqlSelection
getPageLayoutTabs?: (PageLayoutTabGenqlSelection & { __args: {pageLayoutId: Scalars['String']} })
@@ -5726,7 +5710,6 @@ export interface QueryGenqlSelection{
getAutoCompleteAddress?: (AutocompleteResultGenqlSelection & { __args: {address: Scalars['String'], token: Scalars['String'], country?: (Scalars['String'] | null), isFieldCity?: (Scalars['Boolean'] | null)} })
getAddressDetails?: (PlaceDetailsResultGenqlSelection & { __args: {placeId: Scalars['String'], token: Scalars['String']} })
getUsageAnalytics?: (UsageAnalyticsGenqlSelection & { __args?: {input?: (UsageAnalyticsInput | null)} })
getPostgresCredentials?: PostgresCredentialsGenqlSelection
findManyPublicDomains?: PublicDomainGenqlSelection
getEmailingDomains?: EmailingDomainGenqlSelection
findManyMarketplaceApps?: MarketplaceAppGenqlSelection
@@ -5818,9 +5801,9 @@ export interface MutationGenqlSelection{
switchBillingPlan?: BillingUpdateGenqlSelection
cancelSwitchBillingPlan?: BillingUpdateGenqlSelection
cancelSwitchBillingInterval?: BillingUpdateGenqlSelection
setMeteredSubscriptionPrice?: (BillingUpdateGenqlSelection & { __args: {priceId: Scalars['String']} })
setResourceCreditSubscriptionPrice?: (BillingUpdateGenqlSelection & { __args: {priceId: Scalars['String']} })
endSubscriptionTrialPeriod?: BillingEndTrialPeriodGenqlSelection
cancelSwitchMeteredPrice?: BillingUpdateGenqlSelection
cancelSwitchResourceCreditPrice?: BillingUpdateGenqlSelection
deleteWorkspaceInvitation?: { __args: {appTokenId: Scalars['String']} }
resendWorkspaceInvitation?: (SendInvitationsGenqlSelection & { __args: {appTokenId: Scalars['String']} })
sendInvitations?: (SendInvitationsGenqlSelection & { __args: {emails: Scalars['String'][], roleId?: (Scalars['UUID'] | null)} })
@@ -5952,8 +5935,6 @@ export interface MutationGenqlSelection{
startChannelSync?: (ChannelSyncSuccessGenqlSelection & { __args: {connectedAccountId: Scalars['UUID']} })
saveImapSmtpCaldavAccount?: (ImapSmtpCaldavConnectionSuccessGenqlSelection & { __args: {accountOwnerId: Scalars['UUID'], handle: Scalars['String'], connectionParameters: EmailAccountConnectionParameters, id?: (Scalars['UUID'] | null)} })
updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} })
enablePostgresProxy?: PostgresCredentialsGenqlSelection
disablePostgresProxy?: PostgresCredentialsGenqlSelection
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
deletePublicDomain?: { __args: {domain: Scalars['String']} }
@@ -6038,7 +6019,7 @@ export interface UpsertViewWidgetViewFilterGroupInput {id?: (Scalars['UUID'] | n
export interface UpsertViewWidgetViewSortInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],direction?: (ViewSortDirection | null)}
export interface CreateViewSortInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],direction?: (ViewSortDirection | null),viewId: Scalars['UUID']}
export interface CreateViewSortInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],direction?: (ViewSortDirection | null),subFieldName?: (Scalars['String'] | null),viewId: Scalars['UUID']}
export interface UpdateViewSortInput {
/** The id of the view sort to update */
@@ -6046,7 +6027,7 @@ id: Scalars['UUID'],
/** The view sort to update */
update: UpdateViewSortInputUpdates}
export interface UpdateViewSortInputUpdates {direction?: (ViewSortDirection | null)}
export interface UpdateViewSortInputUpdates {direction?: (ViewSortDirection | null),subFieldName?: (Scalars['String'] | null)}
export interface DeleteViewSortInput {
/** The id of the view sort to delete. */
@@ -7107,10 +7088,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const BillingMeteredProductUsage_possibleTypes: string[] = ['BillingMeteredProductUsage']
export const isBillingMeteredProductUsage = (obj?: { __typename?: any } | null): obj is BillingMeteredProductUsage => {
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingMeteredProductUsage"')
return BillingMeteredProductUsage_possibleTypes.includes(obj.__typename)
const BillingResourceCreditUsage_possibleTypes: string[] = ['BillingResourceCreditUsage']
export const isBillingResourceCreditUsage = (obj?: { __typename?: any } | null): obj is BillingResourceCreditUsage => {
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingResourceCreditUsage"')
return BillingResourceCreditUsage_possibleTypes.includes(obj.__typename)
}
@@ -7971,14 +7952,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const PostgresCredentials_possibleTypes: string[] = ['PostgresCredentials']
export const isPostgresCredentials = (obj?: { __typename?: any } | null): obj is PostgresCredentials => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPostgresCredentials"')
return PostgresCredentials_possibleTypes.includes(obj.__typename)
}
const ToolIndexEntry_possibleTypes: string[] = ['ToolIndexEntry']
export const isToolIndexEntry = (obj?: { __typename?: any } | null): obj is ToolIndexEntry => {
if (!obj?.__typename) throw new Error('__typename is missing in "isToolIndexEntry"')
@@ -8705,8 +8678,7 @@ export const enumBillingUsageType = {
export const enumBillingProductKey = {
BASE_PRODUCT: 'BASE_PRODUCT' as const,
RESOURCE_CREDIT: 'RESOURCE_CREDIT' as const,
WORKFLOW_NODE_EXECUTION: 'WORKFLOW_NODE_EXECUTION' as const
RESOURCE_CREDIT: 'RESOURCE_CREDIT' as const
}
export const enumSubscriptionInterval = {
@@ -8759,13 +8731,11 @@ export const enumFeatureFlagKey = {
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const
IS_REST_METADATA_API_NEW_FORMAT_DIRECT: 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' as const
}
export const enumIdentityProviderType = {
File diff suppressed because it is too large Load Diff
@@ -31,7 +31,7 @@ export default defineApplication({
Notes:
- `universalIdentifier` fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
- `applicationVariables` become environment variables for your functions and front components (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `applicationVariables` become environment variables for your functions and front components. In logic functions (server-side), they are available as `process.env.VARIABLE_NAME`. In front components, use `getApplicationVariable('VARIABLE_NAME')` from `twenty-sdk/front-component`. Variables marked with `isSecret: true` are only injected into logic functions. Front components receive only non-secret variables.
- The default role is detected automatically from the role file marked with [`defineApplicationRole()`](/developers/extend/apps/config/roles) — you do not need to reference it from `defineApplication()`.
- Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
- Passing `defaultRoleUniversalIdentifier` explicitly is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
This watches `src/`, rebuilds on every change, and syncs the result to the server. Edit a file, save, and within a second the server reflects the change. You'll see a live status panel in your terminal.
This watches `src/`, rebuilds on every change, and syncs the result to the server. Edit a file, save, and within a few seconds the server reflects the change. You'll see a live status panel in your terminal.
For more detailed output (build logs, sync requests, error traces), add `--verbose`.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Watches and re-syncs on every change. Runs until you stop it. | Interactive local development. |
| `yarn twenty dev --once` | Single build + sync, exits `0` on success, `1` on failure. | CI, pre-commit hooks, AI agents, scripted workflows. |
Both modes need a server in development mode and an authenticated remote.
Both modes need an authenticated remote.
<Warning>
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests — use `yarn twenty deploy` to deploy to production servers. See [Publishing](/developers/extend/apps/operations/publishing).
</Warning>
### Dev mode options
| Flag | Description |
|------|-------------|
| `--once` | Build and sync once, then exit. |
| `--debounceMs <ms>` | Set the file-change debounce delay in milliseconds (default: `2000`). |
| `--verbose` / `--debug` | Show detailed build logs, sync requests, and error traces. |
---
@@ -239,6 +239,38 @@ Available hooks:
| `useFrontComponentId()` | `string` | This component instance's ID |
| `useFrontComponentExecutionContext(selector)` | varies | Access the full execution context with a selector function |
## Application variables
Application variables defined in [`defineApplication()`](/developers/extend/apps/config/application) with `isSecret: false` are available inside front components via the `getApplicationVariable` utility:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Secret variables (`isSecret: true`) are **not** exposed to front components. They are only available in [logic functions](/developers/extend/apps/logic/logic-functions), which run server-side. This prevents sensitive values like API keys from being sent to the browser.
</Warning>
The following system variables are always available via `process.env`:
| Variable | Description |
|----------|-------------|
| `TWENTY_API_URL` | Base URL of the Twenty API |
| `TWENTY_APP_ACCESS_TOKEN` | Short-lived token scoped to your app's role |
## Host communication API
Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`:
@@ -32,7 +32,7 @@ export default defineApplication({
Notizen:
* `universalIdentifier`-Felder sind deterministische IDs, die Ihnen gehören. Erzeugen Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen und Frontend-Komponenten (z. B. ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen und Frontend-Komponenten. In Logikfunktionen (serverseitig) sind sie als `process.env.VARIABLE_NAME` verfügbar. In Frontend-Komponenten verwenden Sie `getApplicationVariable('VARIABLE_NAME')` aus `twenty-sdk/front-component`. Variablen, die mit `isSecret: true` gekennzeichnet sind, werden nur in Logikfunktionen injiziert. Frontend-Komponenten erhalten nur nicht-geheime Variablen.
* Die Standardrolle wird automatisch aus der Rollen-Datei erkannt, die mit [`defineApplicationRole()`](/l/de/developers/extend/apps/config/roles) markiert ist Sie müssen sie nicht aus `defineApplication()` referenzieren.
* Pre- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt — Sie müssen sie in `defineApplication()` nicht referenzieren.
* Die explizite Übergabe von `defaultRoleUniversalIdentifier` wird für die Abwärtskompatibilität weiterhin unterstützt, ist jedoch zugunsten von `defineApplicationRole()` veraltet.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
Dies überwacht `src/`, baut bei jeder Änderung neu und synchronisiert das Ergebnis mit dem Server. Bearbeiten Sie eine Datei, speichern Sie, und innerhalb einer Sekunde spiegelt der Server die Änderung wider. Sie sehen eine Live-Statusanzeige in Ihrem Terminal.
Dies überwacht `src/`, baut bei jeder Änderung neu und synchronisiert das Ergebnis mit dem Server. Bearbeiten Sie eine Datei, speichern Sie, und innerhalb weniger Sekunden spiegelt der Server die Änderung wider. Sie sehen eine Live-Statusanzeige in Ihrem Terminal.
Für ausführlichere Ausgaben (Build-Protokolle, Sync-Anfragen, Fehlerspuren) fügen Sie `--verbose` hinzu.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Überwacht und synchronisiert bei jeder Änderung erneut. Läuft, bis Sie es stoppen. | Interaktive lokale Entwicklung. |
| `yarn twenty dev --once` | Einmaliger Build + Sync, beendet sich mit `0` bei Erfolg, mit `1` bei Fehler. | CI, Pre-Commit-Hooks, KI-Agenten, skriptgesteuerte Workflows. |
Beide Modi benötigen einen Server im Entwicklungsmodus und eine authentifizierte Remote-Verbindung.
Beide Modi benötigen ein authentifiziertes Remote-Repository.
<Warning>
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Sync-Anfragen ab — verwenden Sie `yarn twenty deploy`, um auf Produktionsserver bereitzustellen. Siehe [Apps veröffentlichen](/l/de/developers/extend/apps/operations/publishing).
</Warning>
### Dev-Modus-Optionen
| Flag | Beschreibung |
| ------------------------------------- | ----------------------------------------------------------------------------------- |
| `--once` | Einmal erstellen und synchronisieren, dann beenden. |
| `--debounceMs \<ms>` | Legt die Entprellzeit für Dateiänderungen in Millisekunden fest (Standard: `2000`). |
| `--verbose` / `--debug` | Zeigt ausführliche Build-Protokolle, Sync-Anfragen und Fehler-Traces an. |
---
@@ -239,6 +239,38 @@ Verfügbare Hooks:
| `useFrontComponentId()` | `string` | Die ID dieser Komponenteninstanz |
| `useFrontComponentExecutionContext(selector)` | variiert | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion |
## Anwendungsvariablen
In [`defineApplication()`](/l/de/developers/extend/apps/config/application) mit `isSecret: false` definierte Anwendungsvariablen sind in Front-Komponenten über das Hilfsprogramm `getApplicationVariable` verfügbar:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Geheime Variablen (`isSecret: true`) werden **nicht** in Front-Komponenten offengelegt. Sie sind nur in [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) verfügbar, die serverseitig ausgeführt werden. Dadurch wird verhindert, dass sensible Werte wie API-Schlüssel an den Browser gesendet werden.
</Warning>
Die folgenden Systemvariablen sind immer über `process.env` verfügbar:
| Variable | Beschreibung |
| ------------------------- | ------------------------------------------------------------- |
| `TWENTY_API_URL` | Basis-URL der Twenty API |
| `TWENTY_APP_ACCESS_TOKEN` | Kurzlebiges Token mit dem Geltungsbereich der Rolle Ihrer App |
## Host-Kommunikations-API
Front-Komponenten können Navigation, Modals und Benachrichtigungen mittels Funktionen aus `twenty-sdk` auslösen:
@@ -32,7 +32,7 @@ export default defineApplication({
Notas:
* Os campos `universalIdentifier` são IDs determinísticos que você controla. Gere-os uma vez e mantenha-os estáveis entre sincronizações.
* `applicationVariables` tornam-se variáveis de ambiente para suas funções e componentes de front-end (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`).
* `applicationVariables` tornam-se variáveis de ambiente para suas funções e componentes de front-end. Em funções lógicas (no lado do servidor), elas ficam disponíveis como `process.env.VARIABLE_NAME`. Em componentes de front-end, use `getApplicationVariable('VARIABLE_NAME')` de `twenty-sdk/front-component`. Variáveis marcadas com `isSecret: true` são injetadas apenas em funções lógicas. Componentes de front-end recebem apenas variáveis não secretas.
* O papel padrão é detectado automaticamente a partir do arquivo de definição de papel marcado com [`defineApplicationRole()`](/l/pt/developers/extend/apps/config/roles) — você não precisa referenciá-lo em `defineApplication()`.
* As funções de pré-instalação e pós-instalação são detectadas automaticamente durante a construção do manifesto — você não precisa referenciá-las em `defineApplication()`.
* Passar `defaultRoleUniversalIdentifier` explicitamente ainda é compatível para retrocompatibilidade, mas foi preterido em favor de `defineApplicationRole()`.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
Isso monitora `src/`, recompila a cada alteração e sincroniza o resultado com o servidor. Edite um arquivo, salve e, em um segundo, o servidor refletirá a alteração. Você verá um painel de status em tempo real no seu terminal.
Isso monitora `src/`, recompila a cada alteração e sincroniza o resultado com o servidor. Edite um arquivo, salve e, em alguns segundos, o servidor refletirá a alteração. Você verá um painel de status em tempo real no seu terminal.
Para uma saída mais detalhada (logs de build, solicitações de sincronização, rastros de erro), adicione `--verbose`.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Monitora e ressincroniza a cada alteração. Fica em execução até você interrompê-lo. | Desenvolvimento local interativo. |
| `yarn twenty dev --once` | Executa uma única compilação + sincronização e, em seguida, encerra com o código `0` em caso de sucesso ou `1` em caso de falha. | Scripts, CI, hooks de pre-commit, agentes de IA e fluxos de trabalho com script. |
Ambos os modos precisam de um servidor em modo de desenvolvimento e de um remoto autenticado.
Ambos os modos precisam de um remoto autenticado.
<Warning>
O modo de desenvolvimento só está disponível em instâncias do Twenty em modo de desenvolvimento (`NODE_ENV=development`). Instâncias de produção rejeitam solicitações de sincronização de desenvolvimento — use `yarn twenty deploy` para implantar em servidores de produção. Veja [Publicação](/l/pt/developers/extend/apps/operations/publishing).
</Warning>
### Opções do modo de desenvolvimento
| Opção | Descrição |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `--once` | Compila e sincroniza uma vez e, em seguida, sai. |
| `--debounceMs \<ms>` | Define o atraso de debounce para alterações de arquivo em milissegundos (padrão: `2000`). |
| `--verbose` / `--debug` | Mostra registros detalhados de compilação, solicitações de sincronização e rastreamentos de erro. |
---
@@ -239,6 +239,38 @@ Hooks disponíveis:
| `useFrontComponentId()` | `string` | O ID desta instância do componente |
| `useFrontComponentExecutionContext(selector)` | varia | Acesse o contexto de execução completo com uma função seletora |
## Variáveis de aplicação
Variáveis de aplicação definidas em [`defineApplication()`](/l/pt/developers/extend/apps/config/application) com `isSecret: false` estão disponíveis nos componentes de front por meio do utilitário `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Variáveis secretas (`isSecret: true`) **não** são expostas aos componentes de front. Elas estão disponíveis apenas em [funções de lógica](/l/pt/developers/extend/apps/logic/logic-functions), que são executadas no lado do servidor. Isso impede que valores sigilosos, como chaves de API, sejam enviados para o navegador.
</Warning>
As seguintes variáveis de sistema estão sempre disponíveis via `process.env`:
| Variável | Descrição |
| ------------------------- | ------------------------------------------------------------- |
| `TWENTY_API_URL` | URL base da API da Twenty |
| `TWENTY_APP_ACCESS_TOKEN` | Token de curta duração limitado ao escopo do papel do seu app |
## API de comunicação do host
Componentes de front-end podem acionar navegação, modais e notificações usando funções de `twenty-sdk`:
@@ -32,7 +32,7 @@ export default defineApplication({
Notițe:
* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți. Generați-le o singură dată și mențineți-le stabile între sincronizări.
* `applicationVariables` devin variabile de mediu pentru funcțiile și componentele front-end (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`).
* `applicationVariables` devin variabile de mediu pentru funcțiile și componentele front-end. În funcțiile de logică (server-side), acestea sunt disponibile ca `process.env.VARIABLE_NAME`. În componentele front-end, folosește `getApplicationVariable('VARIABLE_NAME')` din `twenty-sdk/front-component`. Variabilele marcate cu `isSecret: true` sunt injectate doar în funcțiile de logică. Componentele front-end primesc doar variabile non-secrete.
* Rolul implicit este detectat automat din fișierul de rol marcat cu [`defineApplicationRole()`](/l/ro/developers/extend/apps/config/roles) — nu este necesar să faci referire la el în `defineApplication()`.
* Funcțiile de pre-instalare și post-instalare sunt detectate automat în timpul construirii manifestului — nu trebuie să le referiți în `defineApplication()`.
* Transmiterea explicită a `defaultRoleUniversalIdentifier` este în continuare acceptată pentru compatibilitate retroactivă, dar este considerată învechită în favoarea `defineApplicationRole()`.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
Aceasta monitorizează `src/`, reconstruiește la fiecare modificare și sincronizează rezultatul pe server. Editați un fișier, salvați, iar în decurs de o secundă serverul reflectă modificarea. Veți vedea în terminal un panou de stare în timp real.
Aceasta monitorizează `src/`, reconstruiește la fiecare modificare și sincronizează rezultatul pe server. Editați un fișier, salvați, iar în câteva secunde serverul reflectă modificarea. Veți vedea în terminal un panou de stare în timp real.
Pentru o ieșire mai detaliată (jurnale de build, cereri de sincronizare, urme ale erorilor), adăugați `--verbose`.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Monitorizează și resincronizează la fiecare modificare. Rulează până când îl opriți. | Dezvoltare locală interactivă. |
| `yarn twenty dev --once` | Un singur build + sync, iese cu `0` la succes, `1` la eșec. | CI, hook-uri pre-commit, agenți AI, fluxuri de lucru scriptate. |
Ambele moduri necesită un server în modul de dezvoltare și un remote autentificat.
Ambele moduri necesită o conexiune la distanță autentificată.
<Warning>
Modul de dezvoltare este disponibil doar pe instanțele Twenty care rulează în modul development (`NODE_ENV=development`). Instanțele de producție resping cererile de sincronizare din modul de dezvoltare — folosiți `yarn twenty deploy` pentru a implementa pe serverele de producție. Consultați [Publicarea](/l/ro/developers/extend/apps/operations/publishing).
</Warning>
### Opțiuni pentru modul de dezvoltare
| Opțiune | Descriere |
| ------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `--once` | Construiește și sincronizează o singură dată, apoi iese. |
| `--debounceMs \<ms>` | Setează întârzierea de debounce pentru modificarea fișierului în milisecunde (implicit: `2000`). |
| `--verbose` / `--debug` | Afișează jurnale detaliate de construire, cereri de sincronizare și urme ale erorilor. |
---
@@ -239,6 +239,38 @@ Hook-uri disponibile:
| `useFrontComponentId()` | `string` | ID-ul acestei instanțe de componentă |
| `useFrontComponentExecutionContext(selector)` | variază | Accesați întregul context de execuție cu o funcție selector |
## Variabile de aplicație
Variabilele de aplicație definite în [`defineApplication()`](/l/ro/developers/extend/apps/config/application) cu `isSecret: false` sunt disponibile în componentele de interfață prin utilitarul `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Variabilele secrete (`isSecret: true`) **nu** sunt expuse componentelor de interfață. Acestea sunt disponibile doar în [funcțiile logice](/l/ro/developers/extend/apps/logic/logic-functions), care rulează pe server. Acest lucru împiedică trimiterea către browser a valorilor sensibile, cum ar fi cheile API.
</Warning>
Următoarele variabile de sistem sunt întotdeauna disponibile prin `process.env`:
| Variabilă | Descriere |
| ------------------------- | -------------------------------------------------------- |
| `TWENTY_API_URL` | URL de bază al API-ului Twenty |
| `TWENTY_APP_ACCESS_TOKEN` | Token cu durată scurtă, limitat la rolul aplicației dvs. |
## API-ul de comunicare cu gazda
Componentele front-end pot declanșa navigare, ferestre modale și notificări folosind funcții din `twenty-sdk`:
@@ -32,7 +32,7 @@ export default defineApplication({
Заметки:
* Поля `universalIdentifier` — это детерминированные идентификаторы, которые принадлежат вам. Сгенерируйте их один раз и сохраняйте неизменными между синхронизациями.
* `applicationVariables` становятся переменными окружения для ваших функций и фронтенд-компонентов (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`).
* `applicationVariables` становятся переменными окружения для ваших функций и фронтенд-компонентов. В логических функциях (на стороне сервера) они доступны как `process.env.VARIABLE_NAME`. Во фронтенд-компонентах используйте `getApplicationVariable('VARIABLE_NAME')` из `twenty-sdk/front-component`. Переменные, помеченные как `isSecret: true`, внедряются только в логические функции. Фронтенд-компоненты получают только несекретные переменные.
* Роль по умолчанию автоматически определяется из файла роли, помеченного с помощью [`defineApplicationRole()`](/l/ru/developers/extend/apps/config/roles) — вам не нужно ссылаться на неё из `defineApplication()`.
* Предустановочные и постустановочные функции обнаруживаются автоматически во время сборки манифеста — вам не нужно указывать их в `defineApplication()`.
* Явная передача `defaultRoleUniversalIdentifier` по-прежнему поддерживается для обратной совместимости, но считается устаревшей и вместо неё рекомендуется использовать `defineApplicationRole()`.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
Эта команда отслеживает `src/`, пересобирает при каждом изменении и синхронизирует результат с сервером. Отредактируйте файл, сохраните — и через секунду сервер отразит изменения. В терминале появится панель текущего статуса.
Эта команда отслеживает `src/`, пересобирает при каждом изменении и синхронизирует результат с сервером. Отредактируйте файл, сохраните — и через несколько секунд сервер отразит изменения. В терминале появится панель текущего статуса.
Для более подробного вывода (журналы сборки, запросы синхронизации, трассировки ошибок) добавьте `--verbose`.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Отслеживает и повторно синхронизирует при каждом изменении. Продолжает работать, пока вы его не остановите. | Интерактивная локальная разработка. |
| `yarn twenty dev --once` | Одна сборка и синхронизация, завершает работу с кодом `0` при успехе и `1` при ошибке. | CI, хуки pre-commit, AI-агенты, скриптовые рабочие процессы. |
Оба режима требуют сервер в режиме разработки и аутентифицированный удалённый сервер.
Оба режима требуют аутентифицированного удалённого репозитория.
<Warning>
Режим разработки доступен только на экземплярах Twenty, запущенных в режиме разработки (`NODE_ENV=development`). Экземпляры в продакшене отклоняют запросы синхронизации из режима разработки — используйте `yarn twenty deploy` для развёртывания на производственные серверы. См. [Публикация](/l/ru/developers/extend/apps/operations/publishing).
</Warning>
### Параметры режима разработки
| Флаг | Описание |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| `--once` | Выполнить сборку и синхронизацию один раз, затем завершить работу. |
| `--debounceMs \<ms>` | Установить задержку дебаунса изменений файлов в миллисекундах (по умолчанию: `2000`). |
| `--verbose` / `--debug` | Показывать подробные журналы сборки, запросы синхронизации и трассировки ошибок. |
---
@@ -239,6 +239,38 @@ export default defineFrontComponent({
| `useFrontComponentId()` | `string` | ID этого экземпляра компонента |
| `useFrontComponentExecutionContext(selector)` | различается | Доступ к полному контексту выполнения с помощью функции-селектора |
## Переменные приложения
Переменные приложения, определенные в [`defineApplication()`](/l/ru/developers/extend/apps/config/application) с `isSecret: false`, доступны внутри фронтенд-компонентов через утилиту `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Секретные переменные (`isSecret: true`) **не** доступны фронтенд-компонентам. Они доступны только в [логических функциях](/l/ru/developers/extend/apps/logic/logic-functions), которые выполняются на стороне сервера. Это предотвращает отправку в браузер конфиденциальных значений, таких как ключи API.
</Warning>
Следующие системные переменные всегда доступны через `process.env`:
| Переменная | Описание |
| ------------------------- | ----------------------------------------------------------------------------- |
| `TWENTY_API_URL` | Базовый URL API Twenty |
| `TWENTY_APP_ACCESS_TOKEN` | Краткоживущий токен с областью действия, ограниченной ролью вашего приложения |
## API взаимодействия с хостом
Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`:
@@ -32,7 +32,7 @@ export default defineApplication({
Notlar:
* `universalIdentifier` alanları, size ait deterministik kimliklerdir. Bunları bir kez oluşturun ve senkronizasyonlar boyunca kararlı tutun.
* `applicationVariables`, fonksiyonlarınız ve ön bileşenleriniz için ortam değişkenlerine dönüşür (örn. `DEFAULT_RECIPIENT_NAME`, `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir).
* `applicationVariables`, fonksiyonlarınız ve ön bileşenleriniz için ortam değişkenlerine dönüşür. Mantık fonksiyonlarında (sunucu tarafında), `process.env.VARIABLE_NAME` olarak kullanılabilirler. Ön bileşenlerde, `twenty-sdk/front-component` içindeki `getApplicationVariable('VARIABLE_NAME')` fonksiyonunu kullanın. `isSecret: true` ile işaretlenen değişkenler yalnızca mantık fonksiyonlarına enjekte edilir. Ön bileşenler yalnızca gizli olmayan değişkenleri alır.
* Varsayılan rol, [`defineApplicationRole()`](/l/tr/developers/extend/apps/config/roles) ile işaretlenmiş rol dosyasından otomatik olarak algılanır — onu `defineApplication()` içinden belirtmenize gerek yoktur.
* Kurulum öncesi ve kurulum sonrası fonksiyonlar manifest derlemesi sırasında otomatik olarak algılanır — bunlara `defineApplication()` içinde referans vermeniz gerekmez.
* Geriye dönük uyumluluk için `defaultRoleUniversalIdentifier` değerini açıkça geçmek hâlâ desteklenmektedir, ancak `defineApplicationRole()` lehine kullanımdan kaldırılmıştır.
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
Bu, `src/` dizinini izler, her değişiklikte yeniden oluşturur ve sonucu sunucuya eşitler. Bir dosyayı düzenleyin, kaydedin; bir saniye içinde sunucu değişikliği yansıtır. Terminalinizde canlı bir durum paneli göreceksiniz.
Bu, `src/` dizinini izler, her değişiklikte yeniden oluşturur ve sonucu sunucuya eşitler. Bir dosyayı düzenleyin, kaydedin ve birkaç saniye içinde sunucu değişikliği yansıtır. Terminalinizde canlı bir durum paneli göreceksiniz.
Daha ayrıntılı çıktı (derleme günlükleri, eşitleme istekleri, hata izleri) için `--verbose` ekleyin.
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | Her değişiklikte izler ve yeniden eşitler. Siz durdurana kadar çalışır. | Etkileşimli yerel geliştirme. |
| `yarn twenty dev --once` | Tek derleme + eşitleme; başarıda `0`, başarısızlıkta `1` ile çıkar. | CI, pre-commit kancaları, AI ajanları, betiklenmiş iş akışları. |
Her iki mod da geliştirme modunda bir sunucuya ve kimliği doğrulanmış bir uzak uca ihtiyaç duyar.
Her iki kipin de kimliği doğrulanmış bir uzak sunucuya ihtiyaç duyar.
<Warning>
Geliştirme modu yalnızca geliştirme ortamında (`NODE_ENV=development`) çalışan Twenty örneklerinde kullanılabilir. Üretim örnekleri, geliştirme eşitleme isteklerini reddeder — üretim sunucularına dağıtmak için `yarn twenty deploy` kullanın. Bkz. [Yayımlama](/l/tr/developers/extend/apps/operations/publishing).
</Warning>
### Geliştirme kipi seçenekleri
| Bayrak | Açıklama |
| ------------------------------------- | ------------------------------------------------------------------------------------------ |
| `--once` | Bir kez derleyip eşitleyin, ardından çıkın. |
| `--debounceMs \<ms>` | Dosya değişikliği geciktirme süresini milisaniye cinsinden ayarlayın (varsayılan: `2000`). |
| `--verbose` / `--debug` | Ayrıntılı derleme günlüklerini, eşitleme isteklerini ve hata izlerini gösterin. |
---
@@ -239,6 +239,38 @@ Kullanılabilir hook'lar:
| `useFrontComponentId()` | `string` | Bu bileşen örneğinin ID'si |
| `useFrontComponentExecutionContext(selector)` | değişir | Bir seçici işlevle tam yürütme bağlamına erişin |
## Uygulama değişkenleri
`isSecret: false` ile [`defineApplication()`](/l/tr/developers/extend/apps/config/application) içinde tanımlanan uygulama değişkenleri, `getApplicationVariable` yardımcı işlevi aracılığıyla ön uç bileşenleri içinde kullanılabilir:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Gizli değişkenler (`isSecret: true`) ön uç bileşenlere açıklanmaz. Bunlar yalnızca sunucu tarafında çalışan [mantık işlevlerinde](/l/tr/developers/extend/apps/logic/logic-functions) kullanılabilir. Bu, API anahtarları gibi hassas değerlerin tarayıcıya gönderilmesini engeller.
</Warning>
Aşağıdaki sistem değişkenleri her zaman `process.env` aracılığıyla kullanılabilir:
| Değişken | Açıklama |
| ------------------------- | --------------------------------------------------------- |
| `TWENTY_API_URL` | Twenty API'nin temel URL'si |
| `TWENTY_APP_ACCESS_TOKEN` | Uygulamanızın rolüyle sınırlanan kısa ömürlü bir belirteç |
## Host iletişim API'si
Ön uç bileşenleri, `twenty-sdk`'deki işlevleri kullanarak gezinmeyi, modalları ve bildirimleri tetikleyebilir:
@@ -32,7 +32,7 @@ export default defineApplication({
备注:
* `universalIdentifier` 字段是你拥有的确定性 ID。 只需生成一次,并在多次同步过程中保持稳定不变。
* `applicationVariables` 会变成你的函数和前端组件可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)
* `applicationVariables` 会变成你的函数和前端组件可用的环境变量。 在逻辑函数(服务端)中,可以通过 `process.env.VARIABLE_NAME` 使用它们。 在前端组件中,使用 `twenty-sdk/front-component` 中的 `getApplicationVariable('VARIABLE_NAME')`。 标记为 `isSecret: true` 的变量只会注入到逻辑函数中。 前端组件只会接收非机密变量
* 默认角色会根据使用 [`defineApplicationRole()`](/l/zh/developers/extend/apps/config/roles) 标记的角色文件自动检测——你不需要在 `defineApplication()` 中引用它。
* 在构建清单时会自动检测安装前/安装后函数——无需在 `defineApplication()` 中引用它们。
* 显式传递 `defaultRoleUniversalIdentifier` 仍然受支持以保持向后兼容性,但已弃用,推荐改用 `defineApplicationRole()`。
@@ -87,7 +87,7 @@ cd my-twenty-app
yarn twenty dev
```
它会监视 `src/`,在每次更改时重新构建,并将结果同步到服务器。 编辑文件、保存,服务器会在秒内反映出更改。 你会在终端中看到一个实时状态面板。
它会监视 `src/`,在每次更改时重新构建,并将结果同步到服务器。 编辑文件、保存,服务器会在秒内反映出更改。 你会在终端中看到一个实时状态面板。
如需更详细的输出(构建日志、同步请求、错误跟踪),请添加 `--verbose`。
@@ -128,11 +128,15 @@ yarn twenty dev --once
| `yarn twenty dev` | 监视并在每次更改时重新同步。 持续运行,直到你将其停止。 | 交互式本地开发。 |
| `yarn twenty dev --once` | 单次构建与同步,成功时以 `0` 退出,失败时以 `1` 退出。 | CI、pre-commit 钩子、AI 智能体、脚本化工作流。 |
两种模式都需要处于开发模式的服务器和已认证的远程。
两种模式都需要经过身份验证的远程仓库
<Warning>
开发模式仅适用于以开发模式运行的 Twenty 实例(`NODE_ENV=development`)。 生产实例会拒绝开发同步请求——请使用 `yarn twenty deploy` 部署到生产服务器。 参见[发布](/l/zh/developers/extend/apps/operations/publishing)。
</Warning>
### 开发模式选项
| 标志 | 描述 |
| ------------------------------------- | ------------------------------ |
| `--once` | 仅构建并同步一次,然后退出。 |
| `--debounceMs \<ms>` | 以毫秒为单位设置文件更改的防抖延迟(默认值:`2000`)。 |
| `--verbose` / `--debug` | 显示详细的构建日志、同步请求和错误跟踪。 |
---
@@ -239,6 +239,38 @@ export default defineFrontComponent({
| `useFrontComponentId()` | `string` | 此组件实例的 ID |
| `useFrontComponentExecutionContext(selector)` | 因情况而异 | 使用选择器函数访问完整的执行上下文 |
## 应用程序变量
在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 中定义、且 `isSecret: false` 的应用程序变量,可以通过 `getApplicationVariable` 实用工具在前端组件中使用:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
机密变量(`isSecret: true`)**不会**暴露给前端组件。 它们仅在服务器端运行的 [逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions) 中可用。 这可以防止诸如 API 密钥之类的敏感值被发送到浏览器。
</Warning>
以下系统变量始终可以通过 `process.env` 获取:
| 变量 | 描述 |
| ------------------------- | ------------------ |
| `TWENTY_API_URL` | Twenty API 的基础 URL |
| `TWENTY_APP_ACCESS_TOKEN` | 限定在你的应用角色范围内的短期令牌 |
## 宿主通信 API
前端组件可以使用来自 `twenty-sdk` 的函数触发导航、模态框和通知:
@@ -23,6 +23,7 @@ type FrontComponentContentProps = {
applicationAccessToken?: string;
apiUrl?: string;
sdkClientUrls?: SdkClientUrls;
applicationVariables?: Record<string, string>;
executionContext: FrontComponentExecutionContext;
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
onError: (error?: Error) => void;
@@ -34,6 +35,7 @@ export const FrontComponentRenderer = ({
applicationAccessToken,
apiUrl,
sdkClientUrls,
applicationVariables,
executionContext,
frontComponentHostCommunicationApi,
onError,
@@ -55,6 +57,7 @@ export const FrontComponentRenderer = ({
applicationAccessToken={applicationAccessToken}
apiUrl={apiUrl}
sdkClientUrls={sdkClientUrls}
applicationVariables={applicationVariables}
frontComponentId={executionContext.frontComponentId}
setReceiver={setReceiver}
setThread={setThread}
@@ -69,6 +72,7 @@ export const FrontComponentRenderer = ({
applicationAccessToken,
apiUrl,
sdkClientUrls,
applicationVariables,
executionContext.frontComponentId,
]);
@@ -36,6 +36,7 @@ type FrontComponentWorkerEffectProps = {
applicationAccessToken?: string;
apiUrl?: string;
sdkClientUrls?: SdkClientUrls;
applicationVariables?: Record<string, string>;
frontComponentId: string;
setReceiver: React.Dispatch<React.SetStateAction<RemoteReceiver | null>>;
setThread: React.Dispatch<
@@ -52,6 +53,7 @@ export const FrontComponentWorkerEffect = ({
applicationAccessToken,
apiUrl,
sdkClientUrls,
applicationVariables,
frontComponentId,
setReceiver,
setThread,
@@ -121,6 +123,7 @@ export const FrontComponentWorkerEffect = ({
applicationAccessToken,
apiUrl,
sdkClientUrls,
applicationVariables,
})
.catch((error: Error) => {
setError(error);
@@ -143,6 +146,7 @@ export const FrontComponentWorkerEffect = ({
applicationAccessToken,
apiUrl,
sdkClientUrls,
applicationVariables,
frontComponentId,
setError,
setReceiver,
@@ -91,6 +91,13 @@ const render: WorkerExports['render'] = async (
document.body.append(root);
installStyleBridge(root);
if (isDefined(renderContext.applicationVariables)) {
setWorkerEnv({
applicationVariables: JSON.stringify(renderContext.applicationVariables),
});
}
// System variables are set after application variables so they cannot be overridden
if (isDefined(renderContext.apiUrl)) {
setWorkerEnv({
TWENTY_API_URL: renderContext.apiUrl,
@@ -1,4 +1,4 @@
export const setWorkerEnv = (environmentVariables: Record<string, string>) => {
export const setWorkerEnv = (variables: Record<string, string>) => {
const globalObject = globalThis as Record<string, unknown>;
const processObject =
(globalObject['process'] as Record<string, unknown> | undefined) ?? {};
@@ -7,7 +7,7 @@ export const setWorkerEnv = (environmentVariables: Record<string, string>) => {
processObject['env'] = {
...processEnvironment,
...environmentVariables,
...variables,
};
globalObject['process'] = processObject;
@@ -8,4 +8,5 @@ export type HostToWorkerRenderContext = {
applicationAccessToken?: string;
apiUrl?: string;
sdkClientUrls?: SdkClientUrls;
applicationVariables?: Record<string, string>;
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

@@ -270,15 +270,13 @@ export type FeatureFlag = {
};
export enum FeatureFlagKey {
IS_BILLING_V2_ENABLED = 'IS_BILLING_V2_ENABLED',
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED',
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
@@ -294,6 +292,7 @@ export type InstanceAndAllWorkspacesUpgradeStatus = {
__typename?: 'InstanceAndAllWorkspacesUpgradeStatus';
computedAt: Scalars['DateTime'];
instanceUpgradeStatus: InstanceUpgradeStatus;
upToDateWorkspaceCount: Scalars['Int'];
workspacesBehind: Array<WorkspaceUpgradeRef>;
workspacesFailed: Array<WorkspaceUpgradeRef>;
};
File diff suppressed because one or more lines are too long
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "{agentLabel} Agentrol"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} van {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} wil toegang tot jou rekening hê"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} geselekteer"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Byv. {0}{1} vir {count} desimaal} other {Byv. {2}{3} vir {count} desimale}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} Krediete - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "Toegangsleutel-ID"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Toegang tot werkruimtedata"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "By swartlys gevoeg"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Adres 1"
@@ -1333,6 +1329,7 @@ msgstr "Adres 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Adres 2"
@@ -2332,6 +2329,7 @@ msgstr "oplopend"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Kanselleer intervalwisseling"
msgid "Cancel interval switching?"
msgstr "Kanselleer intervalwisseling?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Kanselleer gemeterde vlakwisseling"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Kanselleer gemeterde vlakwisseling?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Kies jou Proeflopie"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Stad"
@@ -3795,11 +3784,6 @@ msgstr "Opgestel"
msgid "Confirm"
msgstr "Bevestig"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Bevestig dat jy jou huidige kredietplan verander."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Bevestig verwydering van {roleName} rol? Dit kan nie ongedaan gemaak wor
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Bevestig verlaagde gradering"
@@ -3823,10 +3806,14 @@ msgstr "Bevestig rolopdatering"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Bevestig opgradering"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Kernobjekte"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Koste"
@@ -4138,11 +4124,6 @@ msgstr "Koste"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Tel unieke waardes"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Land"
@@ -4425,11 +4407,6 @@ msgstr "Kredietverbruik oor tyd."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Kredietkaart Plan"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "aflopend"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Verlaagde gradering"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Fout tydens die kansellasie van intervalwisseling."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Fout tydens die kansellasie van gemeterde vlakwisseling."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Uitdrukking"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Ekstra Krediete Gebruik"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Kon nie modelkeusemodus opdateer nie"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Kon nie prys opdateer nie."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Eerste 2 reëls"
msgid "First 5 lines"
msgstr "Eerste 5 reëls"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Laaste boodskap {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Nuutste datum"
msgid "Latest version"
msgstr "Nuutste weergawe"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Teken uit"
msgid "Logs"
msgstr "Logboeke"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadata"
msgid "Metadata file generation failed"
msgstr "Metadataleêr generasie het misluk"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Gemeterde vlakwisseling is gekanselleer."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Aantal desimale"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Aantal nuwe krediete toegeken elke {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Kies asseblief ten minste een bestemmingsobjek vir hierdie verhouding."
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Begin asseblief eers met jou inskrywing"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Poskode"
@@ -12175,11 +12146,6 @@ msgstr "Vorige"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Prys opdateer."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "Verifikasiestelsel begin"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Staat"
@@ -16316,8 +16283,6 @@ msgstr "Opdaterings"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16362,9 +16327,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Opgradeer na {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17545,11 +17510,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Jy het 'n geskeduleerde gemeterde vlakverandering. Wil jy dit kanselleer?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17587,11 +17547,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "دور الوكيل {agentLabel}"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} من {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} يريد الوصول إلى حسابك"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} مُختار"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr ""
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits, plural, zero {لا اعتمادات} one {اعتماد واحد} two {اعتمادان} few {# اعتمادات} many {# اعتمادًا} other {# اعتماد}} - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "معرّف مفتاح الوصول"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "الوصول إلى بيانات مساحة العمل"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "أضيف إلى القائمة السوداء"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "العنوان 1"
@@ -1333,6 +1329,7 @@ msgstr "العنوان 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "العنوان 2"
@@ -2332,6 +2329,7 @@ msgstr "تصاعدي"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "إلغاء تبديل الفاصل الزمني"
msgid "Cancel interval switching?"
msgstr "إلغاء تبديل الفاصل الزمني؟"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "إلغاء تبديل الطبقة المقاسة"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "إلغاء تبديل الطبقة المقاسة؟"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "اختر تجربتك"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "المدينة"
@@ -3795,11 +3784,6 @@ msgstr "تم الإعداد"
msgid "Confirm"
msgstr "تأكيد"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "تأكيد تغيير خطة الائتمان الحالية الخاصة بك."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "تأكيد حذف دور {roleName}؟ لا يمكن التراجع عن
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "تأكيد الترقية"
@@ -3823,10 +3806,14 @@ msgstr "تأكيد تحديث الدور"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "تأكيد التحديث"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "الكائنات الأساسية"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "التكلفة"
@@ -4138,11 +4124,6 @@ msgstr "التكلفة"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "عدّ القيم الفريدة"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "البلد"
@@ -4425,11 +4407,6 @@ msgstr "استهلاك الرصيد بمرور الوقت."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "خطة الرصيد"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "تنازلي"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "خفض المستوى"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "خطأ أثناء إلغاء تبديل الفاصل الزمني."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "خطأ أثناء إلغاء تبديل الطبقة المقاسة."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "تعبير"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "الإعتمادات الإضافية المستخدمة"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "فشل في تحديث وضع اختيار النموذج"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "فشل في تحديث السعر."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "أول خطين ٢"
msgid "First 5 lines"
msgstr "أول 5 خطوط"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "آخر رسالة {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "تاريخ الأحدث"
msgid "Latest version"
msgstr "أحدث إصدار"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "تسجيل الخروج"
msgid "Logs"
msgstr "السجلات"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "البيانات الوصفية"
msgid "Metadata file generation failed"
msgstr "فشل في توليد ملف البيانات الوصفية"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "تم إلغاء تبديل الطبقة المقاسة."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "عدد الكسور العشرية"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "عدد الاعتمادات الجديدة المخصصة كل {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "يُرجى اختيار كائن وجهة واحد على الأقل ل
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "يرجى بدء اشتراكك أولاً"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "الرمز البريدي"
@@ -12175,11 +12146,6 @@ msgstr "السابق"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "تم تحديث السعر."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "تم بدء عملية التحقق"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "الولاية"
@@ -16316,8 +16283,6 @@ msgstr "التحديثات"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16362,9 +16327,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "الترقية إلى {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17543,11 +17508,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "لقد قمت بجدولة تغيير الطبقة المقاسة. هل تريد إلغاؤه؟"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17585,11 +17545,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "Rol d'agent {agentLabel}"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} de {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} vol accedir al teu compte"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} seleccionats"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Ex. {0}{1} per {count} decimal} other {Ex. {2}{3} per {count} decimals}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} crèdits - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "ID de la clau d'accés"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Accedir a les dades de l'espai de treball"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Afegit a la llista de bloqueig"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Adreça 1"
@@ -1333,6 +1329,7 @@ msgstr "Adreça 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Adreça 2"
@@ -2332,6 +2329,7 @@ msgstr "ascendent"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Cancel·lar el canvi d'interval"
msgid "Cancel interval switching?"
msgstr "Cancel·lar el canvi d'interval?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Cancel·lar el canvi de nivell mesurat"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Cancel·lar el canvi de nivell mesurat?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Trieu la vostra prova"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Ciutat"
@@ -3795,11 +3784,6 @@ msgstr "Configurat"
msgid "Confirm"
msgstr "Confirmar"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Confirma el canvi del teu pla de crèdit actual."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Confirmeu la supressió del rol {roleName}? Aquesta acció no es pot des
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Confirmar la reducció"
@@ -3823,10 +3806,14 @@ msgstr "Confirmeu l'actualització del rol"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Confirmar la millora"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Objectes principals"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Cost"
@@ -4138,11 +4124,6 @@ msgstr "Cost"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Compta valors únics"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "País"
@@ -4425,11 +4407,6 @@ msgstr "Consum de crèdits al llarg del temps."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Pla de crèdit"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "descendent"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Baixar de categoria"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Error en cancel·lar el canvi d'interval."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Error en cancel·lar el canvi de nivell mesurat."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Expressió"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Crèdits extres utilitzats"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "No s'ha pogut actualitzar el mode de selecció de models"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Ha fallat l'actualització del preu."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Primeres 2 línies"
msgid "First 5 lines"
msgstr "Primeres 5 línies"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Últim missatge {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Data més recent"
msgid "Latest version"
msgstr "Última versió"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Tancar sessió"
msgid "Logs"
msgstr "Registres"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadades"
msgid "Metadata file generation failed"
msgstr "Error en la generació del fitxer de metadades"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "El canvi de nivell mesurat ha estat cancel·lat."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Número de decimals"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Nombre de crèdits nous assignats cada {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Si us plau, seleccioneu com a mínim un objecte de destinació per a aqu
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Si us plau, inicieu la vostra subscripció primer"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Codi postal"
@@ -12175,11 +12146,6 @@ msgstr "Anterior"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Preu actualitzat."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "S'ha iniciat el procés de verificació"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Estat"
@@ -16316,8 +16283,6 @@ msgstr "Actualitzacions"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16362,9 +16327,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Actualitza a {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17545,11 +17510,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Has programat un canvi de nivell mesurat. Vols cancel·lar-lo?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17587,11 +17547,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "Role agenta {agentLabel}"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} z {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} chce získat přístup k vašemu účtu"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} vybrané"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Např. {0}{1} pro {count} desetinné místo} few {Např. {2}{3} pro {count} desetinná místa} many {Např. {2}{3} pro {count} desetinných míst} other {Např. {2}{3} pro {count} desetinných míst}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} kreditů - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "ID přístupového klíče"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Přístup k datům pracovního prostoru"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Přidáno do blokovaného seznamu"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Adresa 1"
@@ -1333,6 +1329,7 @@ msgstr "Adresa 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Adresa 2"
@@ -2332,6 +2329,7 @@ msgstr "vzestupně"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Zrušit změnu intervalu"
msgid "Cancel interval switching?"
msgstr "Zrušit změnu intervalu?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Zrušit přepínání úrovně s měřením"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Zrušit přepínání úrovně s měřením?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Vyberte si Trial"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Město"
@@ -3795,11 +3784,6 @@ msgstr "Nakonfigurováno"
msgid "Confirm"
msgstr "Potvrdit"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Potvrďte změnu svého aktuálního úvěrového plánu."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Opravdu chcete smazat roli {roleName}? Toto akci nelze vrátit zpět. V
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Potvrďte zhoršení plánu"
@@ -3823,10 +3806,14 @@ msgstr "Potvrdit aktualizaci role"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Potvrďte upgrade"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Základní objekty"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Cena"
@@ -4138,11 +4124,6 @@ msgstr "Cena"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Počet unikátních hodnot"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Země"
@@ -4425,11 +4407,6 @@ msgstr "Spotřeba kreditů v čase."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Kreditní plán"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "sestupně"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Snížení úrovně"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Chyba při rušení změny intervalu."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Chyba při ukončení přepínání úrovně s měřením."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Výraz"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Použité extra kredity"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Nepodařilo se aktualizovat režim výběru modelu"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Nepodařilo se aktualizovat cenu."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "První 2 řádky"
msgid "First 5 lines"
msgstr "Prvních 5 řádků"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Poslední zpráva {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Nejnovější datum"
msgid "Latest version"
msgstr "Nejnovější verze"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Odhlásit"
msgid "Logs"
msgstr "Protokoly"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadata"
msgid "Metadata file generation failed"
msgstr "Generování souboru metadat selhalo"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Přepínání úrovně s měřením bylo zrušeno."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Počet desetinných míst"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Počet nových kreditů přidělených každé {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Vyberte prosím alespoň jeden cílový objekt pro tento vztah."
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Nejprve prosím zahajte své předplatné."
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "PSČ"
@@ -12175,11 +12146,6 @@ msgstr "Předchozí"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Cena byla aktualizována."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "Zahájen proces ověřování"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Stav"
@@ -16316,8 +16283,6 @@ msgstr "Aktualizace"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16362,9 +16327,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Aktualizovat na {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17545,11 +17510,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Naplánovali jste změnu úrovně s měřením. Chcete ji zrušit?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17587,11 +17547,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "{agentLabel} Agentrolle"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} af {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} ønsker at få adgang til din konto"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} valgt"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Fx. {0}{1} for {count} decimal} other {Fx. {2}{3} for {count} decimaler}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} kreditter - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "Access Key ID"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Få adgang til data i arbejdsområdet"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Tilføjet til blokeringsliste"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Adresse 1"
@@ -1333,6 +1329,7 @@ msgstr "Adresse 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Adresse 2"
@@ -2332,6 +2329,7 @@ msgstr "stigende"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Annuller intervalskift"
msgid "Cancel interval switching?"
msgstr "Annuller intervalskift?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Annuller skift til målt niveau"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Annuller skift til målt niveau?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Vælg din prøveperiode"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "By"
@@ -3795,11 +3784,6 @@ msgstr "Konfigureret"
msgid "Confirm"
msgstr "Bekræft"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Bekræft ændring af din nuværende kreditplan."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Bekræft sletning af {roleName} rolle? Dette kan ikke fortrydes. Alle me
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Bekræft nedgradering"
@@ -3823,10 +3806,14 @@ msgstr "Bekræft rolleændring"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Bekræft opgradering"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Kerneobjekter"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Omkostninger"
@@ -4138,11 +4124,6 @@ msgstr "Omkostninger"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Tæl unikke værdier"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Land"
@@ -4425,11 +4407,6 @@ msgstr "Kreditforbrug over tid."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Kreditplan"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "faldende"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Nedgrader"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Fejl ved annullering af intervalskift."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Fejl ved annullering af skift til målt niveau."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Udtryk"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Ekstra kreditter forbrugt"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Kunne ikke opdatere tilstand for modelvalg"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Kunne ikke opdatere pris."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Første 2 linjer"
msgid "First 5 lines"
msgstr "Første 5 linjer"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Sidste besked {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Seneste dato"
msgid "Latest version"
msgstr "Seneste version"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Logud"
msgid "Logs"
msgstr "Logfiler"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadata"
msgid "Metadata file generation failed"
msgstr "Generering af metadatafil mislykkedes"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Skift til målt niveau er blevet annulleret."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Antal decimaler"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Antal nye tildelte kreditter hver {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Vælg mindst én destinationsobjekt til denne relation."
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Start venligst dit abonnement først"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Postnummer"
@@ -12175,11 +12146,6 @@ msgstr "Forrige"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Pris opdateret."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "Verifikationsprocessen er startet"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Tilstand"
@@ -16318,8 +16285,6 @@ msgstr "Opdateringer"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16364,9 +16329,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Opgrader til {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17547,11 +17512,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Du har planlagt et skift til målt niveau. Vil du annullere det?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17589,11 +17549,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "{agentLabel}-Agentenrolle"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} von {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} möchte auf Ihr Konto zugreifen"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} ausgewählt"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {z.B. {0}{1} für {count} Dezimalstelle} other {z.B. {2}{3} für {count} Dezimalstellen}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} Credits - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "Zugriffsschlüssel-ID"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Auf Arbeitsbereichsdaten zugreifen"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Zur Blockliste hinzugefügt"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Adresse 1"
@@ -1333,6 +1329,7 @@ msgstr "Adresse 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Adresse 2"
@@ -2332,6 +2329,7 @@ msgstr "aufsteigend"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Intervallwechsel abbrechen"
msgid "Cancel interval switching?"
msgstr "Intervallwechsel abbrechen?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Zählertarifwechsel abbrechen"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Zählertarifwechsel abbrechen?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Wählen Sie Ihre Testphase"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Stadt"
@@ -3795,11 +3784,6 @@ msgstr "Konfiguriert"
msgid "Confirm"
msgstr "Bestätigen"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Bestätigen Sie die Änderung Ihres aktuellen Guthabenplans."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Löschung der Rolle {roleName} bestätigen? Dies kann nicht rückgängig
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Herabstufung bestätigen"
@@ -3823,10 +3806,14 @@ msgstr "Rollenaktualisierung bestätigen"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Upgrade bestätigen"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Kernobjekte"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Kosten"
@@ -4138,11 +4124,6 @@ msgstr "Kosten"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Eindeutige Werte zählen"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Land"
@@ -4425,11 +4407,6 @@ msgstr "Guthabenverbrauch im Zeitverlauf."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Guthabenplan"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "absteigend"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Herabstufen"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Fehler beim Abbrechen des Intervallswechsels."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Fehler beim Abbrechen des Zählertarifwechsels."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Ausdruck"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Verwendete zusätzliche Credits"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Fehler beim Aktualisieren des Modellauswahlmodus"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Fehler beim Aktualisieren des Preises."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Ersten 2 Zeilen"
msgid "First 5 lines"
msgstr "Ersten 5 Zeilen"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Letzte Nachricht {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Neuester Termin"
msgid "Latest version"
msgstr "Neueste Version"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Abmelden"
msgid "Logs"
msgstr "Protokolle"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadaten"
msgid "Metadata file generation failed"
msgstr "Generierung der Metadatendatei fehlgeschlagen"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Der Zählertarifwechsel wurde abgebrochen."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Anzahl der Dezimalstellen"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Anzahl der neuen zugewiesenen Guthaben alle {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Bitte wählen Sie mindestens ein Zielobjekt für diese Beziehung aus."
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Bitte starten Sie zuerst Ihr Abonnement"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Postleitzahl"
@@ -12175,11 +12146,6 @@ msgstr "Vorherige"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Preis aktualisiert."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "Verifizierungsprozess gestartet"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Zustand"
@@ -16316,8 +16283,6 @@ msgstr "Aktualisierungen"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16362,9 +16327,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Auf {latestAvailableVersion} aktualisieren"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17545,11 +17510,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Sie haben eine Änderung des Zählertarifs geplant. Möchten Sie diese abbrechen?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17587,11 +17547,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "{agentLabel} Ρόλος Πράκτορα"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} του {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "Το {appName} θέλει να αποκτήσει πρόσβαση στον λογαριασμό σας"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} επιλεγμένα"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Π.χ. {0}{1} για {count} δεκαδικό} other {Π.χ. {2}{3} για {count} δεκαδικά}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} Πιστώσεις - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "Access Key ID"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Πρόσβαση στα δεδομένα του χώρου εργασίας"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Προστέθηκε στη λίστα αποκλεισμού"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Διεύθυνση 1"
@@ -1333,6 +1329,7 @@ msgstr "Διεύθυνση 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Διεύθυνση 2"
@@ -2332,6 +2329,7 @@ msgstr "αύξουσα"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Ακύρωση αλλαγής περιόδου"
msgid "Cancel interval switching?"
msgstr "Ακύρωση αλλαγής περιόδου;"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Ακύρωση αλλαγής τιμολογιακής βαθμίδας"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Ακύρωση αλλαγής τιμολογιακής βαθμίδας;"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Επιλέξτε τη Δοκιμή σας"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Πόλη"
@@ -3795,11 +3784,6 @@ msgstr "Ρυθμισμένο"
msgid "Confirm"
msgstr "Επιβεβαίωση"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Επιβεβαίωση αλλαγής του τρέχοντος πλάνου πιστώσεων."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "Επιβεβαιώστε την διαγραφή του ρόλου {role
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Επιβεβαίωση υποβάθμισης"
@@ -3823,10 +3806,14 @@ msgstr "Επιβεβαίωση ενημέρωσης ρόλου"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Επιβεβαίωση αναβάθμισης"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Κύρια Αντικείμενα"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Κόστος"
@@ -4138,11 +4124,6 @@ msgstr "Κόστος"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Καταμέτρηση μοναδικών τιμών"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Χώρα"
@@ -4425,11 +4407,6 @@ msgstr "Κατανάλωση πιστώσεων με την πάροδο του
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Πρόγραμμα Πιστώσεων"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "φθίνουσα"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Υποβάθμιση"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Σφάλμα κατά την ακύρωση αλλαγής περιόδου."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Σφάλμα κατά την ακύρωση αλλαγής τιμολογιακής βαθμίδας."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Έκφραση"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Πρόσθετες μονάδες χρησιμοποιήθηκαν"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Αποτυχία ενημέρωσης λειτουργίας επιλογής μοντέλου"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Αποτυχία ενημέρωσης τιμής."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Πρώτες 2 γραμμές"
msgid "First 5 lines"
msgstr "Πρώτες 5 γραμμές"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Τελευταίο μήνυμα {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Τελευταία ημερομηνία"
msgid "Latest version"
msgstr "Τελευταία έκδοση"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Αποσύνδεση"
msgid "Logs"
msgstr "Καταγραφές"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Μεταδεδομένα"
msgid "Metadata file generation failed"
msgstr "Αποτυχία δημιουργίας αρχείου μεταδεδομένων"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Η αλλαγή βαθμίδας ακυρώθηκε."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Αριθμός δεκαδικών"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Αριθμός νέων πιστώσεων που διατίθενται κάθε {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Παρακαλώ επιλέξτε τουλάχιστον ένα αντι
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Παρακαλώ ξεκινήστε πρώτα τη συνδρομή σας"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Ταχυδρομικός Κωδικός"
@@ -12175,11 +12146,6 @@ msgstr "Προηγούμενο"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Η τιμή ενημερώθηκε."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14571,6 +14537,7 @@ msgstr "Ξεκίνησε η διαδικασία επαλήθευσης"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Κατάσταση"
@@ -16320,8 +16287,6 @@ msgstr "Ενημερώσεις"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16366,9 +16331,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Αναβάθμιση σε {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17549,11 +17514,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Έχετε προγραμματίσει αλλαγή βαθμίδας. Θέλετε να την ακυρώσετε;"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17591,11 +17551,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS
+41 -81
View File
@@ -231,10 +231,10 @@ msgstr "{agentLabel} Agent Role"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} of {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} wants to access your account"
msgid "{appName} would like to:"
msgstr "{appName} would like to:"
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -261,11 +261,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} selected"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} Credits - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -803,10 +798,10 @@ msgstr "Access"
msgid "Access Key ID"
msgstr "Access Key ID"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Access workspace data"
msgid "Access your workspace data"
msgstr "Access your workspace data"
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1321,6 +1316,7 @@ msgstr "Added to blocklist"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Address 1"
@@ -1328,6 +1324,7 @@ msgstr "Address 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Address 2"
@@ -2327,6 +2324,7 @@ msgstr "ascending"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3039,16 +3037,6 @@ msgstr "Cancel interval switching"
msgid "Cancel interval switching?"
msgstr "Cancel interval switching?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Cancel metered tier switching"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "Cancel metered tier switching?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3362,6 +3350,7 @@ msgstr "Choose your Trial"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "City"
@@ -3790,11 +3779,6 @@ msgstr "Configured"
msgid "Confirm"
msgstr "Confirm"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Confirm changing your current credit plan."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3807,7 +3791,6 @@ msgstr "Confirm deletion of {roleName} role? This cannot be undone. All members
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Confirm downgrade"
@@ -3818,10 +3801,14 @@ msgstr "Confirm role update"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Confirm upgrade"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr "Connect {appName} to your account"
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4123,7 +4110,6 @@ msgid "Core Objects"
msgstr "Core Objects"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Cost"
@@ -4133,11 +4119,6 @@ msgstr "Cost"
msgid "Cost per 1M tokens"
msgstr "Cost per 1M tokens"
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr "Cost per Extra Credits"
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4220,6 +4201,7 @@ msgstr "Count unique values"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "Country"
@@ -4420,11 +4402,6 @@ msgstr "Credit consumption over time."
msgid "Credit pack switching has been cancelled."
msgstr "Credit pack switching has been cancelled."
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Credit Plan"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5326,6 +5303,7 @@ msgstr "descending"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5555,8 +5533,6 @@ msgstr "Dots and comma"
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Downgrade"
@@ -6555,11 +6531,6 @@ msgstr "Error while cancelling credit pack switching."
msgid "Error while cancelling interval switching."
msgstr "Error while cancelling interval switching."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Error while cancelling metered tier switching."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6883,11 +6854,6 @@ msgstr "Exposes the function as a tool that AI agents can call"
msgid "Expression"
msgstr "Expression"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Extra Credits Used"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7106,11 +7072,6 @@ msgstr "Failed to update model recommendations"
msgid "Failed to update model selection mode"
msgstr "Failed to update model selection mode"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Failed to update price."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7478,6 +7439,11 @@ msgstr "First 2 lines"
msgid "First 5 lines"
msgstr "First 5 lines"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr "First name"
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9080,6 +9046,7 @@ msgstr "Last message {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr "Last name"
@@ -9140,6 +9107,11 @@ msgstr "Latest date"
msgid "Latest version"
msgstr "Latest version"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr "Latitude"
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9517,6 +9489,11 @@ msgstr "Logout"
msgid "Logs"
msgstr "Logs"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr "Longitude"
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9828,11 +9805,6 @@ msgstr "Metadata"
msgid "Metadata file generation failed"
msgstr "Metadata file generation failed"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "Metered tier switching has been cancelled."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11187,7 +11159,6 @@ msgstr "Number of decimals"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Number of new credits allocated every {recurringInterval}"
@@ -12064,7 +12035,6 @@ msgstr "Please select at least one destination object for this relation."
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Please start your subscription first"
@@ -12143,6 +12113,7 @@ msgstr "Post-install"
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Postcode"
@@ -12193,11 +12164,6 @@ msgstr "Previous"
msgid "Previous 7 days"
msgstr "Previous 7 days"
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Price updated."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14587,6 +14553,7 @@ msgstr "Started verification process"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "State"
@@ -16366,8 +16333,6 @@ msgstr "Updates"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16412,10 +16377,10 @@ msgstr "Upgrade status refreshed"
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Upgrade to {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgstr "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
#. js-lingui-id: ggd+Ee
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
@@ -17595,11 +17560,6 @@ msgstr "You don't have access to this object."
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr "You have scheduled a credit pack change. Do you want to cancel it?"
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "You have scheduled a metered tier change. Do you want to cancel it?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17639,14 +17599,14 @@ msgstr ""
"You've hit your usage limit. \n"
"Reach to our support team to upgrade."
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
#. js-lingui-id: yZ0wRS
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
+39 -79
View File
@@ -236,10 +236,10 @@ msgstr "Rol de agente de {agentLabel}"
msgid "{aggregateLabel} of {fieldLabel}"
msgstr "{aggregateLabel} de {fieldLabel}"
#. js-lingui-id: LTvJAf
#. js-lingui-id: ZZZxmp
#: src/pages/auth/Authorize.tsx
msgid "{appName} wants to access your account"
msgstr "{appName} quiere acceder a tu cuenta"
msgid "{appName} would like to:"
msgstr ""
#. js-lingui-id: y9covz
#: src/pages/settings/admin-panel/SettingsAdminWorkspacesStatus.tsx
@@ -266,11 +266,6 @@ msgstr "{contextStoreNumberOfSelectedRecords} seleccionados"
msgid "{count, plural, one {E.g. {0}{1} for {count} decimal} other {E.g. {2}{3} for {count} decimals}}"
msgstr "{count, plural, one {Ej. {0}{1} para {count} decimal} other {Ej. {2}{3} para {count} decimales}}"
#. js-lingui-id: aTq2js
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "{credits} Credits - ${price}"
msgstr "{credits} créditos - ${price}"
#. js-lingui-id: q8RNNe
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "{credits} Credits - ${priceDisplay}"
@@ -808,10 +803,10 @@ msgstr ""
msgid "Access Key ID"
msgstr "ID de clave de acceso"
#. js-lingui-id: o0Ci6l
#. js-lingui-id: CrjDqe
#: src/pages/auth/Authorize.tsx
msgid "Access workspace data"
msgstr "Acceder a los datos del espacio de trabajo"
msgid "Access your workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#: src/pages/settings/ai/components/SettingsAiMCP.tsx
@@ -1326,6 +1321,7 @@ msgstr "Añadido a la lista de bloqueo"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 1"
msgstr "Dirección 1"
@@ -1333,6 +1329,7 @@ msgstr "Dirección 1"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Address 2"
msgstr "Dirección 2"
@@ -2332,6 +2329,7 @@ msgstr "ascendente"
#. js-lingui-id: nYD/Cq
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -3044,16 +3042,6 @@ msgstr "Cancelar cambio de intervalo"
msgid "Cancel interval switching?"
msgstr "¿Cancelar cambio de intervalo?"
#. js-lingui-id: /Dmw1h
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching"
msgstr "Cancelar cambio de nivel medido"
#. js-lingui-id: Q/chGD
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Cancel metered tier switching?"
msgstr "¿Cancelar cambio de nivel medido?"
#. js-lingui-id: rRK/Lf
#: src/pages/settings/enterprise/SettingsEnterprise.tsx
#: src/modules/settings/billing/components/SettingsBillingContent.tsx
@@ -3367,6 +3355,7 @@ msgstr "Elija su prueba"
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "City"
msgstr "Ciudad"
@@ -3795,11 +3784,6 @@ msgstr "Configurado"
msgid "Confirm"
msgstr "Confirmar"
#. js-lingui-id: qmnX/6
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm changing your current credit plan."
msgstr "Confirmar el cambio de tu plan de crédito actual."
#. js-lingui-id: q2rgqr
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
msgid "Confirm changing your current resource credit allocation."
@@ -3812,7 +3796,6 @@ msgstr "¿Confirmar eliminación del rol {roleName}? Esto no se puede deshacer.
#. js-lingui-id: kZkFSm
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm downgrade"
msgstr "Confirmar degradación"
@@ -3823,10 +3806,14 @@ msgstr "Confirmar la actualización del rol"
#. js-lingui-id: DDbn3x
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Confirm upgrade"
msgstr "Confirmar actualización"
#. js-lingui-id: LOM1MR
#: src/pages/auth/Authorize.tsx
msgid "Connect {appName} to your account"
msgstr ""
#. js-lingui-id: mFiwDA
#: src/pages/settings/applications/components/SettingsApplicationConnectScopePickerModal.tsx
msgid "Connect {providerDisplayName}"
@@ -4128,7 +4115,6 @@ msgid "Core Objects"
msgstr "Objetos principales"
#. js-lingui-id: I99Miw
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
#: src/modules/ai/components/internal/AiChatContextUsageButton.tsx
msgid "Cost"
msgstr "Costo"
@@ -4138,11 +4124,6 @@ msgstr "Costo"
msgid "Cost per 1M tokens"
msgstr ""
#. js-lingui-id: HUPoL8
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Cost per Extra Credits"
msgstr ""
#. js-lingui-id: 3X5ngu
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Cost per million tokens (USD)"
@@ -4225,6 +4206,7 @@ msgstr "Contar valores únicos"
#: src/modules/ui/field/input/components/AddressInput.tsx
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Country"
msgstr "País"
@@ -4425,11 +4407,6 @@ msgstr "Consumo de créditos a lo largo del tiempo."
msgid "Credit pack switching has been cancelled."
msgstr ""
#. js-lingui-id: Efny36
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Credit Plan"
msgstr "Plan de Crédito"
#. js-lingui-id: qtEx+T
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Credit plan upgraded."
@@ -5331,6 +5308,7 @@ msgstr "descendente"
#. js-lingui-id: Cko536
#: src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsSorts.tsx
#: src/modules/views/editable-chip/components/EditableSortChip.tsx
#: src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
#: src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -5560,8 +5538,6 @@ msgstr ""
#. js-lingui-id: LE8J+K
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Downgrade"
msgstr "Degradar"
@@ -6560,11 +6536,6 @@ msgstr ""
msgid "Error while cancelling interval switching."
msgstr "Error al cancelar el cambio de intervalo."
#. js-lingui-id: a1gs9Y
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling metered tier switching."
msgstr "Error al cancelar el cambio de nivel medido."
#. js-lingui-id: FS6DQK
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Error while cancelling plan switching."
@@ -6888,11 +6859,6 @@ msgstr ""
msgid "Expression"
msgstr "Expresión"
#. js-lingui-id: uE7S0L
#: src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
msgid "Extra Credits Used"
msgstr "Créditos Extra Usados"
#. js-lingui-id: 7Bj3x9
#: src/pages/settings/emailing-domains/utils/getEmailingDomainStatusText.ts
#: src/pages/settings/admin-panel/SettingsAdminInstanceStatus.tsx
@@ -7111,11 +7077,6 @@ msgstr ""
msgid "Failed to update model selection mode"
msgstr "Error al actualizar el modo de selección de modelos"
#. js-lingui-id: HnBqPY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Failed to update price."
msgstr "Error al actualizar el precio."
#. js-lingui-id: J7vly7
#: src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer.tsx
msgid "Failed to update records. Please try again."
@@ -7483,6 +7444,11 @@ msgstr "Primeras 2 líneas"
msgid "First 5 lines"
msgstr "Primeras 5 líneas"
#. js-lingui-id: V1EGGU
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "First name"
msgstr ""
#. js-lingui-id: kODvZJ
#: src/pages/onboarding/CreateProfile.tsx
#: src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx
@@ -9062,6 +9028,7 @@ msgstr "Último mensaje {lastMessageSentAtFormatted}"
#. js-lingui-id: 1ZaQUH
#: src/modules/settings/members/components/MemberNameFields.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Last name"
msgstr ""
@@ -9122,6 +9089,11 @@ msgstr "Última fecha"
msgid "Latest version"
msgstr "Última versión"
#. js-lingui-id: Q7hYLw
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Latitude"
msgstr ""
#. js-lingui-id: ZEP8tT
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "Launch"
@@ -9499,6 +9471,11 @@ msgstr "Cerrar sesión"
msgid "Logs"
msgstr "Registros"
#. js-lingui-id: FJBbw5
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Longitude"
msgstr ""
#. js-lingui-id: tL2lBI
#: src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts
msgid "loop"
@@ -9810,11 +9787,6 @@ msgstr "Metadatos"
msgid "Metadata file generation failed"
msgstr "Fallo en la generación del archivo de metadatos"
#. js-lingui-id: onCJJ6
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "Metered tier switching has been cancelled."
msgstr "El cambio de nivel medido ha sido cancelado."
#. js-lingui-id: fiPesM
#: src/modules/settings/logic-functions/components/triggers/SettingsLogicFunctionHttpTriggerSection.tsx
msgid "Method"
@@ -11169,7 +11141,6 @@ msgstr "Número de decimales"
#. js-lingui-id: tYOaY8
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Number of new credits allocated every {recurringInterval}"
msgstr "Cantidad de nuevos créditos asignados cada {recurringInterval}"
@@ -12046,7 +12017,6 @@ msgstr "Por favor, seleccione al menos un objeto de destino para esta relación.
#. js-lingui-id: 8YnlGz
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Please start your subscription first"
msgstr "Por favor, inicia tu suscripción primero"
@@ -12125,6 +12095,7 @@ msgstr ""
#. js-lingui-id: OKJUcC
#: src/modules/settings/data-model/fields/forms/address/constants/DefaultSelectionAddressWithMessages.ts
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "Postcode"
msgstr "Código postal"
@@ -12175,11 +12146,6 @@ msgstr "Anterior"
msgid "Previous 7 days"
msgstr ""
#. js-lingui-id: jmc+FY
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
msgid "Price updated."
msgstr "Precio actualizado."
#. js-lingui-id: aHCEmh
#: src/pages/settings/admin-panel/SettingsAdminNewAiModel.tsx
msgid "Pricing"
@@ -14569,6 +14535,7 @@ msgstr "Proceso de verificación iniciado"
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormAddressFieldInput.tsx
#: src/modules/object-metadata/hooks/useSortSubFieldChoicesForField.ts
msgid "State"
msgstr "Estado"
@@ -16318,8 +16285,6 @@ msgstr "Actualizaciones"
#: src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/ResourceCreditPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/settings/billing/components/internal/MeteredPriceSelector.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade"
@@ -16364,9 +16329,9 @@ msgstr ""
msgid "Upgrade to {latestAvailableVersion}"
msgstr "Actualizar a {latestAvailableVersion}"
#. js-lingui-id: tLOGrv
#. js-lingui-id: Kutw4+
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid "Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
msgid "Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: ggd+Ee
@@ -17547,11 +17512,6 @@ msgstr ""
msgid "You have scheduled a credit pack change. Do you want to cancel it?"
msgstr ""
#. js-lingui-id: M2lYgq
#: src/modules/settings/billing/components/SettingsBillingSubscriptionInfo.tsx
msgid "You have scheduled a metered tier change. Do you want to cancel it?"
msgstr "Has programado un cambio de nivel medido. ¿Quieres cancelarlo?"
#. js-lingui-id: z8eKS7
#: src/modules/settings/billing/hooks/useBillingWording.ts
msgid "you will be charged ${enterprisePrice} per user per month"
@@ -17589,11 +17549,11 @@ msgid ""
"Reach to our support team to upgrade."
msgstr ""
#. js-lingui-id: UUaKVD
#. js-lingui-id: /fksQo
#: src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
msgid ""
"You've hit your usage limit. \n"
"Upgrade to {nextTierCredits} credits for ${nextTierPrice}/{nextTierInterval}."
"Upgrade to {nextResourceCreditsAmount} credits for ${nextResourceCreditPrice}/{nextTierInterval}."
msgstr ""
#. js-lingui-id: yZ0wRS

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