Table of Contents

도쿠위키 다국어지원 (NLS): 2005-09-22e-1.1.x

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

사용 환경

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

동작 방식 (방문자 입장)

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

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

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

참고로, 번역지표란 문서 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에 번역지표를 붙이기 위해서 제가 사용했고 추천하는 방법은:

  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 배열은 사이트 관리자가 적절히 설정하시면 됩니다):

<?php
/**
 * National Language Support (NLS) script for DokuWiki
 *
 * @version    2005-09-22e-1.1.0
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     CHA Reeseo <http://www.reeseo.net/>
 *
 * 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 "<em>$repr</em>";
                        else
                                echo "<a href=\"".wl($tid)."\">$repr</a>";
                }
        } 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 바로 아래에 추가하십시오 (이 코드는 중반부 이하가 생략되어있습니다):

<?php
/**
 * DokuWiki mainscript
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Andreas Gohr <andi@splitbrain.org>
 */
 
//  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: <?php NLS_transmenu(); ?>

참고로, NLS_transmenu() 함수는 템플릿에 번역판 선택 메뉴를 추가시켜주는 API로서 다음과 같은 매개변수들이 정의되어있습니다:

NLS_transmenu($pid, $delimiter, $withself)

각각을 간단히 부연하면:

문자열 $pid
이 변수가 지정하는 문서의 변역판들을 표시합니다. 기본값은 도쿠위키가 전역 변수로 설정한 $ID (즉, 현재 문서)입니다.
문자열 $delimiter
여러가지 번역판들이 존재할 경우, 이 변수에 지정된 문자열로 각 번역판들을 구분짓습니다. 기본값은 ",\n"입니다.
논리값 $withself
이 변수의 값이 True면 나열되는 번역판 메뉴에 $pid 문서 자체의 언어도 함께 나타나고, False$pid 문서 자체의 언어는 메뉴에 포함되지 않습니다. 기본값은 False입니다.

할 일

토론

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

1) 단, 이는 문서 내용에 대한 번역본들에만 해당됩니다. UI 언어는 URL과 무관하며, 검색 엔진에는 크롤러의 언어 설정이나 사이트 관리자가 지정한 기본값만 등록될 것입니다.
2) 이 기법의 차기 버전(1.2.x 이상)에서는 이런 선수 조건들 중 일부가 없어지거나 바뀔지도 모릅니다.