├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test ├── .jshintrc └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "indent": 2, 9 | "latedef": false, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "trailing": true, 17 | "smarttabs": true, 18 | "validthis": true 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "0.12" 5 | 6 | install: 7 | - npm i -g jshint 8 | - npm i -g mocha 9 | - npm install 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geolang-express 2 | [![Build Status](https://img.shields.io/travis/koalazak/geolang-express.svg)](https://travis-ci.org/koalazak/geolang-express) 3 | [![npm version](https://badge.fury.io/js/geolang-express.svg)](http://badge.fury.io/js/geolang-express) 4 | [![npm dependency status](https://david-dm.org/koalazak/geolang-express.png)](https://david-dm.org/koalazak/geolang-express) 5 | 6 | A simple geolocation middleware for Express.js. This module expose `countryLang` and object with more data `countryData` on app.locals making data visibles in views too. 7 | Also, set the session `cookieLangName` with the language of visitors country, useful for i18n modules. 8 | 9 | NOTE: When using this module, we recommend also using the [i18n-express](https://github.com/koalazak/i18n-express) module, which use the `cookieLangName` session to set i18n translations according that value. 10 | 11 | 12 | ## Requirements 13 | 14 | - Node >= 0.12 15 | - Express.js session enabled 16 | 17 | ## Instalation 18 | 19 | ```bash 20 | $ npm install geolang-express 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```js 26 | var geolang=require("geolang-express"); 27 | 28 | app.use( geolang(options) ); 29 | ``` 30 | 31 | ## Options 32 | 33 | - `cookieLangName` : *(default: `ulang`)* If you provide a cookie name, try to get user lang from this session/cookie. 34 | - `defaultCountry` : *(default: `US`)* Default country, for example if the IP is 127.0.0.1 35 | - `siteLangs` : *(default: `['en']`)* Array of supported langs. (posbile values for clang and json files. see [i18n-express](https://github.com/koalazak/i18n-express)) 36 | 37 | 38 | ## Example 39 | 40 | In your Express app.js: 41 | 42 | ```javascript 43 | var express = require('express'); 44 | var path = require('path'); 45 | var cookieParser = require('cookie-parser'); 46 | var bodyParser = require('body-parser'); 47 | var i18n=require("i18n-express"); 48 | var geolang=require("geolang-express"); 49 | 50 | var indexRoutes = require('./routes/index'); 51 | 52 | var app = express(); 53 | 54 | // view engine setup 55 | app.set('views', path.join(__dirname, 'views')); 56 | app.set('view engine', 'ejs'); 57 | app.use(cookieParser()); 58 | app.use(express.static(path.join(__dirname, 'public'))); 59 | app.use(session({ 60 | secret: 'secret', 61 | saveUninitialized: true, 62 | resave: true 63 | })); 64 | 65 | 66 | app.use(geolang({ 67 | siteLangs: ["en","es"], 68 | cookieLangName: 'ulang' 69 | })); 70 | 71 | 72 | /* 73 | //Read documentation if you need to use i18n-express package 74 | app.use(i18n({ 75 | translationsPath: path.join(__dirname, 'i18n'), 76 | siteLangs: ["en","es"], 77 | cookieLangName: 'ulang' 78 | })); 79 | */ 80 | 81 | 82 | app.use('/', indexRoutes); 83 | 84 | module.exports = app; 85 | 86 | 87 | ``` 88 | 89 | Now in your ejs view you have `countryLang` and object with more data `countryData`: 90 | 91 | ```html 92 |
93 | 94 |

Language by Country IP set to: <%=countryLang%>

95 | 96 |
97 | ``` 98 | 99 | Or in your handlebars view: 100 | 101 | ```html 102 |
103 | 104 |

Language by Country IP set to: <%=countryLang%>

105 | 106 |
107 | ``` 108 | 109 | ## License 110 | 111 | MIT 112 | 113 | ## Author 114 | 115 | - [Facu ZAK](https://github.com/koalazak) 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var geoip = require('geoip-ultralight'); 2 | var countries = require('country-data').countries; 3 | var languages = require('country-data').languages; 4 | 5 | function getCountryCode(ip, defaultCountry){ 6 | 7 | if (ip.indexOf('::ffff:') > -1) { 8 | ip = ip.split(':').reverse()[0]; 9 | } 10 | var countryCode; 11 | var lookedUpCountry = geoip.lookupCountry(ip); 12 | if (lookedUpCountry) { 13 | countryCode = lookedUpCountry; 14 | } 15 | if ((ip === '127.0.0.1') || (!lookedUpCountry)) { 16 | countryCode = defaultCountry; 17 | } 18 | return countryCode; 19 | } 20 | 21 | exports = module.exports = function (opts) { 22 | 23 | var cookieLangName = opts.cookieLangName || "ulang"; 24 | var defaultCountry = opts.defaultCountry || "US"; 25 | var siteLangs = opts.siteLangs || ['en']; 26 | 27 | if (siteLangs.constructor !== Array) throw new Error('siteLangs must be an Array with supported langs.'); 28 | 29 | return function geolang(req, res, next) { 30 | 31 | // Geo data isn't in locals 32 | if(!('countryLangData' in req.app.locals)) { 33 | 34 | var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; 35 | var countryCode=getCountryCode(ip, defaultCountry); 36 | var countryData=countries[countryCode]; 37 | var countryLang = languages[countryData.languages[0]]; 38 | 39 | req.app.locals.countryData=countryData; 40 | req.app.locals.countryLangData=countryLang; 41 | req.app.locals.countryLang=countryLang.alpha2; 42 | } 43 | 44 | // Lang isn't already set so skip 45 | if (!(cookieLangName && req.session && req.session[cookieLangName])) { 46 | if (siteLangs.indexOf(req.app.locals.countryLang) > -1) { 47 | req.session[cookieLangName]=req.app.locals.countryLang; 48 | } 49 | } 50 | 51 | next(); 52 | }; 53 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geolang-express", 3 | "version": "1.0.0", 4 | "description": "A simple geolocation middleware for Express.js to set site lang by client IP.", 5 | "main": "index.js", 6 | "scripts": { 7 | "jshint": "jshint .", 8 | "test": "mocha" 9 | }, 10 | "dependencies": { 11 | "geoip-ultralight": "*", 12 | "country-data": "*" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/koalazak/geolang-express.git" 17 | }, 18 | "keywords": [ 19 | "geoip", 20 | "express", 21 | "i18n", 22 | "language", 23 | "geo", 24 | "middleware", 25 | "express.js", 26 | "geolocation", 27 | "web", 28 | "site" 29 | ], 30 | "author": "koalazak ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/koalazak/geolang-express/issues" 34 | }, 35 | "homepage": "https://github.com/koalazak/geolang-express" 36 | } 37 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "expr": true, 4 | "jasmine": true, 5 | "globals": { 6 | "before": false, 7 | "after": false, 8 | "expect": true 9 | } 10 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var path = require('path'); 3 | 4 | var geolangMod = require('..'); 5 | var res={}; 6 | 7 | function getExpressReq(headers, session, query){ 8 | 9 | var headers = headers || {}; 10 | var session = session || {}; 11 | var query = query || {}; 12 | 13 | return { 14 | session: session, 15 | cookies: {}, 16 | query: query, 17 | headers: headers, 18 | app: {locals:{}}, 19 | connection: {} 20 | } 21 | } 22 | 23 | describe('geolang-express', function() { 24 | describe('middleware', function () { 25 | it('should set default Country and Language', function (done) { 26 | 27 | var req=getExpressReq({'x-forwarded-for':'127.0.0.1'}); 28 | 29 | var mws=geolangMod({ 30 | siteLangs: ['en','es'] 31 | }); 32 | 33 | mws(req, res, function(){ 34 | assert.equal('US', req.app.locals.countryData.alpha2); 35 | assert.equal('en', req.app.locals.countryLang); 36 | 37 | done(); 38 | }); 39 | 40 | }); 41 | 42 | it('should set user defined default Country and Language', function (done) { 43 | 44 | var req=getExpressReq({'x-forwarded-for':'127.0.0.1'}); 45 | 46 | var mws=geolangMod({ 47 | siteLangs: ['en','es'], 48 | defaultCountry: "AR" 49 | }); 50 | 51 | mws(req, res, function(){ 52 | assert.equal('AR', req.app.locals.countryData.alpha2); 53 | assert.equal('es', req.app.locals.countryLang); 54 | 55 | done(); 56 | }); 57 | 58 | }); 59 | 60 | it('should set Jamaica Country and Language', function (done) { 61 | 62 | var req=getExpressReq({'x-forwarded-for':'96.43.160.3'}); 63 | 64 | var mws=geolangMod({ 65 | siteLangs: ['en','es'], 66 | defaultCountry: "AR" 67 | }); 68 | 69 | mws(req, res, function(){ 70 | assert.equal('JM', req.app.locals.countryData.alpha2); 71 | assert.equal('en', req.app.locals.countryLang); 72 | 73 | done(); 74 | }); 75 | 76 | }); 77 | 78 | it('should set Italy Country and Language. And store in session', function (done) { 79 | 80 | var req=getExpressReq({'x-forwarded-for':'62.77.32.3'}); 81 | 82 | var mws=geolangMod({ 83 | siteLangs: ['en','es','it'], 84 | defaultCountry: "AR" 85 | }); 86 | 87 | mws(req, res, function(){ 88 | assert.equal('IT', req.app.locals.countryData.alpha2); 89 | assert.equal('it', req.app.locals.countryLang); 90 | assert.equal('it', req.session.ulang); 91 | 92 | done(); 93 | }); 94 | 95 | }); 96 | 97 | }); 98 | }); 99 | --------------------------------------------------------------------------------