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: Comandos de `yarn twenty` para ejecutar funciones, transmitir registros en tiempo real, gestionar instalaciones de aplicaciones y cambiar entre remotos.
icon: terminal
---
La CLI de `yarn twenty` es tu interfaz para todo lo relacionado con la aplicación. Lista completa de comandos:
| Comando | Qué hace | Documentado en |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `dev` | Supervisar archivos fuente y sincronizar en vivo los cambios | [Inicio rápido](/l/es/developers/extend/apps/getting-started/quick-start) |
| `plan` | Previsualizar cambios de metadatos sin aplicarlos | [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `apply` | Aplicar cambios de metadatos después de mostrar el plan | [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Compilar la aplicación y generar el cliente de la API (`--tarball` para empaquetar un `.tgz`) | [Publicación](/l/es/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Ejecutar la comprobación de tipos de TypeScript | [Pruebas](/l/es/developers/extend/apps/operations/testing) |
| `dev:add` | Generar una nueva entidad | [Generación de entidades](/l/es/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Regenerar el cliente de API tipado | esta página |
| `dev:function:exec` / `dev:function:logs` | Ejecutar funciones y transmitir sus registros | esta página |
| `dev:translations-extract` | Extraer cadenas traducibles en catálogos de `locales/` | [Traducciones](/l/es/developers/extend/apps/operations/translations) |
| `dev:catalog-sync` | Activar la sincronización del catálogo del marketplace | [Publicación](/l/es/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Ciclo de vida de la publicación | [Publicación](/l/es/developers/extend/apps/operations/publishing) y esta página |
| `docker:*` | Administrar el contenedor del servidor local de Twenty | [Servidor local](/l/es/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Administrar conexiones de servidor | esta página |
Todos los comandos aceptan `-r, --remote \<name>` para dirigirse a un remoto específico en lugar del predeterminado.
## Ejecutar funciones (`yarn twenty dev:function:exec`)
Ejecuta manualmente una función de lógica sin activarla mediante HTTP, cron o evento de base de datos:
```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
```
## Ver registros de funciones (`yarn twenty dev:function:logs`)
Transmitir en tiempo real los registros de ejecución de las funciones de lógica de tu aplicación:
```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>
Esto es diferente de `yarn twenty docker:logs`, que muestra los registros del contenedor de Docker. `yarn twenty dev:function:logs` muestra los registros de ejecución de funciones de tu aplicación desde el servidor de Twenty.
</Note>
## Generando el cliente tipado (`yarn twenty dev:generate-client`)
Regenera el cliente de API tipado (`twenty-client-sdk`) a partir del esquema del remoto activo, sin compilar ni sincronizar una aplicación. Úsalo para obtener un cliente tipado en cualquier proyecto — como un servicio backend que vive en un repositorio separado — que se comunica con tu instancia de 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
```
Luego importa el cliente en tu código:
```typescript
import { CoreApiClient } from 'twenty-client-sdk/core';
```
Vuelve a ejecutar el comando cada vez que cambie tu modelo de datos para actualizar los tipos generados.
<Note>
El cliente se genera dentro de `node_modules`, por lo que no se incluye en tus commits de código. Ejecuta `yarn twenty dev:generate-client` después de cada instalación (por ejemplo, en un script de `postinstall` o en CI).
</Note>
## Desinstalar una aplicación (`yarn twenty app:uninstall`)
Elimina tu aplicación del espacio de trabajo activo:
```bash filename="Terminal"
yarn twenty app:uninstall
# Skip the confirmation prompt
yarn twenty app:uninstall --yes
```
## Gestión de remotos
Un **remoto** es un servidor de Twenty al que se conecta tu app. Durante la configuración, el generador crea uno automáticamente para ti. Puedes añadir más remotos o cambiar entre ellos en cualquier momento.
```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>
```
Tus credenciales se almacenan en `~/.twenty/config.json`.

View File

@@ -0,0 +1,32 @@
---
title: Resumen
description: "Compila, prueba y envía tu aplicación: comandos de CLI, pruebas de integración, CI y publicación en un servidor o en npm."
icon: rocket
---
La **capa de operaciones** es todo lo que haces *a* tu aplicación en lugar de *con* ella: invocar comandos de CLI, ejecutar pruebas de integración contra un servidor Twenty real, configurar CI y enviar versiones, ya sea como un tarball implementado en un único servidor o como un paquete de npm listado en el marketplace.
```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)
```
## En esta sección
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/es/developers/extend/apps/operations/cli">
Referencia de `yarn twenty` — exec, logs, uninstall, remotes.
</Card>
<Card title="Sincronización y recuperación" icon="compass" href="/l/es/developers/extend/apps/operations/sync-and-recovery">
Qué comando usar y cuándo, cómo leer el diff de sincronización y una guía escalonada de recuperación.
</Card>
<Card title="Pruebas" icon="flask" href="/l/es/developers/extend/apps/operations/testing">
Configuración de Vitest, pruebas de integración, comprobación de tipos, flujo de trabajo de CI.
</Card>
<Card title="Publicación" icon="upload" href="/l/es/developers/extend/apps/operations/publishing">
Compilar, desplegar un tarball, publicar en npm, instalar.
</Card>
</CardGroup>

View File

@@ -0,0 +1,293 @@
---
title: Publicación
icon: upload
description: Distribuye tu aplicación de Twenty en el marketplace o despliégala internamente.
---
## Resumen
Una vez que tu aplicación esté [compilada y probada localmente](/l/es/developers/extend/apps/getting-started/concepts), tienes dos vías para distribuirla:
* **Desplegar un paquete tar** — sube tu aplicación directamente a un servidor Twenty específico para uso interno o privado.
* **Publicar en npm** — incluye tu aplicación en el marketplace de Twenty para que cualquier espacio de trabajo la descubra e instale.
Ambas rutas comienzan en el mismo paso de **build**.
## Compilar tu aplicación
Ejecuta el comando `build` para compilar tu aplicación y generar un `manifest.json` listo para distribución:
```bash filename="Terminal"
yarn twenty dev:build
```
Esto compila el código fuente de TypeScript, transpila las funciones de lógica y los componentes de frontend, y escribe todo en `.twenty/output/`. Agrega `--tarball` para generar también un paquete `.tgz` para la distribución manual o para el comando `publish`.
## Despliegue en un servidor (tarball)
Para aplicaciones que no quieres que estén disponibles públicamente — herramientas propietarias, integraciones solo para empresas o compilaciones experimentales — puedes desplegar un tarball directamente en un servidor de Twenty.
### Prerrequisitos
Antes de desplegar, necesitas un remoto configurado que apunte al servidor de destino. Los remotos almacenan la URL del servidor y las credenciales de autenticación localmente en `~/.twenty/config.json`.
Agrega un remoto:
```bash filename="Terminal"
yarn twenty remote:add --url https://your-twenty-server.com --as production
```
### Despliegue
Compila y sube tu aplicación al servidor en un solo paso:
```bash filename="Terminal"
yarn twenty app:publish --private
# To deploy to a specific remote:
# yarn twenty app:publish --private --remote production
```
### Compartir una aplicación desplegada
Las aplicaciones en tarball no se listan en el marketplace público, por lo que otros espacios de trabajo en el mismo servidor no las descubrirán navegando. Para compartir una aplicación desplegada:
1. Ve a **Configuración > Aplicaciones > Registros** y abre tu aplicación
2. En la pestaña **Distribución**, haz clic en **Copiar enlace para compartir**
3. Comparte este enlace con usuarios de otros espacios de trabajo — los llevará directamente a la página de instalación de la aplicación
El enlace para compartir usa la URL base del servidor (sin ningún subdominio de espacio de trabajo), por lo que funciona para cualquier espacio de trabajo en el servidor.
### Gestión de versiones
Al actualizar una aplicación tarball ya desplegada, el servidor requiere que la `version` en `package.json` sea **estrictamente mayor** (según el orden de [semver](https://semver.org)) que la versión actualmente desplegada. Volver a desplegar la misma versión, o subir una inferior, se rechaza antes de que se almacene el tarball — verás un error `VERSION_ALREADY_EXISTS` en la CLI.
Para publicar una actualización:
1. Incrementa el campo `version` en tu `package.json` (p. ej., `1.2.3` → `1.2.4`, `1.3.0` o `2.0.0`)
2. Ejecuta `yarn twenty app:publish --private` (o `yarn twenty app:publish --private --remote production`)
3. Los espacios de trabajo que tienen la aplicación instalada y la actualización automática habilitada (en la pestaña Configuración de la aplicación) se actualizan automáticamente en segundo plano; los demás verán la actualización disponible en su configuración
<Note>
Las etiquetas de prelanzamiento funcionan como se espera: incrementar `1.0.0-rc.1` → `1.0.0-rc.2` está permitido, y una versión final como `1.0.0` se reconoce correctamente como superior a `1.0.0-rc.5`. La versión en `package.json` debe ser en sí misma una cadena semver válida.
</Note>
{/* TODO: add screenshot of the Upgrade button */}
### Compatibilidad de la versión del servidor
Si tu app usa una función introducida en una versión específica del servidor Twenty (por ejemplo, proveedores de OAuth agregados en v2.3.0), debes declarar la versión mínima del servidor que tu app requiere usando el campo `engines.twenty` en `package.json`:
```json filename="package.json"
{
"name": "twenty-my-app",
"version": "1.0.0",
"engines": {
"node": "^24.5.0",
"twenty": ">=2.3.0"
}
}
```
El valor es un [rango semver](https://github.com/npm/node-semver#ranges) estándar. Patrones comunes:
| Rango | Significado |
| ---------------------------------- | -------------------------------------------------------------------- |
| `>=2.3.0` | Cualquier servidor desde 2.3.0 en adelante |
| `>=2.3.0 \<3.0.0` | 2.3.0 o posterior, pero por debajo de la siguiente versión principal |
| `^2.3.0` | Igual que `>=2.3.0 \<3.0.0` |
**Qué sucede durante la implementación e instalación:**
* Si `engines.twenty` está configurado y la versión del servidor de destino no cumple el rango, la implementación (carga del tarball) o la instalación se rechaza con un error `SERVER_VERSION_INCOMPATIBLE` y un mensaje que indica tanto el rango requerido como la versión real del servidor.
* Si `engines.twenty` **no está configurado**, la app se acepta en cualquier versión del servidor (retrocompatible con las apps existentes).
* Si el servidor no tiene `APP_VERSION` configurado, se omite la comprobación.
<Note>
El servidor es la autoridad en la comprobación — valida `engines.twenty` tanto en la carga del tarball como en la instalación en el espacio de trabajo. Si implementas un tarball fuera de banda o instalas desde el marketplace, el servidor sigue garantizando la compatibilidad.
</Note>
## CI/CD automatizado (flujos de trabajo preconfigurados)
Las aplicaciones generadas con `create-twenty-app` incluyen de forma predeterminada tres flujos de trabajo de GitHub Actions, en `.github/workflows/`. La CI se ejecuta sin configuración, la CD requiere un único secreto y la publicación en npm requiere una configuración por única vez de Trusted Publisher de npm.
### CI — `ci.yml`
Ejecuta pruebas de integración en cada push a `main` y en cada pull request.
**Qué hace:**
1. Obtiene el código fuente de tu aplicación.
2. Inicia una instancia de prueba aislada de Twenty usando la acción compuesta `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` (el equivalente en CI de `yarn twenty docker:start --test`).
3. Habilita Corepack, configura Node.js desde tu `.nvmrc` e instala las dependencias con `yarn install --immutable`.
4. Ejecuta `yarn test`, pasando `TWENTY_API_URL` y `TWENTY_API_KEY` de la instancia iniciada para que tus pruebas puedan comunicarse con un servidor real.
**Ajustes de configuración:**
* `TWENTY_VERSION` (variable de entorno; por defecto `latest`) — fija la versión del servidor de Twenty usada en CI editando esto en `ci.yml`.
* La concurrencia se agrupa por `github.ref` y cancela las ejecuciones en progreso cuando hay nuevos pushes.
No se requieren secretos — la instancia de prueba es efímera y existe solo durante la ejecución del trabajo.
### CD — `cd.yml`
Despliega tu aplicación en un servidor de Twenty configurado en cada push a `main` y, opcionalmente, desde un pull request cuando se aplica la etiqueta `deploy`.
**Qué hace:**
1. Obtiene el head del PR (para PR etiquetados) o el commit enviado.
2. Ejecuta `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — el equivalente en CI de `yarn twenty app:publish --private`.
3. Ejecuta `twentyhq/twenty/.github/actions/install-twenty-app@main` para que la versión recién desplegada se instale en el espacio de trabajo de destino.
**Configuración necesaria:**
| Configuración | Dónde | Propósito |
| ----------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `env` en `cd.yml` (por defecto `http://localhost:3000`) | El servidor de Twenty al que se va a desplegar. Cámbialo por la URL real de tu servidor antes del primer uso. |
| `TWENTY_DEPLOY_API_KEY` | Repositorio de GitHub **Settings → Secrets and variables → Actions** | Clave de API con permiso de despliegue en el servidor de destino. |
<Note>
La `TWENTY_DEPLOY_URL` predeterminada de `http://localhost:3000` es un marcador de posición — no alcanzará nada desde un runner alojado por GitHub. Actualízala a la URL pública de tu servidor (o usa un runner autohospedado con acceso a la red) antes de habilitar CD.
</Note>
**Activar un despliegue de vista previa desde un PR:**
Añade la etiqueta `deploy` a un pull request. La condición `if:` en `cd.yml` ejecutará el trabajo para ese PR usando el commit head del PR, lo que te permitirá validar un cambio en el servidor de destino antes de hacer merge.
### Publicar — `publish.yml`
Publica tu aplicación en npm con procedencia cuando haces push de una etiqueta de versión (por ejemplo, `v1.0.0`), o cuando ejecutas el flujo de trabajo manualmente desde la pestaña Actions.
**Qué hace:**
1. Hace checkout de tu aplicación, configura Node.js y actualiza npm (la publicación de confianza requiere npm 11.5.1 o posterior).
2. Ejecuta `yarn twenty app:publish`, que compila la aplicación y publica `.twenty/output` en npm. En CI agrega automáticamente `--provenance` y `--access public`, por lo que no se necesitan banderas en el flujo de trabajo.
**Configuración única:**
En npmjs.com abre tu paquete > **Settings → Trusted Publisher** y registra este repositorio con el flujo de trabajo `publish.yml` (consulta la [documentación de publicación de confianza de npm](https://docs.npmjs.com/trusted-publishers)). Publicar con provenance certifica qué repositorio de GitHub compiló el paquete, lo cual también es cómo reclamas la propiedad de tu aplicación en un marketplace de Twenty.
<Note>
npm solo acepta procedencia de repositorios de código fuente **públicos**. Si publicas desde un repositorio privado, npm rechaza el paquete de procedencia OIDC con un `E422 ... Visibilidad del repositorio de origen de GitHub Actions no compatible: "private"` error. Para publicar desde un repositorio privado, excluya la procedencia configurando `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` en el `env` del paso de publicación (se incluye una sugerencia comentada en el archivo `publish.yml` generado automáticamente):
```yaml filename=".github/workflows/publish.yml"
- name: Publish to npm
env:
TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
run: yarn twenty app:publish
```
</Note>
### Fijar las acciones reutilizables
Los flujos de trabajo `ci.yml` y `cd.yml` hacen referencia a acciones reutilizables en `@main`, por lo que las actualizaciones de acciones en el repositorio `twentyhq/twenty` se aplican automáticamente. Si quieres compilaciones deterministas, reemplaza `@main` por un SHA de commit o una etiqueta de versión en cada línea `uses:`.
## Publicación en npm
Publicarla en npm hace que tu aplicación sea visible en el marketplace de Twenty. Cualquier espacio de trabajo de Twenty puede explorar, instalar y actualizar aplicaciones del marketplace directamente desde la interfaz de usuario.
### Requisitos
* Una cuenta de [npm](https://www.npmjs.com)
* La palabra clave `twenty-app` en la matriz `keywords` de tu `package.json` (agrégala manualmente — no se incluye de forma predeterminada en la plantilla `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Metadatos del Marketplace
La configuración de `defineApplication()` admite campos opcionales que controlan cómo aparece tu aplicación en el marketplace. Usa `logo` y `galleryImages` para hacer referencia a imágenes de la carpeta `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',
],
});
```
Consulta el [acordeón de defineApplication](/l/es/developers/extend/apps/config/application#marketplace-metadata) en la página Building Apps para ver la lista completa de campos del marketplace (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
#### Dimensiones recomendadas de las imágenes de la galería
El marketplace muestra `galleryImages` en un contenedor fijo de `8:5` (por ejemplo, `1600×1000 px`).
<Note>
Las imágenes de la galería de cualquier relación de aspecto se muestran completas y nunca se recortan, pero las que sean significativamente más altas o más estrechas que `8:5` mostrarán franjas vacías a los lados.
</Note>
#### Límite de tamaño de imagen
El `logo` y cada archivo de `galleryImages` no deben superar los **10 MB**. Los archivos más grandes se omiten cuando el marketplace vuelve a alojar tus recursos publicados, por lo que no se mostrarán.
### Publicar
```bash filename="Terminal"
yarn twenty app:publish
```
Para publicar con una dist-tag específica (p. ej., `beta` o `next`):
```bash filename="Terminal"
yarn twenty app:publish --tag beta
```
### Cómo funciona el descubrimiento en el marketplace
El servidor de Twenty sincroniza su catálogo del marketplace desde el registro de npm **cada hora**.
Puedes activar la sincronización de inmediato en lugar de esperar:
```bash filename="Terminal"
yarn twenty dev:catalog-sync
# To target a specific remote:
# yarn twenty dev:catalog-sync --remote production
```
Los metadatos que se muestran en el marketplace provienen de tu configuración de `defineApplication()`; consulta [Metadatos del marketplace](#marketplace-metadata) arriba.
<Note>
Si tu aplicación no define un `aboutDescription` en `defineApplication()`, el marketplace usará automáticamente el `README.md` de tu paquete en npm como el contenido de la página Acerca de. Esto significa que puedes mantener un único README tanto para npm como para el marketplace de Twenty. Si quieres una descripción diferente en el marketplace, establece explícitamente `aboutDescription`.
</Note>
### Publicación en CI
El flujo de trabajo `publish.yml` generado automáticamente descrito arriba publica en npm de forma automática en las etiquetas de versión, con provenance. Como `yarn twenty app:publish` añade `--provenance` y `--access public` por ti cuando se ejecuta en CI, el flujo de trabajo no necesita banderas de npm, solo la configuración única de trusted publisher.
Para otros sistemas de CI (GitLab CI, CircleCI, etc.), ejecuta `yarn install` y luego `yarn twenty app:publish`. La provenance se emite cuando el entorno puede generar un token OIDC y, en caso contrario, se omite automáticamente.
<Note>
La **provenance de npm** añade una insignia de confianza a tu ficha de npm, permitiendo que los usuarios verifiquen que el paquete se compiló a partir de un commit específico en una canalización de CI pública. También es lo que te permite reclamar la propiedad de tu aplicación en un marketplace de Twenty. Consulta la [documentación de npm sobre provenance](https://docs.npmjs.com/generating-provenance-statements) para más detalles.
</Note>
## Instalar aplicaciones
Una vez que una aplicación esté publicada (npm) o desplegada (tarball), los espacios de trabajo pueden instalarla a través de la interfaz de usuario.
Ve a la página **Configuración > Aplicaciones** en Twenty, donde se pueden explorar e instalar tanto las aplicaciones del marketplace como las desplegadas mediante tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
También puedes instalar aplicaciones desde la línea de comandos:
```bash filename="Terminal"
yarn twenty app:install
```
<Note>
El servidor aplica el versionado semver al instalar, reflejando las reglas del despliegue:
* Instalar la misma versión que ya está instalada en tu espacio de trabajo se rechaza con un error `APP_ALREADY_INSTALLED`.
* Instalar una versión inferior a la que está instalada actualmente se rechaza con un error `CANNOT_DOWNGRADE_APPLICATION`.
Para instalar una versión más reciente, primero despliégala o publícala y luego vuelve a ejecutar `yarn twenty app:install`.
</Note>

View File

@@ -0,0 +1,126 @@
---
title: Sincronización y recuperación
description: Qué comando usar y cuándo, cómo leer la salida de sincronización y una escalera de recuperación para cuando los metadatos locales se desvían, antes de llegar a un restablecimiento completo.
icon: compass
---
El desarrollo de aplicaciones locales gira en torno a la **sincronización**: la CLI recompila tu manifiesto y el servidor aplica solo la diferencia entre este y los metadatos que ya se encuentran en tu espacio de trabajo. Esta página explica qué comando usar, cómo leer qué cambió una sincronización y qué hacer, en orden, cuando el estado local parece inconsistente.
## Qué comando usar y cuándo
<Note>
Para la iteración local del día a día casi siempre quieres `yarn twenty dev`. La implementación y la publicación son para enviar versiones, **no** para el ciclo local.
</Note>
| Quieres… | Comando | Notas |
| --------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Iterar localmente con sincronización en tiempo real | `yarn twenty dev` | Supervisa tus archivos y sincroniza en cada cambio. |
| Sincronizar una vez y salir (CI, scripts, hooks) | `yarn twenty apply` | Una compilación + sincronización, luego sale. Añade `--force` para omitir la confirmación de cambio destructivo. |
| Previsualizar cambios **sin aplicarlos** | `yarn twenty plan` | Calcula e imprime el diff; no escribe nada. |
| Eliminar la aplicación del espacio de trabajo | `yarn twenty app:uninstall` | Agrega `--yes` para omitir la confirmación. |
| Enviar un tarball a un servidor | `yarn twenty app:publish --private` | Requiere una versión de `package.json` **estrictamente superior**; consulta [Publicación](/l/es/developers/extend/apps/operations/publishing). |
| Publicar en el marketplace (npm) | `yarn twenty app:publish` | — |
| Instalar / actualizar una versión implementada | `yarn twenty app:install` | Instala la versión actualmente implementada. |
| Borrar el servidor local y empezar desde cero | `yarn twenty docker:reset` | Elimina **todos** los datos locales: último recurso. |
<Note>
`yarn twenty dev --once` y `yarn twenty dev --once --dry-run` siguen funcionando como alias obsoletos de `yarn twenty apply` y `yarn twenty plan`.
</Note>
### La sincronización local no necesita un aumento de versión
La regla de `version` estrictamente creciente (`VERSION_ALREADY_EXISTS` al implementar, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` al instalar) se aplica a **`app:publish` / `app:install`**: la ruta de publicación. `yarn twenty dev` sincroniza tu manifiesto en su lugar y nunca requiere un cambio de versión, por lo que no necesitas tocar `package.json` para iterar. Si te encuentras aumentando la versión para probar un cambio local, estás usando la ruta de publicación cuando lo que quieres es el ciclo de desarrollo.
## Leer la salida de la sincronización
Cada sincronización imprime los cambios de metadatos que aplicó (o aplicaría, con `plan`), al estilo de Terraform: un bloque por entidad con sus atributos y luego una línea de resumen:
```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)
```
Este es tu primer diagnóstico: te indica exactamente qué objetos, campos y diseños cambiaron, para que puedas confirmar que una sincronización hizo lo que esperabas antes de revisar la interfaz de usuario.
Los cambios destructivos (`to destroy`) se enumeran con lo que eliminan (p. ej., `objectMetadata "auditNote" — drops the table and all its rows`) y requieren confirmación interactiva, o `--force` en scripts.
Cuando una sincronización falla en una sola entidad, el error nombra la entidad implicada y su `universalIdentifier`, por ejemplo:
```text
Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337) failed
```
Usa ese identificador para encontrar la entidad en tu manifiesto (y, si es necesario, en el espacio de trabajo) en lugar de adivinar cuál entra en conflicto.
## Previsualizar cambios (plan)
`yarn twenty plan` compila tu manifiesto, le pide al servidor el plan de migración y lo imprime, **sin aplicar nada**. Es la forma segura de responder "¿qué cambiaría esta sincronización?" antes de comprometerte a ella.
```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
```
Un plan:
* **No escribe nada**: sin migración de metadatos, sin actualización del registro de la aplicación, sin cambios de roles/pestañas predeterminados y sin generación del cliente de la API.
* Devuelve el **mismo diff** que aplicaría una sincronización real, para que puedas revisar por adelantado las entidades creadas/actualizadas/eliminadas.
* Es útil antes de un cambio arriesgado, al revisar un cambio generado por IA o en un script que deba fallar si está a punto de producirse un cambio inesperado.
<Note>
Un plan solo previsualiza cambios de **metadatos** y requiere que la aplicación se haya sincronizado al menos una vez (para que el espacio de trabajo la conozca). Si la ejecutas con una aplicación que nunca se sincronizó, el servidor indicará que la aplicación no está instalada; ejecuta `yarn twenty dev` una vez primero.
</Note>
## Escalera de recuperación
Cuando los metadatos locales parezcan incorrectos, ve escalando en este orden y detente en cuanto te hayas desbloqueado. Cada paso es más disruptivo que el anterior.
1. **Volver a sincronizar.** Ejecuta `yarn twenty apply` de nuevo. Las sincronizaciones son idempotentes: volver a ejecutar un manifiesto limpio es seguro y suele resolver un problema transitorio.
2. **Previsualizar el plan.** Ejecuta `yarn twenty plan` para ver exactamente qué pretende cambiar la siguiente sincronización, sin aplicarlo.
3. Lee el error identificado. Un conflicto suele señalar un identificador duplicado o reutilizado.
4. **Desinstalar y volver a instalar.** `yarn twenty app:uninstall`, luego vuelve a sincronizar (`yarn twenty dev`). Esto reconstruye los metadatos de la aplicación desde cero manteniendo intacto el resto de tu espacio de trabajo.
5. **Restablecimiento completo (último recurso).** `yarn twenty docker:reset`, luego vuelve a sembrar los datos y a sincronizar.
<Warning>
`yarn twenty docker:reset` elimina **todos** los datos de tu instancia local: todos los espacios de trabajo, registros y aplicaciones. Úsalo solo cuando los pasos anteriores hayan fallado.
</Warning>
<Note>
¿Te has encontrado con un error de metadatos? Por favor, [abre una incidencia](https://github.com/twentyhq/twenty/issues/new/choose) e incluye el mensaje de migración con error (con su tipo de metadatos y `universalIdentifier`), la salida de `Metadata changes` de la sincronización y los comandos que ejecutaste.
</Note>
## Evita sincronizaciones concurrentes en un mismo espacio de trabajo
La sincronización aplica migraciones de metadatos. Ejecutar varias operaciones de sincronización, implementación o instalación contra el **mismo espacio de trabajo al mismo tiempo** (por ejemplo, múltiples terminales o agentes de IA iterando en paralelo) puede entremezclar esas migraciones y dejar los metadatos en un estado parcialmente aplicado.
El servidor serializa las sincronizaciones por espacio de trabajo para evitar esto, pero aun así deberías canalizar las operaciones de metadatos sensibles a través de un proceso **único** en lugar de lanzarlas de forma concurrente. Si orquestas el desarrollo con varios agentes, enruta sus llamadas de sincronización/implementación/instalación a través de una sola cola de modo que solo una se ejecute a la vez.
## Distinguir los tipos de error
Cuando algo sale mal, el diff de metadatos y los errores con nombre te permiten situar el fallo:
* **Error de compilación del manifiesto**: la CLI falla antes de sincronizar (`MANIFEST_BUILD_FAILED`, `TYPECHECK_FAILED`); corrige el código fuente de tu aplicación.
* **Error de sincronización / migración**: la compilación tiene éxito, pero aplicar el diff falla, nombrando la entidad y el `universalIdentifier`; corrige los metadatos en conflicto.
* **Error de tamaño de dependencias** — la sincronización o la instalación fallan porque las `dependencies` de producción de la aplicación son demasiado grandes para instalarse como una capa de tiempo de ejecución (`LOGIC_FUNCTION_DEPENDENCIES_SIZE_EXCEEDED`); mueve a `devDependencies` los paquetes que tus funciones de lógica no importan en tiempo de ejecución (bibliotecas de UI, herramientas de desarrollo).
* **Error de tiempo de ejecución del código de la aplicación**: la sincronización se completa correctamente, pero tus funciones lógicas o componentes se comportan de forma incorrecta en tiempo de ejecución; revisa los [registros de funciones](/l/es/developers/extend/apps/operations/cli).
* **Estado de instancia local**: nada de lo anterior aplica y el espacio de trabajo sigue viéndose mal; desciende por la escalera de recuperación.

View File

@@ -0,0 +1,264 @@
---
title: Pruebas
description: Configuración de Vitest, pruebas de integración contra un servidor real de Twenty, comprobación de tipos e integración continua (CI) con GitHub Actions.
icon: flask
---
El SDK proporciona APIs programáticas que te permiten compilar, desplegar, instalar y desinstalar tu aplicación desde código de pruebas. Combinado con [Vitest](https://vitest.dev/) y los clientes de API tipados, puedes escribir pruebas de integración que verifiquen que tu aplicación funciona de extremo a extremo contra un servidor real de Twenty.
## Uso de paquetes de npm
Puedes instalar y usar cualquier paquete de npm en tu aplicación. Tanto las funciones de lógica como los componentes de frontend se empaquetan con [esbuild](https://esbuild.github.io/), que incorpora sus dependencias en la salida — no se necesitan `node_modules` en tiempo de ejecución.
### Instalar un paquete
```bash filename="Terminal"
yarn add axios
```
Luego impórtalo en tu código:
```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,
});
```
Lo mismo funciona para los componentes de frontend:
```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,
});
```
### Cómo funciona el empaquetado
El paso de compilación usa esbuild para producir un solo archivo autónomo por función de lógica y por componente de frontend. Los paquetes importados se integran en el paquete, excepto los que se enumeran a continuación.
**Las funciones de lógica** se ejecutan en un entorno Node.js. Los módulos integrados de Node (`fs`, `path`, `crypto`, `http`, etc.) están disponibles y no necesitan instalarse.
**Los componentes de frontend** se ejecutan en un Web Worker. Los módulos integrados de Node **no** están disponibles — solo paquetes de npm que funcionen en un entorno de navegador. Ten en cuenta que la sandbox implementa un DOM *parcial*, por lo que un paquete puede compilarse correctamente y aun así fallar en tiempo de ejecución; consulta [Limitaciones actuales](/l/es/developers/extend/apps/layout/front-components#current-limitations).
Ambos entornos tienen `twenty-client-sdk/core` y `twenty-client-sdk/metadata` disponibles como módulos preproporcionados — estos no se incluyen en el bundle sino que se resuelven en tiempo de ejecución por el servidor.
Los componentes de frontend también pueden compartir dependencias mediante un [paquete de dependencias compartidas](/l/es/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components). Los paquetes listados en el campo `frontComponentSharedDependencies` del package.json de la aplicación se construyen una sola vez en un archivo separado y se cargan desde allí en tiempo de ejecución en lugar de integrarse en cada componente, por lo que un componente que importa solo paquetes compartidos prácticamente no lleva código de dependencias propio.
## Configuración
La aplicación generada ya incluye Vitest. Si lo configuras manualmente, instala las dependencias:
```bash filename="Terminal"
yarn add -D vitest vite-tsconfig-paths
```
Crea un `vitest.config.ts` en la raíz de tu aplicación:
```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,
},
},
});
```
Crea un archivo de configuración global que verifique que el servidor es accesible, escriba una configuración de prueba para el SDK (`~/.twenty/config.test.json`) y sincronice la aplicación antes de que se ejecuten las pruebas:
```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 });
}
```
## APIs programáticas del SDK
La subruta `twenty-sdk/cli` exporta funciones que puedes invocar directamente desde el código de pruebas:
| Función | Descripción |
| -------------- | -------------------------------------------------------------------------- |
| `appBuild` | Compilar la aplicación y opcionalmente empaquetar un tarball |
| `appDeploy` | Subir un tarball al servidor |
| `appDevOnce` | Compila y sincroniza la aplicación una vez (igual que `yarn twenty apply`) |
| `appInstall` | Instalar la aplicación en el espacio de trabajo activo |
| `appUninstall` | Desinstalar la aplicación del espacio de trabajo activo |
Cada función devuelve un objeto de resultado con `success: boolean` y `data` o `error`.
## Escribir una prueba de integración
Aquí tienes un ejemplo completo que compila, despliega e instala la aplicación, y luego verifica que aparezca en el espacio de trabajo:
```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();
});
});
```
## Ejecutar pruebas
Asegúrate de que tu servidor local de Twenty esté en ejecución y luego:
```bash filename="Terminal"
yarn test
```
O en modo watch durante el desarrollo:
```bash filename="Terminal"
yarn test:watch
```
## Comprobación de tipos
También puedes ejecutar la comprobación de tipos en tu aplicación sin ejecutar pruebas:
```bash filename="Terminal"
yarn twenty dev:typecheck
```
Esto ejecuta `tsc --noEmit` contra el `tsconfig.json` de tu aplicación e informa cualquier error de tipo. Las aplicaciones generadas también incluyen un script `yarn typecheck` que cubre los archivos de prueba (`tsconfig.spec.json`).
## CI con GitHub Actions
El generador crea un flujo de trabajo listo para usar en `.github/workflows/ci.yml`. En cada push a `main` y en cada pull request, inicia un servidor efímero de Twenty en el runner (mediante la acción `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), luego ejecuta `yarn lint`, `yarn typecheck`, `yarn test:unit` y `yarn test` con `TWENTY_API_URL` / `TWENTY_API_KEY` apuntando a ese servidor. No se requieren secretos y puedes fijar la versión del servidor mediante la variable de entorno `TWENTY_VERSION` en la parte superior del flujo de trabajo.
Consulta [Publicación → CI/CD automatizado](/l/es/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) para ver una guía completa de los tres flujos de trabajo generados (`ci.yml`, la canalización de despliegue `cd.yml` y `publish.yml` para la publicación en npm).

View File

@@ -0,0 +1,131 @@
---
title: Traducciones
description: "Lanza tu aplicación en varios idiomas: traduce las etiquetas del manifiesto y las cadenas de la interfaz de usuario mediante un único catálogo de configuraciones regionales (locales)."
icon: language
---
Las aplicaciones de Twenty están creadas en **inglés**: las cadenas de tu código fuente y del manifiesto son el texto original en inglés, `en` es la configuración regional de origen desde la que traduces y cualquier configuración regional que quede sin traducir volverá a esa configuración.
Tu aplicación tiene dos tipos de texto traducible, y ambos pasan por el mismo catálogo `locales/`:
* **Etiquetas del manifiesto**: nombres de objetos y campos, títulos de vistas, elementos de menú y otras cadenas declaradas en los metadatos de tu aplicación.
* **Cadenas de componentes delanteros** — la interfaz de texto de tus componentes frontales de React.
Marcas las cadenas traducibles, extralas en catálogos por localidad,
traduce esos catálogos, y la construcción sirve el idioma adecuado para el usuario
actual — no hay cableado extra.
## Marcando cadenas de componentes frontales
Importar los ayudantes de traducción de `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>
);
};
```
### Cuándo usar qué
* **`Trans`** — texto estático en JSX. Usa las props `message` y `values` para la interpolación, como en `Trans message="Hi {name}" values={{ name }}`; la interpolación directamente en los children no se puede extraer de forma estática.
* **`useTranslate().t`** — cadenas dinámicas dentro de un componente. Se vuelve a procesar cuando el usuario
cambia de idioma. Preferir esto dentro del render.
* **`t(...)`** (importado directamente) — una traducción entera utilizable **en cualquier lugar**,
incluyendo controladores de eventos, ayudantes y ámbito del módulo — no solo dentro del render.
* **`msg(...)`** — un descriptor perezoso para cadenas declaradas como datos (constantes, configuración
). Resuelva más tarde con `t(descriptor)`.
### Contexto
Pase `context` para desambiguar cadenas de origen idénticas que traduzcan
de forma diferente:
```tsx
t({ message: 'Open', context: 'door' });
t({ message: 'Open', context: 'window' });
<Trans context="card-title">Untitled</Trans>
```
Las cadenas contextuales aparecen en los catálogos de configuración regional como un grupo indexado por su
contexto: `"\<context>": { "\<message>": "\<translation>" }`. Las etiquetas del manifiesto son
contextuales por construcción: cada una lleva el metadato del rol que desempeña (`objectMetadata.labelSingular`, `fieldMetadata.label`, `view.name`, …), por lo que
la misma palabra puede traducirse de manera diferente como nombre de objeto y como nombre de vista.
## Extrayendo y traduciendo
Ejecutar el comando de extracción desde el directorio de la aplicación:
```bash
twenty dev:translations-extract # collect strings into locales/en.json
twenty dev:translations-extract --locale fr-FR # also scaffold a target locale
```
La extracción recopila tanto las etiquetas de tu manifest como las cadenas de `t()`/`msg()`/`Trans` de tu código fuente de front-component en `locales/\<locale>.json`, indexadas por la cadena de origen. Rellena las traducciones:
```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"
}
}
```
Las cadenas sin contexto son entradas planas; las cadenas contextuales —incluida cada etiqueta de manifiesto— están anidadas bajo su clave de contexto.
Los marcadores de posición como `{name}` se sustituyen en tiempo de ejecución — manténgalos en la traducción
. Cualquier cadena que quede vacía regresa al texto original.
## Cómo funciona
`twenty dev:build` compila los catálogos y sirve el idioma correcto para el
usuario actual: las etiquetas del manifiesto se resuelven del lado del servidor y los catálogos de front-component se incluyen en cada paquete de componente. En tiempo de ejecución, un componente lee la configuración regional
desde su contexto de ejecución (el idioma actual del host) y resuelve
cada cadena contra su catálogo, volviendo a la fuente cuando falta una traducción
. Cambiar el idioma en el host vuelve a renderizar en tiempo real las cadenas de `Trans` y `useTranslate().t`.
Las etiquetas de manifiesto se compilan en cada sincronización — `twenty dev:build`, `twenty apply`
y también en el modo de observación continuo `twenty dev` —, por lo que basta con editar un archivo de configuración regional y
sincronizar para ver el nuevo texto. Los catálogos de componentes del front-end se incorporan en
cada paquete de componentes durante la compilación, por lo que cambiar uno de ellos sigue implicando
volver a ejecutar `twenty dev:build` (y volver a implementar).
Los nodos de texto hijos de `Trans` pueden abarcar varias líneas; los espacios en blanco se colapsan de la misma manera en que JSX los colapsa, por lo que ambos se extraen a la clave `Welcome back`:
```tsx
<Trans>Welcome back</Trans>
<Trans>
Welcome
back
</Trans>
```