【问题标题】:Best way to reuse a large translation file within Node / Express在 Node / Express 中重用大型翻译文件的最佳方法
【发布时间】:2014-06-13 21:51:31
【问题描述】:

我是 Node 新手,但我想我会立即开始将 PHP 应用程序转换为 Node/Express。这是一个双语应用程序,使用gettext 和 PO/MO 文件。我找到了一个名为 node-gettext 的 Node 模块。我现在不想将 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 而不是请求变量,那会混淆用户的语言吗?

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

【问题讨论】:

  • 如果你不想每次都加载它,你必须有某种缓存或全局变量。您可能会查看一些数据缓存库。

标签: node.js express gettext


【解决方案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);
}

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

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

每个域支持:

  1. 获取翻译
  2. 获取评论
  3. 设置评论
  4. 设置翻译
  5. 删除翻译
  6. 编译PO
  7. 编译MO

取自这里:

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

更新

稍微更清楚一点。

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

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

进一步优化

显然,您在每次页面加载时都会不断地运行翻译。这意味着gettext 库仍将使用驻留在内存中的翻译数据,这将占用处理时间。为了解决这个问题,最好确保您的 URL 具有使它们对每种不同语言都独一无二的东西,即 my-page/en/my.page.fr 甚至 jp.domain.co.uk/my-page,然后使用类似 @ 的东西启用某种整页缓存987654339@ 或express-view-cache。但是,一旦您开始缓存页面,您需要确保没有任何特定于用户的区域,如果是这样,您需要开始实现对这些区域敏感的更复杂的系统。

记住:优化的黄金法则,在你需要之前不要这样做......基本上意味着我不会担心页面缓存,直到你知道它会成为一个问题,但始终值得牢记您的选择是什么,因为它应该会影响您的代码设计。

更新 2

只是为了进一步说明在 JavaScript 中运行的服务器的行为,以及全局行为如何不仅是 req.app 的属性,而且实际上是作用域链上游的任何对象。

因此,作为示例,您可以在放置中间件的任何位置进一步实例化它,而不是将 var languageDomains = {}; 添加到您的 app.js 中。但是,最好将您的全局实体保存在一个地方,因此 app.js 是更好的地方,但这只是为了说明。

var languageDomains = {};

module.exports = function locale()
{
   /// you can still access languageDomains here, and it will behave 
   /// globally for the entire server.
   languageDomains[lang]
}

所以基本上,与 PHP 一样,整个代码库在每个请求上都会重新执行——因此 languageDomains 每次都会被重新实例化——在 Node 中,代码中唯一需要重新执行的部分-executed 是locale() 中的代码(因为它是作为新请求的一部分触发的)。这个函数仍然会通过作用域链引用已经存在和定义的languageDomains。因为languageDomains 永远不会重置(基于每个请求),所以它将在全局范围内运行。

并发用户

Node.js 是单线程的。这意味着为了让它并发,即同时处理多个请求,您必须以这样一种方式对您的应用程序进行编码,即每个小部分都可以非常快速地执行,然后进入等待状态,而另一个处理另一个请求的一部分。

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

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

【讨论】:

  • 感谢您的洞察力,pebbl。使用这种方法,翻译文件仍然需要在每次页面加载时读取/加载,对吗?但它只需要加载一次,对吧?
  • @Gavin ~ 检查我的回答中的更新,希望它能更详细地澄清。
  • 谢谢!这真的有助于我理解这一点并更好地理解 Node.js。那么,无论哪个用户设置变量,所有用户/连接都可以使用所有 req.app.* 变量吗?例如,如果在某个函数内部我有一个if (user.id == 20) { req.app.local.myvar = 'hello!' } 并且 id=20 的用户访问了该站点,那么从那时起(直到服务器重新启动)每个用户都可以访问这个变量,而不管他们的用户 id 是什么?
  • @Gavin ~ 没问题!是的,差不多就是这样,但它不会只是req.app 任何保持活动状态的变量/对象都将具有相同的行为。因此,全局范围内的任何内容,或至少在基于每个请求调用的任何函数之外。如果您还没有阅读 JavaScript 范围,我建议您这样做,因为它的行为与 PHP 完全不同。查看我的答案中的更新以获取更多信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
相关资源
最近更新 更多