6분

채팅으로 만든 앱을 위한 국제화 아키텍처

채팅으로 만든 앱을 위한 국제화 아키텍처: 안정적 문자열 키, 복수형 규칙, 웹과 모바일에서 일관된 번역 워크플로를 정의하세요.

채팅으로 만든 앱을 위한 국제화 아키텍처

더 많은 언어를 추가할 때 가장 먼저 깨지는 것은 무엇인가요?

가장 먼저 깨지는 것은 코드가 아닙니다. 단어입니다.

채팅으로 만든 앱은 보통 빠른 프로토타입에서 시작합니다. “Save라고 표시되는 버튼을 추가해”라고 입력하면 UI가 생기고 다음으로 넘어갑니다. 몇 주 뒤에 스페인어와 독일어가 필요해지면, 그 “임시” 라벨들이 화면, 컴포넌트, 이메일, 오류 메시지 곳곳에 흩어져 있다는 것을 발견합니다.

문구 변경은 코드 변경보다 더 자주 일어납니다. 제품 이름이 바뀌고, 법적 문구가 수정되고, 온보딩이 다시 작성되며, 고객지원은 더 명확한 오류 메시지를 요청합니다. 텍스트가 UI 코드 안에 직접 있다면 작은 표현 변경도 위험한 릴리스가 되고, 같은 아이디어가 다른 방식으로 표현된 곳을 놓치게 됩니다.

초기에 나타나는 증상들:

  • 한 화면에서 언어가 섞여 있음(일부 문자열은 번역되고 일부는 영어로 남아 있음).
  • 동일한 라벨이 약간씩 달라서 중복됨("Sign up", "Sign Up", "Create account").
  • 텍스트가 길어지면 레이아웃이 깨짐(버튼 넘침, 제목 줄 바꿈 문제).
  • 모바일과 웹이 서로 다른 표현을 사용함(같은 동작에 다른 단어).
  • 지원 콘텐츠와 시스템 메시지가 UI보다 뒤처짐.

현실적인 예: Koder.ai로 간단한 CRM을 만들었더니, 웹 앱은 “Deal stage”, 모바일 앱은 “Pipeline step”, 오류 토스트는 “Invalid status”라고 표시됩니다. 세 가지가 모두 번역되어 있어도 개념이 일치하지 않으면 사용자는 일관성이 없다고 느낍니다.

“일관성”은 “모든 곳에서 동일한 문자”를 의미하지 않습니다. 다음을 의미합니다:

  • 동일한 개념은 화면 전반에서 같은 키와 같은 의미를 사용한다.
  • 웹과 모바일이 번역의 단일 출처를 공유한다.
  • 어조와 용어(격식체 vs 평어, “customer” vs “client”)가 안정적이다.
  • UI 레이아웃이 길거나 짧은 문구를 처리할 수 있게 설계된다.

텍스트를 장식이 아닌 제품 데이터로 다루면 언어 추가가 난리가 아니라 일상의 작업이 됩니다.

기본 개념과 목표

Internationalization(i18n)은 앱이 많은 언어를 지원하도록 재작성 없이 준비하는 작업입니다. Localization(l10n)은 특정 언어와 지역에 맞는 실제 콘텐츠로, 예를 들어 캐나다 프랑스어에 맞는 단어, 날짜 형식, 어조를 말합니다.

간단한 목표: 모든 사용자에게 보이는 텍스트는 UI 코드에 직접 쓰지 말고 안정적인 키로 선택되게 하세요. React 컴포넌트나 Flutter 위젯을 열지 않고 문장을 바꿀 수 있다면 올바른 방향입니다. 채팅으로 생성되는 앱에서는 채팅 세션 중 생성된 하드코딩 문구를 실수로 배포하기 쉽기 때문에 이 원칙이 핵심입니다.

