Files
upstream-mirror e7da97194a
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
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

129 lines
6.1 KiB
Plaintext

---
title: 번역
description: 여러 언어로 앱을 출시하세요 — 단일 로케일 카탈로그에서 매니페스트 레이블과 UI 문자열을 번역하세요.
icon: language
---
Twenty 앱은 **영어**로 작성됩니다. 소스와 매니페스트의 문자열은 영어 원문이며, `en`은 번역의 기준이 되는 소스 로케일입니다. 번역되지 않은 로케일은 `en`을 기본값으로 사용합니다.
앱에는 두 가지 유형의 번역 가능한 텍스트가 있으며, 둘 다 동일한 `locales/` 카탈로그를 통해 처리됩니다.
* **매니페스트 레이블** — 오브젝트와 필드 이름, 뷰 제목, 메뉴 항목 및 앱 메타데이터에 선언된 기타 문자열.
* **Front-component 문자열** — React 프론트 컴포넌트가 렌더링하는 UI 텍스트입니다.
번역 가능한 문자열에 표시를 하고, 이를 로캘별 카탈로그로 추출한 뒤,
그 카탈로그들을 번역하면, 빌드가 현재 사용자에게 맞는 언어를 제공하므로
별도의 추가 작업이 필요 없습니다.
## Front-component 문자열 표시하기
`twenty-sdk/front-component`에서 번역 헬퍼를 import 하세요:
```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` props를 사용하세요
(`Trans message="Hi {name}" values={{ name }}`);
children 안에서 직접 보간하는 것은 정적으로 추출할 수 없습니다.
* **`useTranslate().t`** — 컴포넌트 내부에서 사용하는 동적 문자열입니다. 사용자가 언어를 전환하면
다시 렌더링됩니다. render 내부에서는 이것을 사용하는 것을 권장합니다.
* **`t(...)`** (직접 import) — 이벤트 핸들러, 헬퍼, 모듈 스코프 등
render 내부뿐만 아니라 **어디서든** 사용할 수 있는 즉시 번역 함수입니다.
* **`msg(...)`** — 데이터(상수, 설정)로 선언된 문자열을 위한 지연 평가용 서술자입니다. 나중에 `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`, …)을 담고 있으므로, 같은 단어라도 객체 이름으로 쓰일 때와 뷰 이름으로 쓰일 때 서로 다르게 번역될 수 있습니다.
## 추출 및 번역
앱 디렉터리에서 extract 명령을 실행하세요:
```bash
twenty dev:translations-extract # collect strings into locales/en.json
twenty dev:translations-extract --locale fr-FR # also scaffold a target locale
```
추출 과정에서는 manifest 레이블과 front-component 소스의 `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`는 카탈로그를 컴파일하고 현재 사용자에게 맞는 언어를 제공합니다.
manifest 레이블은 서버 측에서 처리되고, front-component 카탈로그는 각 컴포넌트 번들에 내장됩니다. 런타임에는 컴포넌트가 실행 컨텍스트(호스트의 현재 언어)에서 로케일을 읽고, 각 문자열을 해당 카탈로그에서 찾아 해석하며, 번역이 없으면 소스 텍스트로 대체합니다. 호스트에서 언어를 전환하면 `Trans`와
`useTranslate().t` 문자열이 실시간으로 다시 렌더링됩니다.
매니페스트 레이블은 모든 동기화 시 — `twenty dev:build`, `twenty apply`
및 연속적인 `twenty dev` 감시 모두에서 — 컴파일되므로, 로캘 파일을 편집하고
동기화하면 새 텍스트를 확인할 수 있습니다. Front 컴포넌트 카탈로그는 빌드 시
각 컴포넌트 번들에 포함되므로, 그중 하나를 변경하려면 여전히
`twenty dev:build`를 다시 실행하고 (재배포해야) 합니다.
`Trans`의 텍스트 children은 여러 줄에 걸쳐 있을 수 있으며 — 공백은 JSX에서와 동일한 방식으로 축약되므로,
둘 다 `Welcome back` 키로 추출됩니다.
```tsx
<Trans>Welcome back</Trans>
<Trans>
Welcome
back
</Trans>
```