====== 도쿠위키 다국어지원 (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|도쿠위키 사이트의 팁 페이지]]를 이용해주시기 바랍니다.