사용자에게 보이는 텍스트 범위는 팀이 생각하는 것보다 넓습니다. 버튼, 라벨, 검증 오류, 빈 상태, 온보딩 팁, 푸시 알림, 이메일, PDF 내보내기, 사용자가 볼 수 있거나 들을 수 있는 모든 메시지가 포함됩니다. 보통 내부 로그, DB 컬럼명, 분석 이벤트 ID, 기능 플래그, 관리자 전용 디버그 출력 등은 제외됩니다.

번역은 어디에 두어야 할까요? 현실적으로는 프런트엔드와 백엔드 양쪽에 존재하며, 명확한 경계가 있어야 합니다.

  • 프런트엔드는 내비게이션, 폼, 메뉴 등 대부분의 화면 텍스트를 책임진다.
  • 백엔드는 트랜잭션 이메일, 서버측 검증 오류, 에러 응답으로 반환되는 텍스트를 책임진다.
  • 주문 상태 같은 공유 도메인 문자열은 웹과 모바일이 분기하지 않도록 단일 출처에서 가져와야 한다.

섞어 쓰는 실수를 피하세요. 백엔드가 UI 오류에 대해 완성된 영어 문장을 반환하면 프런트엔드는 이를 깔끔하게 지역화할 수 없습니다. 더 나은 패턴은: 백엔드는 오류 코드(및 안전한 매개변수)를 반환하고 클라이언트가 해당 코드를 지역화된 메시지로 매핑하는 것입니다.

카피 소유권은 기술적 세부사항이 아니라 제품 결정입니다. 누가 단어를 변경하고 어조를 승인할지 초기에 결정하세요.

제품이 카피를 소유한다면 번역을 콘텐츠처럼 다루세요: 버전 관리, 리뷰, 그리고 제품이 변경을 요청할 수 있는 안전한 방법을 제공하세요. 엔지니어링이 카피를 소유한다면 새 UI 문자열이 배포되기 전에 키와 기본 번역이 있어야 한다는 규칙을 세우세요.

예: 회원가입 흐름에서 세 화면에 “Create account”가 나온다면 하나의 키로 통합해 모든 곳에서 사용하세요. 의미가 일관되면 번역가가 더 빠르게 작업하고 작은 표현 변경이 여러 화면 정리로 번지지 않습니다.

문자열 키 구조를 안정적으로 유지하는 방법

키는 UI와 번역 간의 계약입니다. 그 계약이 계속 바뀌면 누락된 텍스트, 급조된 수정, 웹과 모바일 간의 불일치가 발생합니다. 채팅으로 만든 앱의 국제화 아키텍처는 한 가지 규칙으로 시작합니다: 키는 현재 영어 문장을 묘사하지 말고 의미를 설명해야 합니다.

전체 문장을 키로 쓰지 말고 안정적 ID를 사용하세요(예: billing.invoice.payNow 대신 "Pay now" 같은 전체 문장을 키로 사용하는 실수를 피하세요). 문장 키는 누군가 문구를 약간 바꾸거나 구두점이나 대소문자를 수정하면 깨집니다.

읽기 쉽고 실용적인 패턴은: 화면(또는 도메인) + 컴포넌트 + 의도입니다. 지루하고 예측 가능하게 유지하세요.

예시:

  • auth.login.title
  • auth.login.emailLabel
  • billing.checkout.payButton
  • nav.settings
  • errors.network.offline

키를 재사용할지 새로 만들지는 “여기서의 의미가 모든 곳에서 동일한가?”라고 물어 결정하세요. 정말 일반적인 동작에는 키를 재사용하고, 문맥이 달라지면 분리하세요. 예를 들어, 프로필 화면의 “Save”는 단순한 동작일 수 있지만 복잡한 에디터에서의 “Save”는 일부 언어에서 더 구체적인 어조가 필요할 수 있습니다.

공유 UI 텍스트는 별도의 네임스페이스에 모아 중복을 피하세요. 잘 작동하는 공통 버킷 예시:

  • common.actions.* (save, cancel, delete)
  • common.status.* (loading, success)
  • common.fields.* (search, password)
  • errors.* (validation, network)
  • nav.* (tabs, menu items)

