在Node / Express中重用大型转换文件的最佳方法

时间:2014-06-13 21:51:32

标签: node.js express gettext

我是Node的新手,但我想我会直接进入并开始将PHP应用程序转换为Node / Express。这是一个使用gettext与PO / MO文件的双语应用程序。我找到了一个名为node-gettext的节点模块。我现在不想将PO文件转换成另一种格式,所以看起来这个库是我唯一的选择。

所以我关注的是,现在,在每个页面渲染之前,我正在做这样的事情:

exports.home_index = function(req, res)
{
    var gettext = require('node-gettext'),
        gt = new gettext();

    var fs = require('fs');
    gt.textdomain('de');
    var fileContents = fs.readFileSync('./locale/de.mo');
    gt.addTextdomain('de', fileContents);

    res.render(
        'home/index.ejs',
        { gt: gt }
    );
};

我也会在课堂上使用翻译,所以现在我必须在每次想要翻译其他地方的内容时再次加载整个翻译文件。

翻译文件大约是50k,我真的不喜欢在每次页面加载时都要这样做文件操作。在Node / Express中,处理此问题的最有效方法是什么(除了数据库)?通常,用户在第一次之后甚至不会更改他们的语言(如果他们将其从英语更改)。


修改

好吧,我不知道这是不是一个好方法,但它至少让我在应用程序的其他部分重用翻译文件,而无需在需要翻译文本的任何地方重新加载它。

在app.js中:

var express         = require('express'),
    app             = express(),
    ...
    gettext         = require('node-gettext'),
    gt              = new gettext();

然后,同样在app.js中,我创建变量app.locals.gt以包含gettext / translation对象,并且我包含了我的中间件函数:

app.locals.gt = gt;
app.use(locale());

在我的中间件文件中,我有这个:

module.exports = function locale()
{
    return function(req, res, next)
    {
        // do stuff here to populate lang variable
        var fs = require('fs');
        req.app.locals.gt.textdomain(lang);
        var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
        req.app.locals.gt.addTextdomain(lang, fileContents);

        next();
    };
};

将加载的翻译文件分配给app似乎不是一个好主意,因为根据当前请求,该文件将是两种语言之一。如果我将加载的翻译文件分配给app而不是请求变量,那么这会混淆用户的语言吗?

无论如何,我知道必须有更好的方法来做到这一点。

1 个答案:

答案 0 :(得分:1)

最简单的选择是执行以下操作:

在app.js中添加:

var languageDomains = {};

然后修改您的中间件:

module.exports = function locale()
{
    return function(req, res, next)
    {
        // do stuff here to populate lang variable
        if ( !req.app.locals.languageDomains[lang] ) {
          var fs = require('fs');
          var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
          req.app.locals.languageDomains[lang] = true;
          req.app.locals.gt.addTextdomain(lang, fileContents);
        }
        req.textdomain = req.app.locals.gt.textdomain(lang);
        next();
    };
};

通过检查文件是否已经加载,您将阻止该操作多次发生,并且域数据将保持驻留在服务器的内存中。此解决方案简单性的缺点是,如果您在服务器运行时更​​改了.mo文件的内容,则不会考虑更改。但是,可以扩展此代码以密切关注文件的mtime,并相应地重新加载,或者使用fs.watchFile - 如果需要:

if ( !req.app.locals.languageDomains[lang] ) {
  var fs = require('fs'), filename = './locales/' + lang + '.mo';
  var fileContents = fs.readFileSync(filename);
  fs.watchFile(filename, function (curr, prev) {
    req.app.locals.gt.addTextdomain(lang, fs.readFileSync(filename));
  });
  req.app.locals.languageDomains[lang] = true;
  req.app.locals.gt.addTextdomain(lang, fileContents);
}
  

警告:还应该注意,在服务器初始化之外使用同步版本的函数不是一个好主意,因为它可以冻结线程。您最好将同步加载更改为异步等效文件。

