Translations / i18n / localizations for programs/zh CN

From Free Pascal wiki
Revision as of 12:40, 4 July 2015 by FTurtle (talk | contribs)
Jump to navigationJump to search

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上有几种语言环境定义的东西,例如时间和日期格式或千位分隔符。

In order to initialize the RTL you need to iclude the clocale unit in the uses section of your program (lpr file).

(为了初始化RTL你需要引用clocale单元在你的程序(lpr文件uses部分)里。)

Resourcestrings

示例

resourcestring
  Caption1 = 'Some text';
  HelloWorld1 = 'Hello World';

These are like normal string constants, that means you can assign them to any string. For example

(这些都是普通的字符串常量,这意味着你可以将它们任意分配。例如)

Label1.Caption := HelloWorld1;

当fpc编译它们时,会创建每一个单元的unitname.rst文件,包含resourcestring 数据(名称 + 内容)。

.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的翻译单元使用通用的语言代码(en=英文,de=德语,it=意大利语,...)来搜索。例如unit1.po的德语翻译将是unit1.de.po。为了实现这一目标,复制unit1.po文件为unit1.de.po,unit1.it.po,或任何一种语言支持,然后译员可以编辑自己持有的.po文件。

Light bulb  Note: 巴西语/葡萄牙语: Lazarus的IDE和LCL只有一个巴西葡萄牙语翻译,文件名扩展名是pt_BR.po

IDE选项自动更新.po文件

  • 包含字符串资源的单位必须添加到包或项目。
  • 你必须提供一个.po路径,这意味着是一个单独的目录。示例:创建 language目录在包/项目目录。 在工程菜单>工程选项中的国际化,设置PO输出目录

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.

(当你启用多语言支持选项时,IDE将使用.rst或.lrt文件中的信息生成或更新到.po文件。(rstconv工具不是必要的)。升级过程收收集所有找到的.po文件或.rst文件及.lrt文件并更新为xx.po文件。)

删除过时的条目

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.

那些存在于po文件而不存在于.rst和.lrt文件的条目会被删除。随后,在基本的.po文件中找到的,而在翻译后的.po文件未被找到的条目也将被移除。随后,不使用凌乱与过时的条目,翻译与不需要翻译的条目。太拗口了=_=b。

重复的条目

由于某些原因,相同的内容被用于不同的资源字符串,示例在 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 ""

等等

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.

模糊的条目

Changes in resource strings affect translations, for example if initially a resource string was defined like: 资源字符串的变化将影响翻译,例如,如果像最初定义的字符串一样:

dlgEdColor = 'Syntax highlight';

这将产生一个类似.po的条目

#: lazarusidestrconsts.dlgedcolor
msgid "Syntax highlight"
msgstr ""

如果翻译成西班牙语 (这个样本是从Lazarus历史中拿取的),可能导致

#: lazarusidestrconsts.dlgedcolor
msgid "Syntax highlight"
msgstr "Color"

然后假设,源字符串已经被更改为

  dlgEdColor = 'Colors';

由此产生的.po条目可能成为

#: lazarusidestrconsts.dlgedcolor
msgid "Colors"
msgstr ""

请注意,虽然ID保持不变, lazarusidestrconsts.dlgedcolor字符串已经从 'Syntax highlight' 变为 'Colors'了。作为翻译的字符串,旧的翻译可能与新的含义不同。事实上,对于新的字符串 'Colores'或许是更好的翻译。 自动更新工具注意到这种情况,并生成如下条目:

#: 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. 根据.po文件格式, "#,",前缀是指该项目有一个标志(模糊)和翻译程序可能会出现一种特殊的图形用户界面,以用于该项目的翻译用户。在这种情况下,该标志将意味着翻译在其当前状态是值得怀疑的,需要更仔细地审阅翻译。 "#|",前缀表示什么本条目前面的未翻译的字符串,并给出了翻译的提示,为什么条目被标记为模糊。

翻译窗体,数据模块和框架

When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form unit1.pas the IDE creates a unit1.lrt. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.