문구는 바뀌지만 의미가 같다면 키는 유지하고 번역값만 업데이트하세요. 이것이 안정적 ID의 핵심입니다. 의미가 바뀌면(미묘하더라도) 새 키를 만들고 사용 중인지 확인될 때까지 기존 키는 남겨두세요. 이렇게 하면 오래된 번역이 기술적으로는 남아 있지만 잘못된 상태로 쓰이는 “무음” 불일치를 피할 수 있습니다.

Koder.ai 스타일 예시: 채팅이 React 웹 앱과 Flutter 모바일 앱을 모두 생성한다면 두 플랫폼이 common.actions.save를 사용하면 어디서나 일관된 번역을 얻습니다. 반대로 웹이 profile.save를 쓰고 모바일이 account.saveButton을 쓰면 시간이 지나며 분기됩니다. 오늘 영어가 같아 보여도요.

문자열이 위치할 곳과 번역 파일 구성 방법

소스 언어(보통 영어)를 단일 출처로 다루세요. 한 곳에 저장하고 코드처럼 리뷰하며 문자열이 무작위 컴포넌트에 등장하지 않게 하세요. 이것이 하드코딩된 UI 문구와 나중에 발생하는 재작업을 가장 빠르게 피하는 방법입니다.

간단한 규칙: 앱은 i18n 시스템의 텍스트만 표시할 수 있습니다. 누군가 새 문구가 필요하면 먼저 키와 기본 메시지를 추가한 다음 UI에서 그 키를 사용하게 하세요. 이렇게 하면 기능 이동 중에도 국제화 아키텍처가 흔들리지 않습니다.

유지하기 쉬운 폴더 구조

웹과 모바일을 함께 배포한다면 키의 공유 카탈로그와 팀별 작업 공간이 필요합니다. 실용적인 레이아웃 예시:

  • /i18n
  • /i18n/locales/en.json (source)
  • /i18n/locales/es.json, /i18n/locales/fr.json, ...
  • /i18n/features/billing.json, /i18n/features/auth.json, ...
  • /i18n/shared.json (buttons, common labels, errors)

플랫폼이 달라도 키는 동일하게 유지하세요(React 웹, Flutter 모바일). Koder.ai 같은 플랫폼을 사용해 두 앱을 생성하면, 소스 코드를 내보내 양쪽 프로젝트가 같은 키 이름과 메시지 포맷을 가리키도록 하는 것이 유지보수에 훨씬 도움이 됩니다.

번역 변경의 버전 관리와 리뷰

번역도 시간이 지나며 변합니다. 변경을 제품 변경처럼 다루세요: 작게, 리뷰를 거치게, 추적 가능하게. 리뷰는 철자뿐 아니라 의미와 재사용성에 초점을 맞춰야 합니다.

  • 소스 로케일의 변경은 PR 리뷰를 요구하세요.
  • 키를 삭제할 때는 검색과 계획 없이 삭제하지 마세요.
  • 번역가에게 애매한 문구는 주석을 추가하세요.
  • CI에서 간단한 “누락된 번역” 체크를 사용하세요.

키가 팀 간에 흩어지지 않도록 기능별로 키를 소유하게 하고(예: billing., auth.), 문구 변경 때문에 키 이름을 바꾸지 마세요. 메시지는 업데이트하되 키는 유지하세요. 키는 식별자이지 복사물이 아닙니다.

복수형과 문법을 해킹 없이 해결하기

웹과 모바일을 함께 배포하세요
같은 번역 키를 사용하는 React 웹과 Flutter 모바일 앱을 생성하세요.

복수 규칙은 언어마다 다르므로 영어의 단순한 패턴(1 vs 그 외)은 곧 깨집니다. 어떤 언어는 0, 1, 2-4, 그 외처럼 구분이 필요하고, 어떤 언어는 문장 전체가 달라질 수도 있습니다. UI에서 if-else로 복수 로직을 박으면 복사물이 중복되고 예외를 놓치기 쉽습니다.

