10가지 i18n 실수와 피하는 방법
개발자가 저지르는 가장 흔한 i18n 오류와 이를 해결하는 방법을 알아보세요. 이 모범 사례로 앱의 로컬라이제이션 품질을 향상시키세요.
i18n(Internationalisierung)은 간단해 보이지만 독일어 사용자가 버튼이 잘려 보이고 일본어 사용자가 어색한 문장 조각을 얻으며, 아랍어 사용자가 완전히 엉망인 레이아웃을 보게 되는 것을 알게 될 때까지는 그렇지 않습니다. 이는 예외적인 경우가 아닙니다. 전 세계 사용자에게 로컬라이제이션을 처음 다루는 많은 개발 팀이 저지르는 일반적인 오류의 예측 가능한 결과입니다. 이 가이드에서는 가장 흔한 10가지 i18n 오류를 살펴보고, 왜 발생하는지 정확히 설명하며, 하나씩 단계별로 해결하는 방법을 보여줍니다. 새 앱을 만들거나 기존 앱을 업그레이드하더라도 이러한 함정을 피하면 몇 주에 달하는 디버깅 시간을 절약하고 전 세계 사용자에게 다듬어진 경험을 제공합니다.
오류 #1: 하드코딩된 문자열
i18n에서 가장 기본적인 오류는 하드코딩된 문자열입니다. 개발자는 먼저 기능이 작동하는 데 집중하고 '나중에 정리하겠다'고 계획하지만, 나중은 결코 오지 않고 결국 수천 개의 문자열이 수백 개의 파일에 흩어져 있습니다.
['첫 UI 코드 줄을 작성하기 전에 i18n 프레임워크를 구성합니다', '템플릿과 컴포넌트에서 하드코딩된 문자열을 감지하기 위한 린트 플러그인을 사용합니다', '번역 키를 설명적으로 유지하고 기능 또는 페이지별로 구성합니다']
첫 번째 UI 코드 줄을 작성하기 전에 i18n 프레임워크를 구성하세요.
템플릿과 컴포넌트에서 하드코딩된 문자열을 감지하는 린터 플러그인을 사용하세요.
번역 키를 기능이나 페이지별로 설명적으로 유지하고 체계적으로 정리하세요.
일반적인 시나리오
한 개발자가 JSX에서 버튼 레이블을 직접 작성합니다: <button>Submit Order</button>.
앱이 영어로 제공되고 제대로 작동합니다. 6개월 후 회사는 독일로 확장합니다.
로컬라이제이션 팀은 2,000개 이상의 하드코드 문자열을 발견합니다. 후속 작업은 3주가 걸리고 47개의 버그가 발생합니다.
왜 이것이 문제인가
성숙한 코드베이스에서는 하드코딩된 문자열이 수천 개에 이를 수 있습니다. 이를 나중에 추출하려면 모든 파일에 손을 대고 모든 컴포넌트를 다시 테스트하며 어디든 리그레션이 생길 수 있습니다.
하드코딩된 문자열은 소스 코드, 템플릿 또는 컴포넌트에 직접 내장되어 있습니다. 코드를 직접 변경하지 않고는 추출하거나 번역하거나 런타임에 교체할 수 없습니다.
영어가 아닌 로컬에 있는 사용자는 번역되지 않은 UI 요소가 번역된 내용과 섞여 보기에 혼란스럽고 비전문적인 인상을 받습니다.
문제 해결 방법
사용자가 볼 수 있는 모든 문자열을 처음부터 리소스 파일로 옮깁니다.
첫 번째 컴포넌트를 작성하기 전에 i18n 프레임워크를 구성합니다 (예: next-intl, react-i18next, vue-i18n).
리소스 파일 구조를 만들고 messages/de.json 와 같은 파일에 모든 문자열을 t('checkout.submitButton') 같은 번역 키로 참조합니다.
UI 컴포넌트의 원시 문자열을 표시하는 린트 규칙이나 프리커밋 훅을 추가합니다.
오류 10: 모든 것을 문자 그대로 번역하기
모든 콘텐츠를 번역해서는 안 됩니다. 브랜드명, 법인 이름, 기술 용어 및 특정 상품명은 원어를 유지해야 합니다. 과도한 번역은 법적 문제, 브랜드 불일치 및 사용자 혼란을 초래할 수 있습니다.
["번역하지 않는 용어집을 유지하고 모든 번역가와 공유하세요", '브랜드명 및 법적 용어에 대해 '잠긴' 세그먼트나 분리된 네임스페이스를 사용하세요', '모호한 문자열에는 항상 맥락 노트를 제공하여 오역을 방지하세요']
'번역하지 않음' 용어집을 유지하고 모든 번역가와 공유하십시오
브랜드 용어 및 법률 용어에 대해 잠긴 세그먼트나 별도의 네임스페이스를 사용하십시오
모호한 문자열에 대해 항상 맥락 메모를 제공하여 오역을 방지하십시오
일반적인 시나리오
번역 파일에는 회사 이름 'CloudForge Inc.' 및 기술 용어 'OAuth 2.0 Token'이 일반적으로 번역 가능한 문자열로 포함되어 있습니다.
스페인어 번역가는 'CloudForge'를 'ForjaNube'로, 'OAuth 2.0 Token'을 'ficha OAuth 2.0'으로 번역합니다.
결과: 사용자는 회사의 실제 이름으로 회사를 찾지 못하고, 스페인어 문서를 읽는 개발자들은 번역된 알 수 없는 전문 용어로 혼란스러워합니다.
왜 이것이 문제인가
법적 문서에서 잘못된 번역된 한 브랜드 이름이 계약을 무효로 만들 수 있습니다. 과도한 번역을 수정하려면 각 언어의 모든 문자열을 검토해야 하며 수 주가 소요됩니다.
모든 문자열을 맥락 없이 번역에 보내면 번역가는 브랜드 이름('Apple' → 'Apfel'), 법적 용어('GmbH' → 'LLC') 또는 영어로 남아 있어야 하는 기술 용어를 번역할 수 있습니다.
사용자는 잘 아는 브랜드 이름으로 된 제품을 찾지 못하고, 법적 문서는 잘못된 회사를 참조하며, 'API Endpoint'와 같은 용어가 번역되면 기술 문서가 이해하기 어렵게 됩니다.
이 문제를 해결하는 방법
번역되지 않는 콘텐츠를 명확하게 표시하고 번역가를 위한 맥락 노트를 제공하세요.
'번역 제외' 용어집을 만들어, 변경되어서는 안 되는 모든 브랜드명, 제품명, 법인명 및 전문 용어를 나열하십시오.
번역 불가 콘텐츠에는 별도의 네임스페이스나 특수 키를 사용하세요. 많은 i18n 도구가 번역자가 편집할 수 없는 '잠긴' 세그먼트를 지원합니다.
번역 파일에 맥락을 설명하는 번역가 주석/설명을 추가하십시오: '이것은 브랜드명 — 번역하지 마세요' 또는 '전문 용어 — 영어로 유지'.
오류 #2: 문자열 연결
문장을 조각으로 이어 붙이는 것은 영어로는 논리적으로 보일 수 있지만 다른 언어에서는 작동하지 않습니다. 어순, 문법, 문장 구조가 언어마다 크게 달라지므로 연결된 문자열은 번역될 수 없습니다.
['번역된 조각들을 연결하여 문장을 절대 만들지 마세요', '명확성을 위해 위치 기반({0}) 대신 명명된 플레이스홀더 ('{name}') 사용', '번역자에게 각 플레이스홀더가 무엇을 포함하는지 설명하는 컨텍스트 코멘트 제공']
번역된 조각들을 이어 붙여 문장을 만들지 마세요.
명명된 플레이스홀더 ('{name}')를 위치 기반 ({0}) 대신 사용하여 명확성 확보
각 자리표가 포함하는 내용을 설명하는 번역자를 위한 맥락 주석을 제공하십시오
전형적인 시나리오
개발자가 쓴다: 'You have ' + count + ' items in your ' + cartType + ' cart' — 영어에서 완벽하게 작동합니다.
독일어 번역자는 세 개의 개별 조각을 받으며 어순을 바꿔야 하기 때문에 문법적으로 올바른 문장을 만들 수 없다.
결과: 독일 사용자는 'Sie haben 5 Artikel in Ihrem Warenkorb Standard'를 보게 되어 어색하고 비전문적으로 보인다.
왜 이것이 문제인가
연결된 각 문자열은 시한폭탄과 같습니다. 20개 언어와 50개의 연결 문자열이 있으면 1,000개의 잠재적 문법 오류가 생겨 수동으로 수정해야 합니다.
예: 'Willkommen ' + userName + ', du hast ' + count + ' neue Nachrichten' 와 같은 문자열 연결은 특정 어순을 전제로 합니다. 번역가는 맥락 없는 조각들을 받아서 어순을 바꿀 수 없습니다.
사용자는 문법적으로 잘못된 문장을 봅니다. 독일어에서는 동사가 종종 문장의 끝에 위치합니다. 아랍어에서는 전체 구성이 반대로 됩니다. 결과는 말도 안 되는 문장처럼 읽힙니다.
다음과 같이 해결합니다
이름 있는 플레이스홀더를 포함한 매개변수화된 메시지를 사용하면 번역가가 문장의 전체 어순을 재배열할 수 있습니다.
연결 대신 플레이스홀더가 포함된 단일 메시지 키로 교체: 'cart.summary': '당신의 '{cartType}' 장바구니에 {count}개 항목이 있습니다.'
변수를 번역 함수의 매개변수로 전달합니다: t('cart.summary', { count: 5, cartType: 'Premium' }).
이제 번역자가 자유롭게 재배치 가능: '당신의 '{cartType}' 장바구니에는 {count}개 항목이 있습니다' — 문법적으로 올바른 독일어.
오류 #3: 복수형 무시
영어에는 두 가지 복수형이 있습니다: 단수형과 복수형. 개발자는 종종 모든 언어가 똑같이 작동한다고 가정합니다. 그렇지 않습니다. 폴란드어에는 4가지 형태가 있고, 아랍어에는 6가지 형태가 있으며, 프랑스어처럼 영어와 달리 0 형태를 다루는 언어도 있습니다.
['항상 ICU MessageFormat 또는 다중 콘텐츠에 대응하는 동등한 라이브러리를 사용하세요', '자체 다중 로직을 절대 작성하지 말고 — CLDR 규칙을 신뢰하십시오', '0, 1, 2, 5, 21 및 100과 같은 값으로 복수를 테스트하여 모든 카테고리를 다루십시오']
가산 가능한 콘텐츠에는 항상 ICU MessageFormat 또는 동등한 라이브러리를 사용하세요
자신만의 복수 로직을 절대 작성하지 마세요 — CLDR 규칙을 신뢰하십시오
0, 1, 2, 5, 21 및 100과 같은 값으로 복수형을 테스트하여 모든 범주를 포괄하십시오
전형적인 시나리오
개발자가 쓴다: count + (count === 1 ? 'Datei' : 'Dateien') — 독일어를 올바르게 처리합니다.
폴란드어 번역가는 4가지 형태가 필요합니다: 1 plik, 2-4 pliki, 5-21 plików, 22-24 pliki. 단순 삼항연산으로는 이를 표현할 수 없습니다.
결과: 폴란드어 사용자는 '5 pliki'(잘못된 형태)를 보게 되고, 올바른 형태인 '5 plików'가 아니라 앱이 엉망으로 보인다.
왜 이것이 문제인가
앱의 모든 가산 명사는 복수형 처리가 필요합니다. 이런 문자열 50개와 20개 언어의 경우, 1,000개의 복수 규칙이 생겨 수동으로 관리하는 것은 불가능합니다.
간단한 if/else 확인(count === 1 ? 'Datei' : 'Dateien')은 독일어와 영어만 다룹니다. CLDR은 최대 6개의 복수 형태 범주를 정의합니다: zero, one, two, few, many, other. 각 언어는 서로 다른 부분집합을 사용합니다.
사용자는 '1 Nachrichten'과 같은 문법적으로 잘못된 텍스트나 완전히 잘못된 복수형을 보게 됩니다. 형식적인 맥락에서 이는 신뢰성을 해칩니다.
다음과 같이 해결합니다
ICU MessageFormat를 사용하면 모든 CLDR 복수 규칙을 기본적으로 지원합니다.
ICU 구문으로 메시지를 정의합니다: 'fileCount': '{count, plural, one {# Datei} other {# Dateien}}'.
번역가는 각 언어에 필요한 모든 복수형 형태를 제공합니다. 폴란드어: '{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}'.
활성 로케일의 CLDR 규칙에 따라 런타임에 자동으로 올바른 형태를 선택합니다.
오류 #4: 고정된 UI 요소 너비
디자이너는 영어로 픽셀 단위의 레이아웃을 만들고 개발자는 이를 고정 너비로 구현합니다. 하지만 번역된 텍스트는 현저하게 길어지거나 짧아질 수 있습니다. 독일어 텍스트는 영어보다 약 30% 길고, 반면 중국어 텍스트는 약 50% 짧을 수 있습니다.
['번역 가능한 텍스트가 있는 요소에 고정 픽셀 폭을 절대 사용하지 마세요', '베이스라인으로 텍스트 확장을 40%로 계획 — 어떤 언어는 더 많이 확장됩니다', '레이아웃에 CSS Flexbox 또는 Grid를 사용하여 다양한 콘텐츠 길이에 맞춥니다']
번역 가능한 텍스트가 있는 요소에 고정 픽셀 너비를 절대 사용하지 마세요
기준으로 텍스트를 40% 확장하도록 계획하십시오 — 일부 언어는 더 확장됩니다
변동하는 콘텐츠 길이에 맞춰 레이아웃에 대해 CSS Flexbox 또는 Grid를 사용하세요
일반적인 시나리오
디자이너는 정확히 100픽셀 폭의 5개 버튼으로 구성된 네비게이션 바를 만듭니다 — 영어 버전에서는 멋져 보입니다.
독일어 번역: 'Settings'는 'Einstellungen'으로 바뀌고(13 대 8 글자), 'Submit'은 'Absenden'으로 바뀌고(8 대 6 글자). 네비게이션 바가 넘쳐 흐릅니다.
결과: 모바일 기기에서 버튼이 서로 겹치거나 텍스트가 잘려 독일어 사용자의 네비게이션이 사용할 수 없게 됩니다.
왜 이것이 문제인가
고정 너비를 가진 각 요소는 잠재적인 단절점이 됩니다. 일반적인 앱은 텍스트 확장을 견딜 수 있는 수백 개의 버튼, 레이블 및 카드가 있습니다.
고정 너비(width: 120px)의 컨테이너와 고정 크기 버튼은 텍스트가 확장되면 잘리거나 넘칩니다. CSS overflow: hidden은 내용을 조용히 숨기고, overflow: visible은 레이아웃을 망가뜨립니다.
사용자는 'Einstellu...'처럼 잘린 레이블이나 이웃한 요소를 가로지르는 버튼을 보게 됩니다. 중요한 작업은 읽기 어렵거나 클릭할 수 없게 됩니다.
다음과 같이 해결합니다
모든 로케일의 콘텐츠 길이에 맞춰 조정 가능한 유연한 레이아웃을 설계하고 구현하세요.
고정 너비를 min-width, max-width 및 Flex 레이아웃으로 교체하세요. CSS Grid 또는 Flexbox를 사용해 공간을 동적으로 분배합니다.
텍스트 컨테이너를 줄 바꿈에 맞춰 설정하세요: overflow-wrap: break-word를 사용하고 번역 가능한 콘텐츠에 대해 white-space: nowrap을 피하세요.
Worst Case를 시뮬레이션하기 위해 모든 문자열을 40% 길게 하는 의사 지역화로 UI를 테스트하세요 — 문자열을 번역자에게 보내기 전에.
오류 #5: 날짜 및 숫자 형식
데이터와 숫자는 보편적으로 보이지만, 01/02/2025는 미국에서 1월 2일이고 유럽에서는 2월 1일을 의미합니다. 쉼표와 점은 숫자의 의미를 바꿉니다: 1,000.50(USA) 대 1.000,50(독일). 이를 잘못하면 혼란, 데이터 오류, 신뢰 하락으로 이어집니다.
['데이터나 숫자를 문자열 템플릿으로 수동으로 포맷하지 말고 항상 Intl API를 사용하십시오', '모든 데이터를 ISO 8601로 내부 저장하고 통화는 내부적으로 가장 작은 단위(센트)로 저장하십시오', '다른 소수점 구분 기호, 날짜 순서 및 달력 시스템을 가진 로케일로 테스트하십시오']
데이터나 숫자를 문자열 템플릿으로 수동으로 포맷하지 마세요 — 항상 Intl API를 사용하세요
모든 데이터를 내부적으로 ISO 8601 형식으로 저장하고 통화는 가장 작은 단위(센트)로 저장하세요
다른 소수점 구분 기호, 날짜 순서 및 달력 시스템을 사용하는 로케일로 테스트하십시오
일반적인 시나리오
개발자는 날짜를 MM/DD/YYYY 형식으로, 가격을 $1,234.50로 포맷합니다 — 미국 사용자를 위한 올바른 형식입니다.
독일 사용자는 날짜 03/04/2025를 보고 이를 3월 4일로 읽습니다(DD/MM/YYYY 관례). 가격 $1,234.50는 1.234,50으로 표시되어야 한다고 보입니다.
결과: 사용자가 잘못된 날짜로 항공편을 예약하고 요금 형식을 의심합니다. 독일 시장에서 지원 티켓이 15% 증가합니다.
왜 이것이 문제인가
날짜 및 숫자 형식은 앱의 모든 데이터 표시(표, 차트, 양식, 청구서, 보고서)에 영향을 미칩니다. 글로벌 패치로 수백 개의 인스턴스를 다룹니다.
하드코딩된 포맷 문자열 예: toLocaleDateString('en-US') 또는 템플릿 리터럴을 사용한 수동 포맷은 사용자의 실제 로케일을 무시합니다. 올바른 로케일이어도 달력 체계가 잘못되면 문제를 야기합니다(그레고리력 vs 히지리력).
사용자는 데이터를 잘못 읽고 잘못된 형식으로 데이터를 입력합니다. 유럽 사용자가 03/04/2025를 보면 4월 3일로 해석할 수 있어 약속을 놓치거나 잘못된 예약이 발생합니다.
이 문제를 해결하는 방법
날짜, 시간, 숫자 및 통화에 대해 내장 Intl API 또는 로케일 인식 형식 지정 라이브러리를 사용하세요.
날짜에는 Intl.DateTimeFormat(locale)를 사용하고 가격에는 Intl.NumberFormat(locale, { style: 'currency', currency })를 사용하여 수동 포맷을 대체하세요.
데이터를 내부적으로 ISO 8601 형식(YYYY-MM-DD)으로 저장하고, 사용자 로케일로 표시할 때만 포맷하십시오.
필수적인 날짜/숫자 표시를 최소 5개의 서로 다른 로케일(en-US, de-DE, ja-JP, ar-SA, zh-CN)로 테스트하여 가장 중요한 형식 변형을 다룹니다.
오류 #6: RTL 언어를 잊지 마세요
설명: RTL(오른쪽에서 왼쪽으로 쓰는) 언어인 아랍어, 히브리어, 페르시아어는 5억 명이 넘는 사람들이 사용합니다. 그러나 대부분의 앱은 여전히 왼쪽에서 오른쪽(LTR) 레이아웃으로 설계되어 있습니다. RTL 지원은 텍스트를 뒤집는 것뿐만이 아니라 전체 UI를 반사해야 한다는 것을 의미합니다.
['처음부터 논리적인 CSS 속성만 사용합니다(inline-start/end, block-start/end)', "스프린트 리뷰마다 HTML 요소에서 dir='rtl'을 사용해 앱을 테스트하세요", '네비게이션, 아이콘, 양식, 진행 표시를 위한 RTL 테스트 체크리스트를 만드세요']
처음부터 논리적 CSS 속성만 사용하십시오 (inline-start/end, block-start/end)
스프린트 리뷰마다 HTML 요소에서 dir='rtl'로 앱을 테스트하십시오
네비게이션, 아이콘, 양식 및 진행 표시를 위한 RTL 테스트 체크리스트를 만드십시오
일반적인 시나리오
개발자는 전체 앱에서 margin-left: 16px와 text-align: left를 사용합니다 — 표준 LTR 관례입니다.
앱은 사우디아라비아에서 시작됩니다. 뒤로 가기 아이콘이 앞쪽을 가리키고, 사이드바가 잘못된 쪽에 나타나며, 숫자 데이터의 정렬이 올바르게 되어 있지 않습니다.
결과: 아랍어 사용자는 30초 만에 앱을 떠납니다. 팀은 문제를 해결하기 위해 4주간의 긴급 CSS 리팩토링이 필요합니다.
왜 이것이 문제인가
RTL 지원은 앱의 각 구성 요소를 포함합니다. RTL을 후행으로 도입하는 경우 일반적으로 CSS 규칙의 30-50%를 재작성하고 모든 아이콘과 레이아웃을 검사해야 합니다.
margin-left, padding-right, text-align: left 및 float: left 와 같은 CSS 속성은 방향을 하드코딩합니다. 방향을 나타내는 아이콘(화살표, 진행 표시)은 잘못된 방향을 가리킵니다. border-radius 값조차도 반영되어야 합니다.
아랍어 사용자는 내비게이션이 잘못된 모서리에 표시되고 진행 막대가 역방향으로 움직이며 UI 요소와 충돌하는 텍스트를 봅니다. 앱은 낯설고 사용할 수 없다고 느낍니다.
해결 방법
논리적인 CSS 속성을 사용하고 처음부터 RTL 레이아웃으로 테스트하세요.
모든 물리적 CSS 속성을 논리적 동등 값으로 교체하십시오: margin-left → margin-inline-start, padding-right → padding-inline-end, text-align: left → text-align: start.
활성 로케일에 따라 HTML 루트 요소에 dir 속성을 설정합니다. RTL 특화 재정의를 위해 CSS 의 :dir(rtl) 의사 클래스를 사용하세요.
모든 아이콘을 방향 표시 여부를 확인합니다. 방향 의존 아이콘은 반영된 버전으로 교체하거나 RTL 컨텍스트에 대해 CSS transform: scaleX(-1)를 사용하세요.
오류 #7: 텍스트가 포함된 이미지
텍스트를 이미지에 삽입하는 것은 로컬라이제이션의 악몽입니다. 히어로 배너, 버튼, 인포그래픽 또는 스크린샷에 텍스트가 포함된 이미지는 언어마다 새로 제작되어 디자인 비용이 증가하고 릴리스가 지연됩니다.
['번역 가능한 텍스트를 래스터 이미지(PNG, JPG) 안에 직접 삽입하지 마세요', '히어로 배너와 CTA를 위해 배경 이미지에 CSS 텍스트 오버레이를 사용하세요', 'App Store 목록 및 마케팅 페이지의 스크린샷 생성을 자동화하세요']
번역 가능한 텍스트를 직접 래스터 이미지(PNG, JPG)에 삽입하지 마세요
히어로 배너 및 CTA에 대한 배경 이미지에 CSS 텍스트 오버레이를 사용하십시오
App Store 목록 및 마케팅 페이지의 스크린샷 생성을 자동화하세요
일반적인 시나리오
한 디자이너가 'Start Your Free Trial' 텍스트를 이미지 안에 직접 삽입한 프로모션 배너를 만듭니다.
로컬라이제이션 팀은 모든 UI 문자열을 번역하지만 독일어 페이지의 배너에는 여전히 영어 텍스트가 표시됩니다.
결과: 독일어 랜딩페이지에 혼란스러운 영어 배너가 있습니다. 15개 언어에 맞춘 로컬라이즈드 배너를 만드는 데 디자이너 작업 3일이 필요하고 출시가 지연됩니다.
왜 이것이 문제인가
일반적인 마케팅 페이지에는 텍스트가 포함된 이미지가 5-10개 있습니다. 15개 언어에서는 이미지 버전이 75-150개가 되어야 하며, 매 디자인 변경 시 생성·관리·업데이트해야 합니다.
이미지에 텍스트가 포함된 경우(PNG, JPG, 텍스트가 포함된 SVG)는 번역 도구에서 추출할 수 없습니다. 로컬라이즈된 각 버전은 디자이너가 소스 파일을 수동으로 편집하고 내보낸 다음 업로드해야 합니다.
사용자는 다른 언어로 된 텍스트가 포함된 이미지나 더 나쁘게는 로컬라이즈된 UI와 미번역 이미지가 혼합된 것을 보게 됩니다. 이는 일관성이 없게 보이며 브랜드 신뢰를 해칩니다.
다음과 같이 해결하세요
텍스트를 CSS 오버레이, 번역 가능한 텍스트 요소를 참조하는 SVG 또는 동적 이미지 생성을 사용하여 이미지에서 분리합니다.
배경 이미지 위에 번역 가능한 텍스트를 배치하려면 CSS를 사용하세요: 텍스트 레이어를 이미지 컨테이너 위에 절대 위치로 배치합니다.
인포그래픽이나 다이어그램의 경우, 번역 키를 참조하는 <text> 요소가 있는 SVG를 사용하고 원시 문자열을 삽입하지 마십시오.
마케팅 자료의 앱 스크린샷에 대해 Fastlane(모바일) 또는 Playwright(웹)와 같은 도구를 사용하여 모든 로케일에서 화면을 캡처하는 스크린샷 생성을 자동화합니다.
오류 #8: 누락된 번역 처리되지 않음
개발 중에는 번역이 항상 불완전합니다. 새로운 기능은 번역가가 번역할 수 있는 것보다 더 빨리 문자열을 추가합니다. 적절한 폴백 처리가 없으면 누락된 번역이 충돌, 빈 UI 요소, 또는 사용자가 보게 되는 원시 번역 키를 초래합니다.
['i18n 설정에 항상 적어도 하나의 대체 언어를 구성하시오', '모니터링 시스템으로 누락된 번역 키를 로깅하여 추적하시오', 'Locale를 라이브로 배포하기 전에 최소 번역 커버리지를 설정하시오']
i18n 설정에 항상 최소 한 가지 기본 언어를 구성하십시오
누락된 번역 키를 모니터링 시스템에 로깅하여 추적하십시오
로케일이 라이브로 공개되기 전에 최소 번역 커버리지를 설정하십시오
일반적인 시나리오
개발자가 새 'Premium Features' 섹션과 15개의 새로운 번역 키를 추가합니다. 영어 버전이 즉시 배포됩니다.
프랑스어 번역이 아직 완료되지 않았습니다. 프랑스어 페이지에는 원시 키가 표시됩니다: 'premium.feature1.title', 'premium.feature1.description'.
결과: 프랑스어 사용자는 개발자 측 키 이름으로 가득 찬 깨진 페이지를 봅니다. 지원 팀은 수십 건의 버그 보고서를 받습니다.
왜 이것이 문제인가
앱이 커질수록 영어 문자열과 다른 언어의 번역 사이의 격차가 커집니다. 100개 언어와 2,000개 문자열을 가진 앱은 언제든지 10,000개 이상 누락된 번역을 가질 수 있습니다.
폴백 로직이 없으면 누락된 번역 키가 undefined, null 또는 원시 키 문자열을 반환합니다(예: 'checkout.confirmButton'). 템플릿 엔진은 오류를 발생시키거나 페이지가 크래시되거나 아무 것도 렌더링하지 않을 수 있습니다.
사용자는 망가진 UI를 봅니다: 빈 버튼, 누락된 레이블, 'nav.settings.title'와 같이 의미를 알 수 없는 문자열이 실제 텍스트 대신 표시됩니다. 이는 혼란스럽고 전문적이지 않습니다.
해결 방법
강력한 폴백 체인을 구성하고 모든 로케일에 대한 번역 커버리지를 모니터링합니다.
i18n 구성에서 폴백 언어 체인을 설정합니다: 누락된 프랑스어(fr) 키는 자동으로 영어(en)로 되돌아갑니다.
미번역 키를 모니터링 시스템(예: Sentry, Datadog)에 로깅하는 Missing-Key 핸들러를 추가하되, 사용자 경험이 깨지지 않도록 하세요.
국가별/로케일별 번역 커버리지 대시보드를 만들어 커버리지가 임계값(예: 95%) 아래로 떨어지면 릴리스를 차단합니다.
오류 9: 문자 인코딩 문제
문자 인코딩 문제는 현지화의 조용한 살인자입니다. 영어 및 유럽 언어로는 모두 문제가 없어 보이지만 중국어, 일본어, 한국어, 아랍어 또는 이모지를 추가하면 문자 인쇄가 깨지는 현상이 나타납니다. 이러한 버그는 발견하기 어렵기로 유명합니다.
['UTF-8 인코딩을 모든 곳에서 사용합니다 — 소스 파일, 데이터베이스, API 응답 및 HTML 메타 태그', 'MySQL에서 utf8 대신 utf8mb4를 사용하여 이모지를 포함한 전체 유니코드 범위를 지원', 'CJK, 아랍어 및 이모지를 포함한 실제 콘텐츠로 인코딩 문제를 조기에 발견하기 위해 테스트합니다']
모든 곳에서 UTF-8 인코딩을 사용하세요 — 소스 파일, 데이터베이스, API 응답 및 HTML 메타 태그
MySQL에서 전체 유니코드 범위(이모지 포함)를 지원하려면 utf8mb4를 사용하세요(utf8 아님)
CJK, 아랍어 및 이모지의 실제 콘텐츠로 인코딩 문제를 조기에 파악하십시오
일반적인 시나리오
개발자는 오래된 표준인 latin1 콜레이션을 사용하는 MySQL 데이터베이스를 구성합니다. 애플리케이션 소스 코드는 UTF-8을 사용합니다.
일본인 사용자는 실제 이름으로 등록합니다. 데이터베이스는 '田中太郎'를 손상된 바이트로 저장합니다.
결과: 사용자 프로필에 손상된 텍스트가 표시됩니다. 더 나아가 모든 CJK 이름의 검색 및 정렬이 깨져 수천 명의 사용자가 영향을 받습니다.
왜 이것이 문제인가
인코딩 문제는 스택 전체에 확산됩니다. 잘못 구성된 데이터베이스의 정렬 규칙은 수백만 건의 데이터에 손상을 일으킬 수 있으며, 이를 수정하려면 비용이 많이 드는 데이터 마이그레이션이 필요합니다.
스택 전체에 걸친 일관되지 않은 문자 인코딩 — 소스 파일의 UTF-8, 데이터베이스의 Latin-1, API 응답의 Windows-1252 — 다중 바이트 문자를 파괴합니다. 잘못 구성된 단 하나의 계층이 '日本語'를 '????' 또는 '日本èª'로 바꿉니다.
사용자는 자신의 언어가 표시되어야 할 곳에서 손상된 텍스트, 물음표 또는 빈 상자를 봅니다. 최악의 경우 양식 입력이 데이터베이스에 영구적으로 손상될 수 있습니다.
다음과 같이 해결합니다
애플리케이션 스택의 모든 계층에서 UTF-8 인코딩을 일관되게 강제합니다.
모든 소스 파일을 UTF-8로 설정합니다(에디터 및 .editorconfig 구성). HTML에 <meta charset='UTF-8'>를 추가하고 API 응답에 'Content-Type: application/json; charset=utf-8'을 포함시킵니다.
데이터베이스를 utf8mb4로 구성합니다( MySQL에서 utf8은 3바이트 하위집합일 뿐입니다). 연결 정렬을 utf8mb4_unicode_ci로 설정합니다.
대상 문자 시스템을 커버하는 글꼴을 선택합니다: 라틴어, 키릴 문자, CJK, 아랍어, 데바나가리. 로딩 최적화를 위해 시스템 폰트 스택 또는 언어 하위 집합이 있는 Google Fonts를 사용하세요.
i18n 구현을 테스트하기
길이 확장 테스트
모든 번역 문자열을 30-40% 확장하여 독일어, 핀란드어, 그리스어와 같이 단어가 많은 언어에서 텍스트 확장을 시뮬레이션합니다. 고정 폭 컨테이너, 잘린 라벨, 넘치는 버튼을 포함하고 번역을 시작하기 전에 이를 반영합니다. 많은 의사 로컬라이제이션 도구가 이를 내장 기능으로 제공합니다.
"보내기" → "Ṡééééñðéñ_éxpáñðéð" (40% 더 긴)
의사 로컬라이제이션
의사 로컬라이제이션은 각 문자를 악센트가 있는 대응 문자로 바꿔주고(예: 'a' → 'á') [!! 및 !!]와 같은 마커로 문자열을 둘러쌉니다. 이렇게 하면 하드코드된 문자열과 번역 시스템에서 온 문자열을 즉시 구분할 수 있습니다. 회귀를 자동으로 포착하기 위해 CI 파이프라인의 일부로 의사 로컬라이제이션을 실행하세요.
화면에 표시되는 모든 텍스트 중 [!! !!]로 둘러싸여 있지 않은 것은 하드코딩되어 있으며 외부화되어야 합니다. 이 테스트는 누락된 문자열의 약 95%를 1분 이내에 포착합니다.
"메시지 보내기" → "[!! Ñáçḥŕíçḥṫ ṡéñðéñ !!]"
RTL 레이아웃 테스트
아랍어 또는 히브리어 번역이 없어도 HTML 루트 요소에 dir='rtl'을 추가하여 RTL 레이아웃을 테스트할 수 있습니다. 이는 방향성 CSS 버그를 즉시 감지합니다: 잘못 정렬된 아이콘, 잘못된 쪽의 여백, 깨진 탐색 및 잘못 정렬된 Flex 아이템. 이를 매 스프린트 리뷰의 표준 체크로 삼으세요 — 전환은 약 10초가 걸리고 생산환경에서 수정하는 데 대개 몇 주가 걸리는 문제를 포착합니다.
i18n 체크리스트
['사용자가 볼 수 있는 모든 문자열을 리소스 파일로 분리한다', '문장 생성을 위해 문자열 연결을 사용하지 않는다', 'ICU 메시지 포맷 또는 동등한 것을 사용한 복수형 규칙 구현', '날짜, 시간 및 숫자 형식을 로케일 인식 API를 사용하여 처리', '아랍어 또는 히브리어 콘텐츠를 포함한 RTL 레이아웃을 테스트', '텍스트가 포함된 요소에 고정 폭 없이 유연한 UI 설계', '대체 언어를 구성하고 누락된 키를 테스트', '모든 파일과 데이터베이스에서 UTF-8 인코딩을 일관되게 사용', '각 시장에 대해 App Store 및 Play Store 메타데이터를 현지화', '각 언어별 스크린샷과 마케팅 자산 업데이트']