This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
4.7 KiB
172 lines
4.7 KiB
var fs = require('fs') |
|
var path = require('path') |
|
var util = require('util') |
|
|
|
function Y18N (opts) { |
|
// configurable options. |
|
opts = opts || {} |
|
this.directory = opts.directory || './locales' |
|
this.updateFiles = typeof opts.updateFiles === 'boolean' ? opts.updateFiles : true |
|
this.locale = opts.locale || 'en' |
|
this.fallbackToLanguage = typeof opts.fallbackToLanguage === 'boolean' ? opts.fallbackToLanguage : true |
|
|
|
// internal stuff. |
|
this.cache = {} |
|
this.writeQueue = [] |
|
} |
|
|
|
Y18N.prototype.__ = function () { |
|
var args = Array.prototype.slice.call(arguments) |
|
var str = args.shift() |
|
var cb = function () {} // start with noop. |
|
|
|
if (typeof args[args.length - 1] === 'function') cb = args.pop() |
|
cb = cb || function () {} // noop. |
|
|
|
if (!this.cache[this.locale]) this._readLocaleFile() |
|
|
|
// we've observed a new string, update the language file. |
|
if (!this.cache[this.locale][str] && this.updateFiles) { |
|
this.cache[this.locale][str] = str |
|
|
|
// include the current directory and locale, |
|
// since these values could change before the |
|
// write is performed. |
|
this._enqueueWrite([this.directory, this.locale, cb]) |
|
} else { |
|
cb() |
|
} |
|
|
|
return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args)) |
|
} |
|
|
|
Y18N.prototype._enqueueWrite = function (work) { |
|
this.writeQueue.push(work) |
|
if (this.writeQueue.length === 1) this._processWriteQueue() |
|
} |
|
|
|
Y18N.prototype._processWriteQueue = function () { |
|
var _this = this |
|
var work = this.writeQueue[0] |
|
|
|
// destructure the enqueued work. |
|
var directory = work[0] |
|
var locale = work[1] |
|
var cb = work[2] |
|
|
|
var languageFile = this._resolveLocaleFile(directory, locale) |
|
var serializedLocale = JSON.stringify(this.cache[locale], null, 2) |
|
|
|
fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) { |
|
_this.writeQueue.shift() |
|
if (_this.writeQueue.length > 0) _this._processWriteQueue() |
|
cb(err) |
|
}) |
|
} |
|
|
|
Y18N.prototype._readLocaleFile = function () { |
|
var localeLookup = {} |
|
var languageFile = this._resolveLocaleFile(this.directory, this.locale) |
|
|
|
try { |
|
localeLookup = JSON.parse(fs.readFileSync(languageFile, 'utf-8')) |
|
} catch (err) { |
|
if (err instanceof SyntaxError) { |
|
err.message = 'syntax error in ' + languageFile |
|
} |
|
|
|
if (err.code === 'ENOENT') localeLookup = {} |
|
else throw err |
|
} |
|
|
|
this.cache[this.locale] = localeLookup |
|
} |
|
|
|
Y18N.prototype._resolveLocaleFile = function (directory, locale) { |
|
var file = path.resolve(directory, './', locale + '.json') |
|
if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { |
|
// attempt fallback to language only |
|
var languageFile = path.resolve(directory, './', locale.split('_')[0] + '.json') |
|
if (this._fileExistsSync(languageFile)) file = languageFile |
|
} |
|
return file |
|
} |
|
|
|
// this only exists because fs.existsSync() "will be deprecated" |
|
// see https://nodejs.org/api/fs.html#fs_fs_existssync_path |
|
Y18N.prototype._fileExistsSync = function (file) { |
|
try { |
|
return fs.statSync(file).isFile() |
|
} catch (err) { |
|
return false |
|
} |
|
} |
|
|
|
Y18N.prototype.__n = function () { |
|
var args = Array.prototype.slice.call(arguments) |
|
var singular = args.shift() |
|
var plural = args.shift() |
|
var quantity = args.shift() |
|
|
|
var cb = function () {} // start with noop. |
|
if (typeof args[args.length - 1] === 'function') cb = args.pop() |
|
|
|
if (!this.cache[this.locale]) this._readLocaleFile() |
|
|
|
var str = quantity === 1 ? singular : plural |
|
if (this.cache[this.locale][singular]) { |
|
str = this.cache[this.locale][singular][quantity === 1 ? 'one' : 'other'] |
|
} |
|
|
|
// we've observed a new string, update the language file. |
|
if (!this.cache[this.locale][singular] && this.updateFiles) { |
|
this.cache[this.locale][singular] = { |
|
one: singular, |
|
other: plural |
|
} |
|
|
|
// include the current directory and locale, |
|
// since these values could change before the |
|
// write is performed. |
|
this._enqueueWrite([this.directory, this.locale, cb]) |
|
} else { |
|
cb() |
|
} |
|
|
|
// if a %d placeholder is provided, add quantity |
|
// to the arguments expanded by util.format. |
|
var values = [str] |
|
if (~str.indexOf('%d')) values.push(quantity) |
|
|
|
return util.format.apply(util, values.concat(args)) |
|
} |
|
|
|
Y18N.prototype.setLocale = function (locale) { |
|
this.locale = locale |
|
} |
|
|
|
Y18N.prototype.getLocale = function () { |
|
return this.locale |
|
} |
|
|
|
Y18N.prototype.updateLocale = function (obj) { |
|
if (!this.cache[this.locale]) this._readLocaleFile() |
|
|
|
for (var key in obj) { |
|
this.cache[this.locale][key] = obj[key] |
|
} |
|
} |
|
|
|
module.exports = function (opts) { |
|
var y18n = new Y18N(opts) |
|
|
|
// bind all functions to y18n, so that |
|
// they can be used in isolation. |
|
for (var key in y18n) { |
|
if (typeof y18n[key] === 'function') { |
|
y18n[key] = y18n[key].bind(y18n) |
|
} |
|
} |
|
|
|
return y18n |
|
}
|
|
|