더 안전한 접근법은 한 아이디어당 하나의 유연한 메시지를 유지하고 i18n 레이어가 올바른 형태를 선택하게 하는 것입니다. ICU 스타일 메시지는 이를 위해 만들어졌고 문법 결정을 번역에 맡깁니다.

사람들이 놓치기 쉬운 경우를 다루는 작은 예시:

itemsCount = "{count, plural, =0 {No items} one {# item} other {# items}}"

이 한 키로 0, 1, 그 외를 커버합니다. 번역가는 해당 언어에 맞는 복수형으로 바꿀 수 있고 코드 수정은 필요 없습니다.

성별이나 역할 기반 표현이 필요할 때는 제품상 진짜 요구가 아니라면 welcome_male, welcome_female 같은 별도 키를 만들지 마세요. 대신 select를 사용해 문장을 하나의 단위로 유지하세요:

welcomeUser = "{gender, select, female {Welcome, Ms. {name}} male {Welcome, Mr. {name}} other {Welcome, {name}}}"

문법적 격변으로 자신을 곤란하게 만들지 않으려면 문장을 가능한 한 완결된 형태로 유지하세요. {count} + t('items')처럼 조각을 이어붙이지 마세요. 많은 언어에서 단어 순서를 바꿔야 하기 때문입니다. 숫자, 명사, 주변 단어를 하나의 메시지로 포함시키세요.

채팅으로 만드는 앱(예: Koder.ai 프로젝트)에서 실용적인 규칙: 문장에 숫자, 사람, 상태가 포함되면 처음부터 ICU를 사용하세요. 초기 비용은 약간 들지만 번역 부채를 크게 줄입니다.

웹과 모바일 번역을 일관되게 유지하기

React 웹 앱과 Flutter 모바일 앱이 각각 번역 파일을 따로 관리하면 점점 달라집니다. 동일한 버튼이 다른 표현을 쓰고, 같은 키가 플랫폼마다 다른 의미가 되며, 지원 티켓에 “앱은 X라고 하는데 웹사이트는 Y라고 합니다”라는 내용이 올라옵니다.

가장 단순하고 중요한 해결책: 단일 출처 포맷을 선택하고 그것을 코드처럼 다루세요. 대부분의 팀은 ICU 스타일 메시지를 담은 단일 공유 로케일 파일 세트를 사용합니다. 채팅과 제너레이터로 앱을 만들면 텍스트가 두 군데에서 생기는 실수가 쉬우므로 특히 중요합니다.

단일 진실의 출처

실용적인 설정은 작은 "i18n 패키지"나 폴더를 만들어 다음을 포함하는 것입니다:

  • 각 언어용 로케일 파일(모든 곳에서 같은 키)
  • 메시지 포맷 규칙(ICU로 복수형과 플레이스홀더 처리)
  • 키 추가 방법을 설명한 짧은 README

React와 Flutter는 소비자 역할만 하게 합니다. 로컬에서 새로운 키를 만들지 않게 하세요. Koder.ai 스타일 워크플로에서는 같은 키 세트로 두 클라이언트를 생성하고 모든 변경을 코드 변경처럼 리뷰하시면 됩니다.

백엔드 정렬도 같은 이야기입니다. 에러, 알림, 이메일을 Go에서 영어 문장으로 직접 쓰지 마세요. 대신 안정적 오류 코드(예: auth.invalid_password)와 안전한 매개변수를 반환하세요. 클라이언트가 코드를 번역된 텍스트로 매핑합니다. 서버에서 전송하는 이메일은 동일한 키와 로케일 파일로 템플릿을 렌더링할 수 있습니다.

동기화를 유지하는 규칙들

작은 규칙집을 만들고 코드 리뷰에서 강제하세요:

  • 새 UI 텍스트는 먼저 공유 로케일 파일에 새 키로 추가되어야 한다.
  • 키는 위치가 아니라 명확한 네임스페이스와 의도를 포함해야 한다.
  • 모든 키는 플레이스홀더를 한 번 정의하고 모든 곳에서 동일하게 사용해야 한다.
  • 두 문구의 의미가 다르면 같은 키를 쓰면 안 된다.
  • 두 키가 같은 의미라면 하나를 삭제하고 하나만 남긴다.

