gettext

  • gettext有缓存msgstr吗?——有。缓存放在已加载mo链表(_nl_loaded_domains)。
  • dgettext返回的msgstr安全吗?——因为gettext有缓存msgstr,dgettext返回msgstr指向缓存中某个位置,而且是线程安全。前提是该textdomain对应的loaded_l10nfile没被删掉。官方给的源码不会删除loaded_l10nfile,因而dgettext返回的msgstr可放心使用。但如果加了删除loaded_l10nfile,像rose,那要注意在删除该loaded_l10nfile后,保证不会再用到之前得到的msgstr。
  • _nl_make_l10nflist(...)用于根据要求的mo文件路径,生成(没有对应的)或返回(已有对应的)struct loaded_l10nfile。即使要生成loaded_l10nfile,过程中也只是分配并初始化struct loaded_l10nfile,不会加载*.mo。因而在调用_nl_make_l10nflist(...)后,往往会调用_nl_load_domain(...),后者用于加载当中的*.mo。
  • rose修改了DCIGETTEXT(...)[dcigettext.c]。当找不到binding,不是使用“dirname = _nl_default_dirname”,而是直接跳到“return_untranslated”,即认为此次无法翻译。这意味着,要使用一个textdomain,必须先使用bindtextdomain(__domainname, __dirname)创建一个binding,bingding中路径是参数__dirname指定的路径。一旦在binding表不存在该textdomain,不会尝试从_nl_default_dirname(值locale)合成出的*.mo文件中找,像:locale/zh/LC_MESSAGES/aismart-lib.mo

gettext存在两个链表:_nl_domain_bindings和_nl_loaded_domains。前者叫binding表,用于存储已绑定过的textdomain,后者叫已加载mo表,用于存放已加载*.mo。这两个表相互独立,又存在关联。对已加载mo表,关键字是filename,示例:C:/ddksample/apps-res/app-launcher/translations/zh_CN/LC_MESSAGES/launcher-lib.mo,它的前面部分C:/ddksample/apps-res/app-launcher/translations,是binding表中某个textdomain的dirname。

 

一、struct binding *_nl_domain_bindings

_nl_domain_bindings是binding链表的头结点,表中单元类型是struct binding。

textdomain[1/3]
  domainname: aplt_leagor_basic-lib
  codeset: UTF-8
  dirname: C:/ddksample/apps-res/aplt_leagor_basic/translations
textdomain[2/3]
  domainname: launcher-lib
  codeset: UTF-8
  dirname: C:/ddksample/apps-res/app-launcher/translations
textdomain[3/3]
  domainname: rose-lib
  codeset: UTF-8
  dirname: C:/ddksample/apps-res/translations

上面是一个binding表示例,此时表中有三个结点。对每个结点,主要是名称(domainname)、输出编码(codeset)和目录(dirname)。

 

二、struct loaded_l10nfile *_nl_loaded_domains

_nl_loaded_domains是已加载mo链表的头结点,表中单元类型是struct loaded_l10nfile。

loaded_l10nfiles[1/6]
  filename: C:/ddksample/apps-res/translations/zh_CN/LC_MESSAGES/rose-lib.mo
  decided: 1
  mmap_size: 24597
  successor_count: 1
loaded_l10nfiles[2/6]
  filename: C:/ddksample/apps-res/translations/zh/LC_MESSAGES/rose-lib.mo
  decided: 1
  mmap_size: -1
  successor_count: 0
loaded_l10nfiles[3/6]
  filename: C:/ddksample/apps-res/app-launcher/translations/zh_CN/LC_MESSAGES/launcher-lib.mo
  decided: 1
  mmap_size: 12219
  successor_count: 1
loaded_l10nfiles[4/6]
  filename: C:/ddksample/apps-res/app-launcher/translations/zh/LC_MESSAGES/launcher-lib.mo
  decided: 1
  mmap_size: -1
  successor_count: 0
loaded_l10nfiles[5/6]
  filename: C:/ddksample/apps-res/aplt_leagor_basic/translations/zh_CN/LC_MESSAGES/aplt_leagor_basic-lib.mo
  decided: 1
  mmap_size: 4921
  successor_count: 1
loaded_l10nfiles[6/6]
  filename: C:/ddksample/apps-res/aplt_leagor_basic/translations/zh/LC_MESSAGES/aplt_leagor_basic-lib.mo
  decided: 0
  mmap_size: -1
  successor_count: 0

上面是一个已加载mo表示例,此时表中有六个结点。对每个结点,主要是mo文件名(filename),是否已加载mo(decided),mo文件长度(mmap_size),指示如果第一mo无效、可换去从哪mo读取词条的successor,等。

