简析Etoys的多语言支持 之 翻译

Etoys使用gettext的方式处理多语言翻译。当然,由于GNU gettext并没有提供Smalltalk接口,Etoys只是在翻译文件的格式上与GNU gettext保持兼容,而像翻译的接口、字符串的摘取以及翻译文件的读取都是在Etoys中重新实现的。以下就分别予以介绍。

domain

在10年8月份,Etoys将以前的单个po文件拆分为多个文件。是以Package为分类依据的,Package的名称也就是TextDomain的名称。调用PackageOrganizer default packageNames可以获得所有包的名称列表。

消息接口

在GNU gettext中,一般是由gettext()及getttext_noop()处理需要翻译的字符串,通常会定义宏_及N_以方便书写。而在Etoys中也同样有这样两个消息,分别是String>>translated及String>>translatedNoop。功能上是相似的,translated将字符串翻译为LocaleID current所指明的语言。而translatedNoop不直接返回翻译后的文本,它只是一个标记,提示Etoys在摘取字符串时需要摘取这个字符串。真正的翻译动作是在随后使用这个字符串时调用translated来实现,translatedNoop可以延迟翻译,这在处理运行于语言选择之前的代码时需要用到。

translated主要是通过NaturalLanguageTranslator>>translate:toLocaleID:inDomain:消息实现对字符串的翻译。NaturalLanguageTranslator是各类语言翻译的集合,在该消息被调用时,它会根据localeID取得当前语言的一个GetTextTranslator实例,并调用它的translate:inDomain:消息。translate:inDomain:会利用moFileForDomain:查找相应的mo文件,再利用translationFor:在对应的mo文件内容中查找翻译。这里的“mo文件”已经是从文件转化为Etoys内部的结构,具体过程在后面介绍。

字符串的摘取

从上面的说明中可以看到,String>>translated和String>>translatedNoop除了作为翻译的消息调用接口外,还担负着标记的功能,用于生成翻译文件的模板,即pot文件。pot文件的生成主要在GetTextExporter中实现。调用GetTextExporter exportAll会生成多个pot文件,文件可以在Windows用户目录中的etoys里找到。查找所有字符串的过程在TranslatedReceiverFinder中实现,主要是通过SystemNavigation default allCallsOn:找到所有调用过translated和translatedNoop的消息。

读取翻译文件

Etoys在选择了语言后读取相应的mo文件。用户在选择语言之后,通过层层调用,到NaturalLanguageTranslator class>>localeChanged,进而调用了GetTextTranslator>>loadMOFiles。具体的mo文件的解析在MOFile class中实现。

发表在 Uncategorized | 发表评论

简析Etoys的多语言支持 之 操作系统交互

由于Etoys是跑在虚拟机上的,所以有些数据需要从操作系统中获取,比如键盘输入、剪贴板复制粘贴及文件系统访问等。下面就对这几方面中涉及的多语言问题作一说明。

键盘输入

这里的键盘输入是指当我们用输入法输入中文时,Etoys是如何处理的。

在Etoys中,不同的语言环境可以定义不同的键盘事件处理类。比如在选择中文之后,键盘的处理类可以通过SimplifiedChineseEnvironment class>>inputInterpreterClass获得。该消息会根据不同的操作系统环境来选择不同的键盘事件处理类。键盘事件处理类都继承自KeyboardInputInterpreter,一般会重载nextCharFrom: firstEvt:,像在Windows下,从evtBuf中获得的输入已经是中文Unicode编码,无需任何编码转化。

剪贴板

基于剪贴板的复制粘贴与键盘事件的处理类似,处理类通过SimplifiedChineseEnvironment class>>clipboardInterpreterClass获得。外理类继承自ClipboardInterpreter,一般需要重载fromSystemClipboard:及toSystemClipboard,分别表示从系统剪贴板复制及复制到系统剪贴板时需要进行的编码转换。

文件系统

关于文件系统的文件名编码处理,同样是在SimplifiedChineseEnvironment中定义,这回是fileNameConverterClass,它返回的是一个编码转化类,在Windows中,只需要返回UTF8TextConverter即可。

发表在 Uncategorized | 发表评论

简析Etoys的多语言支持 之 字体

在Etoys中,字体显示有多种方式,如StrikeFont,TTCFont,在Linux下还可以使用Pango进行字体渲染,而在Squeak中也可以通过FT2Plugin插件用libfreetype库进行字体渲染。以下主要是介绍StrikeFont及TTCFont这两种机制,以及在多语言支持上的细节。

显示接口

不管是StrikeFont,还是TTCFont,它们最终提供给其它对象使用的接口是统一的,这样其它对象不需要关心当前使用的到底是StrikeFont还是TTCFont。这个统一接口是由AbstractFont类定义的。

其中一个接口是显示字符串。比如(StringMorph contents: ‘ab’) openInWorld这样一个显示StringMorph的动作,其中字符绘制的操作是在StringMorph>>drawOn:中处理的,其中调用了FormCanvas class>>drawString:from:to:in:font:color:,进而调用了AbstractFont的displayString:on:from:to:at:kern:baselineY:。

