Common Lispで多言語対応のためgettextを使う
はじめに
普段Wordpressで、poとかmoとかを見かけるものの、ちゃんと向き合ったことがなかったのでCommon Lispの練習がてら、gettextを使ってみることにしました。
環境
- Debian GNU/Linux 10.2
- ECL 16.1.3
- gettext 0.19.8
- cl-gettext
- poedit 2.2.1
今回の内容のソースコード
インストール
まずはaptで必要なツールをインストールしておきます。
$ sudo apt install gettext poedit
Poeditは、翻訳ファイルを作成するためのソフトウェアです。
プロジェクト作成
https://gitlab.com/hu.moonstone/deepspace を使って、ひとまずプロジェクトの雛形を作成します。
$ deepspace -t project -o create -n locale-sample
$ cd locale-sample
$ tree
.
├── build
├── doc
├── lib
├── locale-sample-test.asd
├── locale-sample.asd
├── resources
│ ├── data
│ ├── images
│ └── sounds
├── src
│ ├── c
│ └── lisp
│ ├── locale-sample.lisp
│ └── package.lisp
└── test
├── c
└── lisp
├── locale-sample-test.lisp
└── package.lisp
14 directories, 8 files
ロケール用のファイルを保存するため、
$ mkdir -p resources/locale
で、ディレクトリを先に作成しておきます。
Quicklispなどでcl-gettextを導入し、ASDファイルを次のように記述します。
(asdf:defsystem :locale-sample
:serial t
:pathname "src/lisp"
:components ((:file "package")
(:file "locale-sample"))
:depends-on (:asdf :alexandria :gettext))
その後、src/lisp/package.lisp
に以下の内容を記述します。
(in-package :cl-user)
(defpackage :locale-sample
(:use :cl :gettext)
(:export :main))
今回gettextを試すために書いたコードは以下のようなものになります。
(in-package :locale-sample)
(setup-gettext :locale-sample "locale-sample")
(preload-catalogs #.(asdf:system-relative-pathname :locale-sample "resources/locale/"))
(defun replace-all (string part replacement &key (test #'char=))
(with-output-to-string (out)
(loop with part-length = (length part)
for old-pos = 0 then (+ pos part-length)
for pos = (search part string
:start2 old-pos
:test test)
do (write-string string out
:start old-pos
:end (or pos (length string)))
when pos do (write-string replacement out)
while pos)))
(defparameter *labels* (make-hash-table))
(dolist (label `(,(_ "Dog")
,(_ "Cat")
,(_ "Penguin")))
(let ((key (alexandria:make-keyword (string-upcase (replace-all label " " "-")))))
(setf (gethash key *labels*) label)))
(defun main ()
(setf *current-locale* "ja")
(format t "-------------------------------~%")
(format t "~A~%" (_ (gethash :dog *labels*)))
(format t "~A~%" (_ (gethash :cat *labels*)))
(format t "~A~%" (_ (gethash :penguin *labels*))))
初期化とリソースの位置を指定
setup-gettext
により、gettextの初期化を行います。その上で、後ほど作成する翻訳ファイルの位置をresources/locale/
以下にあるという形で指定します。
(setup-gettext :locale-sample "locale-sample")
(preload-catalogs #.(asdf:system-relative-pathname :locale-sample "resources/locale/"))
テキストのリストを用意
Poeditはソースコード中の特定のテキストを抽出することができるので、まずは翻訳対象のテキストの一覧を作成します。ここでは、*labels*
というダイナミック変数にキーと値のペアで文字列を登録しておき、キーを呼び出すことで対応する文字列を取得できる形にしたいため、以下のような形で3つの単語をハッシュテーブルに登録します。
(defparameter *labels* (make-hash-table))
(dolist (label `(,(_ "Dog")
,(_ "Cat")
,(_ "Penguin")))
(let ((key (alexandria:make-keyword (string-upcase (replace-all label " " "-")))))
(setf (gethash key *labels*) label)))
キーの名前は自動で作るようにしています。文字列情報を全て大文字に変更し、スペースがある場合はハイフンで繋ぐようにしました。これで、Dog
は:dog
でアクセスでき、Iron Man
は、:iron-man
でアクセスできるようになります。
このようにしてPoeditのソースから抽出する機能を使うと、3つの単語が一覧に表示されるため、対応する日本語のテキストを入力し保存します。mo、poファイルは/resources/locale/ja/LC_MESSAGES
以下に、locale-sample.mo
、locale-sample.po
という名前で保存します。
ロケールの設定
以下の箇所で、ロケールを日本語に変更しています。
(setf *current-locale* "ja")
実行
;;; End of Pass 1.-------------------------------
犬
猫
ペンギン