解释下successor。以filename=C:/ddksample/apps-res/translations/zh_CN/LC_MESSAGES/rose-lib.mo的loaded_l10nfile为例,它存在一个successor,就是那个filename=C:/ddksample/apps-res/translations/zh/LC_MESSAGES/rose-lib.mo的loaded_l10nfile,它们区别是子目录“zh_CN”和“zh”。它们是怎么工作的?假设要翻译msgid=Name,先去第一loaded_l10nfile找,即那个zh_CN。如果在这个mo找不到,到sucessor数组从索引0起挨个找,这里就是第二loaded_l10nfile,即“zh”的。从命名大致也可推出,先是从“中文_简体”找,如果找不到,就从“中文”。

 

2.1 loaded_l10nfile、loaded_domain、mo_file_header

loaded_l10nfile、loaded_domain、mo_file_header是三种struct,在从属关系上,loaded_domain放在了loaded_l10nfile的data字段,mo_file_header则放在了loaded_domain的data字段。所以loaded_l10nfile是那个顶层struct。

一直说_nl_loaded_domains是已加载mo链表,那单元类型loaded_l10nfile就表示了一个已加载mo,要清楚这个,就涉及到loaded_l10nfile如何存储mo数据。

图1 loaded_l10nfile、loaded_domain、mo_file_header

图1显示了_nl_load_domain(..)解析launcher-lib.mo文件后,上述三种类型中各字段情况。Watch 2中的变量data,类型mo_file_header。结合左侧launcher-lib.mo内容,可看到就是让data指向了从文件读出的内存块,因而像data.magic,它是文件的前4个字节,值就是“de 12 04 95”。

在Watch 1,变量domain类型是loaded_domain,字段data的值0x04957168就是Watch 2中的data。只是类型由mo_file_header*变成了const char*。

变量domain_file类型是loaded_l10nfile,字段data的值0x028172d0就是变量domain。只是类型由loaded_domain*变成了const void*。

对domain->mmap_size,值0x2fbb,换成十进制是12219,它就是launcher-lib.mo的文件长度。

对domain_file->decided,它可能是三个值。

  • -1: 此个loaded_l10nfile还没加载过mo。但_nl_load_domain发现decided是-1值,认为和1一样,啥也不做。
  • 0: 此个loaded_l10nfile还没加载过mo。_nl_load_domain发现是0时,需要打开并解析*.mo文件。
  • 1: 此个loaded_l10nfile已没加载过mo。如果加载成功,那data字段指向一个struct loaded_domain。

对domain_file->loaded_l10nfile,类型struct loaded_l10nfile *successor[1],是一个指针数组。表示继承人、后选者,如果无法使用filename指定的*.mo,那尝试使用下面这些loaded_l10nfile。具体含义上文有解释。这里有有疑问,只一个successor字段,怎么知道有几个后选者?——successor是个指针数组,一旦该数组单元值是0了,意味接下没有后选吧。举个例子,successor[0]是非nullptr,successor[1]是nullptr,那意味着只有[0]指向的那个后选者。

因为domain->data就存储着mo文件内容,根据mo文件格式,msgstr是以着明文存储在mo,而且每个msgstr后都会跟着一个字符串终止符'\0',这样间接实现了gettext缓存msgstr功能。

图1断点设在了函数_nl_load_main。_nl_load_main功能是打开*.mo,一次读出整个文件。二进制数据放在domain->data,data类型是loaded_domain。

  • struct mo_file_header。分配时机:_nl_load_domain,分配api:malloc,字节数:*.mo文件长度。它就是*.mo的格式,从0字节开始。像前4节是_MAGIC(0x950412de)。因为它就是loaded_domain::data,释放data,就释放它了。
  • struct loaded_domain。分配时机:_nl_load_domain,分配api:malloc,字节数:sizeof (struct loaded_domain)。它放在domain_file->data,domain_file类型是loaded_l10nfile。

 

三、rose修改源码

DCIGETTEXT时,当找不到binding,不是使用“dirname = _nl_default_dirname”,而是直接跳到“return_untranslated”,即认为此次无法翻译。

<gettext>/gettext-runtime/intl/dcigettext.c
------
char *
DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
	    int plural, unsigned long int n, int category)
{
  ...
  if (binding == NULL) {
	// _nl_default_dirname is 'locale'.
	// No attempt is made to synthesize *.mo files from _nl_default_dirname(value: 'locale'), 
	// such as locale/zh/LC_MESSAGES/aismart-lib.mo
	// dirname = _nl_default_dirname;
	goto return_untranslated;

注销掉“dirname = _nl_default_dirname”,改为“goto return_untranslated”。此中_nl_default_dirname值是“locale”。

  } else {
      dirname = binding->dirname;
}

这意味着,要使用一个textdomain,必须先使用bindtextdomain(__domainname, __dirname)创建一个textdomain。这个路径必须是参数__dirname事先指定的路径。一旦在binding表不存在该textdomain,不会尝试从_nl_default_dirname(值locale)合成出的*.mo文件中找,像:locale/zh/LC_MESSAGES/aismart-lib.mo。

全部评论: 0

    写评论: