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 ユニット内の関数を用いて、プログラムの起動時に、正しいものをロードします。
Date, time and number format
Under Linux, BSD, Mac OS X there are several locales defining things like time and date format or the thousand separator. In order to initialize the RTL you need to iclude the clocale unit in the uses section of your program (lpr file).
Resourcestrings
例:
resourcestring
Caption1 = 'Some text';
HelloWorld1 = 'Hello World';
これらは通常の文字列定数と同様に、いかなる文字列にも代入することができます。例えば
Label1.Caption := HelloWorld1;
コンパイル時にfpc は unitname.rst を各ユニットに一つ生成します。その中にはリソース文字列のデータ(名前と中身)が含まれます。
.po ファイル
生成した .po ファイルを編集するためには、数多くのグラフィカルツールが無料で提供されています。実際 .po ファイルは .rst ファイル同様単なるテキストファイルですが、作者・文字コード・言語・日付といった要素を含んだヘッダのような付随的な要素を含んでいます。FPC をインストールすれば必ず rstconv というツールがついてきます (Windowsでは rstconv.exe)。このツールは .rst ファイルを .po ファイルに変換します。IDE を使うと、この変換操作を自動的に行うことができます。フリーなツールの例としては kbabel、po-auto-translator、poedit、virtaal があります。
Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.
直接 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 という名前のサブディレクトリを作ります。プロジェクトに追加する場合は Project > Project Options に、パッケージに追加する場合は Options > IDE integration に行きます。
When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file.
Removal of Obsolete entries
Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.
Duplicate entries
Duplicate entries occur when for some reason the same text is used for diferent resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:
dlfMouseSimpleGutterSect = 'Gutter';
dlgMouseOptNodeGutter = 'Gutter';
dlgGutter = 'Gutter';
dlgAddHiAttrGroupGutter = 'Gutter';
A converted .rst file for this resource strings would look similar to this in a .po file:
#: lazarusidestrconsts.dlfmousesimpleguttersect msgid "Gutter" msgstr "" #: lazarusidestrconsts.dlgaddhiattrgroupgutter msgid "Gutter" msgstr "" etc.
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:
#: lazarusidestrconsts.dlfmousesimpleguttersect msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect" msgid "Gutter" msgstr "" #: lazarusidestrconsts.dlgaddhiattrgroupgutter msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter" msgid "Gutter" msgstr "" etc.
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.
Fuzzy entries
Changes in resource strings affect translations, for example if initially a resource string was defined like:
dlgEdColor = 'Syntax highlight';
this would produce a .po entry similar to this
#: lazarusidestrconsts.dlgedcolor msgid "Syntax highlight" msgstr ""
which if translated to Spanish (this sample was taken from lazarus history), may result in
#: lazarusidestrconsts.dlgedcolor msgid "Syntax highlight" msgstr "Color"
Suppose then that at a later time, the resource string has been changed to
dlgEdColor = 'Colors';
the resulting .po entry may become
#: lazarusidestrconsts.dlgedcolor msgid "Colors" msgstr ""
Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. The automatic update tool notices this situation and produces an entry like this:
#: lazarusidestrconsts.dlgedcolor #, fuzzy #| msgid "Syntax highlight" msgctxt "lazarusidestrconsts.dlgedcolor" msgid "Colors" msgstr "Color"
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.
フォーム、データモジュール、フレームの翻訳
プロジェクトやパッケージの国際化(i18n、internationalisationの略)オプションが選択されていれば、IDE はユニットを保存する際、自動的に各フォームにつき一つの .lrt ファイルを生成します。例えば、unit1.pas を保存すると unit1.lrt ができます。ですから、最初にこのオプションを選択した後で、全てのフォームを一度は開き、少しいじって、保存してください。コンパイル時には、IDE が全ての .lrt ファイルに存在する全ての文字列を集めて国際化ディレクトリの中の単一の .po ファイルにまとめます(プロジェクト名.po ないしは パッケージ名.po)。
For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units
...
uses
...
LResources;
...
...
initialization
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL): The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.
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.
Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit
...
uses
...
DefaultTranslator;
which will automatically look in several standard places for a .po file (higher precedence) or .mo file (the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings).
If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):
- <Application Directory>/<LANG>/<Application Filename>.<ext>
- <Application Directory>/languages/<LANG>/<Application Filename>.<ext>
- <Application Directory>/locale/<LANG>/<Application Filename>.<ext>
- <Application Directory>/locale/LC_MESSAGES/<LANG/><Application Filename>.<ext>
under unix-like systems it will also look in
- /usr/share/locale/<LANG>/LC_MESSAGES/<Application Filename>.<ext>
as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "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);
// the following dialog now shows translated buttons:
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
end;
Compiling po files into the executable
If you don't want to install the .po files, but put all files of the application into the executable, use the following:
- Create a new unit (not a form!).
- Convert the .po file(s) to .lrs using tools/lazres:
./lazres unit1.lrs unit1.de.po
This will create an include file unit1.lrs beginning with
LazarusResources.Add('unit1.de','PO',[
...
- Add the code:
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}
- Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.
Cross-platform method to determine system language
The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.
uses
Classes, SysUtils {add additional units that may be needed by your code here}
{$IFDEF win32}
, Windows
{$ELSE}
, Unix
{$IFDEF LCLCarbon}
, MacOSAll
{$ENDIF}
{$ENDIF}
;
function GetOSLanguage: string;
{platform-independent method to read the language of the user interface}
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;
Translating the IDE
Files
The .po files of the IDE are in the lazarus source directory:
- lazarus/languages strings for the IDE
- lcl/languages/ strings for the LCL
- ideintf/languages/ strings for the IDE interface
Translators
- german translation is maintained by Joerg Braun.
- finnish translation is maintained by Seppo Suurtarla
- russian translation is maintained by Maxim Ganetsky
When you want to start a new translation, ask on the mailing if someone is already working on that.
Please read carefully: Translations