Translations / i18n / localizations for programs/ja
│
Deutsch (de) │
English (en) │
español (es) │
français (fr) │
日本語 (ja) │
한국어 (ko) │
polski (pl) │
português (pt) │
русский (ru) │
中文(中国大陆) (zh_CN) │
概要
このページでは、プログラムで用いる文字列を英語、中国語、ドイツ語、フィンランド語、イタリア語…といった各国語にあわせて変更することができるようにする方法を紹介します。基本的には、次のようになります。各々のキャプションに resourcestring (リソース文字列定義)を加え、コンパイルして .rst ファイルないしは .po ファイルを得ます(IDE が自動的に行います)。各国語用にそれぞれ一つの翻訳済み .po ファイルを作成し、LCL の translations ユニット内の関数を用いて、プログラムの起動時に、適切なものを読み込みます。
日付、時刻、数値の書式
Linux、BSD、Mac OS X では、時刻や日付、桁区切りなどに対して地域設定があります。右横書きに対応させるには、lpr ファイルあたりの uses 節に clocale ユニットを追加する必要があります。
リソース文字列
定義例:
resourcestring
Caption1 = 'Some text';
HelloWorld1 = 'Hello World';
リソース文字列は通常の文字列定数と同様に、いかなる文字列にも代入することができます。
使用例:
Label1.Caption := HelloWorld1;
コンパイル時に FPC は ユニット名.rst を各ユニットに一つ生成します。その中にはリソース文字列のデータ(名前と中身)が含まれます。
.po ファイル
生成した .po ファイルを編集するためには、数多くのグラフィカルツールが無料で提供されています。実際 .po ファイルは .rst ファイル同様単なるテキストファイルですが、作者・文字コード・言語・日付といった要素を含んだヘッダのような付随的な要素を含んでいます。FPC をインストールすれば必ず rstconv というツールがついてきます(Windows では rstconv.exe)。このツールは .rst ファイルを .po ファイルに変換します。IDE を使うと、この変換操作を自動的に行うことができます。フリーなグラフィカルツールの例としては kbabel、po-auto-translator、poedit、virtaal があります。
virtaal には翻訳メモリ機能があります。翻訳メモリとは、これまでに翻訳された文が原文と対になって集められているもので、さまざまなオープンソースのこれまでの翻訳用語を参考翻訳語として提案します。翻訳作業の軽減と翻訳語の統一に役立つでしょう。(訳注:程度の差はあれ、他のツールにも似たような機能はあります。)
直接 rstconv を用いる例:
rstconv -i unit1.rst -o unit1.po
翻訳
それぞれの言語用に .po ファイルをコピーして翻訳する必要があります。LCL の translation ユニットは標準言語コード (en=英語, de=ドイツ語, it=イタリア語, ...) を用いて言語を検索します。例えば、unit1.po のドイツ語版は unit1.de.po となります。つまり、unit1.po ファイルを unit1.de.po、unit1.it.po などなどサポートしようと思っている言語用の名前でコピーして、そのファイルを各国語の翻訳者が編集すればいいわけです。
.po ファイルを自動更新する IDE のオプション
- リソース文字列を含むユニットをパッケージないしプロジェクトに加えます。
- .po ファイルを出力するパスを指定します。これは隔離された単独のディレクトリにしてください。例えば、パッケージ/プロジェクトのディレクトリに language という名前のサブディレクトリを作ります。この指定をプロジェクトに追加する場合はプロジェクト→プロジェクトオプション、パッケージの場合はオプション→IDE 統合から行ってください。
このオプションが有効になっている場合、IDE は .rst および .lrt ファイルに含まれている情報を使用して原本となる .po ファイルを生成・更新します(別途 rstconv ツールを使う必要はありません)。これは自動更新処理として行われます。また、原本の .po ファイル、.rst と .lrt ファイルにすべての項目をまとめた後に、翻訳ファイル .xx.po に対して該当する以下の機能による更新が行われます。
使われなくなった項目の除去
.rst および .lrt で見つからなかった項目は、原本の .po ファイルから削除されます。その次に、原本の .po ファイルで見つからなかったすべての項目も、翻訳ファイル .xx.po から削除されます。こうすることで、使われなくなった項目で .po ファイルが乱されることはなくなり、これらの項目を無駄に翻訳しなくてすむようになります。
重複項目
いくつかの理由によって同じ文字列が異なったリソース文字列で使われている場合に、重複項目が生じます。例えば、ファイル lazarus/ide/lazarusidestrconst.pas に 'Gutter' という文字列があるとします。
dlfMouseSimpleGutterSect = 'Gutter';
dlgMouseOptNodeGutter = 'Gutter';
dlgGutter = 'Gutter';
dlgAddHiAttrGroupGutter = 'Gutter';
.rst ファイルを経て変換された .po ファイル上では、これらの文字列は同じように見えてしまいます。
#: lazarusidestrconsts.dlfmousesimpleguttersect msgid "Gutter" msgstr "" #: lazarusidestrconsts.dlgaddhiattrgroupgutter msgid "Gutter" msgstr "" ・・・
"#: " で始まる行は重複を考慮したコメントになっていますが、翻訳ツールでこの項目を翻訳しようとすると、繰り返し現れる msgid "Gutter" の行が重複項目と見られ、読み込みや保存時にエラーもしくは警告を発します。関連する複数の状況で使い分けの必要があるため、.po ファイル上における重複項目は少ないながらも存在するのです。これらが区別できるよう、msgctxt キーワードが重複項目へ追加されるようになっています("#: " で始まる文の次行)。この状況(コンテキスト)を示すキーワードも含めて項目を識別するものとして使われます。上にあった例では、以下のように生成されます。
#: lazarusidestrconsts.dlfmousesimpleguttersect msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect" msgid "Gutter" msgstr "" #: lazarusidestrconsts.dlgaddhiattrgroupgutter msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter" msgid "Gutter" msgstr "" ・・・
自動更新処理が行われる翻訳ファイル .xx.po に対して一つ確認してもらいたいことがあります。重複項目が既に翻訳されていた場合、新しく追加された項目がまるで自動的に翻訳されたかのように既存の翻訳を奪ってしまうことがあるのです。
重複の自動検出はまだ完全ではありません。項目が文字列リソースに追加されたのであれば、重複検出を行い、まずは未翻訳項目を洗い出さなければなりません。自動更新処理ですべての重複項目を翻訳対応させるには、更なる改良を必要としているのです。
未確定項目
リソース文字列が変更されると、翻訳されてものにも影響します。例えば、以下に示すようなリソース文字列が最初に定義されていたとします。 (訳注:以後の例は実際の Lazarus の変更履歴を基にしていますが、元の例は日本語ではあまり適切ではないので、別の例を取り上げています。)
lisKMToggleViewCallStack = 'Toggle view Call Stack';
これを基に作られる .po ファイルの該当項目は以下のようになります。
#: lazarusidestrconsts.liskmtoggleviewcallstack msgid "View Call Stack" msgstr ""
日本語の翻訳をすると以下のようになります。
#: lazarusidestrconsts.liskmtoggleviewcallstack msgid "Toggle view Call Stack" msgstr "コールスタックの表示の切り替え"
後日、このリソース文字列が以下のように変更されたとします。
lisKMToggleViewCallStack = 'View Call Stack';
この修正で .po ファイルの項目は以下のようになるでしょう。
#: lazarusidestrconsts.liskmtoggleviewcallstack msgid "View Call Stack" msgstr ""
lazarusidestrconsts.liskmtoggleviewcallstack を頼りに項目を識別して、原文の文字列は 'Toggle view Call Stack' から 'View Call Stack' へ変更されます。原文に対して既に翻訳が行われていると、元の訳は新しい意味に合わなくなります。先の例では、新たに 'コールスタックを表示' と訳すほうがより相応しいでしょう。自動更新処理ではこうした状況を、以下のように項目を生成して知らせるようにしています。
#: lazarusidestrconsts.liskmtoggleviewcallstack #, fuzzy #| msgid "Toggle view Call Stack" msgid "View Call Stack" msgstr "コールスタックの表示の切り替え"
.po ファイル書式の用語では、"#," で始まる行は項目のフラグ (fuzzy, 未確定) を意味していて、翻訳ツールは翻訳ユーザにそれが確認できるように作られています。このフラグは、現在の翻訳状態では不確かなものであるので翻訳者による詳細な再考が必要であることを意味しています。"#|" で始まる行は、この項目の以前の翻訳を示し、不確定フラグが付いた経緯のヒントを翻訳者に提供するものです。
フォーム、データモジュール、フレームの翻訳
プロジェクトやパッケージの国際化(i18n、internationalisation の略)オプションが選択されていれば、IDE はユニットを保存する際、自動的に各フォームにつき一つの .lrt ファイルを生成します。例えば、unit1.pas を保存すると unit1.lrt が作成されます。ですから、このオプションを選択した場合は、すべてのフォームを一度開き、少しいじって、保存しなおしてください。コンパイル時には、IDE が全 .lrt ファイルに対して存在するすべての文字列を集めて国際化ディレクトリの中の単一の .po ファイルにまとめます(プロジェクト名.po ないしは パッケージ名.po)。
実際のフォームの翻訳の適用は実行時に行われますので、いずれかのユニットの initialization 節で LRSTranslator(LResources に定義されています)を当てがう必要があります。
...
uses
...
LResources;
...
...
initialization
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
しかしながら、TPoTranslator クラスは LCL で利用できるもの(出来合いのクラス)ではありません。実装して使うものです(LCL の DefaultTranslator.pas に存在してはいますが)。以下のコードは Lazarus 0.9.29 以降を使っているのであれば必ずしも必要ではありません。uses 節に DefaultTranslator を追加するだけで済んでしまいます。
unit PoTranslator;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, LResources, typinfo, Translations;
type
{ TPoTranslator }
TPoTranslator=class(TAbstractTranslator)
private
FPOFile:TPOFile;
public
constructor Create(POFileName:string);
destructor Destroy;override;
procedure TranslateStringProperty(Sender:TObject;
const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
end;
implementation
{ TPoTranslator }
constructor TPoTranslator.Create(POFileName: string);
begin
inherited Create;
FPOFile:=TPOFile.Create(POFileName);
end;
destructor TPoTranslator.Destroy;
begin
FPOFile.Free;
inherited Destroy;
end;
procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
var
s: String;
begin
if not Assigned(FPOFile) then exit;
if not Assigned(PropInfo) then exit;
{DO we really need this?}
if Instance is TComponent then
if csDesigning in (Instance as TComponent).ComponentState then exit;
{End DO :)}
if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
s:=FPOFile.Translate(Content, Content);
if s<>'' then Content:=s;
end;
end.
あるいは、以下のように DefaultTranslator ユニットを組み込むだけでも構いません。msgfmt を使って .po ファイルを .mo ファイルへ変換したものでも使えます。
...
uses
...
DefaultTranslator;
DefaultTranslator は .po(こちらが優先)または .mo ファイルを標準の場所から自動的に探し出すものです。このユニットを使用した場合、環境変数 LANG(コマンドラインスイッチ --lang で上書き指定できます)を基に自動的に翻訳ファイルの検出を試みます。探索する場所は以下の通りです(LANG は翻訳表示させたい言語、拡張子は po か mo のいずれかです)。
- <アプリケーションディレクトリ>/<LANG>/<アプリケーションファイル名>.<拡張子>
- <アプリケーションディレクトリ>/languages/<LANG>/<アプリケーションファイル名>.<拡張子>
- <アプリケーションディレクトリ>/locale/<LANG>/<アプリケーションファイル名>.<拡張子>
- <アプリケーションディレクトリ>/locale/LC_MESSAGES/<LANG/><アプリケーションファイル名>.<拡張子>
Unix 系システムでは以下の場所も探索対象になります。
- /usr/share/locale/<LANG>/LC_MESSAGES/<アプリケーションファイル名>.<拡張子>
ディレクトリの LANG の部分は短縮表記もできます(例えば、環境変数 LANG が "es_ES" や "es_ES.UTF-8" で該当ディレクトリが存在しない場合、"es" でも探索を試みます)。
プログラム起動時の処理
すべての .po ファイルについて、それぞれに TranslateUnitResourceStrings を呼ばなければなりません。LCL の po ファイルは lclstrconsts です。以下のようにメインフォームの FormCreate などから呼び出します。
uses
..., gettext, translations;
procedure TForm1.FormCreate(Sender: TObject);
var
PODirectory, Lang, FallbackLang: String;
begin
PODirectory := '/path/to/lazarus/lcl/languages/';
GetLanguageIDs(Lang, FallbackLang);
Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
// ダイアログにあるボタンが翻訳されて表示されます。
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
end;
po ファイルを実行ファイルに取り込む
.po ファイルをインストールではなくアプリケーションの実行ファイルに取り込みたい場合、以下のようにします。
- 新しくユニットを作ります(フォームではありませんよ!)。
- 以下のように tools/lazres を使い、.po ファイルを .lrs ファイルへ変換します。
./lazres unit1.lrs unit1.de.po
これによって、以下の文で始まるファイル unit1.lrs が作成されます。
LazarusResources.Add('unit1.de','PO',[
...
- 以下のコードを追加します。
uses LResources, Translations;
resourcestring
MyCaption = 'Caption';
function TranslateUnitResourceStrings: boolean;
var
r: TLResource;
POFile: TPOFile;
begin
r:=LazarusResources.Find('unit1.de','PO');
POFile:=TPOFile.Create;
try
POFile.ReadPOText(r.Value);
Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
finally
POFile.Free;
end;
end;
initialization
{$I unit1.lrs}
- プログラムの最初で TranslateUnitResourceStrings を呼び出します。initialization 節で行っても構いません。
プラットフォームに依存しない OS で使われている言語の取得方法
以下の関数は、ユーザインターフェースで使われている言語名を取得するものです。Linux、Mac OS X、Windows に対応しています。
uses
Classes, SysUtils {必要なユニットを追加します}
{$IFDEF win32}
, Windows
{$ELSE}
, Unix
{$IFDEF LCLCarbon}
, MacOSAll
{$ENDIF}
{$ENDIF}
;
function GetOSLanguage: string;
{プラットフォームに依存しない UI で使われている言語名の読み取り}
var
l, fbl: string;
{$IFDEF LCLCarbon}
theLocaleRef: CFLocaleRef;
locale: CFStringRef;
buffer: StringPtr;
bufferSize: CFIndex;
encoding: CFStringEncoding;
success: boolean;
{$ENDIF}
begin
{$IFDEF LCLCarbon}
theLocaleRef := CFLocaleCopyCurrent;
locale := CFLocaleGetIdentifier(theLocaleRef);
encoding := 0;
bufferSize := 256;
buffer := new(StringPtr);
success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
if success then
l := string(buffer^)
else
l := '';
fbl := Copy(l, 1, 2);
dispose(buffer);
{$ELSE}
{$IFDEF LINUX}
fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
{$ELSE}
GetLanguageIDs(l, fbl);
{$ENDIF}
{$ENDIF}
Result := fbl;
end;
IDE の翻訳
ファイル
IDE の .po ファイルは以下の Lazarus ソースディレクトリにあります。
- lazarus/languages/ - IDE で使われている文字列
- lazarus/lcl/languages/ - LCL で使われている文字列
- lazarus/components/ideintf/languages/ - IDE インターフェースで使われている文字列
翻訳者
- ドイツ語の翻訳は Joerg Braun 氏によってメンテナンスされています。
- フィンランド語の翻訳は Seppo Suurtarla 氏によってメンテナンスされています。
- ロシア語の翻訳は Maxim Ganetsky 氏によってメンテナンスされています。
新たに翻訳を始める場合は、既に誰かが取り掛かっていないかメーリングリストで尋ねてください。
また、翻訳/国際化/地域化 をよくお読みになってください。
(以下訳注追記)
- FreeML - Lazarus/FreePascal 日本語メーリングリスト
- 翻訳ノート(日本語) - 日本語の翻訳ノート