試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonで作成したgettextのテストプログラムをC言語で書いてみる

参考として、「Pythonでgettextを使用してNLS(Native Language Support)によるメッセージの国際化を行う(作業の流れと動作確認・前半)」のテストプログラムをC言語で書いてみた。使用するメッセージカタログは同じもので、コンパイル後の配置場所もPythonのときと同じディレクトリ(work/bin)。
なお、標準のライブラリ(glibc)だけでは大変なので、GLib 2のライブラリを使用している。正規表現を使用しているため、バージョン2.14以上が必要。

コード

Pythonで書いたコードに対する比較である点に注意。後述しているが、不自然な書き方をしているところが幾つかある。
ファイル名: gettexttest.c

/*
gcc -Wall -Werror -Os gettexttest.c -o gettexttest $(pkg-config --cflags glib-2.0) $(pkg-config --libs glib-2.0)
 */

#include <libintl.h>
#include <locale.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib/gprintf.h>

/*
 _()などのマクロを有効にして
 ドメイン名のセットも行う(dgettext()などへの引数になる)
 */
#define GETTEXT_PACKAGE "gettexttest"
#include <glib/gi18n-lib.h>
/*
 GLibを使用しない場合
 #define _ gettext
 のように手動でマクロ定義を行う
 */
/*
#define _ gettext
 */

/*
 フォーマット文字列の後ろに改行を入れて表示する
 */
gint
print(const gchar *format, ...)
{
  /* 可変長引数を扱う(stdarg.hを使用) */
  va_list args;
  va_start(args, format);
  /* フォーマット文字列自体をフォーマット付けして改行を後ろに付ける */
  gchar *newfmt = g_strdup_printf("%s\n", format);
  /* 処理 */
  gint ret = g_vprintf(newfmt, args);  /* 戻り値 */
  /* g_strdup_printf()の確保したメモリを解放 */
  g_free(newfmt);

  return ret;
}

/*
 メイン処理
 */
int
main(gint argc, gchar **argv)
{
  /* ロケールの設定が必須 */
  setlocale(LC_ALL, "");

  /* 相対パスの場合は絶対パスにしてロケールディレクトリのパスを組み立てる */
  gchar *dn, *prefix, *localedir;
  GError *err = NULL;
  if (g_path_is_absolute(argv[0]))
    dn = g_path_get_dirname(argv[0]);
  else
  {
    /* GLib上で正規表現を扱うにはバージョン2.14以上が必要 */
    /* 相対パスの場合、正規表現を使用して先頭の「./」を消す */
    GRegex *patt1 = g_regex_new("^(./){1,}", G_REGEX_OPTIMIZE, 0, &err);
    /* 「/./」は「/」にする */
    GRegex *patt2 = g_regex_new("/./", G_REGEX_OPTIMIZE, 0, &err);
    gchar *tmp = g_regex_replace(patt1, argv[0], strlen(argv[0]), 0, "", 0, &err);
    gchar *exe = g_regex_replace(patt2, tmp, strlen(argv[0]), 0, "/", 0, &err);
    gchar *cwd = g_get_current_dir();
    /* g_build_pathは「[区切り文字], [要素...], NULL」 */
    gchar *__file__ = g_build_path("/", cwd, exe, NULL);  /* 絶対パス */
    dn = g_path_get_dirname(__file__);
    g_free(tmp);
    g_free(exe);
    g_free(cwd);
    g_free(__file__);
  }
  prefix = g_path_get_dirname(dn);
  localedir = g_build_path("/", prefix, "share", "locale", NULL);

  /* gettextを扱うための一連の処理 */
  bindtextdomain("gettexttest", localedir);
  textdomain("gettexttest");

  g_free(dn);
  g_free(prefix);
  g_free(localedir);

  /* 通常のメッセージ */
  /* TRANSLATORS: comment string */
  print(_("message"));

  /* フォーマット */
  gchar *str = "test";
  print(_("string: %s"), str);

  /*
  数の表現
  ngettext()は単数用パターンと複数用パターンを扱える
   */
  gint items = 1;  /* 単数扱い */
  print(ngettext("%d item", "%d items", items), items);
  items = 2;  /* 複数扱い */
  print(ngettext("%d item", "%d items", items), items);
  items = 0;  /* 複数扱い */
  /* dngettext()はドメイン指定ができる */
  print(dngettext("gettexttest", "%d item", "%d items", items), items);

  /*
   順番が逆転する例
   「%1$[フォーマット]」「%2$[フォーマット]」のように順番を示す
   */
  gchar *aaa = "A";
  gchar *bbb = "B";
  /*
   C言語では、メッセージカタログ側で
   msgid "%1$s by %2$s"
   msgstr "%2$s による %1$s"
   のように書いて順番を指定する
   */
  print(_("%1$s by %2$s"), aaa, bbb);

  print(_("newmsg"));

  return EXIT_SUCCESS;
}

実行ファイルの場所を相対パス指定されたときに絶対パスへ変換する処理が必要で、色々と余計な作業が増えてしまった。実際にはロケールディレクトリはビルドシステムがビルド前の設定時にインストール先をもとにマクロとして定義される形となることが多く、このコードのようにはしない。
また、前回のメッセージカタログとPythonのコードに合わせる関係上、printf()系関数で改行を付けるようにする関数を定義していたりもする*1が、実際にC言語向けに書くときには「\n」を含んだフォーマット文字列(例: 「printf(_("message\n"))」)でコードを書いてそれ(この例では「message\n」という翻訳可能文字列)をxgettextで拾って翻訳していけばよいため、このコードのように書く必要はない。

フォーマット付けの順番を入れ替えるときの書き方

フォーマット付けで順番を入れ替えるところでは、Pythonのときに独自の書き方をしていたのだが、C言語では「%1$s」「%2$s」といった書き方になる。この部分はja.po

msgid "%1$s by %2$s"
msgstr "%2$s による %1$s"

を書き加え(もしくはxgettextmsgmergeで上記項目を出してからmsgstrの部分を編集する)、msgfmtをすると

[work/po]$ ../bin/gettexttest
メッセージ
文字列: test
1 個の項目
2 個の項目
0 個の項目
B による A
新しいメッセージ

このように、メッセージが日本語で表示される。
「%1$s」は「%s」の変換*2を行う1番目のフォーマットを示す。上のja.poへの追加部分では「A by B」が元のメッセージなので、日本語にするときには数字を逆転させる。

関連記事:

参考URL:

使用したバージョン:

  • gettext 0.17
  • GLib 2.16.3(2.16.3-r1)

*1:Pythonのprint文はフォーマット変換をしても改行を付けて出力する

*2:printf()関数の変換指定子を参照