====== 도쿠위키 다국어지원 (NLS): 2005-09-22e-1.1.x ======
* 마지막 마이너 버전 업그레이드: 2005-09-22e-1.1.**0**
이 문서에서 소개하는 다국어지원 (NLS) 기능은 도쿠위키의 내장 기능이나 정식 플러그인이 아니라
2005-09-22e 버전을 제가 나름대로 수정하여 구현한 비공식 기능입니다.
어쩌면 도쿠위키의 이후 버전에서는 지금 이 문서에서 소개하는 기능과는 별개로
자체적인 다국어지원 기능이 공식적으로 내장되어 들어갈지도 모릅니다.
도쿠위키의 다국어지원 기능에 관한 더 많은 정보는
[[doku>wiki:multilingual_content|Multilingual sites with DokuWiki]]와 [[doku>wiki:tips:browserlanguagedetection|Browser Language Detection]]을 참조하시기 바랍니다.
===== 사용 환경 =====
이 문서에서 소개하는 기법은 다음과 같은 상황에서 사용할 수 있습니다.
* 웹 사이트의 도메인명과 호스트명이 고정되어있으며 임의로 추가하거나 바꿀 수 없습니다. 즉, [[http://www.wikipedia.org|위키 백과사전]]처럼 언어에 따라서 ''**en**.wikipedia.org''나 ''**ko**.wikipedia.org''를 사용할 수 없는 경우입니다.
* 사용자 관리 등의 여러가지 편의를 위해 오직 한 개의 도쿠위키 설치본만을 운영하고 싶습니다. 즉, UI 언어나 문서 번역본 등에 따라 서로 다른 별도의 위키 설치본을 운영하고싶지는 않습니다.
* 문서 내용은 여러가지 임의의 언어로 번역될 수 있되, 모든 문서들이 같은 언어 집합으로 완전히 번역되지는 않을 예정입니다. 예를 들어, 어떤 문서 ''foo''는 A, B, C 세 가지 언어로 번역되는 반면 다른 문서 ''bar''는 B, C, D, E 네 가지 언어로 번역되고, 또다른 문서 ''baz''는 오직 C 언어로만 제공될 수도 있습니다.
===== 동작 방식 (방문자 입장) =====
이 문서에서 소개하는 기법은 다음과 같이 동작합니다.
* 방문자는 읽고자하는 문서에 대해 존재하는 번역본들 중에서 원하는 번역본을 자유롭게 선택해서 볼 수 있습니다. 만일 방문자가 명시적으로 특정 언어 번역본을 지정하지 않으면, 브라우저 언어 설정에 따라 자동적으로 적절한 번역본으로 redirect됩니다.
* 사용자 인터페이스 (UI) 언어는 현재 선택된 번역본에 상관 없이 무조건 각 방문자의 웹 브라우저 언어 설정에 따라 자동적으로 선택됩니다. (만일 브라우저에 설정된 언어 목록 중 아무것도 도쿠위키 설치본에 들어있지 않으면, 사이트 관리자가 지정한 기본값으로 선택됩니다.)
* 서로 다른 언어로 번역된 문서들은 서로 다른 URL을 가지며, form submit이 아닌 단순 링크에 의해 각 번역본들 사이를 넘나들 수 있습니다. 따라서, 존재하는 모든 번역본들이 검색 엔진에 전부 제대로 등록됩니다.((단, 이는 **문서 내용에 대한 번역본들**에만 해당됩니다. UI 언어는 URL과 무관하며, 검색 엔진에는 크롤러의 언어 설정이나 사이트 관리자가 지정한 기본값만 등록될 것입니다.))
===== 선수 조건 (사이트 관리자, 사용자 입장) =====
이 문서에서 소개하는 기법을 사용하기 위해서는 다음과 같은 사항들을 먼저 만족시켜야만
합니다.((이 기법의 차기 버전(1.2.x 이상)에서는 이런 선수 조건들 중 일부가 없어지거나 바뀔지도 모릅니다.))
* 도쿠위키 2005-09-22e 버전이 설치되어있어야합니다. (다른 버전에서도 동작할지 모르지만, 테스트해보지는 않았습니다.)
* 이 기법을 적용하려는 위키 사이트에 등재된 모든 문서들의 ID 마지막 부분에는 그 문서가 어떤 언어로 번역된 것인지를 나타내는 번역지표가 들어있어야만 합니다. 이 기법이 적용된 이후로는 번역지표가 없는 문서를 더이상 읽지 못하게 될 것이며, 이 기법을 적용하기 전에 **미리 모든 문서들의 ID에 번역지표를 달아두셔야** 합니다.
참고로, 번역지표란 문서 ID의 마지막 부분에 추가되는 언어 코드이며
그 문서가 어떤 언어로 번역된 번역본인지를 나타냅니다.
도쿠위키가 기본적으로 규정하고있는 적법한 문서 (이름공간을 포함한) ID 형식을 //pageID//라고 하면,
번역지표가 포함된 문서 ID는 정규식으로
;#;//pageID//''\.[a-z][a-z](-[a-z][a-z])?'';#;
입니다.
예를 들어 ''foo:bar.ko''는 한국어 번역판이고 ''foo:bar.en''은 같은 내용의 문서에 대한 영어 번역판입니다.
''foo:bar''라는 ID에는 번역지표가 들어있지 않으며,
NLS가 적용된 이후 방문자가 이런 문서를 요청하면 브라우저 언어 설정에 따라 적절한 번역판으로 자동 이동됩니다.
가능한 한 존재하는 번역판들 중에서 가장 적절한 것을 선택하여 이동하지만,
만일 브라우저 언어 설정에 부합하는 번역판이 존재하지 않더라도 **무조건 (없는 번역판으로라도) 이동**됩니다.
즉, 방문자는 이제 ''foo:bar''라는 문서를 더이상 읽지 못하게된다는 뜻이며,
따라서 사이트 관리자는 NLS를 적용하기 전에 미리 모든 문서의 ID에 적절한 번역지표를 달아두어야 합니다.
기존 문서들의 ID에 번역지표를 붙이기 위해서 제가 사용했고 추천하는 방법은:
- 기존 문서 ''foo:bar''의 내용이 어떤 언어로 쓰여있는지 확인합니다. -> ''zz''라는 언어로 쓰여있다고 가정합니다.
- 새로운 문서 ''foo:bar.zz''를 만들고 ''foo:bar''의 내용을 그대로 복사합니다.
- 복사된 내용 중에서 내부링크(internal link)들을 찾아서 링크 ID들을 적절히 --- 번역지표가 붙은 형태로 --- 수정합니다.
- 기존 문서 ''foo:bar''를 삭제합니다.
매우 단순 무식한 방법이지만, 저는 도쿠위키에서 기존 문서의 ID를 깨끗하게 변경하는 더 좋은 방법을 알지 못합니다.
===== 알고리즘 (개발자 입장) =====
알고리즘은 무척 단순합니다.
- 사용자가 어떤 문서를 요청합니다.
- 사용자가 요청한 문서 ID에 번역지표가 없고 (예: ''foo:bar'') 인덱싱 중이 아니라면
- 사용자의 웹 브라우저에 설정된 언어들을 우선 순위에 따라 정렬하여 추출합니다.
- 이 중에서 어떤 언어(예: ''ko'')에 대한 번역판이 존재하면 그 번역판(''foo:bar.ko'')으로 자동 이동시킵니다.
- 브라우저가 원하는 언어 중 어떤 언어로도 번역되어있지 않으면, 사이트 관리자가 지정한 번역판(''foo:bar.{$conf['lang']}'')으로 (그러 번역판이 있든 없든 무조건) 자동 이동시킵니다.
- 사용자가 요청한 문서 ID에 번역지표가 있으면 그 문서를 보여줍니다.
- 브라우저 설정 언어와 도쿠위키 설치본의 ''DOKU_INC/inc/lang/*''를 비교하여 적절한 UI 언어를 자동 선정합니다. (매치되는 것이 없으면 역시 ''$conf['lang']''을 따릅니다.)
- ''$conf['lang']''을 위에서 선정한 UI 언어로 덧씌웁니다.
- 이에 따라 적절한 ''$lang'' 배열을 다시 읽어들입니다.
- locale을 재설정합니다.
- 추후 사이트 관리자가 template에 번역판 이동 메뉴를 표시할 수 있도록 API를 만들어둡니다.
이상입니다. 간단하죠? :-)
===== 코드와 적용 방법 =====
다시 한 번 당부드립니다만, 일단 기존의 모든 문서들이 그 ID에 적절한 번역지표를 갖도록 조치해두셔야합니다.
==== inc/NLS.php 파일 생성 ====
준비가 되셨으면, 아래 코드를 담고 있는 ''inc/NLS.php'' 파일을 생성합니다
(초반부의 ''$NLS_locarr'' 배열과 ''$NLS_langname'' 배열은 사이트 관리자가 적절히 설정하시면 됩니다):
*
* Usage:
* Include this script into 'doku.php',
* between the inclusions of 'inc/pageutils.php' and 'inc/html.php'
*
* CAUTION:
* This script demands a special policy that EVERY page should denote
* its own language (.xx or .xx-xx) at the end of its ID.
* For example, 'foo:bar.ko' is a page translated into Korean
* and 'foo:bar.en' is a page containing the same content in English.
*
* Quering any page having ID without explicit language notation,
* you will be redirected to its appropriate 'localized' page.
* ('Unlocalized' page will be invisible.)
*
* Please rename all the pre-existing pages before applying this script.
*
* To do:
* - Upgrade NLS_locale function
*/
/* ---------------------------------------------------------------
* Configuration options: You can modify or add something to these */
$NLS_locarr['ko'] = 'ko_KR';
$NLS_locarr['en'] = 'en_US';
$NLS_langname['ko'] = '한국어';
$NLS_langname['en'] = 'English';
/* Configuration options end
* --------------------------------------------------------------- */
// Setting $ID
$ID = getID();
// Redirecting if no language is specified at the end of $ID
if (! NLS_pagelang($ID)) {
$target = NLS_page4browser($ID);
if (! array_key_exists("idx", $_GET) && $_GET['do'] != 'recent') // No redirection when indexing
header('Location: ' . wl($target));
}
// Resetting $conf['lang'] according to the language setting of user's browser
$conf['lang'] = NLS_UI4browser();
// Resetting $lang array
@require_once(DOKU_INC.'inc/lang/'.$conf['lang'].'/lang.php');
// Resetting locale
setlocale(LC_ALL, NLS_locale($conf['lang']));
/**
* Getting locale string
*
* FIXME: What a poor function this is!
*/
function NLS_locale($ln = NULL) {
global $conf;
global $lang;
global $NLS_locarr;
if (! $ln) $ln = $conf['lang'];
$loc = array_key_exists($ln, $NLS_locarr) ? $NLS_locarr[$ln] : $ln;
$loc .= '.';
$loc .= array_key_exists('encoding', $lang) ? strtoupper($lang['encoding']) : 'UTF-8';
return $loc;
}
/**
* Printing links to other translations of the given page
* (API for template files such as DOKU_TPL/main.php
*/
function NLS_transmenu($pid = NULL, $delimiter = ",\n", $withself = FALSE) {
global $NLS_langname;
if (! $pid) {
global $ID;
$pid = $ID;
}
$currplang = NLS_pagelang($pid);
$tpages = NLS_transpages($pid);
if ($currplang && ! $withself)
unset($tpages[$currplang]);
if ($tpages) {
$first = TRUE;
foreach ($tpages as $ln => $tid) {
$repr = array_key_exists($ln, $NLS_langname) ? $NLS_langname[$ln] : $ln;
if (! $first)
echo $delimiter;
$first = FALSE;
if ($currplang == $ln)
echo "$repr";
else
echo "$repr";
}
} else {
echo "None.";
}
}
/**
* Selecting a redirection target (page) which best match the browser setting
*
* Default: page of $conf['lang'] (whether it exists or not)
* Choice : among existing translations, highest priority for the browser
*/
function NLS_page4browser($pid = NULL) {
if (! $pid) {
global $ID;
$pid = $ID;
}
$blang = NLS_browserlang();
$existing_pages = NLS_transpages($pid);
$pid_base = NLS_ID_base($pid);
$tmp_page = $pid_base . '.' . $conf['lang']; // default page
foreach ($blang as $lang_str => $priority) {
$lang_str = str_replace("_", "-", strtolower($lang_str));
if (array_key_exists($lang_str, $existing_pages))
$tmp_page = $pid_base . '.' . $lang_str;
elseif (array_key_exists(substr($lang_str, 0, 2), $existing_pages))
$tmp_page = $pid_base . '.' . substr($lang_str, 0, 2);
}
return $tmp_page;
}
/**
* Selecting a language for UI
*
* Default: $conf['lang']
* Choice : among existing 'inc/lang/*', highest priority for the browser
*/
function NLS_UI4browser() {
global $conf;
$tmp_lang = $conf['lang']; // This ($conf['lang']) is the default!
$blang = NLS_browserlang();
foreach ($blang as $lang_str => $priority) {
$lang_str = str_replace("_", "-", strtolower($lang_str));
if (is_dir(DOKU_INC . "inc/lang/" . $lang_str))
$tmp_lang = $lang_str;
elseif (is_dir(DOKU_INC . "inc/lang/" . substr($lang_str, 0, 2)))
$tmp_lang = substr($lang_str, 0, 2);
}
return $tmp_lang;
}
/**
* Getting a sorted (by priority) array of the languages
* from the language setting of the user's web browser
*/
function NLS_browserlang() {
$acclang_arr = split(" *, *", trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
foreach ($acclang_arr as $acclang) {
if (ereg("^(.+) *;.+= *(.+)$", $acclang, $acclang_parts))
$acclang_sorted[$acclang_parts[1]] = (double)($acclang_parts[2]);
else
$acclang_sorted[$acclang] = 1.0;
}
asort($acclang_sorted, SORT_NUMERIC);
reset($acclang_sorted);
return $acclang_sorted;
}
/**
* Getting an associative array (lang => ID) of
* all the translated pages for the given page
*/
function NLS_transpages($pid = NULL) {
global $conf;
if (! $pid) {
global $ID;
$pid = $ID;
}
$transpages = array();
$pid_base_path = $conf['datadir'] . '/' . str_replace(":", "/", NLS_ID_base($pid));
foreach(glob($pid_base_path . ".*.txt") as $fn) {
$aid = str_replace("/", ":", substr($fn, strlen($conf['datadir']) + 1, -4));
if ($alang = NLS_pagelang($aid)) {
$transpages[$alang] = $aid;
}
}
return $transpages;
}
/**
* Drop language notation (.xx or .xx-xx) from the given ID
*/
function NLS_ID_base($pid = NULL) {
if (! $pid) {
global $ID;
$pid = $ID;
}
if (substr($pid, -3, 1) == '.')
return substr($pid, 0, -3);
elseif (substr($pid, -6, 1) == '.' && substr($pid, -3, 1) == '-')
return substr($pid, 0, -6);
else
return $pid;
}
/**
* Get language (xx, xx-xx, or NULL) from the given ID
*/
function NLS_pagelang($pid = NULL) {
if (! $pid) {
global $ID;
$pid = $ID;
}
if (substr($pid, -3, 1) == '.')
return substr($pid, -2);
elseif (substr($pid, -6, 1) == '.' && substr($pid, -3, 1) == '-')
return substr($pid, -5);
else
return NULL;
}
?>
==== doku.php 파일 수정 ====
이제 ''doku.php'' 파일의 초반부에 ''inc/NLS.php'' 파일을 불러오는 부분을 추가하는데,
이 **위치가 무척 중요**합니다:
NLS 스크립트는 가능한 한 일찍 로딩되는 것이 좋지만,
''inc/pageutils.php'' 파일에 들어있는 기능을 꼭 써야하므로 이보다는 뒤에 들어와야합니다.
따라서 아래 코드처럼 ''inc/pageutils.php'' 바로 아래에 추가하십시오
(이 코드는 중반부 이하가 생략되어있습니다):
*/
// xdebug_start_profiling();
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__)).'/');
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC.'inc/common.php');
require_once(DOKU_INC.'inc/pageutils.php');
require_once(DOKU_INC.'inc/NLS.php'); // 바로 이 줄을 추가합니다!
require_once(DOKU_INC.'inc/html.php');
require_once(DOKU_INC.'inc/auth.php');
require_once(DOKU_INC.'inc/actions.php');
//import variables
...
==== 템플릿에 번역판 선택 메뉴 추가 ====
마지막으로, 사용자가 어떤 문서를 읽던 도중에 그 문서의 다른 언어 번역판을 선택할 수 있도록
'번역판 선택 메뉴'를 추가하겠습니다.
현재 사용중인 템플릿의 ''main.php'' 파일 정도에 간단히 다음과 같은 부분을
추가하기만 하면 끝입니다:
Other translations of this page:
참고로, ''NLS_transmenu()'' 함수는 템플릿에 번역판 선택 메뉴를 추가시켜주는 API로서
다음과 같은 매개변수들이 정의되어있습니다:
;#;''NLS_transmenu(//$pid//, //$delimiter//, //$withself//)'';#;
각각을 간단히 부연하면:
; 문자열 ''//$pid//''
= 이 변수가 지정하는 문서의 변역판들을 표시합니다. 기본값은 도쿠위키가 전역 변수로 설정한 ''$ID'' (즉, 현재 문서)입니다.
; 문자열 ''//$delimiter//''
= 여러가지 번역판들이 존재할 경우, 이 변수에 지정된 문자열로 각 번역판들을 구분짓습니다. 기본값은 ''%%",\n"%%''입니다.
; 논리값 ''//$withself//''
= 이 변수의 값이 ''True''면 나열되는 번역판 메뉴에 ''$pid'' 문서 자체의 언어도 함께 나타나고, ''False''면 ''$pid'' 문서 자체의 언어는 메뉴에 포함되지 않습니다. 기본값은 ''False''입니다.
===== 할 일 =====
* 코드에도 주석으로 쓰여있지만, ''NLS_locale()'' 함수의 처리 방식은 한마디로 "It sucks!" 입니다. :-( 전면적인 개선이 필요하며, 이것이 1.1.x 대 버전의 궁극적인 목표입니다.
===== 토론 =====
이 사이트는 개인 웹사이트이기 때문에 방문객들이 문서를 만들거나 고치는 것을 허용하지 않습니다.
조언하실 내용이 있으시면 [[doku>wiki:tips:nls#discussion|도쿠위키 사이트의 팁 페이지]]를 이용해주시기 바랍니다.