├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── basic-handlebars │ ├── README.md │ ├── index.js │ ├── locales │ │ ├── de │ │ │ └── translation.json │ │ └── en │ │ │ └── translation.json │ ├── package.json │ └── views │ │ ├── index.hbs │ │ └── layouts │ │ └── main.hbs ├── basic-locize │ ├── README.md │ ├── index.js │ ├── package-lock.json │ └── package.json ├── basic-pug │ ├── README.md │ ├── index.js │ ├── locales │ │ ├── de │ │ │ └── translation.json │ │ └── en │ │ │ └── translation.json │ ├── package.json │ └── views │ │ └── index.pug └── basic │ ├── README.md │ ├── index.js │ ├── locales │ ├── de │ │ └── translation.json │ └── en │ │ └── translation.json │ └── package.json ├── index.d.ts ├── package.json └── src ├── LanguageDetector.js ├── index.js ├── languageLookups ├── cookie.js ├── header.js ├── path.js ├── querystring.js └── session.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*.{js,jsx,json}] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/* 2 | **/node_modules/* 3 | **/*.min.* 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: babel-eslint 3 | 4 | ecmaFeatures: 5 | arrowFunctions: true 6 | blockBindings: true 7 | classes: true 8 | defaultParams: true 9 | destructuring: true 10 | forOf: true 11 | generators: false 12 | modules: true 13 | objectLiteralComputedProperties: true 14 | objectLiteralDuplicateProperties: false 15 | objectLiteralShorthandMethods: true 16 | objectLiteralShorthandProperties: true 17 | spread: true 18 | superInFunctions: true 19 | templateStrings: true 20 | 21 | env: 22 | browser: true 23 | node: true 24 | es6: true 25 | 26 | globals: 27 | __resourceQuery: false 28 | bootstrap: false 29 | describe: false 30 | describeSaga: false 31 | describeEvent: false 32 | describeCommand: false 33 | describeScene: false 34 | before: false 35 | it: false 36 | xit: false 37 | window : false 38 | beforeEach : false 39 | afterEach : false 40 | after : false 41 | before : false 42 | beforeEachChapter: false 43 | describeScenario: false 44 | describeChapter: false 45 | describeStep: false 46 | document : false 47 | window: false 48 | File : false 49 | FormData: false 50 | QCodeDecoder: false 51 | $: false 52 | L: false 53 | btoa: false 54 | escape: false 55 | angular: false 56 | jQuery: false 57 | ga: false 58 | 59 | settings: 60 | jsx: true 61 | 62 | ecmaFeatures: 63 | jsx: true 64 | 65 | rules: 66 | 67 | # ERRORS 68 | curly: [2, "multi-line"] 69 | 70 | # WARNINGS 71 | no-unused-vars: [1, {vars: all, args: none}] 72 | semi-spacing: 1 73 | no-empty: 1 74 | handle-callback-err: 1 75 | eqeqeq: 1 76 | quotes: [1, 'single'] 77 | no-unused-expressions: 1 78 | no-throw-literal: 1 79 | semi: 1 80 | block-scoped-var: 1 81 | no-alert: 1 82 | no-console: 1 83 | new-cap: 1 84 | space-unary-ops: 1 85 | 86 | # DISABLED 87 | space-after-keywords: 0 88 | dot-notation: 0 89 | consistent-return: 0 90 | brace-style: 0 91 | no-multi-spaces: 0 92 | no-underscore-dangle: 0 93 | key-spacing: 0 94 | comma-spacing: 0 95 | no-shadow: 0 96 | no-mixed-requires: 0 97 | space-infix-ops: 0 98 | strict: 0 99 | camelcase: 0 100 | no-wrap-func: 0 101 | comma-dangle: 0 102 | no-extra-semi: 0 103 | no-use-before-define: [0, "nofunc"] 104 | 105 | # AUTOMATED BY EDITORCONFIG 106 | eol-last: 0 107 | no-trailing-spaces: 0 108 | indent: 0 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore specific files 2 | .settings.xml 3 | .monitor 4 | .DS_Store 5 | *.orig 6 | npm-debug.log 7 | npm-debug.log.* 8 | *.dat 9 | 10 | # Ignore various temporary files 11 | *~ 12 | *.swp 13 | 14 | 15 | # Ignore various Node.js related directories and files 16 | node_modules 17 | node_modules/**/* 18 | coverage/**/* 19 | lib/**/* 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | src/ 3 | coverage/ 4 | examples/ 5 | .babelrc 6 | .editorconfig 7 | .eslintignore 8 | .eslintrc 9 | .gitignore 10 | karma.conf.js 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.0.0 2 | 3 | - typescript: Update typings to export I18NextRequest instead of extending global express Request [211](https://github.com/i18next/i18next-express-middleware/pull/211) 4 | 5 | ### 1.9.1 6 | 7 | - only setHeader if lng has value 8 | 9 | ### 1.9.0 10 | 11 | - Remove Express alias of res.setHeader to support non-Express use with connect [199](https://github.com/i18next/i18next-express-middleware/pull/199) 12 | 13 | ### 1.8.2 14 | 15 | - typescript: fix: TypeScript i18next import [192](https://github.com/i18next/i18next-express-middleware/pull/192) 16 | 17 | ### 1.8.1 18 | 19 | - typescript: update LanguageDetector (fix type error since i18next v17.0.8) [190](https://github.com/i18next/i18next-express-middleware/pull/190) 20 | 21 | ### 1.8.0 22 | 23 | - Add optional "lookupHeader" to replace default header [180](https://github.com/i18next/i18next-express-middleware/pull/180) 24 | 25 | ### 1.7.3 26 | 27 | - typescript: Update typings to allow req.i18n and req.t without Typescript error. [177](https://github.com/i18next/i18next-express-middleware/pull/177) 28 | 29 | ### 1.7.2 30 | 31 | - typescript: Update typings to allow req.language and req.languages [176](https://github.com/i18next/i18next-express-middleware/pull/176) 32 | 33 | ### 1.7.1 34 | 35 | - Insensitive checking language-locale [172](https://github.com/i18next/i18next-express-middleware/pull/172) 36 | 37 | ### 1.7.0 38 | 39 | - add typings [170](https://github.com/i18next/i18next-express-middleware/pull/170) 40 | 41 | ### 1.6.0 42 | 43 | - Handle spaces in Accept-Language header [169](https://github.com/i18next/i18next-express-middleware/pull/169) 44 | 45 | ### 1.5.0 46 | 47 | - ignoreRoute can now be a function [#57](https://github.com/i18next/i18next-express-middleware/issues/57) 48 | 49 | ### 1.4.1 50 | 51 | - Update locals on languageChanged [PR163](https://github.com/i18next/i18next-express-middleware/pull/163) 52 | 53 | ### 1.4.0 54 | 55 | - Set Content-Language response header [PR159](https://github.com/i18next/i18next-express-middleware/pull/159) 56 | 57 | ### 1.3.2 58 | 59 | - npm ignore example folder 60 | 61 | ### 1.3.1 62 | 63 | - check if cookie secure already set [PR155](https://github.com/i18next/i18next-express-middleware/pull/155) 64 | 65 | ### 1.3.0 66 | 67 | - allow setting of cookie secure [PR154](https://github.com/i18next/i18next-express-middleware/pull/154) 68 | 69 | ### 1.2.1 70 | 71 | - add language detector to default export [PR153](https://github.com/i18next/i18next-express-middleware/pull/153) 72 | 73 | ### 1.2.0 74 | 75 | - fixes cookie set crash on newer versions of express (v4) [PR151](https://github.com/i18next/i18next-express-middleware/pull/151) 76 | 77 | ### 1.1.1 78 | 79 | - fixes 1.1.0 detection should return a string not an array 80 | 81 | ### 1.1.0 82 | 83 | - support returning fallback language when is set as Object [PR146](https://github.com/i18next/i18next-express-middleware/pull/146) 84 | 85 | ### 1.0.11 86 | 87 | - check if the headers are sent before cookies set [PR145](https://github.com/i18next/i18next-express-middleware/pull/145) 88 | 89 | ### 1.0.10 90 | 91 | - Use a named function for the middleware over an anonymous [PR144](https://github.com/i18next/i18next-express-middleware/pull/144) 92 | 93 | ### 1.0.9 94 | 95 | - Guard against non-string detections [PR141](https://github.com/i18next/i18next-express-middleware/pull/141) 96 | 97 | ### 1.0.7 98 | 99 | - Fix path detection can crash app (if req.originalUrl is not set) [#137](https://github.com/i18next/i18next-express-middleware/pull/137) 100 | 101 | ### 1.0.6 102 | 103 | - update cookie dependency adding overwrite flag [#127](https://github.com/i18next/i18next-express-middleware/issues/127) 104 | 105 | ### 1.0.5 106 | 107 | - fixes persisting lng on calling changeLanguage [#918](https://github.com/i18next/i18next/issues/918) 108 | 109 | ### 1.0.4 110 | 111 | - fixes rare issue in race condition of init call in cloned instance 112 | 113 | ### 1.0.3 114 | 115 | - Fix path lookup logic where query was being included in path [PR125](https://github.com/i18next/i18next-express-middleware/pull/125) 116 | 117 | ### 1.0.2 118 | 119 | - call next without loading languages if there is no lng 120 | 121 | ### 1.0.1 122 | 123 | - fixes call to loadLanguages 124 | 125 | ### 1.0.0 126 | 127 | - use an i18next instance clone instead of a pseudo/mock object, keep clone lng and req.lng in sync 128 | 129 | ### 0.4.0 130 | 131 | - adds localized routes 132 | - adds default export for es6 users not want to use named exports.default 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 i18next 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | Can be replaced with: [i18next-http-middleware](https://github.com/i18next/i18next-http-middleware) 4 | 5 | --- 6 | 7 | # Introduction 8 | 9 | This is a middleware to use i18next in express.js. 10 | 11 | # Getting started 12 | 13 | Source can be loaded via [npm](https://www.npmjs.com/package/i18next-express-middleware). 14 | 15 | ``` 16 | # npm package 17 | $ npm install i18next-express-middleware 18 | ``` 19 | 20 | ## wire up i18next to request object 21 | 22 | ```js 23 | var i18next = require("i18next"); 24 | var middleware = require("i18next-express-middleware"); 25 | var express = require("express"); 26 | 27 | i18next.use(middleware.LanguageDetector).init({ 28 | preload: ["en", "de", "it"], 29 | ...otherOptions 30 | }); 31 | 32 | var app = express(); 33 | app.use( 34 | middleware.handle(i18next, { 35 | ignoreRoutes: ["/foo"], // or function(req, res, options, i18next) { /* return true to ignore */ } 36 | removeLngFromUrl: false 37 | }) 38 | ); 39 | 40 | // in your request handler 41 | app.get("myRoute", function(req, res) { 42 | var lng = req.language; // 'de-CH' 43 | var lngs = req.languages; // ['de-CH', 'de', 'en'] 44 | req.i18n.changeLanguage("en"); // will not load that!!! assert it was preloaded 45 | 46 | var exists = req.i18n.exists("myKey"); 47 | var translation = req.t("myKey"); 48 | }); 49 | 50 | // in your views, eg. in pug (ex. jade) 51 | div = t("myKey"); 52 | ``` 53 | 54 | ## add routes 55 | 56 | ```js 57 | // missing keys; make sure the body is parsed (i.e. with [body-parser](https://github.com/expressjs/body-parser#bodyparserjsonoptions)) 58 | app.post("/locales/add/:lng/:ns", middleware.missingKeyHandler(i18next)); 59 | 60 | // multiload backend route 61 | app.get("/locales/resources.json", middleware.getResourcesHandler(i18next)); 62 | ``` 63 | 64 | ## add localized routes 65 | 66 | You can add your routes directly to the express app 67 | 68 | ```js 69 | var express = require("express"), 70 | app = express(), 71 | i18next = require("i18next"), 72 | FilesystemBackend = require("i18next-node-fs-backend"), 73 | i18nextMiddleware = require("i18next-express-middleware"), 74 | port = 3000; 75 | 76 | i18next 77 | .use(i18nextMiddleware.LanguageDetector) 78 | .use(FilesystemBackend) 79 | .init({ preload: ["en", "de", "it"], ...otherOptions }, function() { 80 | i18nextMiddleware.addRoute( 81 | i18next, 82 | "/:lng/key-to-translate", 83 | ["en", "de", "it"], 84 | app, 85 | "get", 86 | function(req, res) { 87 | //endpoint function 88 | } 89 | ); 90 | }); 91 | app.use(i18nextMiddleware.handle(i18next)); 92 | app.listen(port, function() { 93 | console.log("Server listening on port", port); 94 | }); 95 | ``` 96 | 97 | or to an express router 98 | 99 | ```js 100 | var express = require("express"), 101 | app = express(), 102 | i18next = require("i18next"), 103 | FilesystemBackend = require("i18next-node-fs-backend"), 104 | i18nextMiddleware = require("i18next-express-middleware"), 105 | router = require("express").Router(), 106 | port = 3000; 107 | 108 | i18next 109 | .use(i18nextMiddleware.LanguageDetector) 110 | .use(FilesystemBackend) 111 | .init({ preload: ["en", "de", "it"], ...otherOptions }, function() { 112 | i18nextMiddleware.addRoute( 113 | i18next, 114 | "/:lng/key-to-translate", 115 | ["en", "de", "it"], 116 | router, 117 | "get", 118 | function(req, res) { 119 | //endpoint function 120 | } 121 | ); 122 | app.use("/", router); 123 | }); 124 | app.use(i18nextMiddleware.handle(i18next)); 125 | app.listen(port, function() { 126 | console.log("Server listening on port", port); 127 | }); 128 | ``` 129 | 130 | ## language detection 131 | 132 | Detects user language from current request. Comes with support for: 133 | 134 | - path 135 | - cookie 136 | - header 137 | - querystring 138 | - session 139 | 140 | Wiring up: 141 | 142 | ```js 143 | var i18next = require("i18next"); 144 | var middleware = require("i18next-express-middleware"); 145 | 146 | i18next.use(middleware.LanguageDetector).init(i18nextOptions); 147 | ``` 148 | 149 | As with all modules you can either pass the constructor function (class) to the i18next.use or a concrete instance. 150 | 151 | ## Detector Options 152 | 153 | ```js 154 | { 155 | // order and from where user language should be detected 156 | order: [/*'path', 'session', */ 'querystring', 'cookie', 'header'], 157 | 158 | // keys or params to lookup language from 159 | lookupQuerystring: 'lng', 160 | lookupCookie: 'i18next', 161 | lookupHeader: 'accept-language', 162 | lookupSession: 'lng', 163 | lookupPath: 'lng', 164 | lookupFromPathIndex: 0, 165 | 166 | // cache user language 167 | caches: false, // ['cookie'] 168 | 169 | // optional expire and domain for set cookie 170 | cookieExpirationDate: new Date(), 171 | cookieDomain: 'myDomain', 172 | cookieSecure: true // if need secure cookie 173 | } 174 | ``` 175 | 176 | Options can be passed in: 177 | 178 | **preferred** - by setting options.detection in i18next.init: 179 | 180 | ```js 181 | var i18next = require("i18next"); 182 | var middleware = require("i18next-express-middleware"); 183 | 184 | i18next.use(middleware.LanguageDetector).init({ 185 | detection: options 186 | }); 187 | ``` 188 | 189 | on construction: 190 | 191 | ```js 192 | var middleware = require("i18next-express-middleware"); 193 | var lngDetector = new middleware.LanguageDetector(null, options); 194 | ``` 195 | 196 | via calling init: 197 | 198 | ```js 199 | var middleware = require("i18next-express-middleware"); 200 | 201 | var lngDetector = new middleware.LanguageDetector(); 202 | lngDetector.init(options); 203 | ``` 204 | 205 | ## Adding own detection functionality 206 | 207 | ### interface 208 | 209 | ```js 210 | module.exports = { 211 | name: 'myDetectorsName', 212 | 213 | lookup: function(req, res, options) { 214 | // options -> are passed in options 215 | return 'en'; 216 | }, 217 | 218 | cacheUserLanguage: function(req, res, lng, options) { 219 | // options -> are passed in options 220 | // lng -> current language, will be called after init and on changeLanguage 221 | 222 | // store it 223 | } 224 | }; 225 | ``` 226 | 227 | ### adding it 228 | 229 | ```js 230 | var i18next = require("i18next"); 231 | var middleware = require("i18next-express-middleware"); 232 | 233 | var lngDetector = new middleware.LanguageDetector(); 234 | lngDetector.addDetector(myDetector); 235 | 236 | i18next.use(lngDetector).init({ 237 | detection: options 238 | }); 239 | ``` 240 | 241 | --- 242 | 243 |

