diff --git a/index.js b/index.js index cfe1f39..23b47d4 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,9 @@ const path = require('path') const express = require('express') const bodyParser = require('body-parser') const fileUpload = require('express-fileupload'); +const i18next = require('i18next'); +const i18nextMiddleware = require('i18next-http-middleware'); +const i18nextBackend = require('i18next-fs-backend'); const api = require('./src/api') const dbMigration = require('./src/database-migration'); @@ -88,9 +91,24 @@ eventService = new EventService(); initDB(db, channelDB) -const guideService = new TVGuideService(xmltv, db, cacheImageService); +i18next + .use(i18nextBackend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + // debug: true, + initImmediate: false, + backend: { + loadPath: path.join(__dirname, '/locales/server/{{lng}}.json'), + addPath: path.join(__dirname, '/locales/server/{{lng}}.json') + }, + lng: 'en', + fallbackLng: 'en', + preload: ['en'], + }); +const guideService = new TVGuideService(xmltv, db, cacheImageService, null, i18next); + let xmltvInterval = { interval: null, @@ -175,6 +193,10 @@ let hdhr = HDHR(db, channelDB) let app = express() eventService.setup(app); +app.use( + i18nextMiddleware.handle(i18next, {}) +); + app.use(fileUpload({ createParentPath: true })); @@ -271,7 +293,7 @@ async function sendEventAfterTime() { eventService.push( "lifecycle", { - "message": `Server Started`, + "message": i18next.t("event.server_started"), "detail" : { "time": t, }, @@ -290,7 +312,7 @@ onShutdown("log" , [], async() => { eventService.push( "lifecycle", { - "message": `Initiated Server Shutdown`, + "message": i18next.t("event.server_shutdown"), "detail" : { "time": t, }, diff --git a/locales/server/en.json b/locales/server/en.json new file mode 100644 index 0000000..1ec933d --- /dev/null +++ b/locales/server/en.json @@ -0,0 +1,15 @@ +{ + "event":{ + "server_started": "Server Started", + "server_shutdown": "Initiated Server Shutdown" + }, + "api": { + "plex_server_not_found": "Plex server not found.", + "missing_name": "Missing name" + }, + "tvGuide": { + "no_channels": "No channels configured", + "no_channels_summary": "Use the dizqueTV web UI to configure channels.", + "xmltv_updated": "XMLTV updated at server time {{t}}" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9542541..782cd0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,20 @@ "dependencies": { "angular": "^1.8.0", "angular-router-browserify": "0.0.2", + "angular-sanitize": "^1.8.2", "angular-vs-repeat": "2.0.13", "axios": "^0.21.1", "body-parser": "^1.19.0", "diskdb": "0.1.17", "express": "^4.17.1", "express-fileupload": "^1.2.1", + "i18next": "^20.3.2", + "i18next-fs-backend": "^1.1.1", + "i18next-http-backend": "^1.2.6", + "i18next-http-middleware": "^3.1.4", "JSONStream": "1.0.5", "merge": "2.1.1", + "ng-i18next": "^1.0.7", "node-graceful-shutdown": "1.1.0", "node-ssdp": "^4.0.0", "random-js": "2.1.0", @@ -1175,12 +1181,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/template": { @@ -1898,6 +1906,11 @@ "resolved": "https://registry.npmjs.org/angular-router-browserify/-/angular-router-browserify-0.0.2.tgz", "integrity": "sha1-euL98uLowGxYz8aXrz56XohkJBg=" }, + "node_modules/angular-sanitize": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.8.2.tgz", + "integrity": "sha512-OB6Goa+QN3byf5asQ7XRl7DKZejm/F/ZOqa9z1skqYVOWA2hoBxoCmt9E7+i7T/TbxZP5zYzKxNZVVJNu860Hg==" + }, "node_modules/angular-vs-repeat": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/angular-vs-repeat/-/angular-vs-repeat-2.0.13.tgz", @@ -4023,6 +4036,14 @@ "sha.js": "^2.4.8" } }, + "node_modules/cross-fetch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", + "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", + "dependencies": { + "node-fetch": "2.6.1" + } + }, "node_modules/crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -7157,6 +7178,32 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-20.3.2.tgz", + "integrity": "sha512-e8CML2R9Ng2sSQOM80wb/PrM2j8mDm84o/T4Amzn9ArVyNX5/ENWxxAXkRpZdTQNDaxKImF93Wep4mAoozFrKw==", + "dependencies": { + "@babel/runtime": "^7.12.0" + } + }, + "node_modules/i18next-fs-backend": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-1.1.1.tgz", + "integrity": "sha512-RFkfy10hNxJqc7MVAp5iAZq0Tum6msBCNebEe3OelOBvrROvzHUPaR8Qe10RQrOGokTm0W4vJGEJzruFkEt+hQ==" + }, + "node_modules/i18next-http-backend": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.2.6.tgz", + "integrity": "sha512-NeNNRofj+rR6Cw+/Elf8bCVaCiqWg2Y6F+CrmDvHiPzAW2Dtxxlk8O0na2et/rr1n3ST6rJr4nMXH/QOFuhaeA==", + "dependencies": { + "cross-fetch": "3.1.4" + } + }, + "node_modules/i18next-http-middleware": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.4.tgz", + "integrity": "sha512-OVjxnw1w9RqhrOBSsXBXbk/7kzpLu+YvaL0LZnLHTCzjs8dMG8/BuoaDxi1l30T1lUfmweb+er/xwRHV3lp0RQ==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8771,6 +8818,23 @@ "rimraf": "bin.js" } }, + "node_modules/ng-i18next": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ng-i18next/-/ng-i18next-1.0.7.tgz", + "integrity": "sha512-eLEAvxi50w05wJdVxlkEF61mIQmhBSn8xhE+9a2y4nkpyJAUf7TTpA9SrID21UYdZQIIyWYoMie+QemRhRnGtw==", + "peerDependencies": { + "angular": ">=1.6.0", + "i18next": ">=10.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-graceful-shutdown": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/node-graceful-shutdown/-/node-graceful-shutdown-1.1.0.tgz", @@ -10082,8 +10146,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "node_modules/regenerator-transform": { "version": "0.14.4", @@ -13087,10 +13150,9 @@ } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -13666,6 +13728,11 @@ "resolved": "https://registry.npmjs.org/angular-router-browserify/-/angular-router-browserify-0.0.2.tgz", "integrity": "sha1-euL98uLowGxYz8aXrz56XohkJBg=" }, + "angular-sanitize": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.8.2.tgz", + "integrity": "sha512-OB6Goa+QN3byf5asQ7XRl7DKZejm/F/ZOqa9z1skqYVOWA2hoBxoCmt9E7+i7T/TbxZP5zYzKxNZVVJNu860Hg==" + }, "angular-vs-repeat": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/angular-vs-repeat/-/angular-vs-repeat-2.0.13.tgz", @@ -15435,6 +15502,14 @@ "sha.js": "^2.4.8" } }, + "cross-fetch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", + "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", + "requires": { + "node-fetch": "2.6.1" + } + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -17974,6 +18049,32 @@ "integrity": "sha512-xK7lO0EtSzfFPiw+oQncQVy/XqV7UVVjxBByc+Iv5iK3yhW9boDoWgvZy3OGo48QKg/hUtZkzz0hi2HXa0kn7w==", "dev": true }, + "i18next": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-20.3.2.tgz", + "integrity": "sha512-e8CML2R9Ng2sSQOM80wb/PrM2j8mDm84o/T4Amzn9ArVyNX5/ENWxxAXkRpZdTQNDaxKImF93Wep4mAoozFrKw==", + "requires": { + "@babel/runtime": "^7.12.0" + } + }, + "i18next-fs-backend": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-1.1.1.tgz", + "integrity": "sha512-RFkfy10hNxJqc7MVAp5iAZq0Tum6msBCNebEe3OelOBvrROvzHUPaR8Qe10RQrOGokTm0W4vJGEJzruFkEt+hQ==" + }, + "i18next-http-backend": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.2.6.tgz", + "integrity": "sha512-NeNNRofj+rR6Cw+/Elf8bCVaCiqWg2Y6F+CrmDvHiPzAW2Dtxxlk8O0na2et/rr1n3ST6rJr4nMXH/QOFuhaeA==", + "requires": { + "cross-fetch": "3.1.4" + } + }, + "i18next-http-middleware": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.4.tgz", + "integrity": "sha512-OVjxnw1w9RqhrOBSsXBXbk/7kzpLu+YvaL0LZnLHTCzjs8dMG8/BuoaDxi1l30T1lUfmweb+er/xwRHV3lp0RQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -19256,6 +19357,17 @@ } } }, + "ng-i18next": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ng-i18next/-/ng-i18next-1.0.7.tgz", + "integrity": "sha512-eLEAvxi50w05wJdVxlkEF61mIQmhBSn8xhE+9a2y4nkpyJAUf7TTpA9SrID21UYdZQIIyWYoMie+QemRhRnGtw==", + "requires": {} + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-graceful-shutdown": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/node-graceful-shutdown/-/node-graceful-shutdown-1.1.0.tgz", @@ -20288,8 +20400,7 @@ "regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "regenerator-transform": { "version": "0.14.4", diff --git a/package.json b/package.json index 1669b4f..2bb8697 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,20 @@ "dependencies": { "angular": "^1.8.0", "angular-router-browserify": "0.0.2", + "angular-sanitize": "^1.8.2", "angular-vs-repeat": "2.0.13", "axios": "^0.21.1", "body-parser": "^1.19.0", "diskdb": "0.1.17", "express": "^4.17.1", "express-fileupload": "^1.2.1", + "i18next": "^20.3.2", + "i18next-fs-backend": "^1.1.1", + "i18next-http-backend": "^1.2.6", + "i18next-http-middleware": "^3.1.4", "JSONStream": "1.0.5", "merge": "2.1.1", + "ng-i18next": "^1.0.7", "node-graceful-shutdown": "1.1.0", "node-ssdp": "^4.0.0", "random-js": "2.1.0", diff --git a/src/api.js b/src/api.js index 64599bc..92b10df 100644 --- a/src/api.js +++ b/src/api.js @@ -62,7 +62,7 @@ function api(db, channelDB, fillerDB, customShowDB, xmltvInterval, guideService name: req.body.name, }); if (servers.length != 1) { - return res.status(404).send("Plex server not found."); + return res.status(404).send(req.t("api.plex_server_not_found")); } let plex = new Plex(servers[0]); let s = await Promise.race( [ diff --git a/src/services/tv-guide-service.js b/src/services/tv-guide-service.js index cd3160d..5360303 100644 --- a/src/services/tv-guide-service.js +++ b/src/services/tv-guide-service.js @@ -8,7 +8,7 @@ class TVGuideService /**** * **/ - constructor(xmltv, db, cacheImageService, eventService) { + constructor(xmltv, db, cacheImageService, eventService, i18next) { this.cached = null; this.lastUpdate = 0; this.updateTime = 0; @@ -20,6 +20,7 @@ class TVGuideService this.cacheImageService = cacheImageService; this.eventService = eventService; this._throttle = throttle; + this.i18next = i18next; } async get() { @@ -322,9 +323,9 @@ class TVGuideService program: { duration: 24*60*60*1000, icon: FALLBACK_ICON, - showTitle: "No channels configured", + showTitle: this.i18next.t("tvGuide.no_channels"), date: formatDateYYYYMMDD(new Date()), - summary : "Use the dizqueTV web UI to configure channels." + summary : this.i18next.t("tvGuide.no_channels_summary") } } ) ] @@ -365,7 +366,7 @@ class TVGuideService eventService.push( "xmltv", { - "message": `XMLTV updated at server time = ${t}`, + "message": this.i18next.t("tvGuide.xmltv_updated", {t}), "module" : "xmltv", "detail" : { "time": new Date(),