[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GNU gettext
에 들어 있는 현재와 같은 메세지 목록이 구현된 한 가지
목적은, 설치하는 사람이 시스템 메세지 목록을 사용하고 싶을 때 그렇게
하기 위함이었다. 그러므로 우리는 먼저 우리가 이미 알고 있는 몇 가지
방법들을 살펴봐야 할 것이다. POSIX 위원회의 사람들은 우리가 아래에서
설명할 거의 공식적인 표준의 한 가지에 대해 의견을 일치하지 못했다. 사실
그 사람들은 어떤 것에 대해서도 의견일 일치할 수 없었고, 그 어느 것도
인터페이스의 사용예를 포함하는 것조차 결정하지 못했다. 주요 유닉스
공급자들은 가장 중요한 두가지 스펙중 어느것을 사용하느냐에 따라 두개로
갈렸다: 한 가지는 X/Open의 catgets이고 또 하나는 Uniforums의 gettext
인터페이스이다. 우리는 두가지 모두를 설명하고, 나중에 이 딜레마에 대한
우리의 방법을 설명하겠다.
8.1 catgets 에 대하여 | ||
8.2 gettext 에 대하여 | ||
8.3 두가지 인터페이스의 비교 | ||
8.4 프로그램에 libintl.a 사용하기 | ||
8.5 gettext 고수가 되기 | ||
8.6 프로그래머 장을 위한 임시 메모 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
catgets
에 대하여catgets
는 X/Open Portability Guide, Volume 3, XSI Supplementary
Definitions, Chapter 5에 정의되어 있다. 하지만 몇몇 유닉스 공급자들은
이 표준을 그대로 따른다면 너무 느릴 것이라고 생각해서 이 표준의 최초
버전에 대해 유닉스 공급자들 자신들이 직접 구현했다. 물론 이 현상은
플랫폼 독립적인 프로그램을 작성할 때의 문제로 이어졌다: catgets
를
사용하더라도 유일한 인터페이스를 보장하지 못한다.
한 가지 여기에 대한 개인적인 의견으로, 오직 몇몇 위원회 멤버만이 이
인터페이스를 구현할 수 있었다. 그들은 정말로 이 인터페이스를 사용해서
프로그램하려고 하지 않았다. catgets
는 빠르고, 메모리를
절약하도록 구현되었고, 사용자는 catgets
에 만족할 수 있다. 하지만
프로그래머들은 catgets
를 증오한다 (최소한 나와 몇몇 다른 사람들은
그렇다…)
하지만 한 가지 잊어서는 안 된다: 그 사람들이 최소한 관계되어 있는
Unix(tm)에 대한 모든 권리가 (catgets
스펙을 낸 곳이기도 한)
X/Open으로 넘어가는 데 따른 문제이다. 이렇게 되면 이 인터페이스가
미래의 유닉스 표준이 될 수도 있고 (예를 들어 Spec1170) 모든 유닉스
구현물의 (구현물, 이 구현물은 Unix라는 이름을 씌우도록 허락된
OS들이다) 일부가 될 수 있다.
8.1.1 인터페이스 | ||
8.1.2 catgets 인터페이스의 문제?! |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
catgets
의 인터페이스는 파일 접근과 관련된 3개의 함수로 구성되어
있다: 사용할 목록을 여는 catopen
, 메세지 테이블을 접근하는
catgets
, 그리고 작업이 끝났을 경우에 쓰는 catclose
이다.
이 함수들에 대한 프로토타입과 필요한 정의문들은 <nl_types.h>
헤더
파일에 있다.
catopen
은 다음과 같이 쓰인다:
nl_catd catd = catopen ("catalog_name", 0); |
이 함수는 목록의 이름을 인자로 받는다. 이 이름은 보통 프로그램이나
패키지의 이름이다. 두번째 인자는 catgets
표준에서 지정되어 있지
않다. 나는 이 두번째 인자가 여러 가지 시스템들 사이에 동일하게 구현되어
있는지도 알지 못한다. 그러므로 보통 이 값으로 0
을 사용하라고
조언한다. 리턴값은 메세지 목록의 핸들로, open
이 리턴하는 파일의
핸들과 동일하다.
이 핸들은 물론 다음과 같이 catgets
함수에서 사용된다:
char *translation = catgets (catd, set_no, msg_id, "original string"); |
첫번째 인수는 목록 디스크립터이다. 두번째 인수는 이 목록에 들어 있는
메세지의 집합을 지정하는데, 이 목록 안에서 msg_id
에 설명된
메세지를 얻을 수 있다. 즉 catgets
는 다음과 같이 세 단계에 걸쳐
번역문을 찾는다:
목록 이름 ⇒ 메세지 집합 번호 ⇒ 메세지 고유번호 ⇒ 번역문 |
네 번째 인자는 번역문을 찾는데 쓰이지 않는다. 네번째 인자는 위의
단계중에 하나라도 실패했을 때 기본값으로 사용된다. 반드시 기억해야 할
중요한 점은 catgets의 리턴 타입이 char *
이지만 그 리턴된 문자열을
바꾸어서는 안 된다. 이 타입이 const char *
이면 좋겠지만,
불행히도 catgets
표준은 1988년에 ANSI C 표준보다 1년 전에
발표되었다.
이 3가지 함수중에 마지막 함수는 누구나 예상할 수 있는 대로 동작한다:
catclose (catd); |
이 함수를 사용한 뒤에는 이 디스크립터를 사용한 어떤 catgets
도
사용할 수 없다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
catgets
인터페이스의 문제?!우리가 문제가 있다고 말하려는 부분인 이 디스크립터는 정말로 쉬워보인다.
사실 이 인터페이스는 아무런 문제없이 사용될 수도 있지만, 메세지 목록을
만드는 작업이이 고통스럽다. 그 이유는 catgets
의 세번째 인자에
있다: 유일한 메세지 ID가 있어야 한다. 이 값은 어떤 한 가지 집합 내에서
모든 메세지들에 대해 유일한 숫자 값을 가져야 한다. 아마 소스 코드를
바꾸면서 이 리스트를 유지해야 할 때 문제점을 예상할 수 있을 것이다.
여기저기에서 새로운 메세지를 추가하고, 지우고 할 경우를 생각해 보자.
물론 이런 혼란을 없애는 많은 도구들이 개발되었지만, 어떤 도구는 추가하는
부분에서 제대로 동작하지 않고 또 어떤 도구는 지우는 부분에서 문제를
일으킨다. 우리는 또 다른 접근방법이 문제가 없다고 말하려는 것은
아니지만, 적어도 catgets
보다는 훨씬 더 사용하기 쉽다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
gettext
에 대하여gettext
인터페이스의 정의는 한 유니포럼 제안서에서 나와서 최소한
한개의 주요 유닉스 공급자(썬)가 최근의 개발품들에서 사용하고 있다.
그럼에도 불구하고 어떤 공식적인 표준으로 지정되지는 않았다.
이 방법의 중요한 점은 표준적인 파일 처리 방법(열기-사용하기-닫기)을 따르지 않고, 프로그래머가 그런 많은 작업, 특히 유일한 키 처리와 같은 작업을 하는 부담을 지우지 않는다. 물론 유일한 키는 필요하지만, 이 키는 그 메세지 자체이다(그 메세지의 길이가 길던 짧던 간에). 이 두가지 방법에 대한 보다 자세한 비교는 See section 두가지 인터페이스의 비교.
다음에서 이 인터페이스에 대한 보다 자세히 설명한다. 이 인터페이스는 GNU
gettext
라이브러리가 선택한 인터페이스이므로 자세히 설명해
놓았다. 이 라이브러리를 사용하고자 하는 프로그래머는 아래 설명에 관심을
가질 것이다.
8.2.1 인터페이스 | ||
8.2.2 애매함을 해결하기 | ||
8.2.3 메세지 목록 파일 찾기 | ||
8.2.4 *gettext 함수를 최적화하기 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
인터페이스에 필요한 최소한의 기능은 (가) 이 문자열이 어디서 나왔는지를 도메인을 선택 (모든 프로그램에 한개의 도메인을 쓰는 건, 만들고 관리하는 데 어렵기 때문에 좋지 않다. 아마도 이렇게 만들고 관리하기는 불가능할 것이다), 그리고 (나) 선택된 도메인 내의 문자열을 읽는 것이다.
여기에서는 gettext
인터페이스의 기초를 설명한다.
gettext
에는 사용할 도메인을 제한하는 한개의 전역 도메인이 있다.
물론 이 도메인은 따로 선택할 수 있다.
char *textdomain (const char *domain_name); |
textdomain
으로 LC_MESSAGES
범주의 현재 전역 도메인의 상태를
바꾸거나 상태를 알아볼 수 잇다. 인자는 null로 끝나는 문자열로, 파일
이름으로 사용할 수 있는 문자로 이루어져야 한다. 만약 domain_name
인자가 NULL
이면, 이 함수는 현재 값을 리턴한다. 어떤 값이
지정되어 있지 않으면, 기본 도메인의 이름이 리턴된다: 이 기본도메인의
값은 messages이다. textdomain
의 값은 char *
이지만
이 리턴 주소내의 값을 바꾸면 안 된다. 또, 그 값이 정말로 사용가능한지
검사하지 않는다는 걸 명심해야 한다. 만약 어떤 이름을 사용할 수 없는
경우에는, 메세지 번역은 일어나지 않을 걸로 사용할 수 없다는 걸 알 수
있다..
textdomain
으로 결정된 도메인을 사용하려면 다음을 사용한다.
char *gettext (const char *msgid); |
이 함수는 누구든 무슨 일을 하는지 알아 챌 수 있는 가장 간단한 함수이다.
msgid 문자열에 대한 번역이 현재 도메인에 있으면 그 번역문이
리턴된다. 번역문이 없으면 첫번째 인수값 자체가 리턴된다. 인자가
NULL
일 경우 그 결과는 정의되지 않았다.
한 가지 명심해야 할 점은 어떤 도메인을 사용할지 명확히 지정하지 않는다는
점이다. 현재 LC_MESSAGES
로케일에 대한 도메인 값이 사용된다.
만약 프로그램 내에 똑같은 gettext
호출 사이에 이 값이 달라진다면,
이 두번의 호출은 각각 다른 메세지 목록을 참조한다.
가장 쉬운 경우, 국제화된 패키지에서 보통 사용되는 방법은, 일단
teextdomain
을 실행한 다음 도메인을 유일한 이름으로 결정하는
것이다. 보통 그 도메인 이름은 패키지의 이름이 된다. 이렇게 하면 모든
문자열은 gettext 함수를 통과해서 번역된다. 이제, 이 패키지는 여러분의
모국어로 말하게 된다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
한 개의 도메인 이름은 대부분의 프로그램에서 잘 동작하지만, 한 개 이상의
도메인에서 번역문을 가져와야 하는 경우도 있다. 물론 textdomain
을
이용해 여러 개의 도메인 사이를 왔다갔다 할 수도 있지만, 쓰기에 편하지도
않고 빠르지도 않다. 가능한 상황 한 가지를 지금 생각할 수 있다: 모든
오류 메세지들은 error
라는 별도의 도메인에 넣는다. 이렇게 하면 이
오류메세지에 대한 번역문은 한 가지만 갖고 있으면 된다.
이러한 이유로 문자열을 가져오는 함수가 두 개가 더 있다.
char *dgettext (const char *domain_name, const char *msgid); char *dcgettext (const char *domain_name, const char *msgid, int category); |
이 두 함수 모두 다 새로운 첫번째 인자를 받는데, 이 인자는
textdomain
의 인자에 해당한다. dcgettext
의 세번째 인자는
LC_MESSAGES
이외의 로케일을 쓸 경우를 위한 것이다. 하지만 나는
이 dcgettext
가 정말로 쓸모가 있는지 의문이다.
domain_name이 NULL
이거나 category가 알려진 값 이외의
값을 가지면, 그 결과는 정의되어 있지 않다. 또 dcgettext
는
솔라리스에 있는 또 다른 gettext
에는 들어 있지 않다.
또 다른 애매함이 발생할 수 있는데, 한 개 이상의 도메인이 같은 이름을 가지는 경우이다. 이 경우는 필요한 메세지 목록 파일이 들어 있는 곳을 직접 지정해서 해결할 수 있다.
char *bindtextdomain (const char *domain_name, const char *dir_name); |
이 함수를 부르면 주어진 도메인을 지정된 디렉토리 내의 파일(이 파일이
정확히 무엇인지는 아래에 설명한다)을 사용하도록 한다. 특히 시스템의
기본 위치에 있는 파일이 지정된 파일과 다르면 쓰지 않는다
(textdomain
만 사용한 경우). dir_name으로 NULL
포인터를 넘기면 domain_name과 관계있는 파일이 리턴된다.
domain_name이 NULL
이면 아무 일도 일어나지 않고 NULL
포인터가 리턴된다. 다른 함수들과 마찬가지로 리턴값을 바꾸면 안 된다!
중요한 점 한 가지로, dir_name 인수로 상대 경로를 쓰면 문제가 발생할
수 있다. 이 경로는 현재 디렉토리에 대해 상대적으로 계산되기 때문에
프로그램이 chdir
명령을 쓰면 결과가 달라질 수 있다. 상대 경로는
이런 문제에 대한 의존성을 없애고, 동작하지 않을 가능성을 없애기 위해
절대 쓰지 말아야 한다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
여러 가지 패키지에 대해 여러 가지 언어로 된 번역문들이 저장되기 때문에,
메세지 목록 파일에 대한 정보를 추가할 방법이 있어야 한다. 유닉스
환경에서 보통 사용되는 파일 이름내에 저장하는 것이고, 여기서도 그렇게
한다. bindtextdomain
의 두번째 인자에 주어진 디렉토리(혹은 기본
디렉토리) 다음에 로케일의 이름과 도메인의 이름이 연결된다:
dir_name/locale/LC_category/domain_name.mo |
dir_name의 기본값은 시스템에 따라 다르다. GNU 라이브러리의 경우, GNU 관습에 다르는 패키지들의 경우, 이 디렉토리는:
/usr/local/share/locale |
locale은 위의 LC_category
의 로케일 이름이다.
gettext
와 dgettext
에서 이 로케일은 언제나
LC_MESSAGES
이다. dcgettext
는 세번째 인자로 이 로케일을
지정한다.(2) (3)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
여기서, GNU gettext
를 사용할때 생기는 좋은 점에 대해 얘기한다.
몇몇 독자들은 국제화된 프로그램에서 어떤 문자열이 반복문 안에서 번역될
경우, 속도가 느려질 수 있다고 지적할 수도 있다. 그 반복문 안에서
문자열이 경우에 따라 바뀔 때는 어쩔 수 없지만, 문자열이 언제나 같은
경우는 시간낭비일 뿐이다. 다음 예를 보자:
{ while (…) { puts (gettext ("Hello world")); } } |
여러번 실행되는 사이에 로케일이 바뀌지 않는 한, 번역된 문자열의 값도 항상 같다. 한 가지 방법은:
{ str = gettext ("Hello world"); while (…) { puts (str); } } |
하지만 이 방법은 모든 경우에 대해 사용할 수 없고 (예를 들어 로케일 선택이 달라질 경우) 읽기 좋지도 않다.
GNU C 컴파일러 버전 2.7이상에는 또 한 가지 해결 방법이 있다. 여기서 ‘intl/libgettext.h’ 파일의 일부를 소개한다. 표현식내의 명령어 블록( expression command block)에 대해서는 (gcc)Statement Exprs section ‘Statements and Declarations in Expressions’ in The GNU CC Manual.
# if defined __GNUC__ && __GNUC__ == 2 && __GNUC_MINOR__ >= 7 extern int _nl_msg_cat_cntr; # define dcgettext(domainname, msgid, category) \ (__extension__ \ ({ \ char *result; \ if (__builtin_constant_p (msgid)) \ { \ static char *__translation__; \ static int __catalog_counter__; \ if (! __translation__ \ || __catalog_counter__ != _nl_msg_cat_cntr) \ { \ __translation__ = \ dcgettext__ ((domainname), (msgid), (category)); \ __catalog_counter__ = _nl_msg_cat_cntr; \ } \ result = __translation__; \ } \ else \ result = dcgettext__ ((domainname), (msgid), (category)); \ result; \ })) # endif |
여기서 재미있는 부분은 __builtin_constant_p
조건문이다. 이는
컴파일시에 계산되고, 최적화는 즉시 이루어진다. 이 두 가지 경우는
구별된다: gettext
의 인자는 상수값이 아닌 경우에,
dcgettext
의 실체인 dcgettext__
를 사용한다.
문자열 인자가 상수인 경우, 로케일 선택이 변하지 않은 경우 한번
사용한 번역문을 재사용할 수 있다. 이 재사용이 여기서 말하려고 하는
것이다. _nl_msg_cat_cntr
변수는 ‘loadmsgcat.c’에 정의되어
있고 ‘libintl.a’에서 사용할 수 있으며 새로운 메세지 목록을 읽을
때마다 변한다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
계속할 얘기는 약간 팔이 안으로 굽는 얘기다. 위에서 말한 바와 같이
우리는 GNU gettext
를 유니포럼의 제안서에 맞게 구현했고 그럴만한
이유가 있었다. 하지만 어떻게 이런 결정을 내리게 되었는지 알려줘야 할
것이다.
맨 처음에 우리는 개발 과정에 대해 생각했다. 우리가 gettext
에
들어 있는 고유어 지원 기능을 사용해 응용 프로그램을 작성할 때, 보통
이렇게 한다. 일단 사용자에게 출력되는, 즉 번역되야 하는 문자열을 만나면
"…"
대신에 gettext("…")
를 사용한다. 우리는 각
소스 파일의 시작 부분에 (혹은 기본 헤더 파일에) 이렇게 정의한다.
#define gettext(String) (String) |
이 정의는 시스템이 C 라이브러리 내에 gettext
함수를 지원하면 쓰지
않을 수 있다. 일단 이 코드를 컴파일하면 결과는 NLS 코드가 사용되지 않은
것과 동일하다. GNU gettext
코드를 살펴보면
gettext("…")
대신에 _("…")
를 사용햇다는 것을
알 수 있다. 이렇게 하면 문자열을 번역하기 위해 더 써야 할 글자의 갯수를
3자로 줄일 수 있다.
이제 생산적인 프로그램이 필요하므로, 앞의 정의문을 이렇게 바꾼다
#define _(String) (String) |
혹은
#include <libintl.h> #define _(String) gettext (String) |
마지막으로, 번역할 수 있는 문자열이 들어 있는 모든 소스 코드에 대해 ‘xgettext’ 프로그램을 실행시키면 된다: 우리는 사용가능한 번역문들에 의존하지 않으면서 동작하는 프로그램이 있지만, 이 프로그램은 언제든지 사용가능한 번역문이 생기면 그 번역문을 사용할 수 있다.
같은 과정이 gettext_noop
에 사용된다 (see section 번역될 수 있는 문자열의 특별한 경우).
일단 gettext_noop
를 아무것도 하지 않는 매크로로 정의한 다음
나중에 ‘libintl.h’의 정의를 사용할 수 있다.
gettext_noop
라는 이름은 Sun이 만든 ‘libintl.h’에서는
사용되지 않으므로, 프로젝트에 다음 코드를 사용해야 좋을 것이다:
#ifdef gettext_noop # define N_(String) gettext_noop (String) #else # define N_(String) (String) #endif |
N_
은 _
와 비슷한 간략한 형태이다. GNU gettext ‘po/’
디렉토리의 ‘Makefile’은 기본적으로 이 간략한 형태들을 알고 있으므로
여기의 제안을 그대로 따르는 편이 더 쉬울 것이다.
catgets
를 생각해 보자. 가장 큰 문제는 프로그래머의 일이다.
언제든지 번역될 수 있는 문자열을 만나면 어떤 숫자(혹은 정의된 상수)를
지정해야 하고, 이 숫자를 메세지 목록 파일에도 지정해야 한다.
프로그래머는 중복해서 항목을 쓰지 않도록, 중복된 메세지 ID를 쓰지 않도록
하는 것까지 신경을 써야 한다. GNU gettext
프로그램과 같은 질을
가진 메세지 목록을 만드려면 그 문자열에 대한 설명과 소스 코드의 위치를
메세지 목록에 주석으로 넣어야 한다. 이런 일은 거의 불가능한
임무(Mission: Impossible)이다.
하지만 사람들은 어떤 점에서는 catgets
에 장점이 있다고도 말한다.
어떤 문자열 내의 단어가 있는데, 이 문자열이 여러 위치에서 사용되고, 그
단어가 경우에 따라 다르게 번역해야 경우이다. 예를 들어:
printf ("%s: %d", gettext ("number"), number_of_errors) printf ("you should see %d %s", number_count, number_count == 1 ? gettext ("number") : gettext ("numbers")) |
여기에서 우리는 "number"
라는 문자열에 대해 두번 번역해야 한다.
설령 영어 이외의 언어를 모른다고 해도 이 두 단어가 다른 의미를 가진다는
걸 알 수 있다. 독일어에서는 첫 번째는 "Anzahl"
(번호)로 번역되고,
두번째는 "Zahl"
(갯수)로 번역된다.
여기에서 위의 예는 정말 예외적인 경우라고 말할 수 있다. 그리고 실제로 그렇다! 이 문제에서 우리 역시 이러한 경우는 예외적이라고 느끼고 있고, 이 문제는 그렇게 중요하지 않다고 결정을 내렸다. 위의 문제에 대한 해결책은 매우 쉽다:
printf ("%s %d", gettext ("number:"), number_of_errors) printf (number_count == 1 ? gettext ("you should see %d number") : gettext ("you should see %d numbers"), number_count) |
우리는 이런 방법으로 모든 충돌을 피해갈 수 있다고 믿는다. 위 방법이 어렵다면 충돌이 생기는 문자열을 조금 다르게 바꿀 수도 있다. 어쨌든 이 문제에 대한 해결이 불가능하지는 않다.
번역자에게: 영어를 말하는 프로그래머들에게 어떤 명사의 복수형은 ‘s’를 하나 추가하는 것만으로는 만들 수 없다는 걸 알려줘야 좋다. 대부분 언어에 따라 여러 가지 방법을 쓴다. 위의 방법은 모든 언어에 대해 대처할 수 있는 일반적인 방법은 아니다. 다음은 Rafal Maszkowski(<rzm@mat.uni.torun.pl>)가 알려준 사실이다:
폴란드어에서, 예를 들면 우리는 plik(file, 파일)을 이렇게 쓴다:
1 plik 2,3,4 pliki 5-21 pliko'w 22-24 pliki 25-31 pliko'w등등 (o’는 okreska라는 8859-2에 있는 o 액센트문자로 aogonek과 비슷하다)
이 문제를 해결할 수 있는 방법은 POSIX.2 표준에 있는 LC_TIME
에
사용된 방법이다. alt_digit
필드의 값은 1부터 100까지의 숫자를
나타내는 100개의 문자열로 이루어져 있다. 국제화된 프로그램에서 이를
이용한다는 뜻은 배열이 번역될 수 있는 문자열의 해당 숫자를 인덱스로 해서
저장한다는 것이다. 작은 예를 들어:
void print_month_info (int month) { const char *month_pos[12] = { N_("first"), N_("second"), N_("third"), N_("fourth"), N_("fifth"), N_("sixth"), N_("seventh"), N_("eighth"), N_("ninth"), N_("tenth"), N_("eleventh"), N_("twelfth") }; printf (_("%s is the %s month\n"), nl_langinfo (MON_1 + month), _(month_pos[month])); } |
이러한 방법은 작은 범위의 숫자들에 대해서만 사용가능하다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
‘libintl.h’의 버전 0.9.4는 단독으로 사용할 수 있다. 즉, 별도의
함수를 쓰지 않고도 ‘libintl.h’를 사용할 수 있다. ‘Makefile’은
$(prefix)
를 사용해 선택된 디렉토리에 헤더 파일과 라이브러리를
설치한다.
한 가지 예외는 HP-UX 시스템이다. 여기에서는 C 라이브러리에 alloca
함수가 없다 (그리고 HP 컴파일러는 alloca
를 인라인하지 못한다).
하지만 이런 바보같은 시스템때문에 전체 라이브러리를 다시 작성하라는 건
아니다. 그 대신에 libintl.a
를 사용하는 모든 패키지에
alloca
함수를 포함한다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
gettext
고수가 되기GNU gettext
라이브러리의 기능을 완전히 활용하려면, 분명
소스코드를 읽는 것이 좋다. 하지만 (복잡하기도 한) 코드를 읽을 시간이
없는 사람들이 알아두면 좋을 만한 것은:
대화적인 프로그램의 경우 실행시에 사용할 언어를 선택하면 좋다. 어떻게
하는지 이해하려면 gettext
함수가 사용할 언어를 사용할 것인지
어떻게 판단하는지 알아야 한다. 여기 보여주는 방법들은 gettext
함수의 GNU 버전에서만 정확히 동작한다. 시스템 C 라이브러리의
catgets
함수나 gettext
와 함께 동작하도록 하는 방법은 없다.
물론 예외는 메세지처리를 위해 GNU gettext
라이브러리를 사용하는
GNU C 라이브러리이다.
dcgettext
함수를 쓸 때마다 가장 큰 우선순위를 갖는 환경 변수의
현재 값이 검사되고 사용된다. 그 우선순위는 다음 리스트에 우선순위가 큰
것부터 열거되어 있다.
LANGUAGE
LC_ALL
LC_xxx
, 선택된 로케일에 따라
LANG
여기서 찾은 값에 따라 경로를 만들고, 거기에 번역 파일이 있으면 그 파일을 읽는다.
이제 예를 들어 LANGUAGE
의 값이 바뀌었을 때 어떻게 되는지 생각해
보자. 위에 설명된 과정에 따라 dcgettext
함수를 쓰자 마자 변수가
새로운 값이 되었다는 걸 알아낸다. 하지만 이 경우 (아마도) 또다른 메세지
목록 파일이 이미 읽혀져 있다. 다른 말로 하면: 사용할 언어가 바뀌었다.
한 가지 방법이 있다. gcc-2.7.0 이상의 코드에서는 최적화 방법이
제공된다. 이 최적화는 새로운 목록이 읽혀지지 않으면 dcgettext
함수를 부르지 않도록 한다.. 하지만 dcgettext
를 부르지 않으면
프로그램은 LANGUAGE
변수가 변했다는 걸 알지도 못할 것이다
(see section *gettext 함수를 최적화하기). 해결책은 매우 쉽다. 다음 줄을 언어를
바꾸는 부분에 추가한다.
/* Change language. */ setenv ("LANGUAGE", "fr", 1); /* Make change known. */ { extern int _nl_msg_cat_cntr; ++_nl_msg_cat_cntr; } |
_nl_msg_cat_cntr
변수는 ‘loadmsgcat.c’ 파일에 정의되어
있다. 프로그래머는 오랫동안 실행되면서 실행시에 사용자가 사용할 언어를
선택하는 프로그램을 개발할 때만 위와 같은 방법을 쓸 것이다. 대화적이지
않은 (모든 자그마한 Unix 도구들처럼) 프로그램들은 위와 같은 방법이 전혀
필요없다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
8.6.1 임시 - 두개의 가능한 구현 | ||
8.6.2 임시 - catgets 에 대해 | ||
8.6.3 임시 - 왜 한 가지만 구현해야 하나 | ||
8.6.4 임시 - 메모 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
언어에 관계없는 메세지 처리를 위한 방법으로 두가지가 경쟁하고 있다:
X/Open의 catgets
와, Uniforum의 gettext
이다.
catgets
방법은 메세지를 숫자로 인덱스한다; gettext
방법은
그 메세지의 영어 문자열로 인덱스한다. catgets
방법은 더
오래되었고 더 많은 유닉스 벤더들이 지원한다. gettext
방법은 썬이
지원하고, COSE multi-vendor initiative가 지원한다는 말을 들은 적이 있다.
이 두가지 방법은 모두 POSIX 표준이 아니다; POSIX.1 위원회는 이 분야에
대해 어떠한 합의도 보지 못했다.
아무것도 표준이 아니다. POSIX.1 위원회는 gettext
를 사용할 지
catgets
(XPG)를 사용할지에 대해 의견이 엇갈렸다. 결국 위원회는
아무 결론을 내리지 못했고 메세지 시스템에 관해서는 표준의 일부가 되지
못했다. 나는 POSIX 표준에 XPG3 메세지 인터페이스가 “…이미 구현된
메세지 시스템의 한 가지 예로서…” 포함될 것이라고 믿는다.
POSIX 위원회는 아주 조심스럽기 때문에, 어떤 다른 인터페이스를 사용하지 말고 어떤 특정 인터페이스를 사용하라고 말하지는 않을 것이다. 이 주제에 관해서는 Programming for Internationalized FAQ를 참조하기 바란다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
catgets
에 대해catgets
를 기반으로 사용하는 문제에 관해 뒤늦게 몇 가지 토의가
있었다. 나는 이 토론에 대한 두가지 반응을 알리고 악마가 조금 더
좋아하는 것을 선택할 것이다 (역자 주: play devil’s advocate for a little
bit).
나는 catgets
가 조금 더 잘 설계되야 한다는 점에는 부인하지
않는다. catgets
는 이미 지적되어 온 바와 같이 너무 제한이 많다.
하지만, 일관성과 표준에 대해 얘기할 필요가 있다. 유닉스 소프트웨어를 만들 때 흔히 발생하는 문제는 유닉스 플랫폼들 사이의 포팅 가능성 문제이다. 모든 유닉스 벤더는 자기 운영체제를 보고 향상시킬 수 있는 부분을 찾는 것처럼 보인다. 의심할 나위 없이 이런 수정은 기술 혁신을 위해 필요하고, 문제를 해결한다. 하지만, 소프트웨어 개발자는 수많은 플랫폼들 사이에 이런 변화에 대응하는 데 너무 많은 시간을 소모한다.
그리고 이러한 문제는 유닉스 벤더들이 그들 시스템에 대한 표준화를 시작하도록 만들었다. 이 문제가 Spec1170가 나오도록 자극한 것이다. 모든 주요 유닉스 벤더는 이 표준을 지원하는 데 참가했고, 모든 유닉스 소프트웨어 개발자는 기뻐하면서 이 표준을 따르는 소프트웨어를 작성하고 그냥 각 플랫폼에 따라 다시 컴파일하면 되는 (autoconf를 사용할 필요없이) 날을 기다렸다.
내가 아는 바로는, Spec1170은 대략 X/Open Portability Guidelines 버전
4(XPG4)에 기초하고 있다. catgets
와 관계된 것들이 XPG4에 정의되어
있기 때문에, catgets
가 Spec1170의 일부가 되고, 모든 Unix 시스템의
표준 컴포넌트가 될 것이라고 믿게 되었다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
메세지 목록을 사용하기 위해 두가지 종류의 시스템을 설치하는 건 낭비로
보일 수도 있다. 우리가 catgets
의 약점에 대해 비판한다면 완전히
새로운 시스템을 만드는 것보다는 catgets
를 (호환성이 있도록)
확장하는 편이 좋지 않는가. 또 다른 한편으로는 한개의 운영체제에 두가지
메세지 목록이 서치돈 경우를 만날 수 있다 - 한개는 GNU gettext
를
국제화 도구로 사용하는 패키지에서 쓰는 메세지, 또 하나는 그 외의
소프트웨어들이 (catgets) 사용하는 메세지. 쓸데없이 부풀어 있는 걸까?
다른 메세지 목록 접근 시스템을 구현한다고 가정해 보자. 어떤 시스템을
추천해야 할까? 최소한 리눅스에서는, 가능한 한 많은 소프트웨어 개발자를
끌어들어야 한다. 즉 우리는 가능한 한 그들의 소프트웨어가 쉽게 포팅되도록
해야 한다. 그리고 그것은 catgets
에 대한 지원을 뜻한다. 우리는
glocale
코드를 우리의 libc
내에 구현했지만, 또다른 메세지
접근 방법도 우리 libc
내에 포함해야 한다는 뜻인가? 그리고
glocale
+ catgets
가 아닌 방법을 쓰려고 하는 사람들의
경우는 어떠한가. 그 사람들이 소프트웨어를 또다른 플랫폼으로 포팅할
경우, front-end (glocale
) 코드와 back-end (catgets
가 아닌)
코드를 모두 포함해야 한다.
하지만 메세지 목록 지원은 빙산의 일각에 불과하다. 여러 가지 로케일
범주들에 대한 데이타의 경우는 어더한가. 이 로케일 데이타도 많은 약점이
있다. 우리는 이것도 버리고 똑같은 목적의 또다른 라이브러리를 만들어야
하는가 (glocale
을 메세지 목록 지원 이상으로 확장해야 하는가)?
지금까지 발전되어 온 많은 유닉스의 일부와 같이, 우리는 과거의 호환성을 지키면서 미래의 혁신을 위한 쓸만한 기능향상 사이를 잘 조화해야 할 난처한 입장에 놓여 있다.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
X/Open은 아주 늦게 표준화를 통과시켰기 때문에, 많은 구현물의 최종 형태를 보면 저마다 다르다. 내가 가진 두개의 시스템(옛날 리눅스 catgets와 Ultrix-4)도 이상한 차이점이 있다.
좋다. 마지막으로 고친 것을 포함시켜서 GNU/리눅스 libc
gettext
함수를 만드는 데 시간을 쏟아야 한다. 이제 미래에는
gettext
를 가진 시스템은 솔라리스만이 아니다.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Autobuild on February 7, 2019 using texi2html 1.82.