完成上述更改后,您应该可以使用gt而不是将req.textdomain传递给模板。似乎gettext库直接在每个域对象上支持许多请求,这意味着您希望不需要在每个请求的基础上引用全局gt对象(这将是在每个请求上更改它的默认域名:

每个域都支持:

  1. getTranslation
  2. getComment
  3. setComment
  4. setTranslation
  5. deleteTranslation
  6. compilePO
  7. compileMO
  8. 从这里采取:

    https://github.com/andris9/node-gettext/blob/e193c67fdee439ab9710441ffd9dd96d027317b9/lib/domain.js

    更新

    进一步澄清。

    一旦服务器第一次将文件加载到内存中,它应该保留在它接收的所有后续连接(对于任何访问者/请求),因为它全局存储并且不会被垃圾收集 - 除非你删除所有引用数据,这意味着gettext需要某种卸载/遗忘域方法。

    Node与PHP的不同之处在于它的环境是共享的并且包装了自己的HTTP服务器(如果你使用像Express这样的东西),这意味着它很容易记住全局数据,因为它有一个恒定的环境,所有的代码在...内执行。 PHP总是在HTTP服务器收到并处理请求(例如Apache)之后执行。然后,每个PHP响应都在自己独立的运行时执行,这意味着您必须依赖数据库,会话和缓存存储来共享简单信息和大多数资源。

    进一步优化

    显然,通过上述内容,您可以在每个页面加载时不断运行翻译。这意味着gettext库仍将使用驻留在内存中的翻译数据,这将占用处理时间。要解决这个问题,最好确保您的网址具有某些内容,使其对于每种不同的语言都是唯一的,即my-page/en/my.page.fr甚至jp.domain.co.uk/my-page然后启用某种完整的使用memcachedexpress-view-cache之类的页面缓存。但是,一旦开始缓存页面,您需要确定没有任何用户特定的区域,如果是这样,您需要开始实施对这些区域敏感的更复杂的系统。

      

    请记住:优化的黄金法则,在您需要之前不要这样做...基本上意味着我不会担心页面缓存,直到您知道它为止`&#39 ;这将是一个问题,但总是值得记住你的选择,因为它应该塑造你的代码设计。

    更新2

    只是为了进一步说明在JavaScript中运行的服务器的行为,以及全局行为不仅仅是req.app的属性,而且实际上是任何在范围链上的对象。< / p>

    因此,作为示例,您可以将app {j}添加到a​​pp.js中,而不是将其放置在中间件所在位置的范围内。最好将您的全球实体放在一个地方,所以app.js是更好的地方,但这只是为了说明。

    var languageDomains = {};

    基本上,就像PHP一样,整个代码库在每个请求上重新执行 - 所以var languageDomains = {}; module.exports = function locale() { /// you can still access languageDomains here, and it will behave /// globally for the entire server. languageDomains[lang] } 每次都会实例化一次 - 在Node中代码的唯一部分是被重新执行的是languageDomains中的代码(因为它是作为新请求的一部分触发的)。此函数仍将通过作用域链引用已存在且已定义的locale()。因为永远不会重置languageDomains(基于每个请求),它将全局运行。

    并发用户

    Node.js是单线程的。这意味着它为了并发,即在&#34;同一&#34;处理多个请求。时间,你必须以这样的方式编写你的应用程序,即每个小部分都可以很快执行,然后进入等待状态,同时处理另一个请求的另一部分。

    这就是Node的异步和回调特性的原因,以及在应用程序运行时避免同步调用的原因。任何一个Sync请求都可以暂停或冻结线程的执行,并延迟处理所有其他请求。我之所以这样说,是为了让您更好地了解多个用户如何与您的代码(以及全局对象)进行交互。

    基本上,一旦您的服务器正在处理请求,它就是唯一的焦点,直到特定的执行周期结束,即您的请求处理程序停止调用需要同步运行的其他代码。一旦发生这种情况,就会处理下一个排队的项目(回调或其他东西),这可能是另一个请求的一部分,或者它可能是当前请求的下一个部分。