Gold Sponsors

244 | 245 |

246 | 247 | 248 | 249 |

250 | -------------------------------------------------------------------------------- /examples/basic-handlebars/README.md: -------------------------------------------------------------------------------- 1 | # run the sample 2 | 3 | ``` 4 | $ npm i 5 | $ npm start 6 | ``` 7 | 8 | open: http://localhost:8080 9 | -------------------------------------------------------------------------------- /examples/basic-handlebars/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const i18next = require('i18next'); 3 | const i18nextMiddleware = require('i18next-express-middleware'); 4 | const Backend = require('i18next-node-fs-backend'); 5 | const expressHbs = require('express-handlebars'); 6 | 7 | const app = express(); 8 | const port = process.env.PORT || 8080; 9 | 10 | i18next 11 | .use(Backend) 12 | .use(i18nextMiddleware.LanguageDetector) 13 | .init({ 14 | backend: { 15 | loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json', 16 | addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json' 17 | }, 18 | fallbackLng: 'en', 19 | preload: ['en', 'de'], 20 | saveMissing: true 21 | }); 22 | 23 | app.engine('hbs', expressHbs({extname:'hbs', defaultLayout:'main.hbs'})); 24 | app.set('view engine', 'hbs') 25 | app.use(i18nextMiddleware.handle(i18next)); 26 | 27 | app.get('/', (req, res) => { 28 | res.render('index') 29 | }); 30 | 31 | app.listen(port, (err) => { 32 | console.log(`Server is listening on port ${port}`); 33 | }); 34 | -------------------------------------------------------------------------------- /examples/basic-handlebars/locales/de/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hallo Welt!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic-handlebars/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hello World!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic-handlebars/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "description": "Node Express server with i18next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "express": "4.15.3", 11 | "express-handlebars": "3.0.0", 12 | "i18next": "8.3.0", 13 | "i18next-express-middleware": "1.0.5", 14 | "i18next-node-fs-backend": "1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/basic-handlebars/views/index.hbs: -------------------------------------------------------------------------------- 1 |

