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

我是Node新手,但我想我会立即开始,并将一个PHP应用程序转换为Node / Express。 这是一个使用PO / MO文件的gettext的双语应用程序。 我find了一个名为node-gettext的Node模块。 我宁愿不把PO文件转换成另一种格式,所以看起来这个库是我唯一的select。

所以我现在担心的是,在每个页面呈现之前,我都是这样做的:

 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中,除了数据库之外,最有效的方法是什么? 通常,用户在第一次之后甚至不会改变他们的语言(如果他们正在从英语改变它)。


编辑:

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

在app.js中:

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

然后,在app.js中,我创buildvariablesapp.locals.gt来包含gettext / translation对象,并且包含我的中间件函数:

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

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

MOD

 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而不是请求variables,是否可以混合用户的语言?

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

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

在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(); }; }; 

通过检查文件是否已经被加载,你可以防止这个动作多次发生,并且域数据将驻留在服务器的内存中。 这种解决scheme简单的缺点是,如果您在服务器运行时更​​改.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); } 

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

经过上面的改变,而不是将gt传递给你的模板,你应该可以使用req.textdomain来代替。 似乎gettext库直接在每个域对象上支持一些请求,这意味着你希望不需要在每个请求的基础上引用全局gt对象(这将改变它在每个请求上的默认域):

每个域支持:

  1. getTranslation
  2. getComment
  3. setComment
  4. setTranslation
  5. deleteTranslation
  6. compilePO
  7. compileMO

采取从这里:

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

更新

一点点进一步清晰。

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

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

进一步优化

很显然,在上面,你每个页面的负载都经常运行翻译。 这意味着gettext库仍将使用驻留在内存中的翻译数据,这将占用处理时间。 为了解决这个问题,最好确保你的URL有一些独特的东西,比如my-page/en/或者my.page.fr ,甚至是jp.domain.co.uk/my-page然后使用类似于memcached或express-view-cache的方式启用某种完整页面caching。 但是,一旦你开始caching页面,你需要确定没有任何区域是用户特定的,如果需要的话,你需要开始实现对这些区域敏感的更复杂的系统。

请记住:优化的黄金规则,不要这样做之前,你需要…基本上意味着我不会担心页面caching,直到你知道这将是一个问题,但它总是值得注意什么选项,因为它应该塑造你的代码devise。

更新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中,要重新执行的代码的唯一部分是代码在locale() (因为它是作为新请求的一部分被触发的)。 这个函数仍然通过作用域链对已经存在和定义的languageDomains进行引用。 由于languageDomains永远不会重置(基于每个请求),因此它将在全局中运行。

并发用户

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

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

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