当启用了i18n选项(在工程菜单,工程选项,国际化中),IDE会为每个窗体创建.lrt文件。它创建.lrt文件保存在单元中。所以,如果你第一次启用,你需要打开每个窗体,移动并点击,这样它被修改了,你需要再保存窗体。例如,如果你保存了unit1.pas的窗体,IDE将创建一个unit1.lrt。在编译时IDE会收集所有字符串,.lrt和.rst文件,并存储为到一个.po文件(projectname.po 或 packagename.po) 在i18n(你设置的多语言目录中)目录。

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

在窗体运行时进行翻译,你需要在引用LResources单元,并在初始化部分赋值给一个翻译。

...
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.

然而没有TPoTranslator类在LCL中可用(即一个类使用翻译.po文件)。这是一个可能的实现(LCL中的DefaultTranslator.pas部分被取消了):</a>如果你使用Lazarus 0.9.29以下的,下面代码将不需要,你只需要引用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;
{我们真的需要这个吗?}
  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

或者,你可以使用msgfmt转换.po为.mo(如果你使用0.9.29将不需要),只需要引入DefaultTranslator单元。

...
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):

它会在特定地方查找.po文件(高优先级)或.mo文件。缺点是你必须保持两个左右.mo文件的DefaultTranslator的单元和.po文件的TranslateUnitResourceStrings。 如果你使用DefaultTranslator,它会尝试检测特定目录,LANG环境变量语言(可重写使用--lang命令行开关),查看这些地方的翻译(LANG代表所需的语言,扩展名可以是po或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>

在类unix系统,也将看

  • /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") 以及使用该语言的缩写(例如, "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

This will create an include file unit1.lrs beginning with

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(False);  //如果 Full=True将会崩溃 (问题 #0026021)
  try
    POFile.ReadPOText(r.Value);
    Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
  finally
    POFile.Free;
  end;
end;

initialization
  {$I unit1.lrs}
  • 在程序开始部分调用TranslateUnitResourceStrings 。你也可以在初始化部分,如果你喜欢。

遗憾的是这段代码将无法在拉撒路1.2.2及更早版本上编译。

对于这些Lazarus版本,你可以像这样使用:

type
  TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);

function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;
var
  LRes : TLResource;
  POFile : TPOFile = nil;
  SStream : TStringStream = nil;
begin
  Result := trResourceNotFound;
  LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
  if LRes <> nil then
  try
    SStream := TStringStream.Create(LRes.Value);
    POFile := TPoFile.Create(SStream, False);
    try
      if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess
      else Result := trTranslationError;
    except
      Result := trTranslationError;
    end;
  finally
    if Assigned(SStream) then SStream.Free;
    if Assigned(POFile) then POFile.Free;
  end;
end;

使用示例:

initialization
  {$I lclstrconsts.de.lrs}
  TranslateFromResource('lclstrconsts', 'de');
end.

跨平台的方法来确定系统语言

下面的函数返回一个字符串表示用户界面语言。它支持Linux,MAC OS X和Windows。

uses
  Classes, SysUtils {根据需要在这里添加额外使用的单元}
  {$IFDEF win32}
  , Windows
  {$ELSE}
  , Unix
    {$IFDEF LCLCarbon}
  , MacOSAll
    {$ENDIF}
  {$ENDIF}
  ;
function GetOSLanguage: string;
{独立于平台的方法来读取用户界面语言}
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;
Light bulb  Note: 似乎上面的代码在 Lazarus 1.2.2上调试不通过,额外引用 LCLProc单元,并将 GetLanguageIDs改为 LCLGetLanguageIDs就可以了。

翻译IDE

文件

IDE的.po文件都在Lazarus源目录:

  • lazarus/languages IDE字符串
  • lazarus/lcl/languages/ LCL字符串
  • lazarus/components/ideintf/languages/ IDE接口字符串

翻译

  • 德语翻译由 Joerg Braun 维护
  • 芬兰语翻译由 Seppo Suurtarla 维护
  • 俄语翻译由 Maxim Ganetsky 维护

When you want to start a new translation, ask on the mailing if someone is already working on that.

(当你开始一个新的翻译,可以发送询问邮件,或许有人已经开始着手做了。)

请仔细阅读:翻译/国际化/本地化

查看更多