{{t "home.title" }}

2 |
3 | english 4 |   |   5 | deutsch 6 |
7 | -------------------------------------------------------------------------------- /examples/basic-handlebars/views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | i18next - express with hbs 4 | 5 | 6 | 7 | {{{body}}} 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/basic-locize/README.md: -------------------------------------------------------------------------------- 1 | # run the sample 2 | 3 | ``` 4 | $ npm i 5 | $ npm start 6 | ``` 7 | 8 | open: http://localhost:8080 9 | -------------------------------------------------------------------------------- /examples/basic-locize/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const i18next = require('i18next'); 3 | const i18nextMiddleware = require('i18next-express-middleware'); 4 | const Backend = require('i18next-node-locize-backend'); 5 | 6 | const app = express(); 7 | const port = process.env.PORT || 8080; 8 | 9 | i18next 10 | .use(Backend) 11 | .use(i18nextMiddleware.LanguageDetector) 12 | .init({ 13 | backend: { 14 | referenceLng: 'en', 15 | projectId: '79a28dc3-b858-44a4-9603-93455e9e8c65' 16 | // apiKey: 'do not show in production', 17 | }, 18 | fallbackLng: 'en', 19 | preload: ['en', 'de'], 20 | debug: true, 21 | saveMissing: true 22 | }); 23 | 24 | app.use(i18nextMiddleware.handle(i18next)); 25 | 26 | app.get('/', (req, res) => { 27 | res.send(req.t('home.title')); 28 | }); 29 | 30 | app.listen(port, err => { 31 | console.log(`Server is listening on port ${port}`); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/basic-locize/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.5.4", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.4.tgz", 10 | "integrity": "sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q==", 11 | "requires": { 12 | "regenerator-runtime": "^0.13.2" 13 | } 14 | }, 15 | "accepts": { 16 | "version": "1.3.7", 17 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 18 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 19 | "requires": { 20 | "mime-types": "~2.1.24", 21 | "negotiator": "0.6.2" 22 | } 23 | }, 24 | "ajv": { 25 | "version": "6.10.1", 26 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", 27 | "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", 28 | "requires": { 29 | "fast-deep-equal": "^2.0.1", 30 | "fast-json-stable-stringify": "^2.0.0", 31 | "json-schema-traverse": "^0.4.1", 32 | "uri-js": "^4.2.2" 33 | } 34 | }, 35 | "array-flatten": { 36 | "version": "1.1.1", 37 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 38 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 39 | }, 40 | "asn1": { 41 | "version": "0.2.4", 42 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 43 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 44 | "requires": { 45 | "safer-buffer": "~2.1.0" 46 | } 47 | }, 48 | "assert-plus": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 51 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 52 | }, 53 | "asynckit": { 54 | "version": "0.4.0", 55 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 56 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 57 | }, 58 | "aws-sign2": { 59 | "version": "0.7.0", 60 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 61 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 62 | }, 63 | "aws4": { 64 | "version": "1.8.0", 65 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 66 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 67 | }, 68 | "bcrypt-pbkdf": { 69 | "version": "1.0.2", 70 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 71 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 72 | "requires": { 73 | "tweetnacl": "^0.14.3" 74 | } 75 | }, 76 | "body-parser": { 77 | "version": "1.19.0", 78 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 79 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 80 | "requires": { 81 | "bytes": "3.1.0", 82 | "content-type": "~1.0.4", 83 | "debug": "2.6.9", 84 | "depd": "~1.1.2", 85 | "http-errors": "1.7.2", 86 | "iconv-lite": "0.4.24", 87 | "on-finished": "~2.3.0", 88 | "qs": "6.7.0", 89 | "raw-body": "2.4.0", 90 | "type-is": "~1.6.17" 91 | }, 92 | "dependencies": { 93 | "qs": { 94 | "version": "6.7.0", 95 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 96 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 97 | } 98 | } 99 | }, 100 | "bytes": { 101 | "version": "3.1.0", 102 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 103 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 104 | }, 105 | "caseless": { 106 | "version": "0.12.0", 107 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 108 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 109 | }, 110 | "combined-stream": { 111 | "version": "1.0.8", 112 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 113 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 114 | "requires": { 115 | "delayed-stream": "~1.0.0" 116 | } 117 | }, 118 | "content-disposition": { 119 | "version": "0.5.3", 120 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 121 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 122 | "requires": { 123 | "safe-buffer": "5.1.2" 124 | }, 125 | "dependencies": { 126 | "safe-buffer": { 127 | "version": "5.1.2", 128 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 129 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 130 | } 131 | } 132 | }, 133 | "content-type": { 134 | "version": "1.0.4", 135 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 136 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 137 | }, 138 | "cookie": { 139 | "version": "0.4.0", 140 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 141 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 142 | }, 143 | "cookie-signature": { 144 | "version": "1.0.6", 145 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 146 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 147 | }, 148 | "cookies": { 149 | "version": "0.7.1", 150 | "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", 151 | "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", 152 | "requires": { 153 | "depd": "~1.1.1", 154 | "keygrip": "~1.0.2" 155 | } 156 | }, 157 | "core-util-is": { 158 | "version": "1.0.2", 159 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 160 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 161 | }, 162 | "dashdash": { 163 | "version": "1.14.1", 164 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 165 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 166 | "requires": { 167 | "assert-plus": "^1.0.0" 168 | } 169 | }, 170 | "debug": { 171 | "version": "2.6.9", 172 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 173 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 174 | "requires": { 175 | "ms": "2.0.0" 176 | } 177 | }, 178 | "delayed-stream": { 179 | "version": "1.0.0", 180 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 181 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 182 | }, 183 | "depd": { 184 | "version": "1.1.2", 185 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 186 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 187 | }, 188 | "destroy": { 189 | "version": "1.0.4", 190 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 191 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 192 | }, 193 | "ecc-jsbn": { 194 | "version": "0.1.2", 195 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 196 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 197 | "requires": { 198 | "jsbn": "~0.1.0", 199 | "safer-buffer": "^2.1.0" 200 | } 201 | }, 202 | "ee-first": { 203 | "version": "1.1.1", 204 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 205 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 206 | }, 207 | "encodeurl": { 208 | "version": "1.0.2", 209 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 210 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 211 | }, 212 | "escape-html": { 213 | "version": "1.0.3", 214 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 215 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 216 | }, 217 | "etag": { 218 | "version": "1.8.1", 219 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 220 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 221 | }, 222 | "express": { 223 | "version": "4.17.1", 224 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 225 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 226 | "requires": { 227 | "accepts": "~1.3.7", 228 | "array-flatten": "1.1.1", 229 | "body-parser": "1.19.0", 230 | "content-disposition": "0.5.3", 231 | "content-type": "~1.0.4", 232 | "cookie": "0.4.0", 233 | "cookie-signature": "1.0.6", 234 | "debug": "2.6.9", 235 | "depd": "~1.1.2", 236 | "encodeurl": "~1.0.2", 237 | "escape-html": "~1.0.3", 238 | "etag": "~1.8.1", 239 | "finalhandler": "~1.1.2", 240 | "fresh": "0.5.2", 241 | "merge-descriptors": "1.0.1", 242 | "methods": "~1.1.2", 243 | "on-finished": "~2.3.0", 244 | "parseurl": "~1.3.3", 245 | "path-to-regexp": "0.1.7", 246 | "proxy-addr": "~2.0.5", 247 | "qs": "6.7.0", 248 | "range-parser": "~1.2.1", 249 | "safe-buffer": "5.1.2", 250 | "send": "0.17.1", 251 | "serve-static": "1.14.1", 252 | "setprototypeof": "1.1.1", 253 | "statuses": "~1.5.0", 254 | "type-is": "~1.6.18", 255 | "utils-merge": "1.0.1", 256 | "vary": "~1.1.2" 257 | }, 258 | "dependencies": { 259 | "qs": { 260 | "version": "6.7.0", 261 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 262 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 263 | }, 264 | "safe-buffer": { 265 | "version": "5.1.2", 266 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 267 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 268 | } 269 | } 270 | }, 271 | "extend": { 272 | "version": "3.0.2", 273 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 274 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 275 | }, 276 | "extsprintf": { 277 | "version": "1.3.0", 278 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 279 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 280 | }, 281 | "fast-deep-equal": { 282 | "version": "2.0.1", 283 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 284 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 285 | }, 286 | "fast-json-stable-stringify": { 287 | "version": "2.0.0", 288 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 289 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 290 | }, 291 | "finalhandler": { 292 | "version": "1.1.2", 293 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 294 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 295 | "requires": { 296 | "debug": "2.6.9", 297 | "encodeurl": "~1.0.2", 298 | "escape-html": "~1.0.3", 299 | "on-finished": "~2.3.0", 300 | "parseurl": "~1.3.3", 301 | "statuses": "~1.5.0", 302 | "unpipe": "~1.0.0" 303 | } 304 | }, 305 | "forever-agent": { 306 | "version": "0.6.1", 307 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 308 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 309 | }, 310 | "form-data": { 311 | "version": "2.3.3", 312 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 313 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 314 | "requires": { 315 | "asynckit": "^0.4.0", 316 | "combined-stream": "^1.0.6", 317 | "mime-types": "^2.1.12" 318 | } 319 | }, 320 | "forwarded": { 321 | "version": "0.1.2", 322 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 323 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 324 | }, 325 | "fresh": { 326 | "version": "0.5.2", 327 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 328 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 329 | }, 330 | "getpass": { 331 | "version": "0.1.7", 332 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 333 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 334 | "requires": { 335 | "assert-plus": "^1.0.0" 336 | } 337 | }, 338 | "har-schema": { 339 | "version": "2.0.0", 340 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 341 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 342 | }, 343 | "har-validator": { 344 | "version": "5.1.3", 345 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 346 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 347 | "requires": { 348 | "ajv": "^6.5.5", 349 | "har-schema": "^2.0.0" 350 | } 351 | }, 352 | "http-errors": { 353 | "version": "1.7.2", 354 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 355 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 356 | "requires": { 357 | "depd": "~1.1.2", 358 | "inherits": "2.0.3", 359 | "setprototypeof": "1.1.1", 360 | "statuses": ">= 1.5.0 < 2", 361 | "toidentifier": "1.0.0" 362 | } 363 | }, 364 | "http-signature": { 365 | "version": "1.2.0", 366 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 367 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 368 | "requires": { 369 | "assert-plus": "^1.0.0", 370 | "jsprim": "^1.2.2", 371 | "sshpk": "^1.7.0" 372 | } 373 | }, 374 | "i18next": { 375 | "version": "17.0.6", 376 | "resolved": "https://registry.npmjs.org/i18next/-/i18next-17.0.6.tgz", 377 | "integrity": "sha512-bdNhzhcM6RG5m82RypVguCrAQNie/ycxW0Q5C6K9UDWD5hqApZfdJFbj4Ikz9jxIR+Ja1eg0yCQLhlCT+opwIg==", 378 | "requires": { 379 | "@babel/runtime": "^7.3.1" 380 | } 381 | }, 382 | "i18next-express-middleware": { 383 | "version": "1.8.0", 384 | "resolved": "https://registry.npmjs.org/i18next-express-middleware/-/i18next-express-middleware-1.8.0.tgz", 385 | "integrity": "sha512-vbEV+YbGoXNpv59WM7MHvdbBpPUS6itMIgpX21mjmu8/naS/NlmZe6vMNFGUIWs5PbXGnKWm7ucKxYmc2RV8vA==", 386 | "requires": { 387 | "cookies": "0.7.1" 388 | } 389 | }, 390 | "i18next-node-locize-backend": { 391 | "version": "2.4.3", 392 | "resolved": "https://registry.npmjs.org/i18next-node-locize-backend/-/i18next-node-locize-backend-2.4.3.tgz", 393 | "integrity": "sha512-nQvGD2H/Achvb9lK/fuP7fP8TOhmfEKLv/dZS5wLjUOpg9gQP1zmr8UEx7nd9EvKtz+LzVFUg0R209wNjAsKiA==", 394 | "requires": { 395 | "request": "^2.88.0" 396 | } 397 | }, 398 | "iconv-lite": { 399 | "version": "0.4.24", 400 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 401 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 402 | "requires": { 403 | "safer-buffer": ">= 2.1.2 < 3" 404 | } 405 | }, 406 | "inherits": { 407 | "version": "2.0.3", 408 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 409 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 410 | }, 411 | "ipaddr.js": { 412 | "version": "1.9.0", 413 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 414 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 415 | }, 416 | "is-typedarray": { 417 | "version": "1.0.0", 418 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 419 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 420 | }, 421 | "isstream": { 422 | "version": "0.1.2", 423 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 424 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 425 | }, 426 | "jsbn": { 427 | "version": "0.1.1", 428 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 429 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 430 | }, 431 | "json-schema": { 432 | "version": "0.2.3", 433 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 434 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 435 | }, 436 | "json-schema-traverse": { 437 | "version": "0.4.1", 438 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 439 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 440 | }, 441 | "json-stringify-safe": { 442 | "version": "5.0.1", 443 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 444 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 445 | }, 446 | "jsprim": { 447 | "version": "1.4.1", 448 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 449 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 450 | "requires": { 451 | "assert-plus": "1.0.0", 452 | "extsprintf": "1.3.0", 453 | "json-schema": "0.2.3", 454 | "verror": "1.10.0" 455 | } 456 | }, 457 | "keygrip": { 458 | "version": "1.0.3", 459 | "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", 460 | "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==" 461 | }, 462 | "media-typer": { 463 | "version": "0.3.0", 464 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 465 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 466 | }, 467 | "merge-descriptors": { 468 | "version": "1.0.1", 469 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 470 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 471 | }, 472 | "methods": { 473 | "version": "1.1.2", 474 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 475 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 476 | }, 477 | "mime": { 478 | "version": "1.6.0", 479 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 480 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 481 | }, 482 | "mime-db": { 483 | "version": "1.40.0", 484 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 485 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 486 | }, 487 | "mime-types": { 488 | "version": "2.1.24", 489 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 490 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 491 | "requires": { 492 | "mime-db": "1.40.0" 493 | } 494 | }, 495 | "ms": { 496 | "version": "2.0.0", 497 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 498 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 499 | }, 500 | "negotiator": { 501 | "version": "0.6.2", 502 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 503 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 504 | }, 505 | "oauth-sign": { 506 | "version": "0.9.0", 507 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 508 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 509 | }, 510 | "on-finished": { 511 | "version": "2.3.0", 512 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 513 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 514 | "requires": { 515 | "ee-first": "1.1.1" 516 | } 517 | }, 518 | "parseurl": { 519 | "version": "1.3.3", 520 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 521 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 522 | }, 523 | "path-to-regexp": { 524 | "version": "0.1.7", 525 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 526 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 527 | }, 528 | "performance-now": { 529 | "version": "2.1.0", 530 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 531 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 532 | }, 533 | "proxy-addr": { 534 | "version": "2.0.5", 535 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 536 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 537 | "requires": { 538 | "forwarded": "~0.1.2", 539 | "ipaddr.js": "1.9.0" 540 | } 541 | }, 542 | "psl": { 543 | "version": "1.2.0", 544 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", 545 | "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" 546 | }, 547 | "punycode": { 548 | "version": "2.1.1", 549 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 550 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 551 | }, 552 | "qs": { 553 | "version": "6.5.2", 554 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 555 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 556 | }, 557 | "range-parser": { 558 | "version": "1.2.1", 559 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 560 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 561 | }, 562 | "raw-body": { 563 | "version": "2.4.0", 564 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 565 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 566 | "requires": { 567 | "bytes": "3.1.0", 568 | "http-errors": "1.7.2", 569 | "iconv-lite": "0.4.24", 570 | "unpipe": "1.0.0" 571 | } 572 | }, 573 | "regenerator-runtime": { 574 | "version": "0.13.2", 575 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", 576 | "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" 577 | }, 578 | "request": { 579 | "version": "2.88.0", 580 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 581 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 582 | "requires": { 583 | "aws-sign2": "~0.7.0", 584 | "aws4": "^1.8.0", 585 | "caseless": "~0.12.0", 586 | "combined-stream": "~1.0.6", 587 | "extend": "~3.0.2", 588 | "forever-agent": "~0.6.1", 589 | "form-data": "~2.3.2", 590 | "har-validator": "~5.1.0", 591 | "http-signature": "~1.2.0", 592 | "is-typedarray": "~1.0.0", 593 | "isstream": "~0.1.2", 594 | "json-stringify-safe": "~5.0.1", 595 | "mime-types": "~2.1.19", 596 | "oauth-sign": "~0.9.0", 597 | "performance-now": "^2.1.0", 598 | "qs": "~6.5.2", 599 | "safe-buffer": "^5.1.2", 600 | "tough-cookie": "~2.4.3", 601 | "tunnel-agent": "^0.6.0", 602 | "uuid": "^3.3.2" 603 | } 604 | }, 605 | "safe-buffer": { 606 | "version": "5.2.0", 607 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 608 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 609 | }, 610 | "safer-buffer": { 611 | "version": "2.1.2", 612 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 613 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 614 | }, 615 | "send": { 616 | "version": "0.17.1", 617 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 618 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 619 | "requires": { 620 | "debug": "2.6.9", 621 | "depd": "~1.1.2", 622 | "destroy": "~1.0.4", 623 | "encodeurl": "~1.0.2", 624 | "escape-html": "~1.0.3", 625 | "etag": "~1.8.1", 626 | "fresh": "0.5.2", 627 | "http-errors": "~1.7.2", 628 | "mime": "1.6.0", 629 | "ms": "2.1.1", 630 | "on-finished": "~2.3.0", 631 | "range-parser": "~1.2.1", 632 | "statuses": "~1.5.0" 633 | }, 634 | "dependencies": { 635 | "ms": { 636 | "version": "2.1.1", 637 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 638 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 639 | } 640 | } 641 | }, 642 | "serve-static": { 643 | "version": "1.14.1", 644 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 645 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 646 | "requires": { 647 | "encodeurl": "~1.0.2", 648 | "escape-html": "~1.0.3", 649 | "parseurl": "~1.3.3", 650 | "send": "0.17.1" 651 | } 652 | }, 653 | "setprototypeof": { 654 | "version": "1.1.1", 655 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 656 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 657 | }, 658 | "sshpk": { 659 | "version": "1.16.1", 660 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 661 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 662 | "requires": { 663 | "asn1": "~0.2.3", 664 | "assert-plus": "^1.0.0", 665 | "bcrypt-pbkdf": "^1.0.0", 666 | "dashdash": "^1.12.0", 667 | "ecc-jsbn": "~0.1.1", 668 | "getpass": "^0.1.1", 669 | "jsbn": "~0.1.0", 670 | "safer-buffer": "^2.0.2", 671 | "tweetnacl": "~0.14.0" 672 | } 673 | }, 674 | "statuses": { 675 | "version": "1.5.0", 676 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 677 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 678 | }, 679 | "toidentifier": { 680 | "version": "1.0.0", 681 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 682 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 683 | }, 684 | "tough-cookie": { 685 | "version": "2.4.3", 686 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 687 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 688 | "requires": { 689 | "psl": "^1.1.24", 690 | "punycode": "^1.4.1" 691 | }, 692 | "dependencies": { 693 | "punycode": { 694 | "version": "1.4.1", 695 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 696 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 697 | } 698 | } 699 | }, 700 | "tunnel-agent": { 701 | "version": "0.6.0", 702 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 703 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 704 | "requires": { 705 | "safe-buffer": "^5.0.1" 706 | } 707 | }, 708 | "tweetnacl": { 709 | "version": "0.14.5", 710 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 711 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 712 | }, 713 | "type-is": { 714 | "version": "1.6.18", 715 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 716 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 717 | "requires": { 718 | "media-typer": "0.3.0", 719 | "mime-types": "~2.1.24" 720 | } 721 | }, 722 | "unpipe": { 723 | "version": "1.0.0", 724 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 725 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 726 | }, 727 | "uri-js": { 728 | "version": "4.2.2", 729 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 730 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 731 | "requires": { 732 | "punycode": "^2.1.0" 733 | } 734 | }, 735 | "utils-merge": { 736 | "version": "1.0.1", 737 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 738 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 739 | }, 740 | "uuid": { 741 | "version": "3.3.2", 742 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 743 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 744 | }, 745 | "vary": { 746 | "version": "1.1.2", 747 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 748 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 749 | }, 750 | "verror": { 751 | "version": "1.10.0", 752 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 753 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 754 | "requires": { 755 | "assert-plus": "^1.0.0", 756 | "core-util-is": "1.0.2", 757 | "extsprintf": "^1.2.0" 758 | } 759 | } 760 | } 761 | } 762 | -------------------------------------------------------------------------------- /examples/basic-locize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "description": "Node Express server with i18next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "express": "4.17.1", 11 | "i18next": "17.0.6", 12 | "i18next-express-middleware": "1.8.0", 13 | "i18next-node-locize-backend": "^2.4.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/basic-pug/README.md: -------------------------------------------------------------------------------- 1 | # run the sample 2 | 3 | ``` 4 | $ npm i 5 | $ npm start 6 | ``` 7 | 8 | open: http://localhost:8080 9 | -------------------------------------------------------------------------------- /examples/basic-pug/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const i18next = require('i18next'); 3 | const i18nextMiddleware = require('i18next-express-middleware'); 4 | const Backend = require('i18next-node-fs-backend'); 5 | 6 | const app = express(); 7 | const port = process.env.PORT || 8080; 8 | 9 | i18next 10 | .use(Backend) 11 | .use(i18nextMiddleware.LanguageDetector) 12 | .init({ 13 | backend: { 14 | loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json', 15 | addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json' 16 | }, 17 | detection: { 18 | order: ['querystring', 'cookie'], 19 | caches: ['cookie'] 20 | }, 21 | fallbackLng: 'en', 22 | preload: ['en', 'de'], 23 | saveMissing: true 24 | }); 25 | 26 | app.set('view engine', 'pug') 27 | app.use(i18nextMiddleware.handle(i18next)); 28 | 29 | app.get('/', (req, res) => { 30 | res.render('index') 31 | }); 32 | 33 | app.listen(port, (err) => { 34 | console.log(`Server is listening on port ${port}`); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/basic-pug/locales/de/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hallo Welt!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic-pug/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hello World!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic-pug/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "description": "Node Express server with i18next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "express": "4.15.3", 11 | "i18next": "8.3.0", 12 | "i18next-express-middleware": "1.0.5", 13 | "i18next-node-fs-backend": "1.0.0", 14 | "pug": "2.0.0-rc.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/basic-pug/views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title i18next - express with pug 4 | body 5 | h1= t('home.title') 6 | div 7 | a(href="/?lng=en") english 8 | |   |   9 | a(href="/?lng=de") deutsch 10 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # run the sample 2 | 3 | ``` 4 | $ npm i 5 | $ npm start 6 | ``` 7 | 8 | open: http://localhost:8080 9 | -------------------------------------------------------------------------------- /examples/basic/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const i18next = require('i18next'); 3 | const i18nextMiddleware = require('i18next-express-middleware'); 4 | const Backend = require('i18next-node-fs-backend'); 5 | 6 | const app = express(); 7 | const port = process.env.PORT || 8080; 8 | 9 | i18next 10 | .use(Backend) 11 | .use(i18nextMiddleware.LanguageDetector) 12 | .init({ 13 | // debug: true, 14 | backend: { 15 | loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json', 16 | addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json' 17 | }, 18 | fallbackLng: 'en', 19 | preload: ['en', 'de'], 20 | saveMissing: true 21 | }); 22 | 23 | app.use(i18nextMiddleware.handle(i18next)); 24 | 25 | app.get('/', (req, res) => { 26 | res.send(req.t('home.title')); 27 | }); 28 | 29 | app.listen(port, (err) => { 30 | console.log(`Server is listening on port ${port}`); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/basic/locales/de/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hallo Welt!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Hello World!" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "description": "Node Express server with i18next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "express": "4.17.1", 11 | "i18next": "19.3.4", 12 | "i18next-express-middleware": "1.9.1", 13 | "i18next-node-fs-backend": "2.1.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as i18next from 'i18next'; 3 | 4 | 5 | declare module 'i18next-express-middleware' { 6 | type I18next = i18next.i18n; 7 | type IgnoreRoutesFunction = (req: express.Request, res: express.Response, options: HandleOptions, i18next: I18next) => boolean; 8 | type App = express.Application | express.Router; 9 | 10 | type I18NextRequest = express.Request & { 11 | language: string; 12 | languages: string[]; 13 | i18n:i18next.i18n; 14 | t:i18next.TFunction; 15 | } 16 | 17 | interface HandleOptions { 18 | ignoreRoutes?: string[] | IgnoreRoutesFunction; 19 | removeLngFromUrl?: boolean; 20 | } 21 | interface GetResourcesHandlerOptions { 22 | maxAge?: number; 23 | cache?: boolean; 24 | lngParam?: string; 25 | nsParam?: string; 26 | } 27 | interface MissingKeyHandlerOptions { 28 | lngParam?: string; 29 | nsParam?: string; 30 | } 31 | 32 | export function handle(i18next: I18next, options?: HandleOptions): express.Handler; 33 | export function getResourcesHandler(i18next: I18next, options?: GetResourcesHandlerOptions): express.Handler; 34 | export function missingKeyHandler(i18next: I18next, options?: MissingKeyHandlerOptions): express.Handler; 35 | export function addRoute(i18next: I18next, route: string, lngs: string[], app: App, verb: string, fc: express.RequestHandler): void; 36 | export function addRoute(i18next: I18next, route: string, lngs: string[], app: App, fc: express.RequestHandler): void; 37 | 38 | // LanguageDetector 39 | type LanguageDetectorServices = any; 40 | type LanguageDetectorOrder = string[]; 41 | type LanguageDetectorCaches = boolean | string[]; 42 | interface LanguageDetectorOptions { 43 | order?: LanguageDetectorOrder; 44 | lookupQuerystring?: string; 45 | lookupCookie?: string; 46 | lookupSession?: string; 47 | lookupFromPathIndex?: number; 48 | caches?: LanguageDetectorCaches; 49 | cookieExpirationDate?: Date; 50 | cookieDomain?: string; 51 | } 52 | interface LanguageDetectorAllOptions { 53 | fallbackLng: boolean | string | string[]; 54 | } 55 | interface LanguageDetectorInterfaceOptions { 56 | [name: string]: any; 57 | } 58 | interface LanguageDetectorInterface { 59 | name: string; 60 | lookup: (req: express.Request, res: express.Response, options?: LanguageDetectorInterfaceOptions) => string | string[]; 61 | 62 | cacheUserLanguage?: (req: express.Request, res: express.Response, lng: string, options?: object) => void; 63 | } 64 | 65 | export class LanguageDetector implements i18next.Module { 66 | type: "languageDetector"; 67 | 68 | constructor(services: LanguageDetectorServices, options?: LanguageDetectorOptions, allOptions?: LanguageDetectorAllOptions); 69 | constructor(options?: LanguageDetectorOptions, allOptions?: LanguageDetectorAllOptions); 70 | 71 | init(services: LanguageDetectorServices, options?: LanguageDetectorOptions, allOptions?: LanguageDetectorAllOptions): void; 72 | init(options?: LanguageDetectorOptions, allOptions?: LanguageDetectorAllOptions): void; 73 | addDetector(detector: LanguageDetectorInterface): void; 74 | detect(req: express.Request, res: express.Response, detectionOrder: LanguageDetectorOrder): void; 75 | cacheUserLanguage(req: express.Request, res: express.Response, lng: string, caches: LanguageDetectorCaches): void; 76 | } 77 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-middleware", 3 | "version": "2.0.0", 4 | "description": "express middleware for i18next", 5 | "main": "./lib/index.js", 6 | "types": "./index.d.ts", 7 | "keywords": [ 8 | "i18next", 9 | "i18next-express", 10 | "express", 11 | "middleware" 12 | ], 13 | "homepage": "https://github.com/i18next/i18next-express-middleware", 14 | "bugs": "https://github.com/i18next/i18next-express-middleware/issues", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/i18next/i18next-express-middleware.git" 18 | }, 19 | "dependencies": { 20 | "cookies": "0.7.1" 21 | }, 22 | "devDependencies": { 23 | "@types/express": "4.16.0", 24 | "babel-cli": "6.7.7", 25 | "babel-core": "6.7.7", 26 | "babel-eslint": "5.0.0", 27 | "babel-preset-es2015": "6.3.13", 28 | "babel-preset-stage-0": "6.5.0", 29 | "chai": "2.3.0", 30 | "eslint": "2.7.0", 31 | "i18next": "13.0.1", 32 | "mocha": "2.2.5", 33 | "sinon": "1.17.1" 34 | }, 35 | "scripts": { 36 | "transpile": "babel src -d lib", 37 | "build": "npm run transpile", 38 | "version": "npm run build", 39 | "postversion": "git push && git push --tags && rm -rf build/temp" 40 | }, 41 | "author": "Jan Mühlemann (https://github.com/jamuhl)", 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /src/LanguageDetector.js: -------------------------------------------------------------------------------- 1 | import * as utils from './utils'; 2 | import cookieLookup from './languageLookups/cookie'; 3 | import querystringLookup from './languageLookups/querystring'; 4 | import pathLookup from './languageLookups/path'; 5 | import headerLookup from './languageLookups/header'; 6 | import sessionLookup from './languageLookups/session'; 7 | 8 | function getDefaults() { 9 | return { 10 | order: [/*'path', 'session' */ 'querystring', 'cookie', 'header'], 11 | lookupQuerystring: 'lng', 12 | lookupCookie: 'i18next', 13 | lookupSession: 'lng', 14 | lookupFromPathIndex: 0, 15 | 16 | // cache user language 17 | caches: false // ['cookie'] 18 | //cookieExpirationDate: new Date(), 19 | //cookieDomain: 'myDomain' 20 | }; 21 | } 22 | 23 | class LanguageDetector { 24 | constructor(services, options = {}, allOptions = {}) { 25 | this.type = 'languageDetector'; 26 | this.detectors = {}; 27 | 28 | this.init(services, options, allOptions); 29 | } 30 | 31 | init(services, options = {}, allOptions = {}) { 32 | this.services = services; 33 | this.options = utils.defaults(options, this.options || {}, getDefaults()); 34 | this.allOptions = allOptions; 35 | 36 | this.addDetector(cookieLookup); 37 | this.addDetector(querystringLookup); 38 | this.addDetector(pathLookup); 39 | this.addDetector(headerLookup); 40 | this.addDetector(sessionLookup); 41 | } 42 | 43 | addDetector(detector) { 44 | this.detectors[detector.name] = detector; 45 | } 46 | 47 | detect(req, res, detectionOrder) { 48 | if (arguments.length < 2) return; 49 | if (!detectionOrder) detectionOrder = this.options.order; 50 | 51 | let found; 52 | detectionOrder.forEach(detectorName => { 53 | if (found || !this.detectors[detectorName]) return; 54 | 55 | let detections = this.detectors[detectorName].lookup(req, res, this.options); 56 | if(!detections) return; 57 | if (!Array.isArray(detections)) detections = [detections]; 58 | 59 | detections.forEach(lng => { 60 | if (found || typeof lng !== 'string') return; 61 | 62 | let cleanedLng = this.services.languageUtils.formatLanguageCode(lng); 63 | 64 | if (this.services.languageUtils.isWhitelisted(cleanedLng)) { 65 | found = cleanedLng; 66 | req.i18nextLookupName = detectorName; 67 | }; 68 | }); 69 | }); 70 | 71 | if (!found) { 72 | let fallbacks = this.allOptions.fallbackLng; 73 | if (typeof fallbacks === 'string') fallbacks = [fallbacks]; 74 | if (!fallbacks) fallbacks = []; 75 | 76 | if (Object.prototype.toString.apply(fallbacks) === '[object Array]') { 77 | found = fallbacks[0]; 78 | } else { 79 | found = fallbacks[0] || fallbacks.default && fallbacks.default[0]; 80 | } 81 | }; 82 | 83 | return found; 84 | } 85 | 86 | cacheUserLanguage(req, res, lng, caches) { 87 | if (arguments.length < 3) return; 88 | if (!caches) caches = this.options.caches; 89 | if (!caches) return; 90 | caches.forEach(cacheName => { 91 | if (this.detectors[cacheName] && this.detectors[cacheName].cacheUserLanguage) this.detectors[cacheName].cacheUserLanguage(req, res, lng, this.options); 92 | }); 93 | } 94 | } 95 | 96 | LanguageDetector.type = 'languageDetector'; 97 | 98 | export default LanguageDetector; 99 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as utils from './utils'; 2 | import LD from './LanguageDetector'; 3 | 4 | export var LanguageDetector = LD; 5 | 6 | export function handle(i18next, options = {}) { 7 | return function i18nextMiddleware(req, res, next) { 8 | if (typeof options.ignoreRoutes === 'function') { 9 | if (options.ignoreRoutes(req, res, options, i18next)) { 10 | return next(); 11 | } 12 | } else { 13 | let ignores = options.ignoreRoutes instanceof Array&&options.ignoreRoutes || []; 14 | for (var i=0;i< ignores.length;i++){ 15 | if (req.path.indexOf(ignores[i]) > -1) return next(); 16 | } 17 | } 18 | 19 | let i18n = i18next.cloneInstance({ initImmediate: false }); 20 | i18n.on('languageChanged', (lng) => { // Keep language in sync 21 | req.language = req.locale = req.lng = lng; 22 | 23 | if (res.locals) { 24 | res.locals.language = lng; 25 | res.locals.languageDir = i18next.dir(lng); 26 | } 27 | 28 | if (!res.headersSent && lng) { 29 | res.setHeader('Content-Language', lng); 30 | } 31 | 32 | req.languages = i18next.services.languageUtils.toResolveHierarchy(lng); 33 | 34 | if (i18next.services.languageDetector) { 35 | i18next.services.languageDetector.cacheUserLanguage(req, res, lng); 36 | } 37 | }); 38 | 39 | let lng = req.lng; 40 | if (!req.lng && i18next.services.languageDetector) lng = i18next.services.languageDetector.detect(req, res); 41 | 42 | // set locale 43 | req.language = req.locale = req.lng = lng; 44 | if (!res.headersSent && lng) { 45 | res.setHeader('Content-Language', lng); 46 | } 47 | req.languages = i18next.services.languageUtils.toResolveHierarchy(lng); 48 | 49 | // trigger sync to instance - might trigger async load! 50 | i18n.changeLanguage(lng || i18next.options.fallbackLng[0]); 51 | 52 | if(req.i18nextLookupName === 'path' && options.removeLngFromUrl) { 53 | req.url = utils.removeLngFromUrl(req.url, i18next.services.languageDetector.options.lookupFromPathIndex); 54 | } 55 | 56 | let t = i18n.t.bind(i18n); 57 | let exists = i18n.exists.bind(i18n); 58 | 59 | // assert for req 60 | req.i18n = i18n; 61 | req.t = t; 62 | 63 | // assert for res -> template 64 | if (res.locals) { 65 | res.locals.t = t; 66 | res.locals.exists = exists; 67 | res.locals.i18n = i18n; 68 | res.locals.language = lng; 69 | res.locals.languageDir = i18next.dir(lng); 70 | } 71 | 72 | if (i18next.services.languageDetector) i18next.services.languageDetector.cacheUserLanguage(req, res, lng); 73 | 74 | // load resources 75 | if (!req.lng) return next(); 76 | i18next.loadLanguages(req.lng, function() { 77 | next(); 78 | }); 79 | }; 80 | }; 81 | 82 | export function getResourcesHandler(i18next, options) { 83 | options = options || {}; 84 | let maxAge = options.maxAge || 60 * 60 * 24 * 30; 85 | 86 | return function(req, res) { 87 | if (!i18next.services.backendConnector) return res.status(404).send('i18next-express-middleware:: no backend configured'); 88 | 89 | let resources = {}; 90 | 91 | res.contentType('json'); 92 | if (options.cache !== undefined ? options.cache : process.env.NODE_ENV === 'production') { 93 | res.header('Cache-Control', 'public, max-age=' + maxAge); 94 | res.header('Expires', (new Date(new Date().getTime() + maxAge * 1000)).toUTCString()); 95 | } else { 96 | res.header('Pragma', 'no-cache'); 97 | res.header('Cache-Control', 'no-cache'); 98 | } 99 | 100 | let languages = req.query[options.lngParam || 'lng'] ? req.query[options.lngParam || 'lng'].split(' ') : []; 101 | let namespaces = req.query[options.nsParam || 'ns'] ? req.query[options.nsParam || 'ns'].split(' ') : []; 102 | 103 | // extend ns 104 | namespaces.forEach(ns => { 105 | if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) i18next.options.ns.push(ns); 106 | }); 107 | 108 | i18next.services.backendConnector.load(languages, namespaces, function() { 109 | languages.forEach(lng => { 110 | namespaces.forEach(ns => { 111 | utils.setPath(resources, [lng, ns], i18next.getResourceBundle(lng, ns)); 112 | }); 113 | }); 114 | 115 | res.send(resources); 116 | }); 117 | }; 118 | }; 119 | 120 | export function missingKeyHandler(i18next, options) { 121 | options = options || {}; 122 | 123 | return function(req, res) { 124 | let lng = req.params[options.lngParam || 'lng']; 125 | let ns = req.params[options.nsParam || 'ns']; 126 | 127 | if (!i18next.services.backendConnector) return res.status(404).send('i18next-express-middleware:: no backend configured'); 128 | 129 | for (var m in req.body) { 130 | i18next.services.backendConnector.saveMissing([lng], ns, m, req.body[m]); 131 | } 132 | res.send('ok'); 133 | }; 134 | }; 135 | 136 | export function addRoute(i18next, route, lngs, app, verb, fc) { 137 | if (typeof verb === 'function') { 138 | fc = verb; 139 | verb = 'get'; 140 | } 141 | 142 | // Combine `fc` and possible more callbacks to one array 143 | var callbacks = [fc].concat(Array.prototype.slice.call(arguments, 6)); 144 | 145 | for (var i = 0, li = lngs.length; i < li; i++) { 146 | var parts = String(route).split('/'); 147 | var locRoute = []; 148 | for (var y = 0, ly = parts.length; y < ly; y++) { 149 | var part = parts[y]; 150 | // if the route includes the parameter :lng 151 | // this is replaced with the value of the language 152 | if (part === ':lng') { 153 | locRoute.push(lngs[i]); 154 | } else if (part.indexOf(':') === 0 || part === '') { 155 | locRoute.push(part); 156 | } else { 157 | locRoute.push(i18next.t(part, { lng: lngs[i] })); 158 | } 159 | } 160 | 161 | var routes = [locRoute.join('/')]; 162 | app[verb || 'get'].apply(app, routes.concat(callbacks)); 163 | } 164 | }; 165 | 166 | export default { 167 | handle, 168 | getResourcesHandler, 169 | missingKeyHandler, 170 | addRoute, 171 | LanguageDetector 172 | } 173 | -------------------------------------------------------------------------------- /src/languageLookups/cookie.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'cookies'; 2 | 3 | export default { 4 | name: 'cookie', 5 | 6 | lookup(req, res, options) { 7 | let found; 8 | 9 | if (options.lookupCookie && typeof req !== 'undefined') { 10 | if (req.cookies) { 11 | found = req.cookies[options.lookupCookie]; 12 | } else { 13 | const cookies = new Cookies(req, res); 14 | found = cookies.get(options.lookupCookie); 15 | } 16 | } 17 | 18 | return found; 19 | }, 20 | 21 | cacheUserLanguage(req, res, lng, options = {}) { 22 | if (options.lookupCookie && req !== 'undefined' && !(res._headerSent || res.headersSent)) { 23 | const cookies = new Cookies(req, res); 24 | 25 | let expirationDate = options.cookieExpirationDate; 26 | if (!expirationDate) { 27 | expirationDate = new Date(); 28 | expirationDate.setFullYear(expirationDate.getFullYear() + 1); 29 | } 30 | 31 | const cookieOptions = { 32 | expires: expirationDate, 33 | domain: options.cookieDomain, 34 | httpOnly: false, 35 | overwrite: true 36 | }; 37 | 38 | if (options.hasOwnProperty('cookieSecure')) { 39 | cookieOptions.secure = options.cookieSecure; 40 | } 41 | 42 | cookies.set(options.lookupCookie, lng, cookieOptions); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/languageLookups/header.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'header', 3 | 4 | lookup(req, res, options) { 5 | let found; 6 | 7 | if (typeof req !== 'undefined') { 8 | let headers = req.headers; 9 | if (!headers) return found; 10 | 11 | let locales = []; 12 | let acceptLanguage = options.lookupHeader ? headers[options.lookupHeader] : headers['accept-language']; 13 | 14 | if (acceptLanguage) { 15 | let lngs = [], i, m; 16 | const rgx = /(([a-z]{2})-?([A-Z]{2})?)\s*;?\s*(q=([0-9.]+))?/gi; 17 | 18 | do { 19 | m = rgx.exec(acceptLanguage); 20 | if (m) { 21 | const lng = m[1], weight = m[5] || '1', q = Number(weight); 22 | if (lng && !isNaN(q)) { 23 | lngs.push({lng, q}); 24 | } 25 | } 26 | } while (m); 27 | 28 | lngs.sort(function(a,b) { 29 | return b.q - a.q; 30 | }); 31 | 32 | for (i = 0; i < lngs.length; i++) { 33 | locales.push(lngs[i].lng); 34 | } 35 | 36 | if (locales.length) found = locales; 37 | } 38 | } 39 | 40 | return found; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/languageLookups/path.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'path', 3 | 4 | lookup (req, res, options) { 5 | let found; 6 | 7 | if (req === undefined) { 8 | return found; 9 | } 10 | 11 | if (options.lookupPath !== undefined && req.params) { 12 | found = req.params[options.lookupPath]; 13 | } 14 | 15 | if (!found && typeof options.lookupFromPathIndex === 'number' && req.originalUrl) { 16 | let path = req.originalUrl.split('?')[0]; 17 | let parts = path.split('/'); 18 | if (parts[0] === '') { // Handle paths that start with a slash, i.e., '/foo' -> ['', 'foo'] 19 | parts.shift(); 20 | } 21 | 22 | if (parts.length > options.lookupFromPathIndex) { 23 | found = parts[options.lookupFromPathIndex]; 24 | } 25 | } 26 | 27 | return found; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/languageLookups/querystring.js: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | 3 | export default { 4 | name: 'querystring', 5 | 6 | lookup(req, res, options) { 7 | let found; 8 | 9 | if (options.lookupQuerystring !== undefined && typeof req !== 'undefined') { 10 | if (req.query) { 11 | found = req.query[options.lookupQuerystring]; 12 | } else { 13 | let querystring = url.parse(req.url, true); 14 | found = querystring.query[options.lookupQuerystring]; 15 | } 16 | } 17 | 18 | return found; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/languageLookups/session.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'session', 3 | 4 | lookup(req, res, options) { 5 | let found; 6 | 7 | if (options.lookupSession !== undefined && typeof req && req.session) { 8 | found = req.session[options.lookupSession]; 9 | } 10 | 11 | return found; 12 | }, 13 | 14 | cacheUserLanguage(req, res, lng, options = {}) { 15 | if (options.lookupSession && req && req.session) { 16 | req.session[options.lookupSession] = lng; 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function setPath(object, path, newValue) { 2 | let stack; 3 | if (typeof path !== 'string') stack = [].concat(path); 4 | if (typeof path === 'string') stack = path.split('.'); 5 | 6 | while(stack.length > 1) { 7 | let key = stack.shift(); 8 | if (key.indexOf('###') > -1) key = key.replace(/###/g, '.'); 9 | if (!object[key]) object[key] = {}; 10 | object = object[key]; 11 | } 12 | 13 | let key = stack.shift(); 14 | if (key.indexOf('###') > -1) key = key.replace(/###/g, '.'); 15 | object[key] = newValue; 16 | } 17 | 18 | let arr = []; 19 | let each = arr.forEach; 20 | let slice = arr.slice; 21 | 22 | export function defaults(obj) { 23 | each.call(slice.call(arguments, 1), function(source) { 24 | if (source) { 25 | for (var prop in source) { 26 | if (obj[prop] === undefined) obj[prop] = source[prop]; 27 | } 28 | } 29 | }); 30 | return obj; 31 | } 32 | 33 | export function extend(obj) { 34 | each.call(slice.call(arguments, 1), function(source) { 35 | if (source) { 36 | for (var prop in source) { 37 | obj[prop] = source[prop]; 38 | } 39 | } 40 | }); 41 | return obj; 42 | } 43 | 44 | export function removeLngFromUrl(url, lookupFromPathIndex) { 45 | let first = ''; 46 | let pos = lookupFromPathIndex; 47 | 48 | if (url[0] === '/') { 49 | pos++; 50 | first = '/'; 51 | } 52 | 53 | // Build new url 54 | let parts = url.split('/'); 55 | parts.splice(pos,1); 56 | url = parts.join('/'); 57 | if (url[0] !== '/') url = first + url; 58 | 59 | return url; 60 | } 61 | --------------------------------------------------------------------------------