Chrome 확장 프로그램 다국어 지원: Manifest V3에서 5개 언어 적용하기
Chrome 확장 프로그램 다국어 지원: Manifest V3에서 5개 언어 적용하기

Chrome 확장 프로그램을 영어로만 만들다가 한국어, 일본어, 스페인어, 독일어까지 지원하려면 단순히 문자열을 번역하는 것만으로는 부족합니다. Manifest V3의 chrome.i18n 구조, 정적 HTML과 JavaScript에서 생성되는 동적 UI, 컨텍스트 메뉴, 기존 사용자 데이터까지 함께 살펴야 합니다.
최근 Simple Side Note 1.4.0에 5개 언어를 적용하면서 사용한 구조를 기준으로, 기존 확장 프로그램을 안전하게 다국어화하는 방법을 정리합니다.
1. _locales 디렉터리 만들기
Chrome 확장 프로그램의 기본 번역 단위는 _locales/<언어>/messages.json입니다.
_locales/
en/messages.json
ko/messages.json
ja/messages.json
es/messages.json
de/messages.json
각 파일은 같은 메시지 키를 가져야 합니다. 영어 파일의 일부는 다음처럼 작성할 수 있습니다.
{
"extensionName": {
"message": "Simple Side Note"
},
"extensionDescription": {
"message": "A private notepad in your browser sidebar"
},
"addToMemo": {
"message": "Add to Memo"
}
}
한국어 파일은 키를 유지하고 message만 번역합니다.
{
"extensionName": {
"message": "Simple Side Note"
},
"extensionDescription": {
"message": "브라우저 사이드바에서 사용하는 개인 메모장"
},
"addToMemo": {
"message": "메모에 추가"
}
}
키 이름은 화면 문구가 아니라 기능을 설명하도록 짓는 편이 좋습니다. 나중에 번역 문구가 바뀌어도 saveButton, trashEmptyMessage 같은 키는 그대로 유지할 수 있기 때문입니다.
2. manifest에 기본 언어 연결하기
manifest.json에는 default_locale을 선언하고 번역할 필드를 __MSG_키__ 형식으로 바꿉니다.
{
"manifest_version": 3,
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",
"default_locale": "en"
}
default_locale을 추가했다면 _locales/en/messages.json이 반드시 존재해야 합니다. 파일이나 키가 빠지면 압축을 풀어 확장 프로그램을 로드하는 단계에서 오류가 발생할 수 있습니다.
Chrome은 브라우저 UI 언어에 맞는 번역을 고르고, 해당 번역이 없으면 기본 언어로 돌아갑니다. 따라서 영어 번역은 다른 언어보다 먼저 완성하고 모든 키가 있는 기준 파일로 관리하는 것이 안전합니다.
3. 정적 HTML과 동적 UI를 구분하기
Manifest의 이름과 설명은 Chrome이 자동 처리하지만, 사이드 패널 HTML 안의 버튼과 안내 문구는 애플리케이션 코드가 바꿔야 합니다. 번역 대상을 속성으로 표시해 두면 HTML 구조를 언어별로 복제하지 않아도 됩니다.
<button data-i18n="saveButton">Save</button>
<input data-i18n-placeholder="searchPlaceholder"
placeholder="Search notes">
<span data-i18n-title="pinNote" title="Pin note">📌</span>
페이지가 로드될 때 메시지를 적용합니다.
function applyChromeMessages(root = document) {
root.querySelectorAll('[data-i18n]').forEach((element) => {
const message = chrome.i18n.getMessage(element.dataset.i18n);
if (message) element.textContent = message;
});
root.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
const message = chrome.i18n.getMessage(element.dataset.i18nPlaceholder);
if (message) element.placeholder = message;
});
}
document.addEventListener('DOMContentLoaded', () => applyChromeMessages());
반면 삭제 확인창, 저장 완료 알림, 메모 개수처럼 실행 중 만들어지는 문장은 생성 시점에 번역해야 합니다.
const t = (key, substitutions) =>
chrome.i18n.getMessage(key, substitutions) || key;
statusElement.textContent = t('notesCount', [String(notes.length)]);
이 두 경로를 나누면 “첫 화면은 번역됐지만 새로 생성된 메뉴는 영어로 보이는” 문제를 줄일 수 있습니다.
4. 앱 내부 언어 테이블이 필요한 경우
chrome.i18n만으로 충분한 앱도 있지만, 화면 전체를 한 번에 렌더링하거나 복수형·날짜 표시를 세밀하게 제어하려면 앱 내부 번역 객체가 편리할 수 있습니다. 이때 브라우저 언어의 지역 코드를 정규화해야 합니다.
const supported = ['en', 'ko', 'ja', 'es', 'de'];
const browserLanguage = chrome.i18n.getUILanguage()
.toLowerCase()
.split('-')[0];
const language = supported.includes(browserLanguage)
? browserLanguage
: 'en';
예를 들어 ko-KR은 ko, de-DE는 de로 처리됩니다. 지원하지 않는 fr-FR은 영어로 돌아갑니다. 언어 코드를 그대로 비교하면 지역 코드가 붙은 환경에서 번역을 찾지 못하는 실수가 자주 생깁니다.
날짜와 숫자는 직접 조합하지 말고 Intl을 사용하는 것이 좋습니다.
const formatted = new Intl.DateTimeFormat(language, {
year: 'numeric', month: 'short', day: 'numeric'
}).format(new Date(note.updatedAt));
5. 컨텍스트 메뉴도 잊지 않기
페이지에서 텍스트를 선택한 뒤 우클릭하는 메뉴는 서비스 워커에서 생성됩니다. UI 파일을 번역해도 이 부분은 자동으로 바뀌지 않습니다.
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'add-to-memo',
title: chrome.i18n.getMessage('addToMemo'),
contexts: ['selection']
});
});
서비스 워커에서는 DOM을 사용할 수 없지만 chrome.i18n.getMessage()는 사용할 수 있습니다. 설치·업데이트 시 메뉴를 다시 만들 때 현재 브라우저 언어의 문자열이 적용됩니다.
6. 저장 데이터는 번역하지 않기
다국어 업데이트에서 가장 중요한 호환성 원칙은 사용자 데이터와 화면 문구를 분리하는 것입니다. 테마 값, 정렬 방식, 자동 정리 기간 같은 저장 값은 언어와 무관한 식별자로 보관합니다.
// 저장 값
settings.theme = 'warm';
settings.sortOrder = 'updated-desc';
// 화면에 표시할 때만 번역
themeLabel.textContent = t(`theme_${settings.theme}`);
저장된 "Warm"을 한국어에서 "따뜻한 테마"로 바꾸면 기존 설정 비교가 깨질 수 있습니다. 내부 값은 고정하고 표시 문자열만 번역하면 1.3.0 사용자가 1.4.0으로 업데이트해도 메모와 설정을 그대로 사용할 수 있습니다.

7. 출시 전 점검 목록
다섯 언어를 모두 능숙하게 읽지 못하더라도 구조적인 오류는 자동·수동 검사로 상당 부분 찾을 수 있습니다.
- 영어 기준 파일과 다른 언어 파일의 키 집합이 같은지 비교합니다.
- 모든
__MSG_*__키가 실제로 존재하는지 확인합니다. - 긴 독일어 문구에서 버튼과 탭이 잘리지 않는지 확인합니다.
- 일본어와 한국어에서 시스템 글꼴과 줄바꿈을 확인합니다.
- 스페인어의 악센트 문자가 JSON과 HTML에서 정상 표시되는지 확인합니다.
- 새 설치와 기존 버전 업데이트를 모두 시험합니다.
- 사이드 패널뿐 아니라 설정, 휴지통, 확인창, 컨텍스트 메뉴도 확인합니다.
CSS에서는 고정 너비보다 유연한 레이아웃을 사용하는 편이 좋습니다.
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.toolbar button {
min-width: max-content;
}
마무리
Chrome 확장 프로그램 다국어화의 핵심은 번역량보다 경계를 명확히 나누는 데 있습니다. Manifest와 컨텍스트 메뉴는 chrome.i18n, 정적 화면은 data-i18n 속성, 동적 문장은 렌더링 시점의 번역 함수로 처리합니다. 사용자 데이터에는 언어와 무관한 값을 저장해야 기존 버전과도 호환됩니다.
처음부터 모든 언어를 완벽하게 지원하려고 하기보다 영어 기준 파일을 만들고, 키 누락 검사와 지역 코드 폴백을 먼저 갖춘 뒤 언어를 하나씩 추가하는 방식이 유지보수에 유리합니다. 이 구조를 잡아 두면 다음 언어 추가는 새 messages.json과 번역 검수에 집중할 수 있습니다.
댓글
댓글 쓰기