중복 키가 의미는 다르게 갖는 것을 방지하려면 번역가와 미래의 자신을 위한 "설명" 필드(또는 주석 파일)를 추가하세요. 예: billing.trial_days_left는 배너로 보여지는지 이메일로 보내지는지 설명해야 합니다. 그 한 문장이 “그려도 괜찮다”는 식의 재사용을 중단시켜 줍니다.

이 일관성이 채팅으로 만든 앱의 국제화 아키텍처의 근간입니다: 하나의 공유 어휘, 여러 표면, 다음 언어를 릴리스할 때 놀라움이 없음.

실제 프로젝트에서 따라할 단계별 설정

문구 변경을 저위험으로 만드세요
카피와 번역을 반복하고 문제가 생기면 안전하게 롤백하세요.

채팅으로 만드는 앱의 국제화 아키텍처는 단순하게 시작합니다: 하나의 메시지 키 세트, 카피의 단일 출처, 웹과 모바일에 동일한 규칙. Koder.ai처럼 빠르게 빌드하더라도 이 구조를 유지하면 속도를 유지하면서 번역 부채를 만들지 않습니다.

실용적인 설정(웹 + 모바일)

로케일을 일찍 정하고 번역이 없을 때 어떻게 할지 결정하세요. 일반적인 선택은: 사용자의 선호 언어가 있으면 보여주고, 없으면 영어로 폴백하며, 누락된 키는 로그에 남겨 다음 릴리스 전에 고칩니다.

그런 다음 다음을 시행하세요:

  • 로케일과 폴백 정의: 지원 언어, 기본 로케일, 명확한 폴백 순서를 결정하세요. 로케일 감지 방법(브라우저/앱 설정, 사용자 프로필)을 합의하세요.
  • 번역 함수와 키 규약 생성: 안정적이고 의미 기반 키를 사용하세요(전체 문장 금지). 예: billing.plan_name.pro 또는 auth.error.invalid_password. 같은 키를 모든 곳에 쓰세요.
  • React와 Flutter에 연결: React에서는 앱을 i18n 프로바이더로 감싸고 컴포넌트에서 t("key")를 사용하세요. Flutter에서는 로컬라이제이션 래퍼를 사용해 위젯에서 같은 키 조회를 호출하세요. 목표는 같은 키이지 같은 라이브러리가 아닙니다.
  • 처음부터 변수와 복수 규칙 지원: "{count, plural, one {# file} other {# files}}""Hello, {name}" 같은 ICU 메시지를 사용하세요. 화면마다 if (count === 1) 같은 해킹을 피할 수 있습니다.
  • 경량 카피 리뷰 단계 추가: 배포 전에 새로 추가되거나 변경된 문자열을 리뷰하세요: 키 명명 확인, 하드코딩된 UI 문구 제거, 플레이스홀더 일치 확인, 웹과 모바일이 변경을 잘 반영했는지 확인합니다.

마지막으로 단어가 긴 언어(독일어 등)와 문장 부호가 다른 언어 하나로 테스트하세요. 버튼 넘침, 제목 줄 바꿈 문제, 영어 길이에만 맞춘 레이아웃을 빠르게 발견할 수 있습니다.

번역을 공유 폴더(또는 생성된 패키지)에 보관하고 카피 변경을 코드 변경처럼 다루면 채팅으로 빠르게 기능을 만들어도 웹과 모바일이 일관성을 유지합니다.

동적 콘텐츠: 날짜, 숫자, 사용자 텍스트

번역된 UI 문자열은 문제의 절반일 뿐입니다. 대부분의 앱은 날짜, 가격, 카운트, 이름처럼 변하는 값도 보여줍니다. 이러한 값을 일반 텍스트로 처리하면 형식이 이상해지고, 잘못된 시간대가 표시되며, 많은 언어에서 문장이 어색해집니다.

