계정: 로그인
AA 📝
다국어 지원

(!) English version of this document also exists.

낡은 문서

리서닷넷에서 사용하던 도쿠위키다국어 지원 (Native Language Support) 기능을 넣기 위해 2005년 말부터 2006년 초에 걸쳐서 작업했던 내용. (현재는 도쿠위키 자체가 더 나은 방식으로 NLS를 제공함.)

이 문서에서 소개하는 다국어지원 (NLS) 기능은 도쿠위키의 내장 기능이나 정식 플러그인이 아니라 2005-09-22e 버전을 제가 나름대로 수정하여 구현한 비공식 기능입니다. 어쩌면 도쿠위키의 이후 버전에서는 지금 이 문서에서 소개하는 기능과는 별개로 자체적인 다국어지원 기능이 공식적으로 내장되어 들어갈지도 모릅니다. 도쿠위키의 다국어지원 기능에 관한 더 많은 정보는 Multilingual sites with DokuWikiBrowser Language Detection을 참조하시기 바랍니다.

사용 환경

이 문서에서 소개하는 기법은 다음과 같은 상황에서 사용할 수 있습니다.

동작 방식 (방문자 입장)

이 문서에서 소개하는 기법은 다음과 같이 동작합니다.

선수 조건 (사이트 관리자, 사용자 입장)

이 문서에서 소개하는 기법을 사용하기 위해서는 다음과 같은 사항들을 먼저 만족시켜야만 합니다.2

참고로, 번역지표란 문서 ID의 마지막 부분에 추가되는 언어 코드이며 그 문서가 어떤 언어로 번역된 번역본인지를 나타냅니다. 도쿠위키가 기본적으로 규정하고있는 적법한 문서 (이름공간을 포함한) ID 형식을 pageID라고 하면, 번역지표가 포함된 문서 ID는 정규식으로

입니다. 예를 들어 foo:bar.ko는 한국어 번역판이고 foo:bar.en은 같은 내용의 문서에 대한 영어 번역판입니다.

foo:bar라는 ID에는 번역지표가 들어있지 않으며, NLS가 적용된 이후 방문자가 이런 문서를 요청하면 브라우저 언어 설정에 따라 적절한 번역판으로 자동 이동됩니다. 가능한 한 존재하는 번역판들 중에서 가장 적절한 것을 선택하여 이동하지만, 만일 브라우저 언어 설정에 부합하는 번역판이 존재하지 않더라도 무조건 (없는 번역판으로라도) 이동됩니다. 즉, 방문자는 이제 foo:bar라는 문서를 더이상 읽지 못하게된다는 뜻이며, 따라서 사이트 관리자는 NLS를 적용하기 전에 미리 모든 문서의 ID에 적절한 번역지표를 달아두어야 합니다.

기존 문서들의 ID에 번역지표를 붙이기 위해서 제가 사용했고 추천하는 방법은:

  1. 기존 문서 foo:bar의 내용이 어떤 언어로 쓰여있는지 확인합니다. (zz라는 언어로 쓰여있다고 가정합니다.)

  2. 새로운 문서 foo:bar.zz를 만들고 foo:bar의 내용을 그대로 복사합니다.

  3. 복사된 내용 중에서 내부링크(internal link)들을 찾아서 링크 ID들을 적절히 — 번역지표가 붙은 형태로 — 수정합니다.
  4. 기존 문서 foo:bar를 삭제합니다.

매우 단순 무식한 방법이지만, 저는 도쿠위키에서 기존 문서의 ID를 깨끗하게 변경하는 더 좋은 방법을 알지 못합니다.

알고리즘 (개발자 입장)

알고리즘은 무척 단순합니다.

  1. 사용자가 어떤 문서를 요청합니다.
  2. 사용자가 요청한 문서 ID에 번역지표가 없고 (예: foo:bar) 인덱싱 중이 아니라면

    1. 사용자의 웹 브라우저에 설정된 언어들을 우선 순위에 따라 정렬하여 추출합니다.
    2. 이 중에서 어떤 언어(예: ko)에 대한 번역판이 존재하면 그 번역판(foo:bar.ko)으로 자동 이동시킵니다.

    3. 브라우저가 원하는 언어 중 어떤 언어로도 번역되어있지 않으면, 사이트 관리자가 지정한 번역판(foo:bar.{$conf['lang']})으로 (그러 번역판이 있든 없든 무조건) 자동 이동시킵니다.

  3. 사용자가 요청한 문서 ID에 번역지표가 있으면 그 문서를 보여줍니다.
  4. 브라우저 설정 언어와 도쿠위키 설치본의 DOKU_INC/inc/lang/*를 비교하여 적절한 UI 언어를 자동 선정합니다. (매치되는 것이 없으면 역시 $conf['lang']을 따릅니다.)

  5. $conf['lang']을 위에서 선정한 UI 언어로 덧씌웁니다.

  6. 이에 따라 적절한 $lang 배열을 다시 읽어들입니다.

  7. locale을 재설정합니다.
  8. 추후 사이트 관리자가 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로서 다음과 같은 매개변수들이 정의되어있습니다:

각각을 간단히 부연하면:

할 일

토론

이 사이트는 개인 웹사이트이기 때문에 방문객들이 문서를 만들거나 고치는 것을 허용하지 않습니다. 조언하실 내용이 있으시면 도쿠위키 사이트의 팁 페이지를 이용해주시기 바랍니다.


  1. 단, 이는 문서 내용에 대한 번역본들에만 해당됩니다. UI 언어는 URL과 무관하며, 검색 엔진에는 크롤러의 언어 설정이나 사이트 관리자가 지정한 기본값만 등록될 것입니다. (1)

  2. 이 기법의 차기 버전(1.2.x 이상)에서는 이런 선수 조건들 중 일부가 없어지거나 바뀔지도 모릅니다. (2)