如我之前的博客文章中提到的,CiviCRM的多语言功能是通过数据库层面的方法实现的,即添加每种语言的列,并通过每种语言的表视图以原始名称暴露它们。这使得CiviCRM可以隐藏其多语言特性,不让不关心它是单语言还是多语言安装的代码知道,但在版本升级时引入了新的挑战。我项目的这部分旨在优化CiviCRM开发的这一领域。

将每种语言的列隐藏在每种语言的视图下(以及一些用于在INSERT操作中智能填充列的巧妙触发器)允许实时重写简单的典型SQL查询,用于对表内容的CRUD(创建、读取、更新、删除)操作。如上所述,这使CiviCRM的多语言功能对任何不显式询问给定安装是单语言还是多语言的代码都是透明的。

不幸的是,这种无需麻烦编码的特性是一种权衡,需要用到的丑陋代码来正确升级多语言CiviCRM数据库。升级给定CiviCRM版本之间的SQL查询通常是手工编写的,不仅调整数据库模式,而且经常将数据从旧表列移动到新表列——有时甚至基于现有数据计算新表内容。我最初的想法是创建一个巧妙的逻辑,能够实时重写单语言查询,就像CRUD查询被重写一样。然而,现实生活中的例子(特别是那些计算新内容的例子)很快表明,这种方法在没有完整的SQL语法分析器的情况下是无法实现的。

幸运的是,在仔细研究了CiviCRM处理单语言与多语言升级路径的方式后,我发现了一个适用于大约95%的案例的模式:两种路径的SQL代码几乎相同,关键的区别在于,在多语言安装中,给定的SQL操作应并行添加/删除/修改几列(而在单语言安装中,相同的SQL代码仅操作一个列)。

此条件逻辑在 Smarty 模板中通过 {if $multilingual}…{else}…{/if} 结构实现,这基本上复制了 SQL 查询 —— else 分支中的简单查询被复制到 if 分支中,所有可本地化的列都被包装在 {foreach} 块中,遍历所有启用的区域设置。或者更准确地说,这是过去的做法 —— 自从昨天开始,所有这些糟糕的条件语句都被重写,多亏了新实现的 {localize} Smarty 块,它抽象了整个复制/按需迭代的流程。查询现在看起来好多了,代码没有重复,尽管仍然需要人工干预(以确定哪些地方需要进行多语言处理),但它比以前要微妙且更容易维护:在最新升级脚本中引入此功能的修补程序(我们真的很快就会发布 CiviCRM 3.0.alpha1...)有令人愉悦的行统计数字 '+124 -312'。:)