빠른 시작#
번역할 요소에 data-l10n을 붙이고, 사전과 초기화 스크립트를 불러오면 바로 동작합니다.
최소 예제#
<!DOCTYPE html>
<html lang="ko">
<body>
<h1 data-l10n>프로젝트 운영 현황</h1>
<p data-l10n>배포 일정은 11월 20일로 예정되어 있습니다.</p>
<!-- 1. 번역 사전 (전역 변수 coForward_l10n_dictionary) -->
<script src="coForward_l10n_dictionary.js"></script>
<!-- 2. 뷰어 초기화 -->
<script src="/api/l10n/coForward_l10n_init.js"
data-from="ko" data-to="en" type="module"></script>
</body>
</html>
이제 <html>의 lang을 en으로 바꾸면 표시된 문구가 영어로 바뀌고, ko로 되돌리면 원문이 다시 표시됩니다.
동작 원리#
<html lang>변경을 감지해 페이지 전체를 다시 번역합니다.- 새로 추가되는 요소와 속성 변경도 자동으로 감지해 번역합니다.
- 사전에 없는 문구는 원문을 그대로 보여줍니다.
설치 및 파일 구성#
방문자에게 번역만 보여주는 뷰어와, 운영자가 화면에서 번역을 등록하는 에디터 중 하나를 선택해 불러옵니다.
파일 위치#
| 파일 | 용도 |
|---|---|
/api/l10n/coForward_l10n_init.js | 뷰어 초기화 (공개 페이지용) |
/api/l10n/coForward_l10n_editor_init.js | 에디터 초기화 (운영자 페이지용) |
/api/l10n/module/coForward_l10n.js | 번역 엔진 클래스 |
/api/l10n/module/coForward_l10n_editor.js | 에디터 클래스 (번역 엔진 확장) |
/api/l10n/css/ | 에디터·마커 스타일 (Shadow DOM 안에서 로드) |
/l10n/dictionary_json.php | 프로젝트 사전 JSON 다운로드 |
뷰어 vs 에디터#
| 구분 | 뷰어 | 에디터 |
|---|---|---|
| 대상 | 일반 방문자 | 번역 운영자 |
| 사전 출처 | 전역 변수 coForward_l10n_dictionary | 서버 API (dictionary.php) |
| API 키 | 필요 없음 | data-key 필수 |
| 추가 기능 | — | 로그인, 마커, 항목 편집, 일괄·직접 등록 |
뷰어
<script src="coForward_l10n_dictionary.js"></script>
<script src="/api/l10n/coForward_l10n_init.js"
data-from="ko" data-to="en" type="module"></script>
에디터
<script src="/api/l10n/coForward_l10n_editor_init.js"
data-key="YOUR_API_KEY"
data-from="ko"
data-to="en"
type="module" defer></script>
data-key는 프로젝트의 번역 등록 권한과 연결됩니다. 운영자 전용 페이지에서만 사용하고 공개 페이지에는 넣지 마세요.
HTML 마킹#
번역할 요소에 data-l10n 속성을 추가합니다. 값은 비워 두어도 됩니다.
기본 마킹#
<h1 data-l10n>프로젝트 운영 현황</h1>
<p data-l10n>운영팀은 사용자 매뉴얼 초안을 검토해 주세요.</p>
<label data-l10n for="title">업무 제목</label>
속성 번역#
title, placeholder, alt 속성은 본문과 함께 자동으로 번역됩니다. 본문이 없는 요소는 data-l10n에 고유 이름을 지정하면 사전 키로 사용됩니다.
<input data-l10n="taskTitle" placeholder="업무 제목을 입력하세요"
title="프로젝트에 등록할 업무의 제목을 입력합니다.">
<img data-l10n="summaryChart" src="chart.svg"
alt="이번 주 프로젝트 진행 현황 차트" title="프로젝트 진행 현황">
하위 요소가 있는 문장#
문장 안의 태그는 [[sub-1]], [[sub-2]] 자리표시자로 바뀌어 사전 키가 만들어집니다. 번역문에서 자리표시자 위치를 옮기면 어순이 다른 언어에서도 태그와 스타일이 유지됩니다. 하위 요소의 문구와 속성도 각각 번역됩니다.
<p data-l10n>배포 일정은 <span>11월 20일</span>로 예정되어 있습니다.</p>
<!-- 사전 키: "배포 일정은 [[sub-1]]로 예정되어 있습니다." -->
<!-- 번역문: "The release is scheduled for [[sub-1]]." -->
줄바꿈#
<br>는 [[br]]로 처리됩니다.
<p data-l10n>주요 일정입니다.<br>최종 배포는 11월 20일입니다.</p>
<!-- 사전 키: "주요 일정입니다.[[br]]최종 배포는 11월 20일입니다." -->
그룹#
같은 원문이라도 화면 영역에 따라 번역을 다르게 하려면 상위 요소에 data-l10n-group을 지정합니다. 그룹 안의 요소는 그룹 사전에서 먼저 번역을 찾습니다.
<section data-l10n-group="operation">
<h1 data-l10n>운영팀 업무 현황</h1>
<p data-l10n>이번 주 고객 문의 처리 현황을 공유합니다.</p>
</section>
번역 추적 속성#
번역된 요소에는 사전 항목 번호가 자동으로 기록됩니다. 언어를 다시 바꿀 때 이 번호로 원문을 찾아 번역하므로 직접 수정하지 마세요.
| 속성 | 설명 |
|---|---|
data-l10n-idx | 본문 사전 항목 번호 |
data-l10n-title | title 속성의 사전 항목 번호 |
data-l10n-placeholder | placeholder 속성의 사전 항목 번호 |
data-l10n-alt | alt 속성의 사전 항목 번호 |
마커 속성 이름은 설정의 markerName으로 바꿀 수 있습니다. 예를 들어 markerName: 'i18n'이면 data-i18n을 사용합니다.
언어 전환#
번역 엔진은 <html> 요소의 lang 속성을 감시합니다. 값만 바꾸면 페이지 전체가 다시 번역됩니다.
document.documentElement.lang = 'en'; // 영어
document.documentElement.lang = 'ja'; // 일본어
document.documentElement.lang = 'ko'; // 원문(한국어)
KO / EN 토글 예시#
버튼의 상태를 aria-checked로 표시하고, 에디터 등 다른 곳에서 언어가 바뀌어도 상태를 맞춥니다.
<button type="button" class="btn-change-lang" role="switch" aria-checked="false">Change Lang</button>
<script type="module">
const html = document.documentElement;
const toggle = document.querySelector('.btn-change-lang');
toggle.addEventListener('click', () => {
html.lang = html.lang === 'ko' ? 'en' : 'ko';
});
const sync = () => toggle.setAttribute('aria-checked', String(html.lang !== 'ko'));
new MutationObserver(sync).observe(html, { attributes: true, attributeFilter: ['lang'] });
sync();
</script>
언어 선택 상자#
<select onchange="document.documentElement.lang = this.value">
<option value="ko">한국어</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
원문 언어(data-from)와 같은 lang을 지정하면 원문이 표시됩니다. 사전에 해당 언어 번역이 없는 문구도 원문으로 표시됩니다.
동적 콘텐츠#
JavaScript로 나중에 추가한 요소도 data-l10n만 있으면 현재 언어로 자동 번역됩니다. 별도 호출이 필요 없습니다.
const item = document.createElement('p');
item.setAttribute('data-l10n', '');
item.textContent = '신규 업무: 사용자 매뉴얼 검토';
document.querySelector('#task-list').appendChild(item); // 추가 즉시 번역
자동 감지 대상#
| 변경 | 동작 |
|---|---|
| 요소 추가 | data-l10n 요소와 그 하위 요소를 번역 |
| 텍스트 변경 | 부모 요소에 data-l10n이 있으면 다시 번역 |
| 속성 변경 | data-l10n, title, placeholder, alt, open, style, lang 변경 시 다시 번역 |
팝업(<dialog>)처럼 열릴 때 내용이 만들어지는 UI도 같은 방식으로 번역됩니다. 번역 중인 요소에는 coForward-l10n-translating 클래스가 붙으므로 전환 효과를 줄 수 있습니다.
딕셔너리 형식#
사전은 원문(사전 키)을 키로, 언어별 번역을 값으로 가지는 JSON 객체입니다. 프로젝트 관리 화면의 사전 JSON 다운로드로 받을 수 있습니다.
기본 형식#
{
"프로젝트 운영 현황": {
"idx": "l10nIdx-1",
"lang": "ko",
"translated": { "en": "Project Operations", "ja": "プロジェクト運営状況" }
},
"배포 일정은 [[sub-1]]로 예정되어 있습니다.": {
"idx": "l10nIdx-2",
"lang": "ko",
"translated": { "en": "The release is scheduled for [[sub-1]]." }
}
}
속성 번역#
본문이 없는 요소는 태그명-data-l10n값이 사전 키가 되고, 속성 번역은 속성 이름별로 저장됩니다.
{
"INPUT-taskTitle": {
"idx": "l10nIdx-19",
"lang": "ko",
"translated": { "en": "Task title" },
"placeholder": {
"업무 제목을 입력하세요": { "translated": { "en": "Enter a task title" } }
},
"title": {
"프로젝트에 등록할 업무의 제목을 입력합니다.": { "translated": { "en": "Enter the title of the task." } }
}
}
}
그룹 형식#
{
"operation": {
"운영팀 업무 현황": {
"idx": "l10nIdx-38",
"lang": "ko",
"translated": { "en": "Operations Team Status" }
}
}
}
필드 설명#
| 필드 | 타입 | 설명 |
|---|---|---|
idx | string | 사전 항목 고유 번호. 요소의 data-l10n-idx에 기록됩니다. |
lang | string | 원문 언어 코드 |
translated | object | 언어 코드 → 번역문 |
title / placeholder / alt | object | 속성 원문 → { translated } |
PHP로 사전 출력#
<?php
$l10n = new L10N();
$dict = $l10n->make_dictionary($project_idx);
?>
<script>
var coForward_l10n_dictionary = <?php echo json_encode($dict, JSON_UNESCAPED_UNICODE); ?>;
</script>
번역 에디터#
운영자가 실제 화면을 보면서 원문을 등록하고 번역을 입력하는 도구입니다. 화면 우측 상단에 고정된 패널로 표시됩니다.
스크립트 속성#
| 속성 | 필수 | 설명 |
|---|---|---|
data-key | 예 | 프로젝트 다국어 API 키 (프로젝트 설정의 l10n 값) |
data-from | 아니요 | 원문 언어. <html lang>이 있으면 그 값을 우선 사용 (기본 ko) |
data-to | 아니요 | 처음 선택할 번역 언어 (기본 en) |
초기화 흐름#
운영자 사용 순서#
- 로그인프로젝트 계정으로 로그인합니다. 로그인 정보는 24시간 동안 브라우저에 유지됩니다.
- 일괄등록현재 화면의 모든
data-l10n문구를 원문으로 한 번에 등록합니다. - 항목 편집번역할 문구의 마커를 눌러 언어별 번역문을 입력하고 저장합니다. 저장하면 화면에 바로 반영됩니다.
- 직접등록화면에 없는 문구(알림 메시지 등)는 원문과 번역문을 직접 입력해 등록합니다.
- 확인번역 언어를 선택하고 번역보기로 결과를 확인합니다. 원본보기를 누르면 원문으로 돌아갑니다.
에디터 기능#
| 기능 | 설명 |
|---|---|
| To (언어 선택) | 번역 대상 언어 선택 (영어, 일본어, 중국어 간체·번체) |
| 원본보기 / 번역보기 | <html lang>을 원문 언어 또는 선택한 언어로 변경 |
| 마커숨김 | 번역 대상 요소 위의 편집 마커 표시 전환 |
| 일괄등록 | 화면의 번역 대상 문구를 사전 원문으로 일괄 등록 |
| 직접등록 | 원문과 번역문을 직접 입력해 등록 |
페이지 스타일과의 충돌#
에디터는 <section id="coForward-l10n-editor">로 추가되고 내부는 Shadow DOM으로 분리됩니다. 페이지에 section 전체에 적용되는 스타일이 있으면 에디터 위치가 흐트러질 수 있으니 제외해 주세요.
section:not(#coForward-l10n-editor){
max-width:960px;
margin:28px auto;
padding:36px 40px;
}
서버 API#
모든 엔드포인트는 /api/l10n/ 아래에 있으며, JSON 본문을 POST로 보내고 JSON으로 응답합니다.
| 엔드포인트 | 요청 | 설명 |
|---|---|---|
validate_key.php | { key } | API 키 검증. { valid, projectIdx } 반환 |
dictionary.php | { projectIdx } | 프로젝트 사전 전체 반환 |
reg.php | 원문·번역 정보 | 번역 항목 등록 |
reg_headword.php | 원문 목록 | 원문(사전 키) 일괄 등록 |
reg_translation.php | 번역문 목록 | 번역문 일괄 등록 |
check_headword.php | 원문 | 원문 중복 확인 |
delete_headword.php | 항목 번호 | 원문과 연결된 번역 삭제 |
const res = await fetch('/api/l10n/validate_key.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'YOUR_API_KEY' })
});
const { valid, projectIdx } = await res.json();
문제 해결#
개발자 도구 콘솔과 Network 탭에서 validate_key.php, dictionary.php 응답을 먼저 확인하세요.
Unexpected token '<' … is not valid JSON#
API가 JSON 대신 HTML(오류 메시지)을 보냈다는 뜻입니다.
- 응답이
<hr />[CLASS_DB] … DB연결이 실패함으로 시작하면 DB 연결 설정을 확인합니다. - 응답이
<br /><b>Warning</b>로 시작하면 PHP 경고가 섞인 것입니다. 예: 같은 상수를 두 번define한 경우. - 로컬 개발 설정은 접속 주소의 호스트 이름(
HTTP_HOST)으로 판별합니다.localhost:8000처럼 포트가 붙으면 로컬로 인식되지 않을 수 있습니다.
Invalid API key for coForward_l10n_editor#
API는 정상 응답했지만 {"valid":false}인 경우입니다. 페이지의 data-key와 연결된 DB의 프로젝트 l10n 값이 정확히 같은지 확인하세요. 다른 DB를 바라보고 있어도 발생합니다.
콘솔에 "사전을 찾지 못함!!!"#
요소에 기록된 data-l10n-idx 번호를 현재 사전에서 찾지 못했다는 뜻입니다. 사전 로딩이 실패했거나, 사전이 갱신되어 번호가 바뀐 경우입니다. 페이지를 새로고침하거나 사전 로딩 오류부터 해결하세요.
속성값이 "false"로 표시됨#
사전에 없는 속성(placeholder, alt 등)이 있는 상태에서 언어를 여러 번 바꾸면 속성값이 false로 바뀔 수 있습니다. 해당 문구를 사전에 등록하면 나타나지 않습니다.
사전 로딩에 실패한 상태에서 언어를 전환하면 모든 속성에서 이 현상이 생길 수 있습니다. 먼저 사전이 정상적으로 로드되는지 확인하세요.
번역이 전혀 적용되지 않음#
- 요소에
data-l10n이 있는지 확인합니다. - 뷰어는
coForward_l10n_dictionary전역 변수가 초기화 스크립트보다 먼저 선언되어야 합니다. - 사전 키는 공백과 특수문자까지 원문과 정확히 같아야 합니다.
- 그룹 안의 요소는 해당 그룹 사전에 번역이 있어야 합니다.
JavaScript 레퍼런스#
번역 엔진을 직접 생성해 사용할 때 참고합니다.
import coForward_l10n from '/api/l10n/module/coForward_l10n.js';
const l10n = new coForward_l10n({
from: 'ko',
to: 'en',
markerName: 'l10n',
dictionary: coForward_l10n_dictionary
});
생성자 설정#
| 키 | 기본값 | 설명 |
|---|---|---|
from | 'ko' | 원문 언어 |
to | 'en' | 기본 번역 언어 |
markerName | 'l10n' | 마킹 속성 이름 (data-{markerName}) |
dictionary | {} | 사전 객체 또는 사전 URL |
메서드#
| 메서드 | 설명 |
|---|---|
setDictionary(dictionary) | 사전 설정. 객체면 바로 적용하고, 문자열이면 URL로 불러옴 |
setDictionaryFromUrl(url) | 서버에서 사전을 받아 적용한 뒤 현재 언어로 번역 |
translatePageByLang(lang) | 페이지의 모든 마킹 요소를 지정 언어로 번역 (현재 언어와 같으면 무시) |
translateElement(el, lang) | 요소 하나와 하위 요소를 번역 |
getTranslatedString(keyText, lang, group) | 사전 키의 번역 조회. { idx, translated } 반환 |
makeKeyText(el) | 요소의 사전 키, 하위 요소, 번역 대상 속성 추출 |
keyTextByIdx(idx, group) | 항목 번호로 사전 키 조회 |
setFormLang(lang) / setToLang(lang) | 원문 언어 / 번역 언어 변경 |
에디터 클래스#
coForward_l10n_editor는 번역 엔진을 확장하며 다음 설정을 추가로 받습니다. 보통은 coForward_l10n_editor_init.js가 자동으로 생성합니다.
| 키 | 설명 |
|---|---|
key | 프로젝트 API 키 (필수) |
url | API 서버 기준 URL (URL 객체, 필수) |
targetLangs | 번역 대상 언어 목록. 기본 ['en','ja','zh-CN','zh-TW'] |