숫자, 통화, 날짜는 직접 포맷하지 말고 로케일 규칙으로 포맷하세요. 프랑스 사용자는 "1 234,50 €"를 기대하고 미국 사용자는 "$1,234.50"을 기대합니다. 날짜도 마찬가지입니다: "03/04/2026"은 모호하지만 로케일 포맷은 명확합니다.

시간대도 함정입니다. 서버는 보통 중립적 형식(UTC)으로 타임스탬프를 저장하지만 사용자는 자신의 시간대로 보길 기대합니다. 예: 주문이 23:30 UTC에 생성되면 도쿄 사용자는 "내일"로 볼 수 있습니다. 화면별로 규칙을 정하세요: 개인 이벤트는 사용자 로컬 시간표시, 매장 픽업 같은 비즈니스 시간은 고정된 비즈니스 시간대로 표시하고 명확히 라벨하세요.

번역된 단편을 이어붙여 문장을 만들지 마세요. 문법이 깨집니다. 예전처럼:

"{count} " + t("items") + " " + t("in_cart")

대신 플레이스홀더를 사용하는 하나의 메시지로 만드세요: "{count} items in your cart". 번역가는 단어 순서를 자유롭게 바꿀 수 있습니다.

오른쪽에서 왼쪽(RTL) 언어

RTL은 단순히 텍스트 방향만이 아닙니다. 레이아웃 흐름이 뒤집히고 일부 아이콘(뒤로가기 화살표 등)은 좌우 반전이 필요하며, 혼합된 콘텐츠(아랍어 + 영어 제품 코드)는 순서가 의외로 표시될 수 있습니다. 실제 화면으로 테스트하고 UI 컴포넌트가 방향 변경을 지원하는지 확인하세요.

사용자 생성 콘텐츠

사용자가 쓴 내용(이름, 주소, 지원 티켓, 채팅 메시지)은 번역하지 마세요. 주변 라벨과 메타데이터(날짜, 숫자)는 번역/포맷할 수 있지만 실제 콘텐츠는 그대로 보여야 합니다. 자동 번역을 나중에 추가할 경우엔 원문/번역 토글 같은 명확한 기능으로 제공하세요.

실용적 예: Koder.ai로 만든 앱이 "{name} renewed on {date} for {amount}"를 보여준다면 이를 하나의 메시지로 유지하고 {date}와 {amount}는 로케일에 맞춰 포맷하며 사용자 시간대를 사용하세요. 이 패턴이 많은 번역 부채를 예방합니다.

짧은 규칙:

  • 타임스탬프는 UTC로 저장하고 뷰어 로케일과 시간대로 포맷하세요.
  • 조각을 붙이지 말고 전체 문장 안에 플레이스홀더를 사용하세요.
  • 통화는 로케일 규칙과 정확한 통화 코드를 사용해 포맷하세요.
  • 실제 화면에서 적어도 하나의 RTL 로케일을 테스트하세요.
  • 사용자 생성 텍스트는 기본적으로 번역하지 마세요.

번역 부채를 만드는 흔한 실수들

i18n을 완전 통제하세요
소스 코드를 내보내 팀이 locale 파일, 리뷰, CI 체크를 관리하도록 하세요.

번역 부채는 보통 "한 문자열만 빨리 고치자"에서 시작해 나중에 수주간의 정리로 이어집니다. 채팅으로 만든 프로젝트에서는 UI 텍스트가 컴포넌트, 폼, 심지어 백엔드 메시지 안에서 생성되기 때문에 더 빠르게 발생할 수 있습니다.

나중에 문제를 일으키는 실수들