这只是字体显示字符串的入口,而真正完成显示操作的,是BitBit>>copyBits消息,这里不对此作详细的描述,简单说来,就是displayString消息利用StrikeFont>>glyphInfoOf:into:或TTCFont>>glyphInfoOf:into:从字体中得到需要显示的字符的点阵,这个点阵数据,在StrikeFont中是直接存放在那的,而TTCFont则是通过计算得到的。displayString在得到字符点阵后,通过BitBit>>copyBits消息将点阵信息拷贝到显示的媒介中。

可以看出,在显示接口上,是与多语言无关的,所有与多语言相关的信息都被包装在glyphInfoOf:into:中。

FontSet

在了解了显示字符的基本接口和原理后,我们再来看看StrikeFontSet及TTCFontSet这两个类,从它们的名称可以看出,这里是字体集的意思,但要注意,它们与StrikeFont还有TTCFont一样都是继承自AbstractFont,也有displayString:on:from:to:at:kern:baselineY和glyphInfoOf:into:等消息,所以对于其它使用它们的对象来说,和一般的字体是没有区别的。这里的字体集的概念是指同一字体名下同一大小不同语言字体的一个集合。它的fontArray实例变量就是根据各种语言的leading char对字体进行分别存放。在处理glyphInfoOf:into:消息时,首先根据leading char选择相应语言的字体,再读取相应的字符点阵。

TextStyle

TextStyle才是对同一字体不同大小组合的抽象,这里列出这个类常用的一些消息:

TextStyle default	-- 系统默认的TextStyle
TextStyle defaultFont	-- 默认TextStyle的默认字体
TextStyle named: 'abc'	-- 返回名称用abc的TextStyle
fontNamesAndSizes	-- 一个TextStyle中的所有字体
fontOfSize:		-- 返回一个TextStyle中指定大小的字体

字体转化

1. StrikeFont

在之前一篇文章中对如何将BDF字体转为StrikeFont作了说明,其中也提到希望能够将文泉驿点阵宋体加入到Squeak中,其实在Etoys中Yoshiki Ohshima已经做了一方面的工作。代码在StrikeFontSet class>>createExternalFontFileForUnicodeSimplifiedChinese:中。有兴趣的可以看一下代码,里面涉及如何在StrikeFont中组织多个编码段的字体。

2. TTCFont

而TTCFont的转化则是通过TTCFontSet class>>newTextStyleFromTTFile:的形式来实现。完整的一个示例如下:

TTCFontReader encodingTag: SimplifiedChineseEnvironment leadingChar.
TTCFontSet newTextStyleFromTTFile: 'C:\Windows\Fonts\simhei.ttf'.
(TTCFont allInstances select: [ :a | a familyName = 'BitstreamVeraSans' ]) do: [:i | i setupDefaultFallbackFontTo: (TextStyle named: 'SimHei')].
发表在 Uncategorized | 发表评论

简析Etoys的多语言支持 之 字符及字符串

字符

在Etoys中,字符在Character类中实现。它只有一个实例变量value,用于存放字符的值及语言信息。

Etoys与其它编程语言的一点不同是,它在每一个字符中都留出了几个位用于表示该字符属于哪一种语言,也就是leading char,或者称encoding tag。这个值可以通过向字符发送leadingChar消息得到,而字符的真实值通过charCode取得。value的值可以通过asInteger返回。下面的例子是在中文环境下运行的。

$一 leadingChar	→ 6 (16r6 2r110)
$一 charCode	→ 19968 (16r4E00 2r100111000000000)
$一 asInteger	→ 25185792 (16r1804E00  2r1100000000100111000000000)

具体哪些值代表哪些语言可以参考Yoshiki Ohshima的The Design and Implementation of Multilingualized Squeak。在其中还可以了解到具体在value中leadingChar和charCode是如何组织的。从上面的例子中也可以看出,后22位表示字符值,再用8位表示语言。

对于在字符中加入leading char的意义,由于Yoshiki没有明确说明,只说是有三点原因,这里只能臆测一下:

1. 各语言文字显示的顺序可能不同。左到右,或者右到左。所以需要通过leading char来判定显示的顺序。
2. 用于从FontSet中读取字体。(这在介绍字体显示时会提及)
3. 与Squeak以前的代码兼容。在加入Unicode之前,Squeak中就使用了类似的encoding tag,那时是对于编码的标识。

需要说明的是,Etoys中现在内部字符都是Unicode的,虽然对不同语言有不同处理,但这只是标记语言,与编码无关。

另外在上面举的例子中,用的是中文字符,Etoys中对256个ASCII字符是有特殊处理,这在SBE中有提及,例子如下:

(Character value: 97) == $a → true
(Character value: 500) == (Character value: 500) → false

这是因为在使用Character的value:生成新的字符实例时,如果value值小于256,则Character会从CharacterTable中直接返回一个实例,而不是重新生成一个Character实例。这可以较大地减少在实例创建和存放时的时间及空间上的消耗,在比较字符时可能这一点需要引起注意。至于CharacterTable在什么时候初始化的,还没有找到相关的代码。

