feat(security,ui): self-host React deps, Tailwind, fonts; strict CSP; local QR; better selection state
Replace CDN React/ReactDOM/Babel with local libs; remove Babel and inline scripts Build Tailwind locally, add safelist; switch to assets/tailwind.css Self-host Font Awesome and Inter (CSS + woff2); remove external font CDNs Implement strict CSP (no unsafe-inline/eval; scripts/styles/fonts from self) Extract inline handlers; move PWA scripts to external files Add local QR code generation (qrcode lib) and remove api.qrserver.com Improve SessionTypeSelector visual selection (highlighted background and ring) Keep PWA working with service worker and offline assets Refs: CSP hardening, offline-first, no external dependencies
This commit is contained in:
188
node_modules/y18n/index.js
generated
vendored
Normal file
188
node_modules/y18n/index.js
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
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 = Object.create(null)
|
||||
this.writeQueue = []
|
||||
}
|
||||
|
||||
Y18N.prototype.__ = function () {
|
||||
if (typeof arguments[0] !== 'string') {
|
||||
return this._taggedLiteral.apply(this, arguments)
|
||||
}
|
||||
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._taggedLiteral = function (parts) {
|
||||
var args = arguments
|
||||
var str = ''
|
||||
parts.forEach(function (part, i) {
|
||||
var arg = args[i + 1]
|
||||
str += part
|
||||
if (typeof arg !== 'undefined') {
|
||||
str += '%s'
|
||||
}
|
||||
})
|
||||
return this.__.apply(null, [str].concat([].slice.call(arguments, 1)))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user