가장 비용이 큰 문제는 앱 전반에 퍼져 찾기 어려워지는 것들입니다.

  • UI 컴포넌트 안에 카피를 하드코딩(플레이스홀더, 빈 상태, 버튼 라벨 포함). 감사하거나 재사용할 수 없으며 작은 표현 변경도 코드 수정을 요구한다.
  • 백엔드 검증 및 API 오류가 영어 문장을 반환하게 둔다. UI에서 언어가 섞이고 오류를 번역된 메시지로 매핑할 수 없다.
  • 전체 영어 문장을 번역 키로 사용한다. 편리해 보이지만 문구가 바뀌면 키가 교체되고 오래된 키가 남아 번역 메모리가 사라진다.
  • 웹과 모바일에서 키를 복사해 따로 편집한다. 시간이 지나며 같은 화면이 달라지고 사용자는 불일치를 느낀다.
  • 첫 비영어권 언어가 추가될 때까지 복수 규칙을 미루면 1 items 같은 버그와 간단한 if문으로는 해결할 수 없는 어색한 문법이 쌓인다.

실제 생활 예시

React 웹 앱과 Flutter 모바일 앱이 청구 배너로 "You have 1 free credit left"를 보여줍니다. 누군가 웹 문구를 "You have one credit remaining"로 바꾸고 키로 전체 문장을 사용합니다. 모바일은 옛 키를 계속 사용합니다. 이제 하나의 개념에 두 개의 키가 생기고 번역가는 둘 다 보게 됩니다.

더 나은 패턴은 안정적 키(예: billing.creditsRemaining)와 ICU 복수 메시지를 사용하는 것입니다. 문법이 모든 언어에서 올바르게 처리됩니다. Koder.ai 같은 도구를 사용한다면 규칙을 일찍 추가하세요: 채팅에서 생성된 모든 사용자 문구는 컴포넌트나 서버 오류 안에 바로 들어가지 말고 번역 파일로 가게 하세요. 이 작은 습관이 프로젝트 성장 시 국제화 아키텍처를 보호합니다.

빠른 체크리스트, 현실적 예시, 다음 단계

국제화가 지저분하게 느껴진다면 보통 기본 원칙이 문서화되지 않았기 때문입니다. 작은 체크리스트와 하나의 구체적 예시는 팀(그리고 미래의 당신)을 번역 부채에서 지켜줍니다.

다음은 새 화면마다 실행할 수 있는 빠른 체크리스트입니다:

  • 안정적 키: 의미당 하나의 키, 문구당 키가 아님(예: billing.invoice.paidStatus, billing.greenLabel 아님).
  • 폴백: 기본 언어와 키가 없을 때의 처리(폴백 텍스트 표시, 로그 기록, 릴리스 차단 등)를 정의하세요.
  • 복수 규칙: 카운트는 ICU 스타일 메시지를 사용하세요(0, 1, 여러).
  • 포맷: 날짜, 돈, 숫자는 로케일에 맞춰 포맷하세요(언어와 통화를 분리하세요).
  • RTL 체크: 최소 한 개의 오른쪽-왼쪽 언어를 일찍 테스트해 레이아웃 문제를 조기에 발견하세요.

간단한 예시: 청구 화면을 영어, 스페인어, 일본어로 출시한다고 합시다. UI에는: "Invoice", "Paid", "Due in 3 days", "1 payment method" / "2 payment methods", 총합 "$1,234.50" 같은 항목이 있습니다. 국제화 아키텍처를 적용하면 키를 한 번 정의하고(웹과 모바일 공통), 각 언어는 값만 채웁니다. "Due in {days} days"는 ICU 메시지가 되고 금액 포맷은 로케일 인식 포맷터에서 나옵니다.

언어 지원은 기능별로 단계적으로 배포하세요, 큰 재작업으로 하지 마세요:

  1. 트래픽이 높은 흐름(청구, 온보딩, 체크아웃) 하나로 시작합니다.
  2. 하드코딩된 UI 카피를 번역 파일로 옮기고 키로 교체합니다.
  3. 동일 흐름에 복수 메시지와 포맷팅을 추가합니다.
  4. 누락된 키가 거의 0에 가까워질 때까지 다음 기능으로 확장합니다.