在涉及Character这类基本的数据时,必需考虙两方面:时间效率、空间效率。就像CharacterTable的引入,就是为了提升这两方面的效率。而另一方面,时间效率还取决于单字符的运算,空间效率则还取决于字符串的存储。关于字符串的存放,在下一节分析,先来看一下单字符的运算。

前面已经看到,Character类中只有一个实例变量value,而Smalltalk是动态类型的,所以其实value可以绑定任意类型。再则,Etoys中的整型类型是根据大小来自动定的。比如下面的例子:

1000000000 class →  SmallInteger
(1000000000 * 100) class → LargePositiveInteger

所以对于字符的类型表示的效率问题,就完全交由Character类中的各个消息来控制。在Etoys中,将Character的value限定在SmallInteger类型范围里使用,这样可以保证对单字符比较或运算的效率。

字符串

字符串主要是通过String, ByteString, WideString这三个类表示。ByteString是以8位存放的,而WideString则是32位。如果在ByteString中加入大于256的字符,ByteString会自动转为WideString。这是在ByteString>>at:put:中实现的。示例如下:

'abc' class			→ ByteString
'一二三' class			→ WideString
('abc' at: 1 put: $一) class	→ WideString
'abc' byteSize		→ 3
'一二三' byteSize	→ 12
'一bc' byteSize		→ 12

区分对待ByteString与WideString主要是出于存储空间上的考虑,按Yoshiki所说,在Squeak 3.2中,String实例占了1.5MB空间,但我在Etoys 4.1.1中,用String allSubinstances统计,好像没有这么多,暂时不清楚1.5MB是如何统计出来的。

发表在 Uncategorized | 一条评论

BDF字体转strike font

在Squeak中字体是以StrikeFont的形式保存的。有一个BDFFontReader类,可以 通过它将bdf字体转化为sf2格式的字体。由于这个久远的bug一直没有被整合,所以在使用 BDFFontReader之前,需要将BDFFontReader class>>new这个selector 手动删除。

另外需要注意的是,BDFFontReader只支持255个字符数的字体(在BDFFontReader class中有一段说明),需要先将bdf字体进行删减,之后才能被正确转化。

删减的方法是修改BDF文件中的CHARS值,一般改成192就可以了,相当于忽略255之后的所有字符。

文件的命名也是有讲究的。需要以nameNN.bdf的形式。其中NN是字体大小。将文件存放到squeak目录。利用 以下语句进行转化,不出意外的话,就可以得到nameNN.sf2文件了。

BDFFontReader convertFilesNamed: 'fixed' toFamilyNamed: 'fixed' inDirectoryNamed: ''.

关于粗体的设置,可以先参考StrikeFont class>>createDejaVu函数。比如分别 生成了fixed13.sf2, fixedB13.sf2和fixedI13.sf2这三个字体文件,可以通过以下代码将13号fixed字体加入系统,也就是就粗体和斜体是作为 derivativeFont的形式加入的。

fontArray := StrikeFont readStrikeFont2Family: 'fixed'.
boldFont := (StrikeFont readStrikeFont2Family: 'fixedB') first.
italicFont := (StrikeFont readStrikeFont2Family: 'fixedI') first.
(fontArray first) derivativeFont: boldFont at: 1.
(fontArray first) derivativeFont: italicFont at: 2.
textStyle := TextStyle fontArray: fontArray.
TextConstants at: 'fixed' asSymbol put: textStyle.

接下来研究一下是否有可能将文泉驿点阵以strikefont的方式加入到系统中。

发表在 Uncategorized | 一条评论

Squeak文件编码

这里可以了解到,Squeak从3.8开始支持Unicode。那篇文章里说明的是Seaside中对编码的处理,这是Seaside经过包装的。而Squeak本身对于Unicode的支持的说明可以看这里,从中可以了解到的一个重要信息是,Squeak中对所有字符都以Unicode表示。

那么对于文件的读写,Squeak又是如何处理的呢?这就需要看看MultiByteFileStream和StandardFileStream了,而CrLfFileStream已经被MultiByteFileStream取代,不需要再考虑。(实际上是它现在基于MultiByteFileStream实现)。

在MultiByteFileStream的注释中可以看到这样一段话:

The interface of this object is similar to good old StandardFileStream, but internally it asks the converter, which is a sub-instance of TextConverter, and do the text conversion.

也就是说MultiByteFileStream会有编码的转化,而StandardFileStream则不进行转化。具体MultiByteFileStream对于converter的设置可以看MultiByteFileStream class>>open:forWrite:实现(该函数会被StandardFileStream class>>readOnlyFileNamed:等函数调用),可以发现,系统默认以UTF-8格式读取文件。

另外,String类中还有squeakToUtf8及utf8ToSqueak方法可以进行方便的编码转化。

发表在 Uncategorized | 发表评论

Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

发表在 Uncategorized | 一条评论