snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
---
title: CLI
description: Команды `yarn twenty` для выполнения функций, потоковой передачи логов, управления установками приложений и переключения удалённых серверов.
icon: terminal
---
CLI `yarn twenty` — ваш инструмент для всего, что связано с приложением. Полный список команд:
| Команда | Что делает | Описано в |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `dev` | Отслеживает исходные файлы и синхронизирует изменения в реальном времени | [Краткое руководство](/l/ru/developers/extend/apps/getting-started/quick-start) |
| `план` | Предпросмотр изменений метаданных без их применения | [Syncing & recovery](/l/ru/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `применить` | Применить изменения метаданных после показа плана | [Syncing & recovery](/l/ru/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Скомпилировать приложение и сгенерировать API‑клиент (`--tarball` для упаковки в `.tgz`) | [Публикация](/l/ru/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Запустить проверку типов TypeScript | [Тестирование](/l/ru/developers/extend/apps/operations/testing) |
| `dev:add` | Создать каркас новой сущности | [Создание каркаса](/l/ru/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Повторно сгенерировать типизированный API‑клиент | эта страница |
| `dev:function:exec` / `dev:function:logs` | Выполнять функции и транслировать их логи | эта страница |
| `dev:translations-extract` | Извлечь переводимые строки в каталоги `locales/` | [Переводы](/l/ru/developers/extend/apps/operations/translations) |
| `dev:catalog-sync` | Запустить синхронизацию каталога маркетплейса | [Публикация](/l/ru/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Жизненный цикл релиза | [Публикация](/l/ru/developers/extend/apps/operations/publishing) и эта страница |
| `docker:*` | Управлять локальным контейнером сервера Twenty | [Локальный сервер](/l/ru/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Управлять подключениями к серверам | эта страница |
Каждая команда принимает параметр `-r, --remote \<name>`, чтобы указать конкретное удалённое подключение вместо подключения по умолчанию.
## Выполнение функций (`yarn twenty dev:function:exec`)
Запустите логическую функцию вручную, не вызывая её через HTTP, cron или событие базы данных:
```bash filename="Terminal"
# Execute by function name
yarn twenty dev:function:exec -n create-new-post-card
# Execute by universalIdentifier
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the install and uninstall hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
yarn twenty dev:function:exec --uninstall
```
## Просмотр логов функций (`yarn twenty dev:function:logs`)
Потоковая передача журналов выполнения логических функций вашего приложения:
```bash filename="Terminal"
# Stream all function logs
yarn twenty dev:function:logs
# Filter by function name
yarn twenty dev:function:logs -n create-new-post-card
# Filter by universalIdentifier
yarn twenty dev:function:logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
<Note>
Это отличается от `yarn twenty docker:logs`, который показывает логи контейнера Docker. `yarn twenty dev:function:logs` показывает журналы выполнения функций вашего приложения с сервера Twenty.
</Note>
## Генерация типизированного клиента (`yarn twenty dev:generate-client`)
Повторно сгенерируйте типизированный API‑клиент (`twenty-client-sdk`) из схемы активного удалённого экземпляра, не выполняя сборку или синхронизацию приложения. Используйте это, чтобы получить типизированного клиента в любом проекте — например, в backend‑сервисе в отдельном репозитории — который взаимодействует с вашим экземпляром Twenty:
```bash filename="Terminal"
# In your project (no Twenty app definition required)
yarn add twenty-sdk twenty-client-sdk
# Connect to the Twenty instance to generate the client from
yarn twenty remote:add
# Generate the typed client into node_modules/twenty-client-sdk
yarn twenty dev:generate-client
```
Затем импортируйте клиента в свой код:
```typescript
import { CoreApiClient } from 'twenty-client-sdk/core';
```
Повторно запустите команду при каждом изменении вашей модели данных, чтобы обновить сгенерированные типы.
<Note>
Клиент генерируется внутри `node_modules`, поэтому он не добавляется в ваш репозиторий вместе с кодом. Запускайте `yarn twenty dev:generate-client` после каждой установки (например, в скрипте `postinstall` или в CI).
</Note>
## Удаление приложения (`yarn twenty app:uninstall`)
Удалите свое приложение из активного рабочего пространства:
```bash filename="Terminal"
yarn twenty app:uninstall
# Skip the confirmation prompt
yarn twenty app:uninstall --yes
```
## Управление удалёнными серверами
**Remote** — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скаффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые удалённые серверы или переключаться между ними.
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote:add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote:add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote:add --url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Ваши учётные данные хранятся в `~/.twenty/config.json`.

View File

@@ -0,0 +1,32 @@
---
title: Обзор
description: Собирайте, тестируйте и поставляйте свое приложение — команды CLI, интеграционные тесты, CI и публикация на сервер или в npm.
icon: rocket
---
**Операционный уровень** — это все, что вы делаете *над* своим приложением, а не *с ним*: выполнение команд CLI, запуск интеграционных тестов на реальном сервере Twenty, настройка CI и поставка релизов — либо в виде tar-архива, разворачиваемого на одном сервере, либо как пакет npm, размещенный в маркетплейсе.
```text
develop ─▶ test ─▶ build ─▶ deploy / publish
─────── ──── ───── ─────────────────
yarn yarn yarn yarn twenty app:publish --private (tarball → one server)
twenty test twenty
dev dev:build yarn twenty app:publish (npm → marketplace)
```
## В этом разделе
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/ru/developers/extend/apps/operations/cli">
Справочник по `yarn twenty` — exec, logs, uninstall, remotes.
</Card>
<Card title="Синхронизация и восстановление" icon="compass" href="/l/ru/developers/extend/apps/operations/sync-and-recovery">
Какие команды когда использовать, как читать diff синхронизации и пошаговое восстановление.
</Card>
<Card title="Тестирование" icon="flask" href="/l/ru/developers/extend/apps/operations/testing">
Настройка Vitest, интеграционные тесты, проверка типов, рабочий процесс CI.
</Card>
<Card title="Публикация" icon="upload" href="/l/ru/developers/extend/apps/operations/publishing">
Сборка, развертывание tarball, публикация в npm, установка.
</Card>
</CardGroup>

View File

@@ -0,0 +1,293 @@
---
title: Публикация
icon: upload
description: Распространяйте своё приложение Twenty в маркетплейсе или разверните его для внутреннего использования.
---
## Обзор
После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/getting-started/concepts), у вас есть два пути для его распространения:
* **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования.
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
Оба пути начинаются с одного и того же шага **build**.
## Сборка вашего приложения
Выполните команду сборки, чтобы скомпилировать приложение и сгенерировать готовый к распространению `manifest.json`:
```bash filename="Terminal"
yarn twenty dev:build
```
Это компилирует исходные файлы TypeScript, транспилирует функции логики и фронтенд-компоненты и записывает всё в `.twenty/output/`. Добавьте `--tarball`, чтобы также создать пакет `.tgz` для ручного распространения или для команды publish.
## Развертывание на сервер (tarball)
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
### Требования
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
Добавьте remote:
```bash filename="Terminal"
yarn twenty remote:add --url https://your-twenty-server.com --as production
```
### Развертывание
Соберите и загрузите ваше приложение на сервер в одном шаге:
```bash filename="Terminal"
yarn twenty app:publish --private
# To deploy to a specific remote:
# yarn twenty app:publish --private --remote production
```
### Общий доступ к развернутому приложению
Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением:
1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение
2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа**
3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения
Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере.
### Управление версиями
При обновлении уже развернутого tarball-приложения сервер требует, чтобы значение `version` в `package.json` было **строго выше** (согласно упорядочиванию по [semver](https://semver.org)), чем текущая развернутая версия. Повторное развёртывание той же версии или публикация более низкой версии отклоняются до сохранения tarball — в CLI вы увидите ошибку `VERSION_ALREADY_EXISTS`.
Чтобы выпустить обновление:
1. Увеличьте значение поля `version` в вашем `package.json` (например: `1.2.3` → `1.2.4`, `1.3.0` или `2.0.0`).
2. Выполните `yarn twenty app:publish --private` (или `yarn twenty app:publish --private --remote production`)
3. Рабочие пространства, в которых приложение установлено и для него включено автообновление (на вкладке «Настройки» приложения), обновляются автоматически в фоновом режиме; в остальных рабочих пространствах пользователи увидят доступное обновление в своих настройках
<Note>
Пререлизные теги работают как ожидается: повышение версии `1.0.0-rc.1` → `1.0.0-rc.2` допускается, а финальный релиз вроде `1.0.0` корректно распознаётся как более высокий, чем `1.0.0-rc.5`. Версия в `package.json` должна сама по себе быть корректной строкой semver.
</Note>
{/* TODO: add screenshot of the Upgrade button */}
### Совместимость версий сервера
Если ваше приложение использует функцию, появившуюся в конкретной версии сервера Twenty (например, провайдеры OAuth, добавленные в v2.3.0), следует объявить минимальную требуемую версию сервера с помощью поля `engines.twenty` в `package.json`:
```json filename="package.json"
{
"name": "twenty-my-app",
"version": "1.0.0",
"engines": {
"node": "^24.5.0",
"twenty": ">=2.3.0"
}
}
```
Значение — это стандартный [диапазон semver](https://github.com/npm/node-semver#ranges). Типовые шаблоны:
| Диапазон | Значение |
| ---------------------------------- | -------------------------------------------------- |
| `>=2.3.0` | Любой сервер версии 2.3.0 и новее |
| `>=2.3.0 \<3.0.0` | 2.3.0 или новее, но ниже следующей мажорной версии |
| `^2.3.0` | То же, что и `>=2.3.0 \<3.0.0` |
**Что происходит при развёртывании и установке:**
* Если `engines.twenty` задано и версия целевого сервера не удовлетворяет диапазону, развёртывание (загрузка tarball-архива) или установка отклоняются с ошибкой `SERVER_VERSION_INCOMPATIBLE` и сообщением, указывающим как требуемый диапазон, так и фактическую версию сервера.
* Если `engines.twenty` **не задано**, приложение принимается на сервере любой версии (обратная совместимость с существующими приложениями).
* Если на сервере `APP_VERSION` не задано, проверка пропускается.
<Note>
Сервер выполняет окончательную проверку — он проверяет `engines.twenty` как при загрузке tarball-архива, так и при установке в рабочем пространстве. Если вы развёртываете tarball вне стандартного процесса или устанавливаете из маркетплейса, сервер всё равно принудительно проверяет совместимость.
</Note>
## Автоматизированный CI/CD (рабочие процессы, сгенерированные шаблоном)
Приложения, созданные с помощью `create-twenty-app`, «из коробки» включают три рабочих процесса GitHub Actions в каталоге `.github/workflows/`. CI запускается без какой-либо настройки, для CD требуется один секрет, а публикация в npm требует однократной настройки доверенного издателя npm (trusted-publisher).
### CI — `ci.yml`
Автоматически запускает интеграционные тесты при каждом пуше в `main` и для каждого pull request.
**Что делает:**
1. Извлекает исходный код вашего приложения.
2. Запускает изолированный тестовый экземпляр Twenty с помощью составного действия `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` (эквивалент для CI `yarn twenty docker:start --test`).
3. Включает Corepack, настраивает Node.js на основе вашего `.nvmrc` и устанавливает зависимости с помощью `yarn install --immutable`.
4. Запускает `yarn test`, передавая `TWENTY_API_URL` и `TWENTY_API_KEY` из запущенного экземпляра, чтобы ваши тесты могли взаимодействовать с реальным сервером.
**Параметры конфигурации:**
* `TWENTY_VERSION` (переменная окружения, по умолчанию `latest`) — зафиксируйте версию сервера Twenty, используемую в CI, отредактировав это значение в `ci.yml`.
* Параллельные запуски группируются по `github.ref` и отменяют выполняющиеся прогоны при новых пушах.
Секреты не требуются — тестовый экземпляр эфемерен и существует только на время выполнения задания.
### CD — `cd.yml`
Разворачивает ваше приложение на настроенном сервере Twenty при каждом пуше в `main` и, при необходимости, из pull request при наличии метки `deploy`.
**Что делает:**
1. Извлекает head-коммит PR (для PR с меткой) или запушенный коммит.
2. Запускает `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — эквивалент для CI `yarn twenty app:publish --private`.
3. Запускает `twentyhq/twenty/.github/actions/install-twenty-app@main`, чтобы новая развернутая версия была установлена в целевое рабочее пространство.
**Обязательная конфигурация:**
| Настройка | Где | Назначение |
| ----------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `env` в `cd.yml` (по умолчанию `http://localhost:3000`) | Сервер Twenty, на который выполняется деплой. Перед первым использованием замените это на URL вашего реального сервера. |
| `TWENTY_DEPLOY_API_KEY` | В репозитории GitHub — **Settings → Secrets and variables → Actions** | Ключ API с правом деплоя на целевом сервере. |
<Note>
Значение `TWENTY_DEPLOY_URL` по умолчанию — `http://localhost:3000` — это заглушка: с хостируемого GitHub раннера к ней не будет доступа. Перед включением CD замените его на публичный URL вашего сервера (или используйте self-hosted раннер с сетевым доступом).
</Note>
**Запуск предварительного деплоя из PR:**
Добавьте к pull request метку `deploy`. Условие `if:` в `cd.yml` запустит задачу для этого PR, используя его head-коммит, что позволит проверить изменение на целевом сервере до слияния.
### Публикация — `publish.yml`
Публикует ваше приложение в npm с указанием происхождения (provenance), когда вы отправляете тег версии (например, `v1.0.0`), или когда вы запускаете рабочий процесс вручную на вкладке Actions.
**Что делает:**
1. Клонирует ваше приложение, настраивает Node.js и обновляет npm (для доверенной публикации требуется npm версии 11.5.1 или новее).
2. Запускает `yarn twenty app:publish`, который собирает приложение и публикует `.twenty/output` в npm. В CI он автоматически добавляет `--provenance` и `--access public`, поэтому в рабочем процессе флаги не требуются.
**Первоначальная настройка:**
На npmjs.com откройте свой пакет > **Settings → Trusted Publisher** и зарегистрируйте этот репозиторий, указав рабочий процесс `publish.yml` (см. [документацию по доверенной публикации в npm](https://docs.npmjs.com/trusted-publishers)). Публикация с provenance подтверждает, какой репозиторий GitHub собрал пакет, а также позволяет вам заявить права на ваше приложение в маркетплейсе Twenty.
<Note>
npm принимает подтверждение происхождения только из **публичных** репозиториев с исходным кодом. Если вы публикуете из приватного репозитория, npm отклоняет пакет подтверждения происхождения OIDC с ошибкой `E422 ... Ошибка `Unsupported GitHub Actions source repository visibility: "private"`. Чтобы публиковать из приватного репозитория, отключите подтверждение происхождения, установив `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'`в`env`шага публикации (закомментированная подсказка включена в сгенерированный`publish.yml\`):
```yaml filename=".github/workflows/publish.yml"
- name: Publish to npm
env:
TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
run: yarn twenty app:publish
```
</Note>
### Закрепление версий повторно используемых действий
Рабочие процессы `ci.yml` и `cd.yml` ссылаются на повторно используемые действия с указанием `@main`, поэтому обновления действий в репозитории `twentyhq/twenty` подхватываются автоматически. Если вам нужны детерминированные сборки, замените `@main` на SHA коммита или тег релиза в каждой строке `uses:`.
## Публикация в npm
Публикация в npm делает ваше приложение видимым в маркетплейсе Twenty. Любое рабочее пространство Twenty может просматривать, устанавливать и обновлять приложения из маркетплейса непосредственно из интерфейса.
### Требования
* Учётная запись [npm](https://www.npmjs.com)
* Ключевое слово `twenty-app` в массиве `keywords` вашего `package.json` (добавьте его вручную — по умолчанию оно не включено в шаблон `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Метаданные маркетплейса
Конфигурация `defineApplication()` поддерживает необязательные поля, которые определяют, как ваше приложение отображается в маркетплейсе. Используйте `logo` и `galleryImages`, чтобы ссылаться на изображения из папки `public/`:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
logo: 'public/logo.png',
galleryImages: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
См. [аккордеон defineApplication](/l/ru/developers/extend/apps/config/application#marketplace-metadata) на странице «Создание приложений» для полного списка полей маркетплейса (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` и т. д.).
#### Рекомендуемые размеры изображений галереи
Маркетплейс отображает `galleryImages` в контейнере с фиксированным соотношением сторон `8:5` (например, `1600×1000 px`).
<Note>
Изображения галереи с любым соотношением сторон отображаются полностью и никогда не обрезаются, но всё, что значительно выше или уже, чем `8:5`, будет иметь пустые поля по бокам.
</Note>
#### Ограничение размера изображения
Файл `logo` и каждый файл из `galleryImages` не должны превышать **10 MB**. Более крупные файлы пропускаются при повторном размещении ваших опубликованных ресурсов на маркетплейсе, поэтому они не будут отображаться.
### Публикация
```bash filename="Terminal"
yarn twenty app:publish
```
Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`):
```bash filename="Terminal"
yarn twenty app:publish --tag beta
```
### Как работает обнаружение приложений в маркетплейсе
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**.
Вы можете запустить синхронизацию немедленно, вместо ожидания:
```bash filename="Terminal"
yarn twenty dev:catalog-sync
# To target a specific remote:
# yarn twenty dev:catalog-sync --remote production
```
Метаданные, отображаемые в маркетплейсе, берутся из вашей конфигурации `defineApplication()` — см. раздел [Метаданные маркетплейса](#marketplace-metadata) выше.
<Note>
Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`.
</Note>
### Публикация через CI
Сгенерированный выше рабочий процесс `publish.yml` автоматически публикует в npm по тегам версий, с provenance. Поскольку `yarn twenty app:publish` при запуске в CI добавляет за вас `--provenance` и `--access public`, в рабочем процессе не нужны флаги npm — требуется только однократная настройка доверенного издателя.
Для других систем CI (GitLab CI, CircleCI и т. д.) запустите `yarn install`, затем `yarn twenty app:publish`. Provenance создается, когда среда может выпустить токен OIDC, и в противном случае автоматически пропускается.
<Note>
**npm provenance** добавляет значок доверия к вашему пакету в npm, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. Это также то, что позволяет вам заявить права на ваше приложение в маркетплейсе Twenty. Подробности см. в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Note>
## Установка приложений
После публикации приложения (npm) или его развертывания (tarball) рабочие пространства могут установить его через интерфейс.
Перейдите на страницу **Настройки > Приложения** в Twenty, где можно просматривать и устанавливать как приложения из маркетплейса, так и развернутые через tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
Вы также можете устанавливать приложения из командной строки:
```bash filename="Terminal"
yarn twenty app:install
```
<Note>
Сервер при установке применяет версионирование semver, аналогичное правилам при развёртывании:
* Установка той же версии, которая уже установлена в вашем рабочем пространстве, отклоняется с ошибкой `APP_ALREADY_INSTALLED`.
* Установка версии ниже текущей отклоняется с ошибкой `CANNOT_DOWNGRADE_APPLICATION`.
Чтобы установить более новую версию, сначала разверните или опубликуйте её, затем снова выполните `yarn twenty app:install`.
</Note>

View File

@@ -0,0 +1,126 @@
---
title: Синхронизация и восстановление
description: Какую команду когда использовать, как читать вывод синхронизации и поэтапный план восстановления на случай расхождения локальных метаданных — до того, как дойдет до полного сброса.
icon: compass
---
Локальная разработка приложения строится вокруг **синхронизации**: CLI пересобирает ваш манифест, а сервер применяет только разницу между ним и метаданными, которые уже есть в вашем рабочем пространстве. На этой странице описано, какую команду выбрать, как читать, что изменила синхронизация, и что делать — по шагам — когда локальное состояние выглядит несогласованным.
## Какую команду и когда использовать
<Note>
Для повседневной локальной разработки вам почти всегда нужна команда `yarn twenty dev`. Развертывание и публикация предназначены для выпуска релизов, **а не** для локального цикла разработки.
</Note>
| Вы хотите… | Команда | Заметки |
| ---------------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Выполнять локальные итерации с синхронизацией в реальном времени | `yarn twenty dev` | Отслеживает ваши файлы и синхронизирует при каждом изменении. |
| Выполнить одну синхронизацию и выйти (CI, скрипты, хуки) | `yarn twenty apply` | Одна сборка + синхронизация, затем завершение работы. Добавьте `--force`, чтобы пропустить подтверждение разрушительных изменений. |
| Предпросмотр изменений **без их применения** | `yarn twenty plan` | Вычисляет и выводит diff; ничего не записывает. |
| Удалить приложение из рабочего пространства | `yarn twenty app:uninstall` | Добавьте `--yes`, чтобы пропустить запрос подтверждения. |
| Отправить на сервер tar-архив | `yarn twenty app:publish --private` | Требуется **строго более высокая** версия в `package.json` — см. [Публикация](/l/ru/developers/extend/apps/operations/publishing). |
| Опубликовать на маркетплейсе (npm) | `yarn twenty app:publish` | — |
| Установить / обновить развернутую версию | `yarn twenty app:install` | Устанавливает версию, которая сейчас развернута. |
| Очистить локальный сервер и начать с нуля | `yarn twenty docker:reset` | Удаляет **все** локальные данные — крайняя мера. |
<Note>
`yarn twenty dev --once` и `yarn twenty dev --once --dry-run` по-прежнему работают как устаревшие псевдонимы для `yarn twenty apply` и `yarn twenty plan`.
</Note>
### Для локальной синхронизации не нужно повышать версию
Правило строго возрастающей `version` (`VERSION_ALREADY_EXISTS` при deploy, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` при install) относится к **`app:publish` / `app:install`** — пути релизов. `yarn twenty dev` синхронизирует ваш манифест на месте и никогда не требует изменения версии, поэтому вам не нужно трогать `package.json`, чтобы делать итерации. Если вы ловите себя на том, что поднимаете версию, чтобы протестировать локальное изменение, значит вы используете релизный путь, когда вам нужен цикл разработки (dev loop).
## Чтение вывода синхронизации
Каждая синхронизация выводит изменения метаданных, которые она применила (или применила бы, с `plan`), в стиле Terraform — по одному блоку на сущность с её атрибутами, затем строку с итогами:
```text filename="Terminal"
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
Это ваша первая диагностическая точка: она показывает, какие именно объекты, поля и макеты изменились, чтобы вы могли подтвердить, что синхронизация сделала то, что вы ожидали, до проверки в интерфейсе.
Разрушительные изменения (`to destroy`) перечисляются вместе с тем, что они удаляют (например, `objectMetadata "auditNote" — drops the table and all its rows`) и требуют интерактивного подтверждения или `--force` в скриптах.
Когда синхронизация завершается с ошибкой на одной сущности, в сообщении указываются проблемная сущность и её `universalIdentifier`, например:
```text
Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337) failed
```
Используйте этот идентификатор, чтобы найти сущность в своем манифесте (и, при необходимости, в рабочем пространстве), вместо того чтобы гадать, какая из них конфликтует.
## Предпросмотр изменений (plan)
`yarn twenty plan` собирает ваш манифест, запрашивает у сервера план миграции и выводит его — **без применения чего-либо**. Это безопасный способ ответить на вопрос «что изменит эта синхронизация?» до того, как вы на неё согласитесь.
```bash filename="Terminal"
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "crewCapacity" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
План:
* **Ничего не записывает** — ни миграции метаданных, ни обновления записи приложения, ни изменений ролей/вкладок по умолчанию, ни генерации API‑клиента.
* Возвращает **тот же diff**, который применит реальная синхронизация, чтобы вы могли заранее просмотреть создаваемые/обновляемые/удаляемые сущности.
* Полезен перед рискованным изменением, при проверке изменения, сгенерированного ИИ, или в скрипте, который должен завершаться с ошибкой, если вот-вот будет применено неожиданное изменение.
<Note>
План только предварительно показывает изменения **метаданных** и требует, чтобы приложение хотя бы один раз уже было синхронизировано (чтобы рабочее пространство знало о нём). Если вы запускаете его для приложения, которое никогда не синхронизировалось, сервер сообщит, что приложение не установлено — сначала один раз выполните `yarn twenty dev`.
</Note>
## Лестница восстановления
Когда локальные метаданные выглядят неверно, действуйте поэтапно в следующем порядке и останавливайтесь, как только проблема решена. Каждый следующий шаг более разрушителен, чем предыдущий.
1. **Повторно синхронизируйте.** Снова выполните `yarn twenty apply`. Синхронизации идемпотентны — повторный запуск корректного манифеста безопасен и часто устраняет временный сбой.
2. **Просмотрите план.** Выполните `yarn twenty plan`, чтобы увидеть, что именно намеревается изменить следующая синхронизация, не применяя эти изменения.
3. **Прочитайте сообщение об ошибке.** Если синхронизация завершается с ошибкой, обратите внимание на тип метаданных и `universalIdentifier` в сообщении (см. выше) и найдите эту сущность в своем манифесте. Конфликт обычно указывает на дублированный или повторно используемый идентификатор.
4. **Удалите и переустановите.** Выполните `yarn twenty app:uninstall`, затем синхронизируйте снова (`yarn twenty dev`). Это пересобирает метаданные приложения с нуля, сохраняя остальную часть вашего рабочего пространства нетронутой.
5. **Полный сброс (крайняя мера).** Выполните `yarn twenty docker:reset`, затем заново выполните начальное наполнение данными и синхронизацию.
<Warning>
`yarn twenty docker:reset` удаляет **все** данные в вашей локальной инсталляции — все рабочие пространства, записи и приложения. Используйте его только после того, как предыдущие шаги не помогли.
</Warning>
<Note>
Столкнулись с ошибкой метаданных? Пожалуйста, [создайте issue](https://github.com/twentyhq/twenty/issues/new/choose) и приложите сообщение о сбое миграции (с типом метаданных и `universalIdentifier`), вывод `Metadata changes` из синхронизации и команды, которые вы запускали.
</Note>
## Избегайте одновременных синхронизаций в одном рабочем пространстве
Синхронизация применяет миграции метаданных. Запуск нескольких операций sync, deploy или install по отношению к **одному и тому же рабочему пространству одновременно** — например, из нескольких терминалов или при параллельных итерациях агентов ИИ — может перемешать эти миграции и оставить метаданные в частично примененном состоянии.
Сервер последовательно обрабатывает синхронизации для каждого рабочего пространства, чтобы предотвратить это, но вам всё равно следует пропускать чувствительные операции с метаданными через **один** процесс, а не запускать их параллельно. Если вы организуете разработку с несколькими агентами, направляйте их вызовы sync/deploy/install через одну очередь, чтобы в каждый момент времени выполнялась только одна операция.
## Как различать типы сбоев
Когда что‑то идёт не так, diff метаданных и именованные ошибки помогают определить, на каком этапе произошел сбой:
* **Ошибка сборки манифеста** — CLI завершается с ошибкой до синхронизации (`MANIFEST_BUILD_FAILED`, `TYPECHECK_FAILED`); исправьте исходный код приложения.
* **Ошибка синхронизации / миграции** — сборка проходит успешно, но применение diff завершается сбоем с указанием сущности и `universalIdentifier`; исправьте конфликтующие метаданные.
* **Ошибка размера зависимостей** — синхронизация или установка завершается сбоем, потому что продакшен‑зависимости приложения (`dependencies`) слишком велики, чтобы их установить в качестве слоя среды выполнения (`LOGIC_FUNCTION_DEPENDENCIES_SIZE_EXCEEDED`); перенесите пакеты, которые ваши логические функции не импортируют во время выполнения (UI-библиотеки, инструменты разработки), в `devDependencies`.
* **Ошибка выполнения кода приложения** — синхронизация проходит успешно, но ваши логические функции или компоненты ведут себя неправильно во время выполнения; проверьте [журналы функций](/l/ru/developers/extend/apps/operations/cli).
* **Локальное состояние экземпляра** — ни один из вышеперечисленных пунктов не подходит, и рабочее пространство всё ещё выглядит неправильно; двигайтесь вниз по лестнице восстановления.

View File

@@ -0,0 +1,264 @@
---
title: Тестирование
description: Настройка Vitest, интеграционные тесты с использованием реального сервера Twenty, проверка типов и CI с GitHub Actions.
icon: flask
---
SDK предоставляет программные API, которые позволяют собирать, разворачивать, устанавливать и удалять ваше приложение из тестового кода. В сочетании с [Vitest](https://vitest.dev/) и типизированными клиентами API вы можете писать интеграционные тесты, которые проверяют, что ваше приложение работает сквозным образом на реальном сервере Twenty.
## Использование пакетов npm
Вы можете устанавливать и использовать любые пакеты npm в своём приложении. И логические функции, и компоненты фронтенда собираются с помощью [esbuild](https://esbuild.github.io/), который встраивает их зависимости в выходной файл — каталоги `node_modules` во время выполнения не нужны.
### Установка пакета
```bash filename="Terminal"
yarn add axios
```
Затем импортируйте его в своём коде:
```ts src/logic-functions/fetch-data.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import axios from 'axios';
const handler = async (): Promise<any> => {
const { data } = await axios.get('https://api.example.com/data');
return { data };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-data',
description: 'Fetches data from an external API',
timeoutSeconds: 10,
handler,
});
```
То же самое работает для компонентов фронтенда:
```tsx src/front-components/chart.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { format } from 'date-fns';
const DateWidget = () => {
return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'date-widget',
component: DateWidget,
});
```
### Как работает бандлинг
Этап сборки использует esbuild для создания одного самодостаточного файла на каждую логическую функцию и на каждый компонент фронтенда. Импортированные пакеты встраиваются в бандл, за исключением перечисленных ниже.
**Логические функции** выполняются в среде Node.js. Встроенные модули Node (`fs`, `path`, `crypto`, `http` и т. д.) доступны и не требуют установки.
**Компоненты фронтенда** выполняются в Web Worker. Встроенные модули Node **недоступны** — доступны только пакеты npm, работающие в браузерной среде. Обратите внимание, что в песочнице реализован *частичный* DOM, поэтому пакет может успешно собраться, но все равно завершиться с ошибкой во время выполнения; см. раздел [Текущие ограничения](/l/ru/developers/extend/apps/layout/front-components#current-limitations).
В обеих средах доступны как предварительно предоставленные модули `twenty-client-sdk/core` и `twenty-client-sdk/metadata` — они не включаются в бандл, а подставляются сервером во время выполнения.
Фронтенд‑компоненты также могут совместно использовать зависимости через [общий бандл зависимостей](/l/ru/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components). Пакеты, перечисленные в поле `frontComponentSharedDependencies` файла package.json приложения, один раз собираются в отдельный файл и загружаются из него во время выполнения вместо внедрения в каждый компонент, поэтому компонент, который импортирует только общие пакеты, практически не содержит собственного кода зависимостей.
## Настройка
Приложение, созданное скэффолдером, уже включает Vitest. Если вы настраиваете его вручную, установите зависимости:
```bash filename="Terminal"
yarn add -D vitest vite-tsconfig-paths
```
Создайте `vitest.config.ts` в корне вашего приложения:
```ts vitest.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
projects: ['tsconfig.spec.json'],
ignoreConfigErrors: true,
}),
],
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Создайте глобальный файл настройки, который проверяет, что сервер доступен, записывает тестовую конфигурацию для SDK (`~/.twenty/config.test.json`) и выполняет синхронизацию приложения перед запуском тестов:
```ts src/__tests__/global-setup.ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
// Verify the server is running
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## Программные API SDK
Подпуть `twenty-sdk/cli` экспортирует функции, которые можно вызывать напрямую из тестового кода:
| Функция | Описание |
| -------------- | --------------------------------------------------------------------------------- |
| `appBuild` | Собрать приложение и при необходимости упаковать tar-архив |
| `appDeploy` | Загрузить tar-архив на сервер |
| `appDevOnce` | Один раз собрать и синхронизировать приложение (то же, что и `yarn twenty apply`) |
| `appInstall` | Установить приложение в активное рабочее пространство |
| `appUninstall` | Удалить приложение из активного рабочего пространства |
Каждая функция возвращает объект результата с `success: boolean` и либо `data`, либо `error`.
## Написание интеграционного теста
Полный пример, который собирает, разворачивает и устанавливает приложение, а затем проверяет, что оно появляется в рабочем пространстве:
```ts src/__tests__/app-install.integration-test.ts
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const APP_PATH = process.cwd();
describe('App installation', () => {
beforeAll(async () => {
const buildResult = await appBuild({
appPath: APP_PATH,
tarball: true,
onProgress: (message: string) => console.log(`[build] ${message}`),
});
if (!buildResult.success) {
throw new Error(`Build failed: ${buildResult.error?.message}`);
}
const deployResult = await appDeploy({
tarballPath: buildResult.data.tarballPath!,
onProgress: (message: string) => console.log(`[deploy] ${message}`),
});
if (!deployResult.success) {
throw new Error(`Deploy failed: ${deployResult.error?.message}`);
}
const installResult = await appInstall({ appPath: APP_PATH });
if (!installResult.success) {
throw new Error(`Install failed: ${installResult.error?.message}`);
}
});
afterAll(async () => {
await appUninstall({ appPath: APP_PATH });
});
it('should find the installed app in the workspace', async () => {
const metadataClient = new MetadataApiClient();
const result = await metadataClient.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});
const installedApp = result.findManyApplications.find(
(app: { universalIdentifier: string }) =>
app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);
expect(installedApp).toBeDefined();
});
});
```
## Запуск тестов
Убедитесь, что ваш локальный сервер Twenty запущен, затем:
```bash filename="Terminal"
yarn test
```
Или в режиме наблюдения во время разработки:
```bash filename="Terminal"
yarn test:watch
```
## Проверка типов
Вы также можете запустить проверку типов для своего приложения без запуска тестов:
```bash filename="Terminal"
yarn twenty dev:typecheck
```
Это запускает `tsc --noEmit` для `tsconfig.json` вашего приложения и сообщает о любых ошибках типов. Сгенерированные с помощью скэффолдера приложения также поставляются со скриптом `yarn typecheck`, который проверяет и тестовые файлы (`tsconfig.spec.json`).
## CI с GitHub Actions
Скэффолдер генерирует готовый к использованию рабочий процесс в `.github/workflows/ci.yml`. При каждом пуше в `main` и каждом pull request он запускает эфемерный сервер Twenty в раннере (через экшен `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), затем выполняет `yarn lint`, `yarn typecheck`, `yarn test:unit` и `yarn test` с `TWENTY_API_URL` / `TWENTY_API_KEY`, указывающими на этот сервер. Секреты не требуются, и вы можете зафиксировать версию сервера через переменную окружения `TWENTY_VERSION` в начале рабочего процесса.
См. раздел [Публикация → Автоматизированный CI/CD](/l/ru/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) для полного пошагового руководства по трем сгенерированным рабочим процессам (`ci.yml`, конвейер деплоя `cd.yml` и `publish.yml` для публикации в npm).

View File

@@ -0,0 +1,144 @@
---
title: Переводы
description: Выпускайте приложение на нескольких языках — переводите метки манифеста и строки пользовательского интерфейса через единый каталог локалей.
icon: language
---
Приложения Twenty создаются на **английском**: строки в вашем исходном коде и манифесте
являются исходным текстом на английском, `en` — это исходная локаль, с которой вы переводите, а
любая непереведенная локаль будет использовать ее по умолчанию.
В вашем приложении есть два типа переводимого текста, и оба проходят через один и тот же
каталог `locales/`:
* **Метки манифеста** — имена объектов и полей, заголовки представлений, пункты меню и
другие строки, объявленные в метаданных вашего приложения.
* **Строки фронт-компонента** — пользовательский интерфейс для отображения React фронтальных компонентов.
Вы помечаете переводимые строки, извлекаете их в каталоги для каждой локали,
переводите эти каталоги, и сборка отдает правильный язык для
текущего пользователя — без дополнительной настройки.
## Маркировка переднего компонента
Импортировать помощников перевода из `twenty-sdk/front-component`:
```tsx
import { Trans, t, msg, useTranslate } from 'twenty-sdk/front-component';
const STATUSES = [
{ id: 'draft', label: msg('Draft') },
{ id: 'sent', label: msg('Sent') },
];
const Card = ({ count, name }: { count: number; name: string }) => {
const { t } = useTranslate();
return (
<section>
{/* Static text — reactive to the user's locale */}
<Trans>Loading postcard…</Trans>
{/* Disambiguate identical sources with a context */}
<Trans context="card-title">Untitled</Trans>
{/* Interpolation: pass values explicitly */}
<p>{t('Hi {name}', { name })}</p>
<p>{t('Saved {count} cards', { count })}</p>
{/* Resolve a lazily-declared descriptor */}
<ul>{STATUSES.map((s) => <li key={s.id}>{t(s.label)}</li>)}</ul>
</section>
);
};
```
### Когда использовать, какой
* **`Trans`** — статический текст в JSX. Используйте пропсы `message` и `values` для
интерполяции, как в `Trans message="Hi {name}" values={{ name }}`;
интерполяция напрямую в дочерних элементах не может быть статически извлечена.
* **`useTranslate().t`** — динамические строки внутри компонента. Повторно отображается, когда
пользователь переключает язык. Предпочитать это внутри рендера.
* **`t(...)`** (импортировано напрямую) — перевод можно использовать **где**,
включая обработчики событий, вспомогательные и область модулей — не только внутри рендера.
* **`msg(...)`** — ленивый дескриптор строк, объявленный данными (константы,
config). Разрешите позже с помощью `t(descriptor)`.
### Контекст
Передайте `context` в недвусмысленный перевод идентичных исходных строк, которые переводят
по-разному:
```tsx
t({ message: 'Open', context: 'door' });
t({ message: 'Open', context: 'window' });
<Trans context="card-title">Untitled</Trans>
```
Контекстные строки отображаются в каталогах локалей в виде группы, индексируемой
по их контексту: `"\<context>": { "\<message>": "\<translation>" }`. Манифестные метки являются
контекстными по своей природе — каждая из них несёт метаданные о своей роли
(`objectMetadata.labelSingular`, `fieldMetadata.label`, `view.name`, …), поэтому
одно и то же слово может переводиться по‑разному как имя объекта и как имя представления.
## Извлечение и перевод
Выполнить команду извлечения из вашей папки приложения:
```bash
twenty dev:translations-extract # collect strings into locales/en.json
twenty dev:translations-extract --locale fr-FR # also scaffold a target locale
```
При извлечении собираются как метки манифеста, так и строки `t()`/`msg()`/`Trans`
из исходного кода вашего фронт‑компонента в `locales/\<locale>.json`, где ключом
является исходная строка. Заполните перевод:
```json
// locales/fr-FR.json
{
"Loading postcard…": "Chargement de la carte…",
"Hi {name}": "Bonjour {name}",
"Saved {count} cards": "{count} cartes enregistrées",
"door": {
"Open": "Ouvrir"
},
"objectMetadata.labelSingular": {
"Postcard": "Carte postale"
}
}
```
Строки без контекста являются плоскими записями; контекстные строки — включая каждую
метку манифеста — вложены под своим ключом контекста.
Такие держатели, как `{name}`, заменяются во время выполнения — сохраняйте их в переводе
. Любая оставленная строка пуста возвращается к исходному тексту.
## Как он запускается
`twenty dev:build` компилирует каталоги и предоставляет нужный язык для
текущего пользователя: метки манифеста разрешаются на стороне сервера, а каталоги фронтенд-компонентов
встраиваются в каждый бандл компонента. Во время выполнения компонент читает локаль
из контекста выполнения (текущий язык узла) и разрешает
каждую строку в соответствии со своим каталогом, возвращение к источнику, когда перевод
отсутствует. Переключение языка в хосте повторно рендерит строки `Trans` и
`useTranslate().t` в реальном времени.
Метки манифеста компилируются при каждой синхронизации — `twenty dev:build`, `twenty apply`
и в постоянном режиме отслеживания `twenty dev` — поэтому достаточно изменить файл локали и
выполнить синхронизацию, чтобы увидеть новый текст. Каталоги компонентов Front встраиваются в
каждый пакет компонентов во время сборки, поэтому изменение одного из них по-прежнему требует
повторного запуска `twenty dev:build` (и повторного развертывания).
Дочерние текстовые элементы `Trans` могут занимать несколько строк — пробельные символы схлопываются
так же, как их схлопывает JSX, поэтому оба этих варианта извлекаются в ключ `Welcome back`:
```tsx
<Trans>Welcome back</Trans>
<Trans>
Welcome
back
</Trans>
```