새 기능이 일관되게 유지되도록 두 가지를 문서화하세요: 키 명명 규칙(예시 포함)과 문자열의 완료 정의(하드코딩 없음, 복수형은 ICU, 날짜/숫자 포맷, 공유 카탈로그에 추가).

다음 단계: Koder.ai에서 빌드 중이라면 Planning Mode로 화면과 키를 정의한 뒤 UI를 생성하세요. 스냅샷과 롤백을 사용하면 웹과 모바일의 카피와 번역을 안전하게 반복하면서 깨진 릴리스를 방지할 수 있습니다.

자주 묻는 질문

번역 키는 어떻게 이름을 지어야 하나요?

billing.invoice.payNow처럼 안정적이고 의미를 기준으로 한 키를 사용하세요. 영어 문장 자체를 키로 쓰지 마세요. 그러면 코드 참조를 바꾸거나 중복 번역을 만들지 않고도 문구를 수정할 수 있습니다.

어떤 텍스트를 번역 파일에 넣어야 하나요?

오류, 빈 상태, 이메일, 알림, 온보딩 팁을 포함해 사용자가 보는 모든 문자열을 번역 시스템에 넣으세요. 내부 로그, 분석 ID, 데이터베이스 필드는 일반적으로 번역할 필요가 없습니다.

웹과 모바일의 문구를 어떻게 일관되게 유지하나요?

두 클라이언트에서 하나의 공유 키 카탈로그와 로케일 메시지를 사용하세요. React와 Flutter는 서로 다른 라이브러리를 사용할 수 있지만, 동일한 키 이름과 메시지 규칙을 참조해야 합니다.

백엔드가 번역된 오류 메시지를 반환해야 하나요?

백엔드에서는 안정적인 오류 코드와 안전한 값만 반환하고, 클라이언트에서 현지화된 메시지를 표시하세요. 예를 들어 영어 문장 대신 auth.invalid_password를 반환하세요.

언어마다 다른 복수형은 어떻게 처리해야 하나요?

각 언어가 자체 문법 규칙을 적용할 수 있도록 ICU 스타일의 복수형 메시지를 사용하세요. 숫자와 별도로 번역한 명사를 붙이지 말고, 전체 문장을 하나의 메시지에 넣으세요.

번역 파일은 어디에 두어야 하나요?

원본 로케일, 대개 영어를 기준으로 삼고 코드와 함께 변경 사항을 검토하세요. 메시지는 기능별로 묶고, 공통 작업, 필드, 탐색, 오류에는 공유 네임스페이스를 마련하세요.

문구가 바뀔 때마다 새 키가 필요한가요?

문구가 바뀌어도 의미가 같다면 기존 키를 유지하세요. 의미나 맥락이 바뀔 때만 새 키를 만들고, 더 이상 사용하지 않는 것을 확인한 뒤 이전 키를 제거하세요.

날짜, 금액, 시간대는 어떻게 현지화해야 하나요?

타임스탬프는 UTC로 저장한 뒤, 보는 사람의 로케일과 시간대에 맞게 날짜, 시간, 숫자, 통화를 형식화하세요. 쉼표, 통화 기호, 날짜 형식을 하드코딩하지 말고 로케일을 인식하는 포맷터를 사용하세요.

오른쪽에서 왼쪽으로 쓰는 언어에서는 무엇을 테스트해야 하나요?

라벨만이 아니라 전체 화면을 RTL 로케일에서 디자인하세요. 필요한 곳에서는 레이아웃 방향을 반전하고, 뒤로 가기 화살표 같은 방향성 아이콘을 좌우로 맞추며, 제품 코드처럼 아랍어와 라틴 문자가 섞인 텍스트를 테스트하세요.

기존 앱에 i18n을 추가하는 가장 간단한 방법은 무엇인가요?

온보딩, 결제 청구, 체크아웃처럼 사용량이 많은 흐름 하나부터 시작하세요. 하드코딩된 문구를 공유 메시지로 옮기고, 복수형과 형식화 지원을 추가한 뒤 더 긴 번역을 테스트하고 다음 기능에도 반복하세요.

Related posts