국제화
국제화 (Internationalization)
Backstage 핵심 기능은 플러그인과 앱을 위한 국제화를 제공해요. 기반 라이브러리는 키에 대한 유형 안전성을 위한 Backstage TypeScript 마법이 추가된 i18next예요.
출처: 문서
본문
개요 (Overview)
Backstage 핵심 기능은 플러그인과 앱을 위한 국제화를 제공해요. 기반 라이브러리는 키에 대한 유형 안전성을 위한 Backstage TypeScript 마법이 추가된 i18next예요.
플러그인 개발자용
플러그인을 만들 때 createTranslationRef를 사용해 플러그인의 모든 메시지를 정의할 수 있어요. 예를 들어:
import { createTranslationRef } from '@backstage/frontend-plugin-api';/** @alpha */export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { indexPage: { title: 'All your components', createButtonTitle: 'Create new component', }, entityPage: { notFound: 'Entity not found', }, },});
그런 다음 컴포넌트에서 이 메시지들을 다음과 같이 사용해요.
import { useTranslationRef } from '@backstage/frontend-plugin-api';const { t } = useTranslationRef(myPluginTranslationRef);return ( <PageHeader title={t('indexPage.title')}> <Button onClick={handleCreateComponent}> {t('indexPage.createButtonTitle')} </Button> </PageHeader>);
초기 사전 구조와 중첩이 점 표기법으로 변환되는 것을 볼 수 있어요. 그래서 키 이름에 camelCase를 사용하고 키를 분리하기 위해 중첩 구조에 기대는 것을 권장해요.
i18n 메시지와 키에 대한 지침
i18n 메시지와 키 API는 매우 유연한 API라서 제대로 다루기 꽤 까다로울 수 있어요. 플러그인 번역에 대해 생각할 때 좋은 관행을 장려하는 시작을 돕기 위한 몇 가지 지침을 정리했어요.
키 이름
메시지를 정의할 때 번역의 의미적 계층 구조를 나타내는 중첩 구조를 사용하는 것이 권장돼요. 이를 통해 구조를 더 잘 구성하고 이해할 수 있어요. 예를 들어:
export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { dashboardPage: { title: 'All your components', subtitle: 'Create new component', widgets: { weather: { title: 'Weather', description: 'Shows the weather', }, calendar: { title: 'Calendar', description: 'Shows the calendar', }, }, }, entityPage: { notFound: 'Entity not found', }, },});
텍스트 콘텐츠 자체보다 콘텐츠의 의미적 배치를 생각하세요. 관련 번역을 공통 접두어 아래에 모으고, 중첩을 사용해 애플리케이션의 다른 부분 간의 관계를 나타내세요. 확장, 페이지 섹션 또는 시각적 범위와 경험 아래에서 그룹화를 시작하는 것이 좋아요.
번역은 가능하면 자신의 텍스트 콘텐츠를 키로 사용하는 것을 피해야 해요. 번역이 변경되면 혼란을 일으킬 수 있기 때문이에요. 대신 텍스트의 위치나 용도를 설명하는 키를 사용하는 것을 선호하세요.
공통 키 이름
이 목록은 시간이 지나며 성장하도록 의도됐지만, 아래는 가능하면 사용하도록 권장하는 공통 키 이름과 패턴의 몇 가지 예시예요.
-
${page}.title -
${page}.subtitle -
${page}.description -
${page}.header.title
키 재사용
여러 위치에서 같은 키를 재사용하는 것은 권장되지 않아요. 이는 모호함을 방지하고 각 키의 사용을 가능한 한 명확하게 유지하는 데 도움이 돼요. 대신 의미적 섹션 아래에 그룹화된 중복 키를 만드는 것을 고려하세요.
평면 키
루트 수준에서 평면 키 구조를 피하세요. 명명 충돌을 일으키고 번역 파일을 관리하고 시간이 지나며 변경하기 어렵게 만들 수 있기 때문이에요. 대신 번역을 공통 접두어 아래에 그룹화하세요.
export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { // this is BAD title: 'My page', subtitle: 'My subtitle', // this is GOOD dashboardPage: { header: { title: 'All your components', subtitle: 'Create new component', }, }, },});
복수형 (Plurals)
기반 구현으로 사용되는 i18next 라이브러리는 복수화에 대한 내장 지원이 있어요. 문서에 설명된 대로 이 기능을 사용할 수 있어요.
이 기능을 사용하고 복수화된 콘텐츠에 대해 다른 키 접두어를 만드는 것을 피할 것을 권장해요. 예를 들어:
export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { dashboardPage: { title: 'All your components', subtitle: 'Create new component', cards: { title_one: 'You have one card', title_two: 'You have two cards', title_other: 'You have many cards ({{count}})', }, }, entityPage: { notFound: 'Entity not found', }, },});
JSX 요소
번역 API는 JSX 요소를 번역 함수에 값으로 직접 전달해 보간하는 것을 지원해요. 제공된 보간 값 중 하나라도 JSX 요소라면 번역 함수는 문자열 대신 JSX 요소를 반환해요.
예를 들어 다음 메시지를 정의할 수 있어요.
메시지 정의
export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { entityPage: { redirect: { message: 'The entity you are looking for has been moved to {{link}}.', link: 'new location', }, }, },});
컴포넌트 내에서 다음과 같이 사용할 수 있어요.
컴포넌트 내 사용
const { t } = useTranslationRef(myPluginTranslationRef);return ( <div> {t('entityPage.redirect.message', { link: <a href="/new-location">{t('entityPage.redirect.link')}</a>, })} </div>);
바깥 t 함수의 반환 유형은 JSX.Element이며, 기본 값은 메시지의 여러 부분의 React fragment예요.
애플리케이션 개발자용
앱 개발자로서 어떤 플러그인의 기본 영어 메시지든 재정의할 수 있고, 추가 언어에 대한 번역을 제공할 수도 있어요.
메시지 재정의하기
새 언어를 추가하지 않고 특정 메시지를 커스터마이즈하려면 @backstage/plugin-app-react의 TranslationBlueprint와 @backstage/frontend-plugin-api의 createTranslationMessages를 함께 사용해 번역 확장을 만드세요.
import { createTranslationMessages } from '@backstage/frontend-plugin-api';import { TranslationBlueprint } from '@backstage/plugin-app-react';import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';const catalogTranslations = TranslationBlueprint.make({ name: 'catalog-overrides', params: { resource: createTranslationMessages({ ref: catalogTranslationRef, messages: { 'indexPage.title': 'Service directory', 'indexPage.createButtonTitle': 'Register new service', }, }), },});
그런 다음 앱에 기능으로 설치하세요.
import { createApp } from '@backstage/frontend-defaults';const app = createApp({ features: [catalogTranslations],});
재정의하려는 키만 포함하면 돼요. 누락된 키는 플러그인의 기본값으로 대체돼요.
언어 번역 추가하기
추가 언어를 지원하려면 각 언어에 대해 지연 로드되는 메시지 파일이 있는 번역 리소스를 만들고 TranslationBlueprint를 사용해 설치하세요.
import { createTranslationResource } from '@backstage/frontend-plugin-api';import { TranslationBlueprint } from '@backstage/plugin-app-react';import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';const userSettingsTranslations = TranslationBlueprint.make({ name: 'user-settings-zh', params: { resource: createTranslationResource({ ref: userSettingsTranslationRef, translations: { zh: () => import('./userSettings-zh'), }, }), },});
번역 메시지는 유형 안전성을 위해 createTranslationMessages를 사용해 정의할 수 있어요.
// packages/app/src/translations/userSettings-zh.tsimport { createTranslationMessages } from '@backstage/frontend-plugin-api';import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';const zh = createTranslationMessages({ ref: userSettingsTranslationRef, full: false, // False means that this is a partial translation messages: { 'languageToggle.title': '语言', 'languageToggle.select': '选择{{language}}', },});export default zh;
또는 일반 객체 export로:
// packages/app/src/translations/userSettings-zh.tsexport default { 'languageToggle.title': '语言', 'languageToggle.select': '选择{{language}}', 'languageToggle.description': '切换语言', 'themeToggle.title': '主题', 'themeToggle.description': '切换主题', 'themeToggle.select': '选择{{theme}}', 'themeToggle.selectAuto': '选择自动主题', 'themeToggle.names.auto': '自动', 'themeToggle.names.dark': '暗黑', 'themeToggle.names.light': '明亮',};
앱에 번역 확장을 설치하세요.
import { createApp } from '@backstage/frontend-defaults';const app = createApp({ features: [userSettingsTranslations],});
사용 가능한 언어 선언하기
번역 확장을 설치하면 번역된 메시지를 사용할 수 있게 되지만, Settings 페이지의 언어 전환기는 둘 이상의 지원 언어를 선언한 뒤에만 나타나요. app-config.yaml에서 api:app/app-language 확장을 사용해 이를 구성하세요.
app: extensions: - api:app/app-language: config: availableLanguages: - en - zh defaultLanguage: en
availableLanguages 배열은 Settings 페이지에서 옵션으로 나타날 언어를 제어해요. defaultLanguage는 사용자가 선택하기 전에 사용되는 언어를 설정하며, 지정하지 않으면 en으로 기본 설정돼요. availableLanguages를 구성하면 defaultLanguage는 해당 항목 중 하나여야 해요. defaultLanguage를 생략하면 availableLanguages에 en이 포함되어 있는지 확인하세요. 그렇지 않으면 앱이 시작에 실패할 수 있어요.
Settings 페이지로 이동하세요. 언어 선택 드롭다운이 보일 거예요. 언어를 전환해 번역이 올바르게 로드되는지 확인하세요.
전체 번역 워크플로에 CLI 사용하기
앱을 다른 언어로 규모 있게 번역할 때 — 특히 외부 번역 시스템과 함께 작업할 때 — Backstage CLI는 모든 플러그인 의존성에 걸친 번역 메시지의 추출과 연결을 자동화하는 translations export와 translations import 명령을 제공해요.
기본 메시지 내보내기
앱 패키지 디렉터리(예: packages/app)에서 다음을 실행하세요.
yarn backstage-cli translations export
이 명령은 모든 프론트엔드 플러그인 의존성(전이적 의존성 포함)에서 TranslationRef 정의를 스캔하고 기본 영어 메시지를 JSON 파일로 작성해요.
translations/ manifest.json messages/ catalog.en.json org.en.json scaffolder.en.json ...
각 .en.json 파일은 평면화된 메시지 키와 기본값을 포함해요.
{ "indexPage.title": "All your components", "indexPage.createButtonTitle": "Create new component", "entityPage.notFound": "Entity not found"}
번역 만들기
export된 파일을 복사해 대상 언어로 번역하세요.
cp translations/messages/catalog.en.json translations/messages/catalog.zh.json
그런 다음 catalog.zh.json을 번역된 문자열로 편집하세요. 번역하려는 키만 포함하면 돼요. 누락된 키는 런타임에 영어 기본값으로 대체돼요.
연결 코드 생성하기
번역된 파일이 준비되면 다음을 실행하세요.
yarn backstage-cli translations import
이 명령은 모든 것을 연결하는 src/translations/resources.ts에 TypeScript 모듈을 생성해요.
// This file is auto-generated by backstage-cli translations import// Do not edit manually.import { createTranslationResource } from '@backstage/frontend-plugin-api';import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';export default [ createTranslationResource({ ref: catalogTranslationRef, translations: { zh: () => import('../../translations/messages/catalog.zh.json'), }, }),];
생성된 리소스를 앱에 기능으로 설치하세요.
import { createApp } from '@backstage/frontend-defaults';import translationResources from './translations/resources';const app = createApp({ features: translationResources,});
커스텀 파일 패턴
기본적으로 메시지 파일은 messages/{id}.{lang}.json 패턴(예: messages/catalog.en.json)을 사용해요. --pattern 옵션으로 변경할 수 있어요.
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
이렇게 하면 언어별로 그룹화된 디렉터리 구조가 생성돼요.
translations/en/catalog.jsontranslations/zh/catalog.json
패턴은 매니페스트에 저장되므로 import 명령이 자동으로 같은 레이아웃을 사용해요.
외부 번역 시스템과의 통합
export된 JSON 파일은 대부분의 외부 번역 시스템과 호환되는 표준 키-값 쌍이에요. 일반적인 워크플로는 다음과 같아요.
-
translations export를 실행해 원본 영어 파일을 생성 -
.en.json파일을 번역 시스템에 업로드 -
번역된 파일을 translations 디렉터리에 다시 다운로드
-
translations import를 실행해 연결 코드를 재생성
전체 명령 참조는 CLI 명령 문서를 참고하세요.