|
낡은 문서
리서닷넷에서 사용하던 도쿠위키에 다국어 지원 (Native Language Support) 기능을 넣기 위해 2005년 말부터 2006년 초에 걸쳐서 작업했던 내용. (현재는 도쿠위키 자체가 더 나은 방식으로 NLS를 제공함.)
/이전 배포판: 1.0판도 참조
이 문서에서 소개하는 다국어지원 (NLS) 기능은 도쿠위키의 내장 기능이나 정식 플러그인이 아니라 2005-09-22e 버전을 제가 나름대로 수정하여 구현한 비공식 기능입니다. 어쩌면 도쿠위키의 이후 버전에서는 지금 이 문서에서 소개하는 기능과는 별개로 자체적인 다국어지원 기능이 공식적으로 내장되어 들어갈지도 모릅니다. 도쿠위키의 다국어지원 기능에 관한 더 많은 정보는 Multilingual sites with DokuWiki와 Browser Language Detection을 참조하시기 바랍니다.
마지막 마이너 버전 업그레이드: 2005-09-22e-1.1.0
Contents
사용 환경
이 문서에서 소개하는 기법은 다음과 같은 상황에서 사용할 수 있습니다.
웹 사이트의 도메인명과 호스트명이 고정되어있으며 임의로 추가하거나 바꿀 수 없습니다. 즉, 위키 백과사전처럼 언어에 따라서 en.wikipedia.org나 ko.wikipedia.org를 사용할 수 없는 경우입니다.
- 사용자 관리 등의 여러가지 편의를 위해 오직 한 개의 도쿠위키 설치본만을 운영하고 싶습니다. 즉, UI 언어나 문서 번역본 등에 따라 서로 다른 별도의 위키 설치본을 운영하고싶지는 않습니다.
문서 내용은 여러가지 임의의 언어로 번역될 수 있되, 모든 문서들이 같은 언어 집합으로 완전히 번역되지는 않을 예정입니다. 예를 들어, 어떤 문서 foo는 A, B, C 세 가지 언어로 번역되는 반면 다른 문서 bar는 B, C, D, E 네 가지 언어로 번역되고, 또다른 문서 baz는 오직 C 언어로만 제공될 수도 있습니다.
동작 방식 (방문자 입장)
이 문서에서 소개하는 기법은 다음과 같이 동작합니다.
- 방문자는 읽고자하는 문서에 대해 존재하는 번역본들 중에서 원하는 번역본을 자유롭게 선택해서 볼 수 있습니다. 만일 방문자가 명시적으로 특정 언어 번역본을 지정하지 않으면, 브라우저 언어 설정에 따라 자동적으로 적절한 번역본으로 redirect됩니다.
- 사용자 인터페이스 (UI) 언어는 현재 선택된 번역본에 상관 없이 무조건 각 방문자의 웹 브라우저 언어 설정에 따라 자동적으로 선택됩니다. (만일 브라우저에 설정된 언어 목록 중 아무것도 도쿠위키 설치본에 들어있지 않으면, 사이트 관리자가 지정한 기본값으로 선택됩니다.)
서로 다른 언어로 번역된 문서들은 서로 다른 URL을 가지며, form submit이 아닌 단순 링크에 의해 각 번역본들 사이를 넘나들 수 있습니다. 따라서, 존재하는 모든 번역본들이 검색 엔진에 전부 제대로 등록됩니다.1
선수 조건 (사이트 관리자, 사용자 입장)
이 문서에서 소개하는 기법을 사용하기 위해서는 다음과 같은 사항들을 먼저 만족시켜야만 합니다.2
- 도쿠위키 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 배열은 사이트 관리자가 적절히 설정하시면 됩니다):
1 <?php
2 /**
3 * National Language Support (NLS) script for DokuWiki
4 *
5 * @version 2005-09-22e-1.1.0
6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author CHA Reeseo <http://www.reeseo.net/>
8 *
9 * Usage:
10 * Include this script into 'doku.php',
11 * between the inclusions of 'inc/pageutils.php' and 'inc/html.php'
12 *
13 * CAUTION:
14 * This script demands a special policy that EVERY page should denote
15 * its own language (.xx or .xx-xx) at the end of its ID.
16 * For example, 'foo:bar.ko' is a page translated into Korean
17 * and 'foo:bar.en' is a page containing the same content in English.
18 *
19 * Quering any page having ID without explicit language notation,
20 * you will be redirected to its appropriate 'localized' page.
21 * ('Unlocalized' page will be invisible.)
22 *
23 * Please rename all the pre-existing pages before applying this script.
24 *
25 * To do:
26 * - Upgrade NLS_locale function
27 */
28
29
30 /* ---------------------------------------------------------------
31 * Configuration options: You can modify or add something to these */
32
33 $NLS_locarr['ko'] = 'ko_KR';
34 $NLS_locarr['en'] = 'en_US';
35
36 $NLS_langname['ko'] = '한국어';
37 $NLS_langname['en'] = 'English';
38
39 /* Configuration options end
40 * --------------------------------------------------------------- */
41
42
43
44 // Setting $ID
45 $ID = getID();
46
47 // Redirecting if no language is specified at the end of $ID
48 if (! NLS_pagelang($ID)) {
49 $target = NLS_page4browser($ID);
50 if (! array_key_exists("idx", $_GET) && $_GET['do'] != 'recent') // No redirection when indexing
51 header('Location: ' . wl($target));
52 }
53
54 // Resetting $conf['lang'] according to the language setting of user's browser
55 $conf['lang'] = NLS_UI4browser();
56 // Resetting $lang array
57 @require_once(DOKU_INC.'inc/lang/'.$conf['lang'].'/lang.php');
58 // Resetting locale
59 setlocale(LC_ALL, NLS_locale($conf['lang']));
60
61
62 /**
63 * Getting locale string
64 *
65 * FIXME: What a poor function this is!
66 */
67 function NLS_locale($ln = NULL) {
68 global $conf;
69 global $lang;
70 global $NLS_locarr;
71 if (! $ln) $ln = $conf['lang'];
72 $loc = array_key_exists($ln, $NLS_locarr) ? $NLS_locarr[$ln] : $ln;
73 $loc .= '.';
74 $loc .= array_key_exists('encoding', $lang) ? strtoupper($lang['encoding']) : 'UTF-8';
75 return $loc;
76 }
77
78
79 /**
80 * Printing links to other translations of the given page
81 * (API for template files such as DOKU_TPL/main.php
82 */
83 function NLS_transmenu($pid = NULL, $delimiter = ",\n", $withself = FALSE) {
84 global $NLS_langname;
85 if (! $pid) {
86 global $ID;
87 $pid = $ID;
88 }
89 $currplang = NLS_pagelang($pid);
90 $tpages = NLS_transpages($pid);
91 if ($currplang && ! $withself)
92 unset($tpages[$currplang]);
93 if ($tpages) {
94 $first = TRUE;
95 foreach ($tpages as $ln => $tid) {
96 $repr = array_key_exists($ln, $NLS_langname) ? $NLS_langname[$ln] : $ln;
97 if (! $first)
98 echo $delimiter;
99 $first = FALSE;
100 if ($currplang == $ln)
101 echo "<em>$repr</em>";
102 else
103 echo "<a href=\"".wl($tid)."\">$repr</a>";
104 }
105 } else {
106 echo "None.";
107 }
108 }
109
110
111
112 /**
113 * Selecting a redirection target (page) which best match the browser setting
114 *
115 * Default: page of $conf['lang'] (whether it exists or not)
116 * Choice : among existing translations, highest priority for the browser
117 */
118 function NLS_page4browser($pid = NULL) {
119 if (! $pid) {
120 global $ID;
121 $pid = $ID;
122 }
123 $blang = NLS_browserlang();
124 $existing_pages = NLS_transpages($pid);
125 $pid_base = NLS_ID_base($pid);
126 $tmp_page = $pid_base . '.' . $conf['lang']; // default page
127 foreach ($blang as $lang_str => $priority) {
128 $lang_str = str_replace("_", "-", strtolower($lang_str));
129 if (array_key_exists($lang_str, $existing_pages))
130 $tmp_page = $pid_base . '.' . $lang_str;
131 elseif (array_key_exists(substr($lang_str, 0, 2), $existing_pages))
132 $tmp_page = $pid_base . '.' . substr($lang_str, 0, 2);
133 }
134 return $tmp_page;
135 }
136
137
138 /**
139 * Selecting a language for UI
140 *
141 * Default: $conf['lang']
142 * Choice : among existing 'inc/lang/*', highest priority for the browser
143 */
144 function NLS_UI4browser() {
145 global $conf;
146 $tmp_lang = $conf['lang']; // This ($conf['lang']) is the default!
147 $blang = NLS_browserlang();
148 foreach ($blang as $lang_str => $priority) {
149 $lang_str = str_replace("_", "-", strtolower($lang_str));
150 if (is_dir(DOKU_INC . "inc/lang/" . $lang_str))
151 $tmp_lang = $lang_str;
152 elseif (is_dir(DOKU_INC . "inc/lang/" . substr($lang_str, 0, 2)))
153 $tmp_lang = substr($lang_str, 0, 2);
154 }
155 return $tmp_lang;
156 }
157
158
159 /**
160 * Getting a sorted (by priority) array of the languages
161 * from the language setting of the user's web browser
162 */
163 function NLS_browserlang() {
164 $acclang_arr = split(" *, *", trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
165 foreach ($acclang_arr as $acclang) {
166 if (ereg("^(.+) *;.+= *(.+)$", $acclang, $acclang_parts))
167 $acclang_sorted[$acclang_parts[1]] = (double)($acclang_parts[2]);
168 else
169 $acclang_sorted[$acclang] = 1.0;
170 }
171 asort($acclang_sorted, SORT_NUMERIC);
172 reset($acclang_sorted);
173 return $acclang_sorted;
174 }
175
176
177 /**
178 * Getting an associative array (lang => ID) of
179 * all the translated pages for the given page
180 */
181 function NLS_transpages($pid = NULL) {
182 global $conf;
183 if (! $pid) {
184 global $ID;
185 $pid = $ID;
186 }
187 $transpages = array();
188 $pid_base_path = $conf['datadir'] . '/' . str_replace(":", "/", NLS_ID_base($pid));
189 foreach(glob($pid_base_path . ".*.txt") as $fn) {
190 $aid = str_replace("/", ":", substr($fn, strlen($conf['datadir']) + 1, -4));
191 if ($alang = NLS_pagelang($aid)) {
192 $transpages[$alang] = $aid;
193 }
194 }
195 return $transpages;
196 }
197
198
199 /**
200 * Drop language notation (.xx or .xx-xx) from the given ID
201 */
202 function NLS_ID_base($pid = NULL) {
203 if (! $pid) {
204 global $ID;
205 $pid = $ID;
206 }
207 if (substr($pid, -3, 1) == '.')
208 return substr($pid, 0, -3);
209 elseif (substr($pid, -6, 1) == '.' && substr($pid, -3, 1) == '-')
210 return substr($pid, 0, -6);
211 else
212 return $pid;
213 }
214
215
216 /**
217 * Get language (xx, xx-xx, or NULL) from the given ID
218 */
219 function NLS_pagelang($pid = NULL) {
220 if (! $pid) {
221 global $ID;
222 $pid = $ID;
223 }
224 if (substr($pid, -3, 1) == '.')
225 return substr($pid, -2);
226 elseif (substr($pid, -6, 1) == '.' && substr($pid, -3, 1) == '-')
227 return substr($pid, -5);
228 else
229 return NULL;
230 }
231 ?>
doku.php 파일 수정
이제 doku.php 파일의 초반부에 inc/NLS.php 파일을 불러오는 부분을 추가하는데, 이 위치가 무척 중요합니다: NLS 스크립트는 가능한 한 일찍 로딩되는 것이 좋지만, inc/pageutils.php 파일에 들어있는 기능을 꼭 써야하므로 이보다는 뒤에 들어와야합니다. 따라서 아래 코드처럼 inc/pageutils.php 바로 아래에 추가하십시오 (이 코드는 중반부 이하가 생략되어있습니다):
1 <?php
2 /**
3 * DokuWiki mainscript
4 *
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
8
9 // xdebug_start_profiling();
10
11 if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__)).'/');
12 require_once(DOKU_INC.'inc/init.php');
13 require_once(DOKU_INC.'inc/common.php');
14 require_once(DOKU_INC.'inc/pageutils.php');
15 require_once(DOKU_INC.'inc/NLS.php'); // 바로 이 줄을 추가합니다!
16 require_once(DOKU_INC.'inc/html.php');
17 require_once(DOKU_INC.'inc/auth.php');
18 require_once(DOKU_INC.'inc/actions.php');
19
20 //import variables
21 ...
템플릿에 번역판 선택 메뉴 추가
마지막으로, 사용자가 어떤 문서를 읽던 도중에 그 문서의 다른 언어 번역판을 선택할 수 있도록 '번역판 선택 메뉴'를 추가하겠습니다. 현재 사용중인 템플릿의 main.php 파일 정도에 간단히 다음과 같은 부분을 추가하기만 하면 끝입니다:
1 Other translations of this page: <?php NLS_transmenu(); ?>
참고로, 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 대 버전의 궁극적인 목표입니다.
토론
이 사이트는 개인 웹사이트이기 때문에 방문객들이 문서를 만들거나 고치는 것을 허용하지 않습니다. 조언하실 내용이 있으시면 도쿠위키 사이트의 팁 페이지를 이용해주시기 바랍니다.