├── test ├── locales │ ├── en.json │ ├── en_US.json │ ├── fr_FR.json │ ├── jp_JP.json │ └── tr_TR.json ├── config-files │ ├── config-empty.json │ ├── config-default.json │ └── config-deep.json ├── 01.js ├── hapi │ ├── create-server.js │ └── create-server copy.js ├── 03.js └── 02.js ├── index.js ├── doc ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ └── OpenSans-LightItalic-webfont.woff ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── lang-css.js │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js ├── styles │ ├── prettify-jsdoc.css │ ├── prettify-tomorrow.css │ └── jsdoc-default.css ├── module-exposed.html ├── index.html ├── index.js.html └── module-_hapi-locale_.html ├── jsdoc-conf.json ├── LICENSE ├── package.json ├── History.md ├── .gitignore ├── README-JSDOC.md ├── lib └── index.js └── README.md /test/locales/en.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/locales/en_US.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/locales/fr_FR.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/locales/jp_JP.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/locales/tr_TR.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index.js'); -------------------------------------------------------------------------------- /test/config-files/config-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "This file does not contain locale configuration" 3 | } -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /doc/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /doc/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /doc/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/hapi-locale/HEAD/doc/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /test/config-files/config-default.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "This file contains locale configuration", 3 | "locales": ["en_US", "tr_TR", "fr_FR"] 4 | } -------------------------------------------------------------------------------- /test/config-files/config-deep.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "This file contain locale configuration", 3 | "options": { 4 | "why": "To test deeply nested config key", 5 | "locales": ["en_US", "tr_TR"] 6 | } 7 | } -------------------------------------------------------------------------------- /doc/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /doc/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /jsdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | 4 | "encoding": "utf8", 5 | "destination": "./doc/", 6 | "recurse": true, 7 | "private": false 8 | }, 9 | "source": { 10 | "include": ["lib"] 11 | }, 12 | "tags": { 13 | "allowUnknownTags" : true 14 | }, 15 | "plugins": ["plugins/markdown"], 16 | "templates": { 17 | "cleverLinks": true, 18 | "monospaceLinks": true, 19 | "default": { 20 | "outputSourceFiles" : true 21 | }, 22 | "applicationName": "hapi-locale", 23 | "disqus": "", 24 | "googleAnalytics": "", 25 | "openGraph": { 26 | "title": "", 27 | "type": "website", 28 | "image": "", 29 | "site_name": "", 30 | "url": "" 31 | }, 32 | "meta": { 33 | "title": "", 34 | "description": "", 35 | "keyword": "" 36 | }, 37 | "linenums": false 38 | }, 39 | "markdown": { 40 | "parser": "gfm", 41 | "hardwrap": true, 42 | "tags": ["examples"] 43 | } 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Özüm Eldoğan 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. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-locale", 3 | "version": "2.0.2", 4 | "description": "Configurable plugin for determine request language in hapi.js applications.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ozum/hapi-locale.git" 9 | }, 10 | "scripts": { 11 | "doc": "rm -R doc; jsdoc -c jsdoc-conf.json README-JSDOC.md; rm -f README.md; jsdoc2md --no-gfm --src \"lib/**/*\" >JSDOC.md; cat README-JSDOC.md JSDOC.md History.md LICENSE > README.md; rm -r -f JSDOC.md;", 12 | "test": "lab --globals BigUint64Array,BigInt64Array,BigInt,Reflect,SharedArrayBuffer,Atomics,WebAssembly,URL,URLSearchParams,TextEncoder,TextDecoder,queueMicrotask" 13 | }, 14 | "keywords": [ 15 | "hapi", 16 | "i18n", 17 | "l10n", 18 | "request", 19 | "langauge", 20 | "locale" 21 | ], 22 | "author": "Özüm Eldoğan", 23 | "license": "MIT", 24 | "dependencies": { 25 | "@hapi/boom": "^9.1.0", 26 | "@hapi/joi": "^17.1.1", 27 | "accept-language-parser": "^1.5.0", 28 | "lodash": "^4.17.19" 29 | }, 30 | "devDependencies": { 31 | "@hapi/code": "^8.0.1", 32 | "@hapi/hapi": "^19.2.0", 33 | "@hapi/lab": "^22.0.4", 34 | "rewire": "^5.0.0", 35 | "zoo-jsdoc": "0.0.1" 36 | }, 37 | "engines": { 38 | "node": ">=8.3.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------- 3 | 4 | History & Notes 5 | ================ 6 | Note: Simple documentation updates are not listed here. 7 | 8 | #### 1.0.0 / 2015-10-16 9 | * Changed: node.js 4 (ES6) is used. 10 | * Some changes are incompatible with 0.x versions. 11 | * Changed: Internal structure of the plugin is completly changed. It is class based now. 12 | * Added: JOI validations for plugin options. 13 | * Changed: Code cleaned up to make it easily understandable. 14 | * Changed: Documentation is updated. 15 | * Changed: Option names are simplified. 16 | * Changed: `options.throw404` only affects URL parameter now. 17 | * Changed: `options.callback` is not used anymore. `options.setter` is used both as a setter and as a callback. 18 | * Changed: Best match algorithm now tries every method until requested language is one of the available ones. 19 | 20 | #### 0.4.4 / 2015-10-14 21 | * Fixed: Accept language header parsed wrong. 22 | 23 | #### 0.4.3 / 2015-10-13 24 | * Changed: Tests ported from Mocha/Chai to Lab/Code. 25 | * Fixed: Created setter function does not work. 26 | 27 | #### 0.4.0 / 2015-10-09 28 | 29 | * Changed: `options.createGetterOn` and `options.createSetterOn` are renamed as `options.getter` and `options.setter`. 30 | * Added: `options.createAccessorsIfNotExists` added. 31 | * Fixed: Wrong path parameter caused reply called twice. Fixed. 32 | 33 | #### 0.3.0 / 2015-10-07 34 | * Added: Cookie support. `options.order` to change order of process to determine locale. It is possible to proritize query etc. over url parameters now. 35 | 36 | #### 0.2.1 / 2015-10-07 37 | * Changed: `options.createGetter` and `options.createSetter` are renamed as `options.createGetterOn` and `options.createSetterOn` 38 | 39 | #### 0.2.0 / 2015-10-07 40 | * Added: `getDefaultLocale()` exposed function. 41 | 42 | #### 0.1.0 / 2015-10-07 43 | * Added: `getLocale()` and `getLocales()` exposed functions. 44 | 45 | #### 0.0.1 / 2015-10-06 46 | * Initial version. 47 | 48 | LICENSE 49 | ======= 50 | 51 | -------------------------------------------------------------------------------- /doc/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /test/01.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /*jslint node: true, nomen: true */ 4 | const Lab = require('@hapi/lab'); 5 | const { expect } = require('@hapi/code'); 6 | const path = require('path'); 7 | const rewire = require('rewire'); 8 | const plugin = rewire('../lib/index.js'); 9 | 10 | const { describe, it } = exports.lab = Lab.script(); 11 | 12 | var Internal = plugin.__get__('Internal'); 13 | var options = { 14 | configFile: path.join(__dirname, 'config-files', 'config-default.json'), 15 | scan: { 16 | path: path.join(__dirname, 'locales') 17 | } 18 | }; 19 | var internal = new Internal(options); 20 | 21 | 22 | 23 | describe('scan', function() { 24 | 25 | it('should scan files and directories', function() { 26 | expect(internal.scan()).to.equal(['en', 'en_US', 'fr_FR', 'jp_JP', 'tr_TR' ]); 27 | }); 28 | 29 | it('should scan only files', function() { 30 | const localOptions = { ...options, scan: { path: path.join(__dirname, 'locales'), directories: false }} 31 | var internal = new Internal(localOptions); 32 | expect(internal.scan()).to.equal(['en', 'en_US', 'fr_FR', 'jp_JP', 'tr_TR' ]); 33 | }) 34 | }); 35 | 36 | 37 | 38 | describe('getAvailableLocales', function() { 39 | 40 | it('should return for default config', function() { 41 | expect(internal.getAvailableLocales()).to.equal(["en_US", "tr_TR", "fr_FR"]); 42 | 43 | }); 44 | 45 | it('should return for deep config', function() { 46 | var localOptions = { ...options, 47 | configFile: path.join(__dirname, 'config-files', 'config-deep.json'), 48 | configKey: 'options.locales' 49 | }; 50 | var internal = new Internal(localOptions); 51 | expect(internal.getAvailableLocales()).to.equal(["en_US", "tr_TR"]); 52 | 53 | }); 54 | 55 | it('should return for empty config', function() { 56 | var localOptions = { ...options, 57 | configFile: path.join(__dirname, 'config-files', 'config-empty.json'), 58 | }; 59 | var internal = new Internal(localOptions); 60 | expect(internal.getAvailableLocales()).to.equal(['en', 'en_US', 'fr_FR', 'jp_JP', 'tr_TR' ]); 61 | 62 | }); 63 | 64 | it('should prioritize options', function() { 65 | var localOptions = { ...options, locales: ['tr_TR'] }; 66 | var internal = new Internal(localOptions); 67 | expect(internal.getAvailableLocales()).to.equal(['tr_TR']); 68 | 69 | }); 70 | }); -------------------------------------------------------------------------------- /doc/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /test/hapi/create-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Hapi = require("@hapi/hapi"); 4 | const rewire = require('rewire'); 5 | const plugin = rewire('../../lib/index.js'); 6 | const lodash = require('lodash'); 7 | 8 | const defaultOptions = plugin.__get__('defaultOptions'); 9 | const getter = defaultOptions.getter; 10 | const setter = defaultOptions.setter; 11 | 12 | 13 | exports.init = async (plugins) => { 14 | const server = Hapi.server({ 15 | port: 8000, 16 | host: 'localhost' 17 | }); 18 | 19 | server.route([ 20 | { 21 | path: "/locale", 22 | method: "GET", 23 | handler: function(request, reply) { 24 | var getLocale = lodash.get(request, getter); 25 | return { locale: getLocale() }; 26 | } 27 | }, 28 | { 29 | path: "/{lang}/locale", 30 | method: "GET", 31 | handler: function(request, reply) { 32 | var getLocale = lodash.get(request, getter); 33 | return { locale: getLocale() }; 34 | } 35 | }, 36 | { 37 | path: "/getter-setter", 38 | method: "GET", 39 | handler: function(request, reply) { 40 | var getLocale = lodash.get(request, getter); 41 | var setLocale = lodash.get(request, setter); 42 | setLocale('ru_RU'); 43 | return { 44 | locale: getLocale() 45 | }; 46 | } 47 | }, 48 | { 49 | path: "/exposed", 50 | method: "GET", 51 | handler: function(request, reply) { 52 | var plugin = request.server.plugins['hapi-locale']; 53 | return { 54 | getLocales: plugin.getLocales(), 55 | getLocale: plugin.getLocale(request, reply), 56 | getDefaultLocale: plugin.getDefaultLocale() 57 | }; 58 | 59 | } 60 | }, 61 | { 62 | path: "/{lang}/exposed", 63 | method: "GET", 64 | handler: function(request, reply) { 65 | var plugin = request.server.plugins['hapi-locale']; 66 | return { 67 | getLocales: plugin.getLocales(), 68 | getLocale: plugin.getLocale(request, reply), 69 | getDefaultLocale: plugin.getDefaultLocale() 70 | }; 71 | 72 | } 73 | } 74 | ]); 75 | 76 | await server.register(plugins); 77 | 78 | await server.initialize(); 79 | return server; 80 | }; 81 | 82 | 83 | 84 | process.on('unhandledRejection', (err) => { 85 | console.log(err); 86 | process.exit(1); 87 | }); -------------------------------------------------------------------------------- /test/03.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /*jslint node: true, nomen: true */ 4 | /*global describe, it, before, beforeEach, after, afterEach */ 5 | 6 | const Lab = require('@hapi/lab'); 7 | const { expect } = require('@hapi/code'); 8 | const path = require('path'); 9 | 10 | const { afterEach, beforeEach, describe, it } = exports.lab = Lab.script(); 11 | const { init } = require('./hapi/create-server'); 12 | 13 | describe('hapi-locale', function() { 14 | let server; 15 | 16 | beforeEach(async () => { 17 | const plugins = [ 18 | { 19 | plugin: require('../index.js'), 20 | options: { 21 | configFile: path.join(__dirname, 'config-files', 'config-default.json'), 22 | scan: { 23 | path: path.join(__dirname, 'locales') 24 | } 25 | } 26 | } 27 | ]; 28 | 29 | server = await init(plugins); 30 | }); 31 | 32 | afterEach(async () => { 33 | await server.stop(); 34 | }); 35 | 36 | it('should expose functions', async function () { 37 | const options = { method: "GET", url: "/exposed?lang=tr_TR" }; 38 | 39 | const response = await server.inject(options); 40 | expect(response.result).to.equal({ 41 | getLocales: ['en_US', 'tr_TR', 'fr_FR'], 42 | getLocale: 'tr_TR', 43 | getDefaultLocale: 'en_US', 44 | }); 45 | }); 46 | }); 47 | 48 | describe('hapi-locale', function() { 49 | let server; 50 | 51 | beforeEach(async () => { 52 | const plugins = [ 53 | { 54 | plugin: require('../index.js'), 55 | options: { 56 | configFile: path.join(__dirname, 'config-files', 'config-default.json'), 57 | scan: { 58 | path: path.join(__dirname, 'locales') 59 | }, 60 | getter: null, 61 | setter: null, 62 | } 63 | } 64 | ]; 65 | 66 | server = await init(plugins); 67 | }); 68 | 69 | afterEach(async () => { 70 | await server.stop(); 71 | }); 72 | 73 | 74 | it('should expose function without poolluting request object', async function () { 75 | const options = { method: "GET", url: "/exposed?lang=tr_TR" }; 76 | const response = await server.inject(options) 77 | expect(response.result).to.equal(response.result, { 78 | getLocales: [ 'en_US', 'tr_TR', 'fr_FR' ], 79 | getLocale: 'tr_TR', 80 | getDefaultLocale: 'en_US', 81 | }); 82 | }); 83 | 84 | it('should return undef for wrong locale', async function () { 85 | const options = { method: "GET", url: "/NA_NA/exposed" }; 86 | const response = await server.inject(options); 87 | expect(response.result.getLocale).to.equal(undefined); 88 | }); 89 | }); -------------------------------------------------------------------------------- /test/hapi/create-server copy.js: -------------------------------------------------------------------------------- 1 | // var hapi = require('hapi'), 2 | // rewire = require('rewire'), 3 | // plugin = rewire('../../lib/index.js'), 4 | // lodash = require('lodash'); 5 | 6 | // var defaultOptions = plugin.__get__('defaultOptions'), 7 | // getter = defaultOptions.getter, 8 | // setter = defaultOptions.setter; 9 | 10 | 11 | 12 | // module.exports = function defineRoutes(plugins) { 13 | // "use strict"; 14 | // var server = new hapi.Server(); 15 | 16 | // server.connection({ 17 | // host: 'localhost', 18 | // port: 8080 19 | // }); 20 | 21 | // server.route([ 22 | // { 23 | // path: "/locale", 24 | // method: "GET", 25 | // handler: function(request, reply) { 26 | // var getLocale = lodash.get(request, getter); 27 | // reply({ locale: getLocale() }); 28 | // } 29 | // }, 30 | // { 31 | // path: "/{lang}/locale", 32 | // method: "GET", 33 | // handler: function(request, reply) { 34 | // var getLocale = lodash.get(request, getter); 35 | // reply({ locale: getLocale() }); 36 | // } 37 | // }, 38 | // { 39 | // path: "/getter-setter", 40 | // method: "GET", 41 | // handler: function(request, reply) { 42 | // var getLocale = lodash.get(request, getter); 43 | // var setLocale = lodash.get(request, setter); 44 | // setLocale('ru_RU'); 45 | // reply({ 46 | // locale: getLocale() 47 | // }); 48 | // } 49 | // }, 50 | // { 51 | // path: "/exposed", 52 | // method: "GET", 53 | // handler: function(request, reply) { 54 | // var plugin = request.server.plugins['hapi-locale']; 55 | // return reply({ 56 | // getLocales: plugin.getLocales(), 57 | // getLocale: plugin.getLocale(request, reply), 58 | // getDefaultLocale: plugin.getDefaultLocale() 59 | // }); 60 | 61 | // } 62 | // }, 63 | // { 64 | // path: "/{lang}/exposed", 65 | // method: "GET", 66 | // handler: function(request, reply) { 67 | // var plugin = request.server.plugins['hapi-locale']; 68 | // return reply({ 69 | // getLocales: plugin.getLocales(), 70 | // getLocale: plugin.getLocale(request, reply), 71 | // getDefaultLocale: plugin.getDefaultLocale() 72 | // }); 73 | 74 | // } 75 | // } 76 | // ]); 77 | 78 | 79 | // server.register(plugins, function (err) { 80 | // if (err) throw err; 81 | // if (!module.parent) { 82 | // server.start(function () { 83 | // console.log('Server started at: ' + server.info.uri); 84 | // }); 85 | // } 86 | // }); 87 | 88 | // return server; 89 | // }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # moe-scripts 2 | .opt-in 3 | .opt-out 4 | 5 | # Created by https://www.gitignore.io/api/node,webstorm,visualstudiocode,macos,windows,linux 6 | 7 | ### Linux ### 8 | *~ 9 | 10 | # temporary files which can be created if a process still has a handle open of a deleted file 11 | .fuse_hidden* 12 | 13 | # KDE directory preferences 14 | .directory 15 | 16 | # Linux trash folder which might appear on any partition or disk 17 | .Trash-* 18 | 19 | # .nfs files are created when an open file is removed but is still being accessed 20 | .nfs* 21 | 22 | ### macOS ### 23 | *.DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | 27 | # Icon must end with two \r 28 | Icon 29 | 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | .com.apple.timemachine.donotpresent 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | 50 | ### Node ### 51 | # Logs 52 | logs 53 | *.log 54 | npm-debug.log* 55 | yarn-debug.log* 56 | yarn-error.log* 57 | 58 | # Runtime data 59 | pids 60 | *.pid 61 | *.seed 62 | *.pid.lock 63 | 64 | # Directory for instrumented libs generated by jscoverage/JSCover 65 | lib-cov 66 | 67 | # Coverage directory used by tools like istanbul 68 | coverage 69 | 70 | # nyc test coverage 71 | .nyc_output 72 | 73 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 74 | .grunt 75 | 76 | # Bower dependency directory (https://bower.io/) 77 | bower_components 78 | 79 | # node-waf configuration 80 | .lock-wscript 81 | 82 | # Compiled binary addons (http://nodejs.org/api/addons.html) 83 | build/Release 84 | 85 | # Dependency directories 86 | node_modules/ 87 | jspm_packages/ 88 | 89 | # Typescript v1 declaration files 90 | typings/ 91 | 92 | # Optional npm cache directory 93 | .npm 94 | 95 | # Optional eslint cache 96 | .eslintcache 97 | 98 | # Optional REPL history 99 | .node_repl_history 100 | 101 | # Output of 'npm pack' 102 | *.tgz 103 | 104 | # Yarn Integrity file 105 | .yarn-integrity 106 | 107 | # dotenv environment variables file 108 | .env 109 | 110 | 111 | ### VisualStudioCode ### 112 | .vscode/* 113 | !.vscode/settings.json 114 | !.vscode/tasks.json 115 | !.vscode/launch.json 116 | !.vscode/extensions.json 117 | 118 | ### WebStorm ### 119 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 120 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 121 | 122 | # User-specific stuff: 123 | .idea/**/workspace.xml 124 | .idea/**/tasks.xml 125 | .idea/dictionaries 126 | 127 | # Sensitive or high-churn files: 128 | .idea/**/dataSources/ 129 | .idea/**/dataSources.ids 130 | .idea/**/dataSources.xml 131 | .idea/**/dataSources.local.xml 132 | .idea/**/sqlDataSources.xml 133 | .idea/**/dynamic.xml 134 | .idea/**/uiDesigner.xml 135 | 136 | # Gradle: 137 | .idea/**/gradle.xml 138 | .idea/**/libraries 139 | 140 | # Mongo Explorer plugin: 141 | .idea/**/mongoSettings.xml 142 | 143 | ## File-based project format: 144 | *.iws 145 | 146 | ## Plugin-specific files: 147 | 148 | # IntelliJ 149 | /out/ 150 | 151 | # mpeltonen/sbt-idea plugin 152 | .idea_modules/ 153 | 154 | # JIRA plugin 155 | atlassian-ide-plugin.xml 156 | 157 | # Cursive Clojure plugin 158 | .idea/replstate.xml 159 | 160 | # Crashlytics plugin (for Android Studio and IntelliJ) 161 | com_crashlytics_export_strings.xml 162 | crashlytics.properties 163 | crashlytics-build.properties 164 | fabric.properties 165 | 166 | ### WebStorm Patch ### 167 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 168 | 169 | # *.iml 170 | # modules.xml 171 | # .idea/misc.xml 172 | # *.ipr 173 | 174 | # Sonarlint plugin 175 | .idea/sonarlint 176 | 177 | ### Windows ### 178 | # Windows thumbnail cache files 179 | Thumbs.db 180 | ehthumbs.db 181 | ehthumbs_vista.db 182 | 183 | # Folder config file 184 | Desktop.ini 185 | 186 | # Recycle Bin used on file shares 187 | $RECYCLE.BIN/ 188 | 189 | # Windows Installer files 190 | *.cab 191 | *.msi 192 | *.msm 193 | *.msp 194 | 195 | # Windows shortcuts 196 | *.lnk 197 | 198 | # End of https://www.gitignore.io/api/node,webstorm,visualstudiocode,macos,windows,linux 199 | -------------------------------------------------------------------------------- /test/02.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /*jslint node: true, nomen: true */ 4 | const Lab = require('@hapi/lab'); 5 | const { expect } = require('@hapi/code'); 6 | const path = require('path'); 7 | 8 | const { afterEach, beforeEach, describe, it } = exports.lab = Lab.script(); 9 | const { init } = require('./hapi/create-server'); 10 | 11 | describe('hapi-locale with config file', function() { 12 | let server; 13 | 14 | beforeEach(async () => { 15 | const plugins = [ 16 | { 17 | plugin: require('../index.js'), 18 | options: { 19 | configFile: path.join(__dirname, 'config-files', 'config-default.json'), 20 | scan: { 21 | path: path.join(__dirname, 'locales') 22 | } 23 | } 24 | } 25 | ]; 26 | 27 | server = await init(plugins); 28 | }); 29 | 30 | afterEach(async () => { 31 | await server.stop(); 32 | }); 33 | 34 | it('should determine language from query', async function () { 35 | const options = { method: "GET", url: "/locale?lang=tr_TR" }; 36 | const response = await server.inject(options); 37 | expect(response.result).to.equal({ locale: 'tr_TR' }); 38 | }); 39 | 40 | it('should determine language from parameter', async function() { 41 | var options = { method: "GET", url: "/fr_FR/locale" }; 42 | const response = await server.inject(options); 43 | expect(response.result).to.equal( { locale: 'fr_FR' }); 44 | }); 45 | 46 | it('should determine language from header', async function() { 47 | var options = { method: "GET", url: "/locale", headers: { "Accept-Language": "tr-TR,tr;q=0.8" } }; 48 | const response = await server.inject(options); 49 | expect(response.result).to.equal({ locale: 'tr_TR' }); 50 | }); 51 | 52 | }); 53 | 54 | 55 | 56 | describe('hapi-locale with scan dir', function() { 57 | let server; 58 | 59 | beforeEach(async () => { 60 | const plugins = [ 61 | { 62 | plugin: require('../index.js'), 63 | options: { 64 | configFile: path.join(__dirname, 'config-files', 'config-empty.json'), 65 | scan: { 66 | path: path.join(__dirname, 'locales') 67 | } 68 | } 69 | } 70 | ]; 71 | 72 | server = await init(plugins); 73 | }); 74 | 75 | afterEach(async () => { 76 | await server.stop(); 77 | }); 78 | 79 | it('should determine language from query', async function() { 80 | var options = { method: "GET", url: "/locale?lang=jp_JP" }; 81 | const response = await server.inject(options); 82 | expect(response.result).to.equal({ locale: 'jp_JP' }); 83 | }); 84 | 85 | it('should determine language from parameter', async function() { 86 | var options = { method: "GET", url: "/fr_FR/locale" }; 87 | const response = await server.inject(options); 88 | expect(response.result).to.equal({ locale: 'fr_FR' }); 89 | }); 90 | 91 | it('should determine language from header', async function() { 92 | var options = { method: "GET", url: "/locale", headers: { "accept-language": "tr-TR,tr;q=0.8" } }; 93 | const response = await server.inject(options); 94 | expect(response.result).to.equal({ locale: 'tr_TR' }); 95 | }); 96 | }); 97 | 98 | 99 | 100 | 101 | describe('hapi-locale with options', function() { 102 | let server; 103 | 104 | beforeEach(async () => { 105 | const plugins = [ 106 | { 107 | plugin: require('../index.js'), 108 | options: { 109 | locales: ['fr_CA'] 110 | } 111 | } 112 | ]; 113 | 114 | server = await init(plugins); 115 | }); 116 | 117 | afterEach(async () => { 118 | await server.stop(); 119 | }); 120 | 121 | it('should determine language from query', async function() { 122 | var options = { method: "GET", url: "/locale?lang=fr_CA" }; 123 | const response = await server.inject(options); 124 | expect(response.result).to.equal({ locale: 'fr_CA' }); 125 | }); 126 | }); 127 | 128 | 129 | describe('hapi-locale with different order', function() { 130 | let server; 131 | 132 | beforeEach(async () => { 133 | const plugins = [ 134 | { 135 | plugin: require('../index.js'), 136 | options: { 137 | order: ['query', 'params'], 138 | configFile: path.join(__dirname, 'config-files', 'config-empty.json'), 139 | scan: { 140 | path: path.join(__dirname, 'locales') 141 | } 142 | } 143 | } 144 | ]; 145 | 146 | server = await init(plugins); 147 | }); 148 | 149 | afterEach(async () => { 150 | await server.stop(); 151 | }); 152 | 153 | it('should determine language from query', async function() { 154 | var options = { method: "GET", url: "/tr_TR/locale?lang=fr_FR" }; 155 | const response = await server.inject(options); 156 | expect(response.result).to.equal({ locale: 'fr_FR' }); 157 | }); 158 | 159 | 160 | it('should return second order language', async function() { 161 | var options = { method: "GET", url: "/tr_TR/locale?lang=NA_NA" }; 162 | const response = await server.inject(options); 163 | expect(response.result).to.equal({ locale: 'tr_TR' }); 164 | }); 165 | 166 | it('should define setter', async function() { 167 | var options = { method: "GET", url: "/getter-setter" }; 168 | // Handler set locale as ru_RU. 169 | const response = await server.inject(options); 170 | expect(response.result).to.equal({ locale: 'ru_RU' }); 171 | }); 172 | 173 | it('should throw 404', async function() { 174 | var options = { method: "GET", url: "/NA_NA/locale" }; 175 | const response = await server.inject(options); 176 | expect(response.statusCode).to.equal(404); 177 | }); 178 | }); -------------------------------------------------------------------------------- /doc/styles/jsdoc-default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('../fonts/OpenSans-Regular-webfont.eot'); 6 | src: 7 | local('Open Sans'), 8 | local('OpenSans'), 9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), 10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), 11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); 12 | } 13 | 14 | @font-face { 15 | font-family: 'Open Sans Light'; 16 | font-weight: normal; 17 | font-style: normal; 18 | src: url('../fonts/OpenSans-Light-webfont.eot'); 19 | src: 20 | local('Open Sans Light'), 21 | local('OpenSans Light'), 22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), 23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'), 24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg'); 25 | } 26 | 27 | html 28 | { 29 | overflow: auto; 30 | background-color: #fff; 31 | font-size: 14px; 32 | } 33 | 34 | body 35 | { 36 | font-family: 'Open Sans', sans-serif; 37 | line-height: 1.5; 38 | color: #4d4e53; 39 | background-color: white; 40 | } 41 | 42 | a, a:visited, a:active { 43 | color: #0095dd; 44 | text-decoration: none; 45 | } 46 | 47 | a:hover { 48 | text-decoration: underline; 49 | } 50 | 51 | header 52 | { 53 | display: block; 54 | padding: 0px 4px; 55 | } 56 | 57 | tt, code, kbd, samp { 58 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 59 | } 60 | 61 | .class-description { 62 | font-size: 130%; 63 | line-height: 140%; 64 | margin-bottom: 1em; 65 | margin-top: 1em; 66 | } 67 | 68 | .class-description:empty { 69 | margin: 0; 70 | } 71 | 72 | #main { 73 | float: left; 74 | width: 70%; 75 | } 76 | 77 | article dl { 78 | margin-bottom: 40px; 79 | } 80 | 81 | section 82 | { 83 | display: block; 84 | background-color: #fff; 85 | padding: 12px 24px; 86 | border-bottom: 1px solid #ccc; 87 | margin-right: 30px; 88 | } 89 | 90 | .variation { 91 | display: none; 92 | } 93 | 94 | .signature-attributes { 95 | font-size: 60%; 96 | color: #aaa; 97 | font-style: italic; 98 | font-weight: lighter; 99 | } 100 | 101 | nav 102 | { 103 | display: block; 104 | float: right; 105 | margin-top: 28px; 106 | width: 30%; 107 | box-sizing: border-box; 108 | border-left: 1px solid #ccc; 109 | padding-left: 16px; 110 | } 111 | 112 | nav ul { 113 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 114 | font-size: 100%; 115 | line-height: 17px; 116 | padding: 0; 117 | margin: 0; 118 | list-style-type: none; 119 | } 120 | 121 | nav ul a, nav ul a:visited, nav ul a:active { 122 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 123 | line-height: 18px; 124 | color: #4D4E53; 125 | } 126 | 127 | nav h3 { 128 | margin-top: 12px; 129 | } 130 | 131 | nav li { 132 | margin-top: 6px; 133 | } 134 | 135 | footer { 136 | display: block; 137 | padding: 6px; 138 | margin-top: 12px; 139 | font-style: italic; 140 | font-size: 90%; 141 | } 142 | 143 | h1, h2, h3, h4 { 144 | font-weight: 200; 145 | margin: 0; 146 | } 147 | 148 | h1 149 | { 150 | font-family: 'Open Sans Light', sans-serif; 151 | font-size: 48px; 152 | letter-spacing: -2px; 153 | margin: 12px 24px 20px; 154 | } 155 | 156 | h2, h3 157 | { 158 | font-size: 30px; 159 | font-weight: 700; 160 | letter-spacing: -1px; 161 | margin-bottom: 12px; 162 | } 163 | 164 | h4 165 | { 166 | font-size: 18px; 167 | letter-spacing: -0.33px; 168 | margin-bottom: 12px; 169 | color: #4d4e53; 170 | } 171 | 172 | h5, .container-overview .subsection-title 173 | { 174 | font-size: 120%; 175 | font-weight: bold; 176 | letter-spacing: -0.01em; 177 | margin: 8px 0 3px 0; 178 | } 179 | 180 | h6 181 | { 182 | font-size: 100%; 183 | letter-spacing: -0.01em; 184 | margin: 6px 0 3px 0; 185 | font-style: italic; 186 | } 187 | 188 | .ancestors { color: #999; } 189 | .ancestors a 190 | { 191 | color: #999 !important; 192 | text-decoration: none; 193 | } 194 | 195 | .clear 196 | { 197 | clear: both; 198 | } 199 | 200 | .important 201 | { 202 | font-weight: bold; 203 | color: #950B02; 204 | } 205 | 206 | .yes-def { 207 | text-indent: -1000px; 208 | } 209 | 210 | .type-signature { 211 | color: #aaa; 212 | } 213 | 214 | .name, .signature { 215 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 216 | } 217 | 218 | .details { margin-top: 14px; border-left: 2px solid #DDD; } 219 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } 220 | .details dd { margin-left: 70px; } 221 | .details ul { margin: 0; } 222 | .details ul { list-style-type: none; } 223 | .details li { margin-left: 30px; padding-top: 6px; } 224 | .details pre.prettyprint { margin: 0 } 225 | .details .object-value { padding-top: 0; } 226 | 227 | .description { 228 | margin-bottom: 1em; 229 | margin-top: 1em; 230 | } 231 | 232 | .code-caption 233 | { 234 | font-style: italic; 235 | font-size: 107%; 236 | margin: 0; 237 | } 238 | 239 | .prettyprint 240 | { 241 | border: 1px solid #ddd; 242 | width: 80%; 243 | overflow: auto; 244 | } 245 | 246 | .prettyprint.source { 247 | width: inherit; 248 | } 249 | 250 | .prettyprint code 251 | { 252 | font-size: 100%; 253 | line-height: 18px; 254 | display: block; 255 | padding: 4px 12px; 256 | margin: 0; 257 | background-color: #fff; 258 | color: #4D4E53; 259 | } 260 | 261 | .prettyprint code span.line 262 | { 263 | display: inline-block; 264 | } 265 | 266 | .prettyprint.linenums 267 | { 268 | padding-left: 70px; 269 | -webkit-user-select: none; 270 | -moz-user-select: none; 271 | -ms-user-select: none; 272 | user-select: none; 273 | } 274 | 275 | .prettyprint.linenums ol 276 | { 277 | padding-left: 0; 278 | } 279 | 280 | .prettyprint.linenums li 281 | { 282 | border-left: 3px #ddd solid; 283 | } 284 | 285 | .prettyprint.linenums li.selected, 286 | .prettyprint.linenums li.selected * 287 | { 288 | background-color: lightyellow; 289 | } 290 | 291 | .prettyprint.linenums li * 292 | { 293 | -webkit-user-select: text; 294 | -moz-user-select: text; 295 | -ms-user-select: text; 296 | user-select: text; 297 | } 298 | 299 | .params, .props 300 | { 301 | border-spacing: 0; 302 | border: 0; 303 | border-collapse: collapse; 304 | } 305 | 306 | .params .name, .props .name, .name code { 307 | color: #4D4E53; 308 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 309 | font-size: 100%; 310 | } 311 | 312 | .params td, .params th, .props td, .props th 313 | { 314 | border: 1px solid #ddd; 315 | margin: 0px; 316 | text-align: left; 317 | vertical-align: top; 318 | padding: 4px 6px; 319 | display: table-cell; 320 | } 321 | 322 | .params thead tr, .props thead tr 323 | { 324 | background-color: #ddd; 325 | font-weight: bold; 326 | } 327 | 328 | .params .params thead tr, .props .props thead tr 329 | { 330 | background-color: #fff; 331 | font-weight: bold; 332 | } 333 | 334 | .params th, .props th { border-right: 1px solid #aaa; } 335 | .params thead .last, .props thead .last { border-right: 1px solid #ddd; } 336 | 337 | .params td.description > p:first-child, 338 | .props td.description > p:first-child 339 | { 340 | margin-top: 0; 341 | padding-top: 0; 342 | } 343 | 344 | .params td.description > p:last-child, 345 | .props td.description > p:last-child 346 | { 347 | margin-bottom: 0; 348 | padding-bottom: 0; 349 | } 350 | 351 | .disabled { 352 | color: #454545; 353 | } 354 | -------------------------------------------------------------------------------- /doc/module-exposed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Module: exposed 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Module: exposed

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 |
39 | 40 | 41 |

Exposed functions and attributes are listed under exposed name.
To access those attributes request.server.plugins['hapi-locale'] can be used.

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
Source:
89 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
Example
116 | 117 |
<p>var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.</p>
118 | 119 | 120 | 121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |

Methods

139 | 140 | 141 | 142 | 143 | 144 | 145 |

(inner) getDefaultLocale() → {string}

146 | 147 | 148 | 149 | 150 | 151 |
152 |

Returns default locale.

153 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 |
168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
Source:
195 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 |
206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 |
Returns:
220 | 221 | 222 |
223 |
    224 |
  • Default locale
  • 225 |
226 |
227 | 228 | 229 | 230 |
231 |
232 | Type 233 |
234 |
235 | 236 | string 237 | 238 | 239 |
240 |
241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 |

(inner) getLocale(request) → {string}

252 | 253 | 254 | 255 | 256 | 257 |
258 |

Returns requested language.

259 |
260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
Parameters:
270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 |
NameTypeDescription
request 298 | 299 | 300 | Object 301 | 302 | 303 | 304 |

Hapi.js request object

316 | 317 | 318 | 319 | 320 | 321 | 322 |
323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 |
Source:
350 |
353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 |
361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 |
Returns:
375 | 376 | 377 |
378 |

Locale

379 |
380 | 381 | 382 | 383 |
384 |
385 | Type 386 |
387 |
388 | 389 | string 390 | 391 | 392 |
393 |
394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 |

(inner) getLocales() → {Array.<string>}

405 | 406 | 407 | 408 | 409 | 410 |
411 |

Returns all available locales as an array.

412 |
413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 |
427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 |
Source:
454 |
457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 |
465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 |
Returns:
479 | 480 | 481 |
482 |
    483 |
  • Array of locales.
  • 484 |
485 |
486 | 487 | 488 | 489 |
490 |
491 | Type 492 |
493 |
494 | 495 | Array.<string> 496 | 497 | 498 |
499 |
500 | 501 | 502 | 503 | 504 |
Example
505 | 506 |
<p>var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.</p>
507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 |
517 | 518 |
519 | 520 | 521 | 522 | 523 |
524 | 525 | 528 | 529 |
530 | 531 | 534 | 535 | 536 | 537 | 538 | -------------------------------------------------------------------------------- /doc/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README-JSDOC.md: -------------------------------------------------------------------------------- 1 | hapi-locale 2 | =========== 3 | Configurable plugin to determine request language in hapi.js applications. 4 | 5 | Description 6 | =========== 7 | This plugin determines requested loclale by looking followings: (Order can be changed or skipped via `options.order`) 8 | 9 | * URL parameter, 10 | * Cookie, 11 | * Query parameter, 12 | * HTTP header. 13 | 14 | Optionally creates getter and setters or uses already available ones in request. Calls setter method with requested locale. Also provide plugin methods such as `server.plugins['hapi-locale'].getLocale()`; 15 | 16 | Nearly every aspect of the plugin can be configured with options. Sensible defaults are tried to be provided. 17 | 18 | Synopsis 19 | ======== 20 | 21 | Create server 22 | 23 | ... 24 | var plugins = [{ 25 | register: 'hapi-locale' 26 | options: { 27 | createAccessors: true, 28 | getter: 'i18n.getLocale', 29 | setter: 'i18n.setLocale' 30 | } 31 | }] 32 | ... 33 | server.register(plugins, function (err) { 34 | if (err) throw err; 35 | server.start(function () { 36 | console.log('Server started at: ' + server.info.uri); 37 | }); 38 | }); 39 | 40 | In handlers: 41 | 42 | var locale = request.i18n.getLocale(); 43 | 44 | WHY 45 | === 46 | It is easy to determine locale in hapi.js. So why is this plugin wirtten? We are tired of writing repetitive code for every application/module and decided to export this functionality as a hapi plugin. 47 | 48 | Also we make it tested and documented. 49 | 50 | Most Important Options: 51 | ======================= 52 | 53 | The options below are most important ones, because they change/write to request object and may cause undesirable results if configured unsutiable to your needs. 54 | 55 | | Option | Default Value | Description 56 | |-------------------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------| 57 | | `createAccessors` | true | Enables creating getter and setter methods in request object. If set to false it is assumed getter and setter methods are already available. | 58 | | `getter` | i18n.getLocale | Getter method in request object to get current locale. (Created if `options.createAccessors` is true. | 59 | | `setter` | i18n.setLocale | Setter method in request object to set current locale. (Created if `options.createAccessors` is true. | 60 | | `attribute` | i18n.locale | Key in request object which will be used to store locale name. (Created if `options.createAccessors` is true. | 61 | 62 | Please see all options below in hapiLocale~PluginOptions in API section 63 | 64 | How it works 65 | ============ 66 | The workflow of the plugin is as below: 67 | 68 | Plugin 69 | 70 | 1. Determines which locales are available in application. This happens one time during plugin registration. 71 | 2. Tries to find which locale is prefered looking incoming request. This and other steps below happen in every request. Event for this step is configured by `options.onEvent` 72 | 3. Matches requested locale with available locales. If no match is found: 73 | a. If `options.throw404` is true and URL param has a locale which is not available. 74 | b. Sets default locale. 75 | 4. (Optional) Adds getter and setter methods in request object. By deafult `request.i18n.getLocale` and `request.i18n.setLocale`. 76 | 5. Setter is called. 77 | 78 | 79 | ### 1. Available locales 80 | 81 | Available locales are determined with methods in the following order. If one of the methods succeeds no other methods are tried. One or more steps may be cancelled via `options`. Available locales are searched one time during plugin registration. 82 | 83 | Plugin 84 | 85 | 1. Looks locales in plugin options `options.locales`. Set empty `[]` to skip. 86 | 2. Looks `package.json` or other json file set by `options.configFile` and `options.configKey`. Key may be set with nested format such as 'pref.of.my.app.locales'. Set `null` to skip. 87 | 3. Scans path given by `options.scan.path` excluding files and directories given by `options.path.exclude`. Set `null` to skip. 88 | 89 | 90 | ### 2. Requested locale(s) 91 | 92 | One or more locale may be preferred in requests. To determine most wanted locale for every request following steps are taken in order set by `options.order`. One or more steps may be cancelled via setting null in related `options` or not including to `options.order`. 93 | 94 | Plugin (in default order, which can be changed from `options.order`) 95 | 96 | 1. `params` looks path paramater such as `{lang}/member` for `/en_US/member`. Path parameter name can be set via `options.param`. 97 | 2. `cookie` looks cookie. Cookie name can be set via `options.cookie`, cookie key to look in cookie can be set `options.cookieKey`. 98 | 3. `query` looks query paramater such as `/member?lang=en_US`. Query parameter name can be set via `options.query`. 99 | 4. `header` looks `accept-language` header of request. Header name can be set via `options.header`. 100 | 101 | 102 | ### 3. Match Requested locale 103 | 104 | Plugin tries to find first preferred locale which is available in application: 105 | 106 | 1. If a match is found, locale is determined. 107 | 2. If no match is found plugin either throws 404 for URL parameter if `options.throw404` set true. 108 | 3. If no 404 is thrown, default locale is used as a result. Default locale may set via `options.default`, otherwise first available locale is used as default. 109 | 110 | 111 | ### 4. Getter and Setter Methods 112 | 113 | Plugin uses getter and setter methods. It creates them if `options.createAccessors` is true and they do not exist. Name of the methods are set via `options.getter` and `options.setter` options. Default values are `i18n.getLocale` and `i18n.setLocale`. 114 | 115 | 116 | ### 5. Callback is called 117 | 118 | Callback is called with locale name as only parameter. Callback name is configured via `options.callback`. If callback name is given as a function reference, it is called directly. If it is given as string it is called as a chained method of request object. Default is "i18n.setLocale" which results as `request.i18n.setLocale`. It is possible to use a chained method name such as "i18n.setLocale" which results as `request.i18n.setLocale`. 119 | 120 | Order & Prioritization 121 | ======================== 122 | By default this plugin looks URL Part (`request.params`), Cookie (`request.state`), Query String (`request.query`), Header (`request.headers`) in this order: 'params', 'cookie', 'query', 'headers'. If you wish to change this order you can set it with `options.order` array. 123 | 124 | Event Times 125 | =========== 126 | Available locales are determined one time during server start plugin registration. Per request operations happens on event set by `options.onEvent`. 127 | 128 | Exposed Functions & Attributes 129 | ============================== 130 | This plugin exposes some functions and attributes using server.expose mechanism of hapi.js. They are documented under API section's exposed part. See there for details. 131 | 132 | // This function may be used to access requested locale manually. 133 | var locale = request.server.plugins['hapi-locale'].getLocale(request, reply); // 'tr_TR' 134 | 135 | var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 136 | 137 | Examples 138 | ======== 139 | 140 | ### Use with default options: 141 | 142 | var server = new hapi.Server(), 143 | path = require('path'); 144 | 145 | server.connection({ 146 | host: 'localhost', 147 | port: 8080 148 | }); 149 | 150 | var plugins = ['hapi-locale'] 151 | 152 | server.route([ 153 | { 154 | path: "/locale", 155 | method: "GET", 156 | handler: function(request, reply) { 157 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 158 | } 159 | }, 160 | { 161 | path: "/{lang}/locale", 162 | method: "GET", 163 | handler: function(request, reply) { 164 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 165 | } 166 | } 167 | ]); 168 | 169 | 170 | server.register(plugins, function (err) { 171 | if (err) throw err; 172 | server.start(function () { 173 | console.log('Server started at: ' + server.info.uri); 174 | }); 175 | }); 176 | 177 | ### Providing options 178 | 179 | Options below are also default options. 180 | 181 | var server = new hapi.Server(), 182 | path = require('path'); 183 | 184 | server.connection({ 185 | host: 'localhost', 186 | port: 8080 187 | }); 188 | 189 | var rootDir = __dirname; 190 | 191 | // Those are also default options: 192 | var plugins = [ 193 | { 194 | register: 'hapi-locale', 195 | options: { 196 | locales : [], 197 | configFile : path.join(rootDir, 'package.json'), 198 | configKey : 'locales', 199 | scan : { 200 | path : path.join(rootDir, 'locales'), 201 | fileType : 'json', 202 | directories : true, 203 | exclude : ['templates', 'template.json'] 204 | }, 205 | param : 'lang', 206 | query : 'lang', 207 | cookie : 'lang', 208 | cookieKey : 'lang', 209 | header : 'accept-language', 210 | order : ['params', 'cookie', 'query', 'headers'], 211 | throw404 : true, 212 | getter : 'i18n.getLocale', 213 | setter : 'i18n.setLocale', 214 | createAccessors : true, 215 | attribute : 'i18n.locale', 216 | callback : 'setLocale', 217 | onEvent : 'onPreAuth' 218 | } 219 | } 220 | ]; 221 | 222 | server.route([ 223 | { 224 | path: "/locale", 225 | method: "GET", 226 | handler: function(request, reply) { 227 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 228 | } 229 | }, 230 | { 231 | path: "/{lang}/locale", 232 | method: "GET", 233 | handler: function(request, reply) { 234 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 235 | } 236 | } 237 | ]); 238 | 239 | 240 | server.register(plugins, function (err) { 241 | if (err) throw err; 242 | server.start(function () { 243 | console.log('Server started at: ' + server.info.uri); 244 | }); 245 | }); 246 | 247 | 248 | ### Routes 249 | 250 | | **ROUTE** | **REQUEST** | **HEADER** | **LOCALE** | **REASON (Default Config)** 251 | |---------------------|-------------------------------|--------------------------------|-----------------|-----------------------| 252 | | /{lang}/account | GET /en_US/account | | en_US | Path | 253 | | /{lang}/account | GET /tr_TR/account?lang=fr_FR | accept-language=jp_JP;jp;q=0.8 | tr_TR | Path has more priority| 254 | | /api/{lang}/account | GET api/en_US/account | | en_US | Path | 255 | | /account | GET /account?lang=en_US | | en_US | Query | 256 | | /api/account | GET api/account?lang=en_US | | en_US | Query | 257 | | /account | GET /account | accept-language=en_US;en;q=0.8 | en_US | Header | 258 | | /{lang}/account | GET /nonsense/account | | *404* | Not found URL | 259 | | /account | GET account?lang=nonsense | | *Default Locale*| Not found URL | 260 | 261 | 262 | API 263 | === 264 | 265 | -------------------------------------------------------------------------------- /doc/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p 2 | 3 | 4 | 5 | JSDoc: Home 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Home

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |

hapi-locale

Configurable plugin to determine request language in hapi.js applications.

47 |

Description

This plugin determines requested loclale by looking followings: (Order can be changed or skipped via options.order)

48 |
    49 |
  • URL parameter,
  • 50 |
  • Cookie,
  • 51 |
  • Query parameter,
  • 52 |
  • HTTP header.
  • 53 |
54 |

Optionally creates getter and setters or uses already available ones in request. Calls setter method with requested locale. Also provide plugin methods such as server.plugins['hapi-locale'].getLocale();

55 |

Nearly every aspect of the plugin can be configured with options. Sensible defaults are tried to be provided.

56 |

Synopsis

Create server

57 |
...
 58 | var plugins = [{ 
 59 |     register: 'hapi-locale'
 60 |     options: {
 61 |         createAccessors: true,
 62 |         getter: 'i18n.getLocale',
 63 |         setter: 'i18n.setLocale'
 64 |     }
 65 | }]
 66 | ...
 67 | server.register(plugins, function (err) {
 68 |     if (err) throw err;
 69 |     server.start(function () {
 70 |         console.log('Server started at: ' + server.info.uri);
 71 |     });
 72 | });

In handlers:

73 |
var locale = request.i18n.getLocale();

WHY

It is easy to determine locale in hapi.js. So why is this plugin wirtten? We are tired of writing repetitive code for every application/module and decided to export this functionality as a hapi plugin.

74 |

Also we make it tested and documented.

75 |

Most Important Options:

The options below are most important ones, because they change/write to request object and may cause undesirable results if configured unsutiable to your needs.

76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
OptionDefault ValueDescription
createAccessorstrueEnables creating getter and setter methods in request object. If set to false it is assumed getter and setter methods are already available.
getteri18n.getLocaleGetter method in request object to get current locale. (Created if options.createAccessors is true.
setteri18n.setLocaleSetter method in request object to set current locale. (Created if options.createAccessors is true.
attributei18n.localeKey in request object which will be used to store locale name. (Created if options.createAccessors is true.
107 |

Please see all options below in hapiLocale~PluginOptions in API section

108 |

How it works

The workflow of the plugin is as below:

109 |

Plugin

110 |
    111 |
  1. Determines which locales are available in application. This happens one time during plugin registration.
  2. 112 |
  3. Tries to find which locale is prefered looking incoming request. This and other steps below happen in every request. Event for this step is configured by options.onEvent
  4. 113 |
  5. Matches requested locale with available locales. If no match is found:
     a. If `options.throw404` is true and URL param has a locale which is not available. 
    114 |  b. Sets default locale.
  6. 115 |
  7. (Optional) Adds getter and setter methods in request object. By deafult request.i18n.getLocale and request.i18n.setLocale.
  8. 116 |
  9. Setter is called.
  10. 117 |
118 |

1. Available locales

Available locales are determined with methods in the following order. If one of the methods succeeds no other methods are tried. One or more steps may be cancelled via options. Available locales are searched one time during plugin registration.

119 |

Plugin

120 |
    121 |
  1. Looks locales in plugin options options.locales. Set empty [] to skip.
  2. 122 |
  3. Looks package.json or other json file set by options.configFile and options.configKey. Key may be set with nested format such as 'pref.of.my.app.locales'. Set null to skip.
  4. 123 |
  5. Scans path given by options.scan.path excluding files and directories given by options.path.exclude. Set null to skip.
  6. 124 |
125 |

2. Requested locale(s)

One or more locale may be preferred in requests. To determine most wanted locale for every request following steps are taken in order set by options.order. One or more steps may be cancelled via setting null in related options or not including to options.order.

126 |

Plugin (in default order, which can be changed from options.order)

127 |
    128 |
  1. params looks path paramater such as {lang}/member for /en_US/member. Path parameter name can be set via options.param.
  2. 129 |
  3. cookie looks cookie. Cookie name can be set via options.cookie, cookie key to look in cookie can be set options.cookieKey.
  4. 130 |
  5. query looks query paramater such as /member?lang=en_US. Query parameter name can be set via options.query.
  6. 131 |
  7. header looks accept-language header of request. Header name can be set via options.header.
  8. 132 |
133 |

3. Match Requested locale

Plugin tries to find first preferred locale which is available in application:

134 |
    135 |
  1. If a match is found, locale is determined.
  2. 136 |
  3. If no match is found plugin either throws 404 for URL parameter if options.throw404 set true.
  4. 137 |
  5. If no 404 is thrown, default locale is used as a result. Default locale may set via options.default, otherwise first available locale is used as default.
  6. 138 |
139 |

4. Getter and Setter Methods

Plugin uses getter and setter methods. It creates them if options.createAccessors is true and they do not exist. Name of the methods are set via options.getter and options.setter options. Default values are i18n.getLocale and i18n.setLocale.

140 |

5. Callback is called

Callback is called with locale name as only parameter. Callback name is configured via options.callback. If callback name is given as a function reference, it is called directly. If it is given as string it is called as a chained method of request object. Default is "i18n.setLocale" which results as request.i18n.setLocale. It is possible to use a chained method name such as "i18n.setLocale" which results as request.i18n.setLocale.

141 |

Order & Prioritization

By default this plugin looks URL Part (request.params), Cookie (request.state), Query String (request.query), Header (request.headers) in this order: 'params', 'cookie', 'query', 'headers'. If you wish to change this order you can set it with options.order array.

142 |

Event Times

Available locales are determined one time during server start plugin registration. Per request operations happens on event set by options.onEvent.

143 |

Exposed Functions & Attributes

This plugin exposes some functions and attributes using server.expose mechanism of hapi.js. They are documented under API section's exposed part. See there for details.

144 |
// This function may be used to access requested locale manually.
145 | var locale  = request.server.plugins['hapi-locale'].getLocale(request, reply); // 'tr_TR'
146 | 
147 | var locales = request.server.plugins['hapi-locale'].getLocales();   // ['tr_TR', 'en_US'] etc.

Examples

Use with default options:

var server  = new hapi.Server(),
148 |     path    = require('path');
149 | 
150 | server.connection({
151 |     host: 'localhost',
152 |     port: 8080
153 | });
154 | 
155 | var plugins = ['hapi-locale']
156 | 
157 | server.route([
158 |     {
159 |         path: "/locale",
160 |         method: "GET",
161 |         handler: function(request, reply) {
162 |             reply({ locale: request.i18n.getLocale() });    // This method is added by hapi-locale
163 |         }
164 |     },
165 |     {
166 |         path: "/{lang}/locale",
167 |         method: "GET",
168 |         handler: function(request, reply) {
169 |             reply({ locale: request.i18n.getLocale() });    // This method is added by hapi-locale
170 |         }
171 |     }
172 | ]);
173 | 
174 | 
175 | server.register(plugins, function (err) {
176 |     if (err) throw err;
177 |     server.start(function () {
178 |         console.log('Server started at: ' + server.info.uri);
179 |     });
180 | });

Providing options

Options below are also default options.

181 |
var server  = new hapi.Server(),
182 |     path    = require('path');
183 | 
184 | server.connection({
185 |     host: 'localhost',
186 |     port: 8080
187 | });
188 | 
189 | var rootDir = __dirname;
190 | 
191 | // Those are also default options:
192 | var plugins = [
193 |     {
194 |         register: 'hapi-locale',
195 |         options: {
196 |             locales         : [],
197 |             configFile      : path.join(rootDir, 'package.json'),
198 |             configKey       : 'locales',
199 |             scan            : {
200 |                 path        : path.join(rootDir, 'locales'),
201 |                 fileType    : 'json',
202 |                 directories : true,
203 |                 exclude     : ['templates', 'template.json']
204 |             },
205 |             param           : 'lang',
206 |             query           : 'lang',
207 |             cookie          : 'lang',
208 |             cookieKey       : 'lang',
209 |             header          : 'accept-language',
210 |             order           : ['params', 'cookie', 'query', 'headers'],
211 |             throw404        : true,
212 |             getter          : 'i18n.getLocale',
213 |             setter          : 'i18n.setLocale',
214 |             createAccessors : true,
215 |             attribute       : 'i18n.locale',
216 |             callback        : 'setLocale',
217 |             onEvent         : 'onPreAuth'
218 |          }
219 |     }
220 | ];
221 | 
222 | server.route([
223 |     {
224 |         path: "/locale",
225 |         method: "GET",
226 |         handler: function(request, reply) {
227 |             reply({ locale: request.i18n.getLocale() });    // This method is added by hapi-locale
228 |         }
229 |     },
230 |     {
231 |         path: "/{lang}/locale",
232 |         method: "GET",
233 |         handler: function(request, reply) {
234 |             reply({ locale: request.i18n.getLocale() });    // This method is added by hapi-locale
235 |         }
236 |     }
237 | ]);
238 | 
239 | 
240 | server.register(plugins, function (err) {
241 |     if (err) throw err;
242 |     server.start(function () {
243 |         console.log('Server started at: ' + server.info.uri);
244 |     });
245 | });

Routes

246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 |
ROUTEREQUESTHEADERLOCALEREASON (Default Config)
/{lang}/accountGET /en_US/accounten_USPath
/{lang}/accountGET /tr_TR/account?lang=fr_FRaccept-language=jp_JP;jp;q=0.8tr_TRPath has more priority
/api/{lang}/accountGET api/en_US/accounten_USPath
/accountGET /account?lang=en_USen_USQuery
/api/accountGET api/account?lang=en_USen_USQuery
/accountGET /accountaccept-language=en_US;en;q=0.8en_USHeader
/{lang}/accountGET /nonsense/account404Not found URL
/accountGET account?lang=nonsenseDefault LocaleNot found URL
314 |

API

315 |
316 | 317 | 318 | 319 | 320 | 321 | 322 |
323 | 324 | 327 | 328 |
329 | 330 |
331 | Documentation generated by JSDoc 3.4.0-dev on Fri Oct 16 2015 16:30:26 GMT+0300 (EEST) 332 |
333 | 334 | 335 | 336 | 337 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true, nomen: true, stupid: true */ 2 | 3 | 4 | /** 5 | * @module 'hapi-locale' 6 | * @description 7 | * Configurable plugin for determine request language in hapi.js applications. 8 | */ 9 | const Boom = require('@hapi/boom'), 10 | fs = require('fs'), 11 | path = require('path'), 12 | lodash = require('lodash'), 13 | headerParser = require('accept-language-parser'), 14 | Joi = require('@hapi/joi'), 15 | pkg = require("../package"); 16 | 17 | var rootDir = path.join(__dirname, '../../..'); 18 | var locales = []; 19 | 20 | /** 21 | * @typedef {Object} PluginOptions - Plugin configuration options. 22 | * @property {Array.} [locales=[]] - List of locales to use in application. 23 | * @property {string|null} [default=1st Locale] - Default locale to use if no locale is given. 24 | * @property {string|null} [configFile=package.json] - Configuration file to get available locales. 25 | * @property {string|null} [configKey=locales] - Key to look in configuration file to get available locales. May be nested key such as 'a.b.c'. 26 | * @property {Object} [scan] - Scanning options to get available locales 27 | * @property {string} [scan.path=locale] - Path or paths to scan locale files to get available locales. 28 | * @property {string} [scan.fileTypes=json] - File types to scan. ie. "json" for en_US.json, tr_TR.json 29 | * @property {boolean} [scan.directories=true] - whether to scan directory names to get available locales. 30 | * @property {Array.} [scan.exclude=[templates]] - Directory or file names to exclude from scan results. 31 | * @property {string|null} [param=lang] - Name of the path parameter to determine language. ie. /{lang}/account 32 | * @property {string|null} [query=lang] - Name of the query parameter to determine language. ie. /account?lang=tr_TR 33 | * @property {string|null} [cookie=lang] - Name of the cookie to determine language. 34 | * @property {string|null} [cookieKey=lang] - Name of the key to look inside cookie to determine language. May be nested key such as 'a.b.c'. 35 | * @property {string|null} [header=accept-language] - Name of the header parameter to determine language. 36 | * @property {Array.} [order=['params', 'cookie', 'query', 'headers']] - Order in which language determination process follows. First successful method returns requested language. 37 | * @property {boolean} [throw404=true] - Whether to throw 404 not found if locale is not found. Does not apply path parameters, it always throws 404. 38 | * @property {string|null} [getter=i18n.getLocale] - Getter method in request object to get current locale. May be nested object such as 'a.b.c' 39 | * @property {string|null} [setter=i18n.setLocale] - Setter method in request object to set current locale. May be nested object such as 'a.b.c' 40 | * @property {string|null} [attribute=i18n.locale] - Key in request object which will be used to store locale name. May be nested path such as 'a.b.c'. 41 | * @property {boolean} [createAccessors=true] - Enables creating getter and setter methods in request object. 42 | * @property {string} [onEvent=onPreAuth] - Event on which locale determination process is fired. 43 | */ 44 | 45 | /** 46 | * @type {PluginOptions} 47 | * @private 48 | */ 49 | var defaultOptions = { 50 | locales: [], 51 | configFile: path.join(rootDir, 'package.json'), 52 | configKey: 'locales', 53 | scan: { 54 | path: path.join(rootDir, 'locales'), 55 | fileType: 'json', 56 | directories: true, 57 | exclude: ['templates', 'template.json'] 58 | }, 59 | param: 'lang', 60 | query: 'lang', 61 | cookie: 'lang', 62 | cookieKey: 'lang', 63 | header: 'accept-language', 64 | order: ['params', 'cookie', 'query', 'headers'], 65 | throw404: true, 66 | getter: 'i18n.getLocale', 67 | setter: 'i18n.setLocale', 68 | attribute: 'i18n.locale', 69 | createAccessors: true, 70 | onEvent: 'onPreAuth', 71 | default: null, 72 | }; 73 | 74 | var orderParameters = { 75 | // Process in options.order array and JS method which will be called for that process. 76 | params: 'parseParam', 77 | query: 'parseQuery', 78 | headers: 'parseHeader', 79 | cookie: 'parseCookie' 80 | }; 81 | 82 | var optionsSchema = Joi.object({ 83 | locales: Joi.array().items(Joi.string()).default(defaultOptions.locales), 84 | default: Joi.string().allow(null).default(defaultOptions.default), 85 | configFile: Joi.string().allow(null).default(defaultOptions.configFile), 86 | configKey: Joi.string().allow(null).default(defaultOptions.configKey), 87 | scan: Joi.object({ 88 | path: Joi.string().default(defaultOptions.scan.path), 89 | fileType: Joi.string().default(defaultOptions.scan.fileType), 90 | directories: Joi.boolean().default(defaultOptions.scan.directories), 91 | exclude: Joi.array().items(Joi.string()).allow(null).default(defaultOptions.scan.exclude) 92 | }).allow(null).default(defaultOptions.scan), 93 | param: Joi.string().allow(null).default(defaultOptions.param), 94 | query: Joi.string().allow(null).default(defaultOptions.query), 95 | cookie: Joi.string().allow(null).default(defaultOptions.cookie), 96 | cookieKey: Joi.string().allow(null).default(defaultOptions.cookieKey), 97 | header: Joi.string().allow(null).default(defaultOptions.header), 98 | order: Joi.array().items(Joi.any().valid(...Object.keys(orderParameters))).default(defaultOptions.order), 99 | throw404: Joi.boolean().default(defaultOptions.throw404), 100 | getter: Joi.string().allow(null).default(defaultOptions.getter), 101 | setter: Joi.string().allow(null).default(defaultOptions.setter), 102 | attribute: Joi.string().allow(null).default(defaultOptions.attribute), 103 | createAccessors: Joi.string().allow(null).default(defaultOptions.createAccessors), 104 | onEvent: Joi.string().default(defaultOptions.onEvent) 105 | }); 106 | 107 | /** 108 | * Class to implement inner working of plugin. 109 | * @param {PluginOptions} options - Plugin configuration options. 110 | * @constructor 111 | * @private 112 | */ 113 | var Internal = function (options) { 114 | 115 | if ((options.setter && options.setter.indexOf('.') > -1) || (options.getter && options.getter.indexOf('.') > -1)) { 116 | throw new Error('Getter (' + options.getter + ') and setter (' + options.setter + ') methods cannot be nested, so they cannot contain dot(.)'); 117 | } 118 | 119 | this.options = Joi.attempt(options, optionsSchema); 120 | this.locales = this.getAvailableLocales(); 121 | this.default = this.options.default || this.locales[0]; 122 | //this.callback = this.getCallback(this.options.callback); 123 | }; 124 | 125 | /** 126 | * Returns requested languages as an array by looking url part. 127 | * @param {Object} request - Hapi request object. 128 | * @returns {string|undefined} - Requested locale or undefined. 129 | * @private 130 | */ 131 | Internal.prototype.parseParam = function parseParam(request) { 132 | 133 | if (!request.params.hasOwnProperty(this.options.param)) return; 134 | var name = this.options.param, 135 | locales = lodash.get(request.params, name), 136 | match = this.bestMatch(locales); 137 | 138 | if (!match && this.options.throw404) { 139 | throw new Error('Requested locale/language ' + locales + ' cannot be found.'); 140 | } 141 | 142 | return match; 143 | }; 144 | 145 | /** 146 | * Returns requested languages as an array by looking query parameter. 147 | * @param {Object} request - Hapi request object. 148 | * @returns {string|undefined} - Requested locale or undefined. 149 | * @private 150 | */ 151 | Internal.prototype.parseQuery = function parseQuery(request) { 152 | var name = this.options.query, 153 | locales = lodash.get(request.query, name); 154 | 155 | return this.bestMatch(locales); 156 | }; 157 | 158 | /** 159 | * Returns requested language from cookie if found in available languages. 160 | * @param {Object} request - Hapi request object. 161 | * @returns {string|undefined} - Requested locale or undefined. 162 | * @private 163 | */ 164 | Internal.prototype.parseCookie = function parseCookie(request) { 165 | 166 | var name = this.options.cookie, 167 | key = this.options.cookieKey, 168 | locales = lodash.get(request.state[name], key); 169 | 170 | return this.bestMatch(locales); 171 | }; 172 | 173 | /** 174 | * Returns requested language from header if found in available languages. 175 | * @param {Object} request - Hapi request object 176 | * @returns {string|undefined} - Requested locale or undefined. 177 | * @private 178 | */ 179 | Internal.prototype.parseHeader = function parseHeader(request) { 180 | var name = this.options.header, 181 | raw = headerParser.parse(request.headers[name]), 182 | locales = raw.map(function (value) { 183 | return value.region ? value.code + '_' + value.region : value.code; 184 | }); 185 | 186 | return this.bestMatch(locales); 187 | }; 188 | 189 | /** 190 | * Returns best match for requested locale among available locales. First matched locale will be returned. 191 | * @param {string|Array.} requested - Requested locale or list of requested locales. 192 | * @returns {string|undefined} - Matched locale or null if not any match found. 193 | * @private 194 | */ 195 | Internal.prototype.bestMatch = function bestMatch(requested) { 196 | if (!requested) return; 197 | if (!Array.isArray(requested)) requested = [requested]; 198 | 199 | for (let one of requested) { 200 | if (this.locales.indexOf(one) > -1) return one; 201 | } 202 | }; 203 | 204 | 205 | /** 206 | * Checks synchroniously if given file or directory exists. Returns true or false. 207 | * @param {string} path - Path of the file or directory. 208 | * @param {boolean} shouldBeDir - Whether given path should be directory. 209 | * @returns {boolean} 210 | * @private 211 | */ 212 | function fileExists(path, shouldBeDir) { 213 | 214 | try { 215 | var lstat = fs.lstatSync(path); 216 | if (shouldBeDir && lstat.isDirectory()) { 217 | return true; 218 | } 219 | if (!shouldBeDir && lstat.isFile()) { 220 | return true; 221 | } 222 | } catch (err) { 223 | return false; 224 | } 225 | return false; 226 | } 227 | 228 | 229 | /** 230 | * Scans path in options.scan.path and returns list of available locale files. 231 | * @returns {Array.} 232 | * @throws {Error} - Throws error if locales directory is not found. 233 | * @private 234 | */ 235 | Internal.prototype.scan = function scan() { 236 | // Check if scan path is available 237 | if (this.options.scan && !fileExists(this.options.scan.path, true)) { 238 | throw new Error('Locales directory "' + this.options.scan.path + '"cannot be found.'); 239 | } 240 | 241 | let dir = this.options.scan.path, 242 | files = fs.readdirSync(dir), 243 | locales = []; 244 | 245 | for (let file of files) { 246 | let fullPath = path.join(dir, file); 247 | 248 | // Skip if it is in exclude list or it is directory and scan.directories is false 249 | if (this.options.scan.exclude.indexOf(file) > -1 || (fs.statSync(fullPath).isDirectory() && !this.options.scan.directories)) { 250 | continue; 251 | } 252 | 253 | locales.push(path.basename(file, path.extname(file))); // Strip extension such as .json 254 | } 255 | 256 | return lodash.uniq(locales); 257 | }; 258 | 259 | 260 | /** 261 | * Determines which locales are available. It tries to determine available locales in given order: 262 | * 1. Returns if locales are present in options.locales. 263 | * 2. If not found, looks for given config file and searches opted key in config file. 264 | * 3. If not found, scans path given in options.scan.path for files and directories excluding files in options.scan.exclude. 265 | * @returns {Array|null} - List of available locales 266 | * @throws {Error} - Throws error if necessary files are not found or no locales are available. 267 | * @private 268 | */ 269 | Internal.prototype.getAvailableLocales = function getAvailableLocales() { 270 | 271 | let locales = []; 272 | 273 | if (Array.isArray(this.options.locales) && this.options.locales.length > 0) { 274 | locales = this.options.locales; 275 | if (!Array.isArray(locales)) locales = []; 276 | } 277 | 278 | // Config file 279 | if (locales.length === 0 && this.options.configFile) { 280 | 281 | // Check if config file is available 282 | if (!fileExists(this.options.configFile, false)) { 283 | throw new Error('Configuration file "' + this.options.configFile + '" cannot be found'); 284 | } 285 | 286 | locales = lodash.get(require(this.options.configFile), this.options.configKey); // key chain string to reference: 'options.locale' => options.locale 287 | if (!Array.isArray(locales)) locales = []; 288 | } 289 | 290 | // Locale files 291 | if (locales.length === 0 && this.options.scan.path) { 292 | locales = this.scan(); 293 | } 294 | 295 | if (locales.length === 0) { 296 | throw new Error('Cannot found any locale.') 297 | } 298 | 299 | return lodash.uniq(locales); 300 | }; 301 | 302 | 303 | /** 304 | * @param {Object} request - hapi.js request object 305 | * @returns {string|undefined} - Locale 306 | * @private 307 | */ 308 | Internal.prototype.determineLocale = function determineLocale(request) { 309 | let requestedLocale; 310 | 311 | for (let method of this.options.order) { 312 | requestedLocale = this[orderParameters[method]](request); // this.parseParam | this.parseCookie ... etc. 313 | if (requestedLocale) break; 314 | } 315 | 316 | return requestedLocale || this.default; 317 | }; 318 | 319 | 320 | /** 321 | * 322 | * @param {Object} request - hapi.js request object 323 | * @param {Function} reply - hapi.js reply object 324 | * @returns {*} 325 | */ 326 | Internal.prototype.processRequest = function processRequest(request, h) { 327 | 328 | try { 329 | var locale = this.determineLocale(request); 330 | } catch (err) { 331 | throw Boom.notFound(err); 332 | } 333 | 334 | let getter = this.options.getter, 335 | setter = this.options.setter, 336 | attribute = this.options.attribute; 337 | 338 | // Create accessors if necessary 339 | if (this.options.createAccessors) { 340 | if (!lodash.get(request, getter)) { 341 | lodash.set(request, getter, function () { 342 | return lodash.get(request, attribute); 343 | }); 344 | } 345 | if (!lodash.get(request, setter)) { 346 | lodash.set(request, setter, function (locale) { 347 | return lodash.set(request, attribute, locale); 348 | }); 349 | } 350 | } 351 | 352 | // Call setter. 353 | lodash.get(request, setter)(locale); 354 | 355 | return h.continue; 356 | }; 357 | 358 | 359 | exports.plugin = { 360 | name: pkg.name, 361 | version: pkg.version, 362 | pkg: pkg, 363 | once: true, 364 | multiple: false, 365 | /** 366 | * Hapi plugin function which adds i18n support to request and response objects. 367 | * @param {Object} server - Hapi server object 368 | * @param {PluginOptions} options - Plugin configuration options. 369 | */ 370 | register: async function (server, options) { 371 | try { 372 | var internal = new Internal(options); 373 | } catch (err) { 374 | throw new Boom.Boom(err); 375 | } 376 | 377 | /** 378 | * @module exposed 379 | * @description 380 | * Exposed functions and attributes are listed under exposed name. 381 | * To access those attributes `request.server.plugins['hapi-locale']` can be used. 382 | * @example 383 | * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 384 | */ 385 | 386 | /** 387 | * Returns all available locales as an array. 388 | * @name getLocales 389 | * @function 390 | * @returns {Array.} - Array of locales. 391 | * @example 392 | * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 393 | */ 394 | server.expose('getLocales', function getLocales() { 395 | return internal.locales; 396 | }); 397 | 398 | /** 399 | * Returns default locale. 400 | * @name getDefaultLocale 401 | * @function 402 | * @returns {string} - Default locale 403 | */ 404 | server.expose('getDefaultLocale', function getDefaultLocale() { 405 | return internal.default; 406 | }); 407 | 408 | /** 409 | * Returns requested language. 410 | * @name getLocale 411 | * @function 412 | * @param {Object} request - Hapi.js request object 413 | * @returns {string} Locale 414 | */ 415 | server.expose('getLocale', function getLocale(request) { 416 | try { 417 | return lodash.get(request, internal.options.getter)(); 418 | } catch (err) { 419 | return null; 420 | } 421 | }); 422 | 423 | server.ext(internal.options.onEvent, internal.processRequest, {bind: internal}); 424 | } 425 | }; 426 | -------------------------------------------------------------------------------- /doc/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: index.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: index.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
/*jslint node: true, nomen: true, stupid: true */
 30 | "use strict";
 31 | 
 32 | /**
 33 |  * @module 'hapi-locale'
 34 |  * @description
 35 |  * Configurable plugin for determine request language in hapi.js applications.
 36 |  */
 37 | var Boom            = require('boom'),
 38 |     fs              = require('fs'),
 39 |     path            = require('path'),
 40 |     lodash          = require('lodash'),
 41 |     headerParser    = require('accept-language-parser'),
 42 |     Joi             = require('joi');
 43 | 
 44 | var rootDir     = path.join(__dirname, '../../..');
 45 | var locales     = [];
 46 | 
 47 | /**
 48 |  * @typedef {Object}                    PluginOptions                   - Plugin configuration options.
 49 |  * @property {Array.<string>}           [locales=[]]                    - List of locales to use in application.
 50 |  * @property {string|null}              [default=1st Locale]            - Default locale to use if no locale is given.
 51 |  * @property {string|null}              [configFile=package.json]       - Configuration file to get available locales.
 52 |  * @property {string|null}              [configKey=locales]             - Key to look in configuration file to get available locales. May be nested key such as 'a.b.c'.
 53 |  * @property {Object}                   [scan]                          - Scanning options to get available locales
 54 |  * @property {string}                   [scan.path=locale]              - Path or paths to scan locale files to get available locales.
 55 |  * @property {string}                   [scan.fileTypes=json]           - File types to scan. ie. "json" for en_US.json, tr_TR.json
 56 |  * @property {boolean}                  [scan.directories=true]         - whether to scan directory names to get available locales.
 57 |  * @property {Array.<string>}           [scan.exclude=[templates]]      - Directory or file names to exclude from scan results.
 58 |  * @property {string|null}              [param=lang]                    - Name of the path parameter to determine language. ie. /{lang}/account
 59 |  * @property {string|null}              [query=lang]                    - Name of the query parameter to determine language. ie. /account?lang=tr_TR
 60 |  * @property {string|null}              [cookie=lang]                   - Name of the cookie to determine language.
 61 |  * @property {string|null}              [cookieKey=lang]                - Name of the key to look inside cookie to determine language. May be nested key such as 'a.b.c'.
 62 |  * @property {string|null}              [header=accept-language]        - Name of the header parameter to determine language.
 63 |  * @property {Array.<string>}           [order=['params', 'cookie', 'query', 'headers']] - Order in which language determination process follows. First successful method returns requested language.
 64 |  * @property {boolean}                  [throw404=true]                 - Whether to throw 404 not found if locale is not found. Does not apply path parameters, it always throws 404.
 65 |  * @property {string|null}              [getter=i18n.getLocale]         - Getter method in request object to get current locale. May be nested object such as 'a.b.c'
 66 |  * @property {string|null}              [setter=i18n.setLocale]         - Setter method in request object to set current locale. May be nested object such as 'a.b.c'
 67 |  * @property {string|null}              [attribute=i18n.locale]         - Key in request object which will be used to store locale name. May be nested path such as 'a.b.c'.
 68 |  * @property {boolean}                  [createAccessors=true]          - Enables creating getter and setter methods in request object.
 69 |  * @property {string}                   [onEvent=onPreAuth]             - Event on which locale determination process is fired.
 70 |  */
 71 | 
 72 | /**
 73 |  * @type {PluginOptions}
 74 |  * @private
 75 |  */
 76 | var defaultOptions = {
 77 |     locales         : [],
 78 |     configFile      : path.join(rootDir, 'package.json'),
 79 |     configKey       : 'locales',
 80 |     scan            : {
 81 |         path        : path.join(rootDir, 'locales'),
 82 |         fileType    : 'json',
 83 |         directories : true,
 84 |         exclude     : ['templates', 'template.json']
 85 |     },
 86 |     param           : 'lang',
 87 |     query           : 'lang',
 88 |     cookie          : 'lang',
 89 |     cookieKey       : 'lang',
 90 |     header          : 'accept-language',
 91 |     order           : ['params', 'cookie', 'query', 'headers'],
 92 |     throw404        : true,
 93 |     getter          : 'i18n.getLocale',
 94 |     setter          : 'i18n.setLocale',
 95 |     attribute       : 'i18n.locale',
 96 |     createAccessors : true,
 97 |     onEvent         : 'onPreAuth'
 98 | };
 99 | 
100 | var orderParameters = {
101 |     // Process in options.order array and JS method which will be called for that process.
102 |     params  : 'parseParam',
103 |     query   : 'parseQuery',
104 |     headers : 'parseHeader',
105 |     cookie  : 'parseCookie'
106 | };
107 | 
108 | var optionsSchema = {
109 |     locales         : Joi.array().items(Joi.string()).default(defaultOptions.locales),
110 |     default         : Joi.string().allow(null).default(defaultOptions.default),
111 |     configFile      : Joi.string().allow(null).default(defaultOptions.configFile),
112 |     configKey       : Joi.string().allow(null).default(defaultOptions.configKey),
113 |     scan            : Joi.object({
114 |         path        : Joi.string().default(defaultOptions.scan.path),
115 |         fileType    : Joi.string().default(defaultOptions.scan.fileType),
116 |         directories : Joi.boolean().default(defaultOptions.scan.directories),
117 |         exclude     : Joi.array().items(Joi.string()).allow(null).default(defaultOptions.scan.exclude)
118 |     }).allow(null).default(defaultOptions.scan),
119 |     param           : Joi.string().allow(null).default(defaultOptions.param),
120 |     query           : Joi.string().allow(null).default(defaultOptions.query),
121 |     cookie          : Joi.string().allow(null).default(defaultOptions.cookie),
122 |     cookieKey       : Joi.string().allow(null).default(defaultOptions.cookieKey),
123 |     header          : Joi.string().allow(null).default(defaultOptions.header),
124 |     order           : Joi.array().items(Joi.any().valid(Object.keys(orderParameters))).default(defaultOptions.order),
125 |     throw404        : Joi.boolean().default(defaultOptions.throw404),
126 |     getter          : Joi.string().allow(null).default(defaultOptions.getter),
127 |     setter          : Joi.string().allow(null).default(defaultOptions.setter),
128 |     attribute       : Joi.string().allow(null).default(defaultOptions.attribute),
129 |     createAccessors : Joi.string().allow(null).default(defaultOptions.createAccessors),
130 |     onEvent         : Joi.string().default(defaultOptions.onEvent)
131 | };
132 | 
133 | /**
134 |  * Class to implement inner working of plugin.
135 |  * @param {PluginOptions} options     - Plugin configuration options.
136 |  * @constructor
137 |  * @private
138 |  */
139 | var Internal = function(options) {
140 |     "use strict";
141 |     if ((options.setter && options.setter.indexOf('.') > -1) || (options.getter && options.getter.indexOf('.') > -1)) {
142 |         throw new Error('Getter (' + options.getter + ') and setter (' + options.setter + ') methods cannot be nested, so they cannot contain dot(.)');
143 |     }
144 | 
145 |     this.options    = Joi.attempt(options, optionsSchema);
146 |     this.locales    = this.getAvailableLocales();
147 |     this.default    = this.options.default || this.locales[0];
148 |     //this.callback   = this.getCallback(this.options.callback);
149 | };
150 | 
151 | /**
152 |  * Returns requested languages as an array by looking url part.
153 |  * @param {Object}          request     - Hapi request object.
154 |  * @returns {string|undefined}          - Requested locale or undefined.
155 |  * @private
156 |  */
157 | Internal.prototype.parseParam = function parseParam(request) {
158 |     "use strict";
159 |     if (!request.params.hasOwnProperty(this.options.param)) return;
160 |     var name    = this.options.param,
161 |         locales = lodash.get(request.params, name),
162 |         match   = this.bestMatch(locales);
163 | 
164 |     if (!match && this.options.throw404) {
165 |         throw new Error('Requested locale/language ' + locales + ' cannot be found.');
166 |     }
167 | 
168 |     return match;
169 | };
170 | 
171 | /**
172 |  * Returns requested languages as an array by looking query parameter.
173 |  * @param {Object}          request     - Hapi request object.
174 |  * @returns {string|undefined}          - Requested locale or undefined.
175 |  * @private
176 |  */
177 | Internal.prototype.parseQuery = function parseQuery(request) {
178 |     "use strict";
179 |     var name    = this.options.query,
180 |         locales = lodash.get(request.query, name);
181 | 
182 |     return this.bestMatch(locales);
183 | };
184 | 
185 | /**
186 |  * Returns requested language from cookie if found in available languages.
187 |  * @param {Object}          request     - Hapi request object.
188 |  * @returns {string|undefined}          - Requested locale or undefined.
189 |  * @private
190 |  */
191 | Internal.prototype.parseCookie = function parseCookie(request) {
192 |     "use strict";
193 |     var name    = this.options.cookie,
194 |         key     = this.options.cookieKey,
195 |         locales = lodash.get(request.state[name], key);
196 | 
197 |     return this.bestMatch(locales);
198 | };
199 | 
200 | /**
201 |  * Returns requested language from header if found in available languages.
202 |  * @param {Object}          request     - Hapi request object
203 |  * @returns {string|undefined}          - Requested locale or undefined.
204 |  * @private
205 |  */
206 | Internal.prototype.parseHeader = function parseHeader(request) {
207 |     var name    = this.options.header,
208 |         raw     = headerParser.parse(request.headers[name]),
209 |         locales = raw.map(function(value) {
210 |             return value.region ? value.code + '_' + value.region : value.code;
211 |         });
212 | 
213 |     return this.bestMatch(locales);
214 | };
215 | 
216 | /**
217 |  * Returns best match for requested locale among available locales. First matched locale will be returned.
218 |  * @param {string|Array.<string>} requested     - Requested locale or list of requested locales.
219 |  * @returns {string|undefined}                  - Matched locale or null if not any match found.
220 |  * @private
221 |  */
222 | Internal.prototype.bestMatch = function bestMatch(requested) {
223 |     if (!requested) return;
224 |     if (!Array.isArray(requested)) requested = [requested];
225 | 
226 |     for (let one of requested) {
227 |         if (this.locales.indexOf(one) > -1) return one;
228 |     }
229 | };
230 | 
231 | 
232 | /**
233 |  * Checks synchroniously if given file or directory exists. Returns true or false.
234 |  * @param {string}  path        - Path of the file or directory.
235 |  * @param {boolean} shouldBeDir - Whether given path should be directory.
236 |  * @returns {boolean}
237 |  * @private
238 |  */
239 | function fileExists(path, shouldBeDir) {
240 |     "use strict";
241 |     try {
242 |         var lstat = fs.lstatSync(path);
243 |         if (shouldBeDir && lstat.isDirectory()) { return true; }
244 |         if (!shouldBeDir && lstat.isFile()) { return true; }
245 |     } catch(err) {
246 |         return false;
247 |     }
248 |     return false;
249 | }
250 | 
251 | 
252 | 
253 | /**
254 |  * Scans path in options.scan.path and returns list of available locale files.
255 |  * @returns {Array.<string>}
256 |  * @throws {Error} - Throws error if locales directory is not found.
257 |  * @private
258 |  */
259 | Internal.prototype.scan = function scan() {
260 |     // Check if scan path is available
261 |     if (this.options.scan && !fileExists(this.options.scan.path, true)) {
262 |         throw new Error('Locales directory "' + this.options.scan.path + '"cannot be found.');
263 |     }
264 | 
265 |     let dir     = this.options.scan.path,
266 |         files   = fs.readdirSync(dir),
267 |         locales = [];
268 | 
269 |     for (let file of files) {
270 |         let fullPath = path.join(dir, file);
271 | 
272 |         // Skip if it is in exclude list or it is directory and scan.directories is false
273 |         if (this.options.scan.exclude.indexOf(file) > -1 || (fs.statSync(fullPath).isDirectory() && !this.options.scan.directories)) {
274 |             continue;
275 |         }
276 | 
277 |         locales.push(path.basename(file, path.extname(file)));  // Strip extension such as .json
278 |     }
279 | 
280 |     return lodash.unique(locales);
281 | };
282 | 
283 | 
284 | /**
285 |  * Determines which locales are available. It tries to determine available locales in given order:
286 |  * 1. Returns if locales are present in options.locales.
287 |  * 2. If not found, looks for given config file and searches opted key in config file.
288 |  * 3. If not found, scans path given in options.scan.path for files and directories excluding files in options.scan.exclude.
289 |  * @returns {Array|null}    - List of available locales
290 |  * @throws {Error}          - Throws error if necessary files are not found or no locales are available.
291 |  * @private
292 |  */
293 | Internal.prototype.getAvailableLocales = function getAvailableLocales() {
294 |     "use strict";
295 |     let locales = [];
296 | 
297 |     if (Array.isArray(this.options.locales) && this.options.locales.length > 0) {
298 |         locales = this.options.locales;
299 |         if (!Array.isArray(locales)) locales = [];
300 |     }
301 | 
302 |     // Config file
303 |     if (locales.length === 0 && this.options.configFile) {
304 | 
305 |         // Check if config file is available
306 |         if (!fileExists(this.options.configFile, false)) {
307 |             throw new Error('Configuration file "' + this.options.configFile + '" cannot be found');
308 |         }
309 | 
310 |         locales = lodash.get(require(this.options.configFile), this.options.configKey);       // key chain string to reference: 'options.locale' => options.locale
311 |         if (!Array.isArray(locales)) locales = [];
312 |     }
313 | 
314 |     // Locale files
315 |     if (locales.length === 0 && this.options.scan.path) {
316 |         locales = this.scan();
317 |     }
318 | 
319 |     if (locales.length === 0) {
320 |         throw new Error('Cannot found any locale.')
321 |     }
322 | 
323 |      return lodash.unique(locales);
324 | };
325 | 
326 | 
327 | /**
328 |  * @param {Object}              request - hapi.js request object
329 |  * @returns {string|undefined}          - Locale
330 |  * @private
331 |  */
332 | Internal.prototype.determineLocale = function determineLocale(request) {
333 |     let requestedLocale;
334 | 
335 |     for (let method of this.options.order) {
336 |         requestedLocale = this[orderParameters[method]](request);       // this.parseParam | this.parseCookie ... etc.
337 |         if (requestedLocale) break;
338 |     }
339 | 
340 |     return requestedLocale || this.default;
341 | };
342 | 
343 | 
344 | 
345 | /**
346 |  *
347 |  * @param {Object}              request - hapi.js request object
348 |  * @param {Function}            reply   - hapi.js reply object
349 |  * @returns {*}
350 |  */
351 | Internal.prototype.processRequest = function processRequest(request, reply) {
352 |     "use strict";
353 |     try {
354 |         var locale = this.determineLocale(request);
355 |     } catch(err) {
356 |         return reply(Boom.notFound(err));
357 |     }
358 | 
359 |     let getter      = this.options.getter,
360 |         setter      = this.options.setter,
361 |         attribute   = this.options.attribute;
362 | 
363 |     // Create accessors if necessary
364 |     if (this.options.createAccessors) {
365 |         if (! lodash.get(request, getter)) {
366 |             lodash.set(request, getter, function() {
367 |                 return lodash.get(request, attribute);
368 |             });
369 |         }
370 |         if (! lodash.get(request, setter)) {
371 |             lodash.set(request, setter, function (locale) {
372 |                 return lodash.set(request, attribute, locale);
373 |             });
374 |         }
375 |     }
376 | 
377 |     // Call setter.
378 |     lodash.get(request, setter)(locale);
379 | 
380 |     return reply.continue();
381 | };
382 | 
383 | 
384 | 
385 | 
386 | 
387 | /**
388 |  * Hapi plugin function which adds i18n support to request and response objects.
389 |  * @param {Object}          server      - Hapi server object
390 |  * @param {PluginOptions}   options     - Plugin configuration options.
391 |  * @param {Function}        next        - Callback function.
392 |  */
393 | exports.register = function(server, options, next) {
394 |     try {
395 |         var internal = new Internal(options);
396 |     } catch(err) {
397 |         return next(err);
398 |     }
399 | 
400 |     /**
401 |      * @module exposed
402 |      * @description
403 |      * Exposed functions and attributes are listed under exposed name.
404 |      * To access those attributes `request.server.plugins['hapi-locale']` can be used.
405 |      * @example
406 |      * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.
407 |      */
408 | 
409 |     /**
410 |      * Returns all available locales as an array.
411 |      * @name getLocales
412 |      * @function
413 |      * @returns {Array.<string>}    - Array of locales.
414 |      * @example
415 |      * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.
416 |      */
417 |     server.expose('getLocales', function getLocales() { return internal.locales; } );
418 | 
419 |     /**
420 |      * Returns default locale.
421 |      * @name getDefaultLocale
422 |      * @function
423 |      * @returns {string}    - Default locale
424 |      */
425 |     server.expose('getDefaultLocale', function getDefaultLocale() { return internal.default; } );
426 | 
427 |     /**
428 |      * Returns requested language.
429 |      * @name getLocale
430 |      * @function
431 |      * @param {Object}      request - Hapi.js request object
432 |      * @returns {string}    Locale
433 |      */
434 |     server.expose('getLocale', function getLocale(request) {
435 |         try {
436 |             return lodash.get(request, internal.options.getter)();
437 |         } catch(err) {
438 |             return null;
439 |         }
440 |     });
441 | 
442 |     server.ext(internal.options.onEvent, internal.processRequest, { bind: internal });
443 | 
444 |     return next();
445 | };
446 | 
447 | exports.register.attributes = {
448 |     pkg: require('./../package.json')
449 | };
450 |
451 |
452 | 453 | 454 | 455 | 456 |
457 | 458 | 461 | 462 |
463 | 464 |
465 | Documentation generated by JSDoc 3.4.0-dev on Fri Oct 16 2015 16:30:26 GMT+0300 (EEST) 466 |
467 | 468 | 469 | 470 | 471 | 472 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hapi-locale 2 | =========== 3 | Configurable plugin to determine request language in hapi.js applications. 4 | 5 | Description 6 | =========== 7 | This plugin determines requested loclale by looking at the following: (Order can be changed or skipped via `options.order`) 8 | 9 | * URL parameter, 10 | * Cookie, 11 | * Query parameter, 12 | * HTTP header. 13 | 14 | Optionally creates getter and setters or uses already available ones in request. Calls setter method with requested locale. Also provides plugin methods such as `server.plugins['hapi-locale'].getLocale()`; 15 | 16 | Nearly every aspect of the plugin can be configured with options. Sensible defaults are tried to be provided. 17 | 18 | Synopsis 19 | ======== 20 | 21 | Create server 22 | 23 | ... 24 | var plugins = [{ 25 | register: 'hapi-locale' 26 | options: { 27 | createAccessors: true, 28 | getter: 'i18n.getLocale', 29 | setter: 'i18n.setLocale' 30 | } 31 | }] 32 | ... 33 | server.register(plugins, function (err) { 34 | if (err) throw err; 35 | server.start(function () { 36 | console.log('Server started at: ' + server.info.uri); 37 | }); 38 | }); 39 | 40 | In handlers: 41 | 42 | var locale = request.i18n.getLocale(); 43 | 44 | WHY 45 | === 46 | It is easy to determine locale in hapi.js. So why is this plugin written? We are tired of writing repetitive code for every application/module and decided to export this functionality as a hapi plugin. 47 | 48 | Also we make it tested and documented. 49 | 50 | Most Important Options: 51 | ======================= 52 | 53 | The options below are the most important ones, because they change/write the request object and may cause undesirable results if not properly configured to suit your needs. 54 | 55 | | Option | Default Value | Description 56 | |-------------------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------| 57 | | `createAccessors` | true | Enables creating getter and setter methods in request object. If set to false it is assumed getter and setter methods are already available. | 58 | | `getter` | i18n.getLocale | Getter method in request object to get current locale. (Created if `options.createAccessors` is true. | 59 | | `setter` | i18n.setLocale | Setter method in request object to set current locale. (Created if `options.createAccessors` is true. | 60 | | `attribute` | i18n.locale | Key in request object which will be used to store locale name. (Created if `options.createAccessors` is true. | 61 | 62 | Please see all options below in hapiLocale~PluginOptions in API section 63 | 64 | How it works 65 | ============ 66 | The workflow of the plugin is as below: 67 | 68 | Plugin 69 | 70 | 1. Determines which locales are available in application. This happens one time during plugin registration. 71 | 2. Tries to find which locale is prefered looking incoming request. This and other steps below happen in every request. Event for this step is configured by `options.onEvent` 72 | 3. Matches requested locale with available locales. If no match is found: 73 | a. If `options.throw404` is true and URL param has a locale which is not available. 74 | b. Sets default locale. 75 | 4. (Optional) Adds getter and setter methods in request object. By deafult `request.i18n.getLocale` and `request.i18n.setLocale`. 76 | 5. Setter is called. 77 | 78 | 79 | ### 1. Available locales 80 | 81 | Available locales are determined with methods in the following order. If one of the methods succeeds no other methods are tried. One or more steps may be cancelled via `options`. Available locales are searched one time during plugin registration. 82 | 83 | Plugin 84 | 85 | 1. Looks locales up in plugin options `options.locales`. Set empty `[]` to skip. 86 | 2. Looks `package.json` or other json file set by `options.configFile` and `options.configKey`. Key may be set with nested format such as 'pref.of.my.app.locales'. Set `null` to skip. 87 | 3. Scans path given by `options.scan.path` excluding files and directories given by `options.path.exclude`. Set `null` to skip. 88 | 89 | 90 | ### 2. Requested locale(s) 91 | 92 | One or more locale may be preferred in requests. To determine the most wanted locale for every request the following steps are taken in order set by `options.order`. One or more steps may be cancelled via setting null in related `options` or not including to `options.order`. 93 | 94 | Plugin (in default order, which can be changed from `options.order`) 95 | 96 | 1. `params` looks path paramater such as `{lang}/member` for `/en_US/member`. Path parameter name can be set via `options.param`. 97 | 2. `cookie` looks cookie. Cookie name can be set via `options.cookie`, cookie key to look in cookie can be set `options.cookieKey`. 98 | 3. `query` looks query paramater such as `/member?lang=en_US`. Query parameter name can be set via `options.query`. 99 | 4. `header` looks `accept-language` header of request. Header name can be set via `options.header`. 100 | 101 | 102 | ### 3. Match Requested locale 103 | 104 | Plugin tries to find first preferred locale which is available in application: 105 | 106 | 1. If a match is found, locale is determined. 107 | 2. If no match is found plugin either throws 404 for URL parameter if `options.throw404` set true. 108 | 3. If no 404 is thrown, default locale is used as a result. Default locale may set via `options.default`, otherwise first available locale is used as default. 109 | 110 | 111 | ### 4. Getter and Setter Methods 112 | 113 | Plugin uses getter and setter methods. It creates them if `options.createAccessors` is true and they do not exist. Name of the methods are set via `options.getter` and `options.setter` options. Default values are `i18n.getLocale` and `i18n.setLocale`. 114 | 115 | 116 | ### 5. Callback is called 117 | 118 | Callback is called with locale name as only parameter. Callback name is configured via `options.callback`. If callback name is given as a function reference, it is called directly. If it is given as string it is called as a chained method of request object. Default is "i18n.setLocale" which results as `request.i18n.setLocale`. It is possible to use a chained method name such as "i18n.setLocale" which results as `request.i18n.setLocale`. 119 | 120 | Order & Prioritization 121 | ======================== 122 | By default this plugin looks URL Part (`request.params`), Cookie (`request.state`), Query String (`request.query`), Header (`request.headers`) in this order: 'params', 'cookie', 'query', 'headers'. If you wish to change this order you can set it with `options.order` array. 123 | 124 | Event Times 125 | =========== 126 | Available locales are determined one time during server start plugin registration. Per request operations happens on event set by `options.onEvent`. 127 | 128 | Exposed Functions & Attributes 129 | ============================== 130 | This plugin exposes some functions and attributes using server.expose mechanism of hapi.js. They are documented under API section's exposed part. See there for details. 131 | 132 | // This function may be used to access requested locale manually. 133 | var locale = request.server.plugins['hapi-locale'].getLocale(request, reply); // 'tr_TR' 134 | 135 | var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 136 | 137 | Examples 138 | ======== 139 | 140 | ### Use with default options: 141 | 142 | var server = new hapi.Server(), 143 | path = require('path'); 144 | 145 | server.connection({ 146 | host: 'localhost', 147 | port: 8080 148 | }); 149 | 150 | var plugins = ['hapi-locale'] 151 | 152 | server.route([ 153 | { 154 | path: "/locale", 155 | method: "GET", 156 | handler: function(request, reply) { 157 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 158 | } 159 | }, 160 | { 161 | path: "/{lang}/locale", 162 | method: "GET", 163 | handler: function(request, reply) { 164 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 165 | } 166 | } 167 | ]); 168 | 169 | 170 | server.register(plugins, function (err) { 171 | if (err) throw err; 172 | server.start(function () { 173 | console.log('Server started at: ' + server.info.uri); 174 | }); 175 | }); 176 | 177 | ### Providing options 178 | 179 | Options below are also default options. 180 | 181 | var server = new hapi.Server(), 182 | path = require('path'); 183 | 184 | server.connection({ 185 | host: 'localhost', 186 | port: 8080 187 | }); 188 | 189 | var rootDir = __dirname; 190 | 191 | // Those are also default options: 192 | var plugins = [ 193 | { 194 | register: 'hapi-locale', 195 | options: { 196 | locales : [], 197 | configFile : path.join(rootDir, 'package.json'), 198 | configKey : 'locales', 199 | scan : { 200 | path : path.join(rootDir, 'locales'), 201 | fileType : 'json', 202 | directories : true, 203 | exclude : ['templates', 'template.json'] 204 | }, 205 | param : 'lang', 206 | query : 'lang', 207 | cookie : 'lang', 208 | cookieKey : 'lang', 209 | header : 'accept-language', 210 | order : ['params', 'cookie', 'query', 'headers'], 211 | throw404 : true, 212 | getter : 'i18n.getLocale', 213 | setter : 'i18n.setLocale', 214 | createAccessors : true, 215 | attribute : 'i18n.locale', 216 | callback : 'setLocale', 217 | onEvent : 'onPreAuth' 218 | } 219 | } 220 | ]; 221 | 222 | server.route([ 223 | { 224 | path: "/locale", 225 | method: "GET", 226 | handler: function(request, reply) { 227 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 228 | } 229 | }, 230 | { 231 | path: "/{lang}/locale", 232 | method: "GET", 233 | handler: function(request, reply) { 234 | reply({ locale: request.i18n.getLocale() }); // This method is added by hapi-locale 235 | } 236 | } 237 | ]); 238 | 239 | 240 | server.register(plugins, function (err) { 241 | if (err) throw err; 242 | server.start(function () { 243 | console.log('Server started at: ' + server.info.uri); 244 | }); 245 | }); 246 | 247 | 248 | ### Routes 249 | 250 | | **ROUTE** | **REQUEST** | **HEADER** | **LOCALE** | **REASON (Default Config)** 251 | |---------------------|-------------------------------|--------------------------------|-----------------|-----------------------| 252 | | /{lang}/account | GET /en_US/account | | en_US | Path | 253 | | /{lang}/account | GET /tr_TR/account?lang=fr_FR | accept-language=jp_JP;jp;q=0.8 | tr_TR | Path has more priority| 254 | | /api/{lang}/account | GET api/en_US/account | | en_US | Path | 255 | | /account | GET /account?lang=en_US | | en_US | Query | 256 | | /api/account | GET api/account?lang=en_US | | en_US | Query | 257 | | /account | GET /account | accept-language=en_US;en;q=0.8 | en_US | Header | 258 | | /{lang}/account | GET /nonsense/account | | *404* | Not found URL | 259 | | /account | GET account?lang=nonsense | | *Default Locale*| Not found URL | 260 | 261 | 262 | API 263 | === 264 | 265 | ## Modules 266 |
267 |
'hapi-locale'
268 |

Configurable plugin to determine request language in hapi.js applications.

269 |
270 |
exposed
271 |

Exposed functions and attributes are listed under exposed name. 272 | To access those attributes request.server.plugins['hapi-locale'] can be used.

273 |
274 |
275 | 276 | ## 'hapi-locale' 277 | Configurable plugin to determine request language in hapi.js applications. 278 | 279 | 280 | * ['hapi-locale'](#module_'hapi-locale') 281 | * _static_ 282 | * [.register(server, options, next)](#module_'hapi-locale'.register) 283 | * _inner_ 284 | * [~PluginOptions](#module_'hapi-locale'..PluginOptions) : Object 285 | 286 | 287 | ### 'hapi-locale'.register(server, options, next) 288 | Hapi plugin function which adds i18n support to request and response objects. 289 | 290 | **Kind**: static method of ['hapi-locale'](#module_'hapi-locale') 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 301 | 302 | 304 | 305 | 307 | 308 |
ParamTypeDescription
serverObject

Hapi server object

300 |
optionsPluginOptions

Plugin configuration options.

303 |
nextfunction

Callback function.

306 |
309 | 310 | 311 | ### 'hapi-locale'~PluginOptions : Object 312 | Plugin configuration options. 313 | 314 | **Kind**: inner typedef of ['hapi-locale'](#module_'hapi-locale') 315 | **Properties** 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 |
NameTypeDefaultDescription
localesArray.<string>[]List of locales to use in application.
defaultstring | null"1st Locale"Default locale to use if no locale is given.
configFilestring | null"package.json"Configuration file to get available locales.
configKeystring | null"locales"Key to look in configuration file to get available locales. May be nested key such as 'a.b.c'.
scanObjectScanning options to get available locales
scan.pathstring"locale"Path or paths to scan locale files to get available locales.
scan.fileTypesstring"json"File types to scan. ie. "json" for en_US.json, tr_TR.json
scan.directoriesbooleantruewhether to scan directory names to get available locales.
scan.excludeArray.<string>[templates]Directory or file names to exclude from scan results.
paramstring | null"lang"Name of the path parameter to determine language. ie. /{lang}/account
querystring | null"lang"Name of the query parameter to determine language. ie. /account?lang=tr_TR
cookiestring | null"lang"Name of the cookie to determine language.
cookieKeystring | null"lang"Name of the key to look inside cookie to determine language. May be nested key such as 'a.b.c'.
headerstring | null"accept-language"Name of the header parameter to determine language.
orderArray.<string>['params', 'cookie', 'query', 'headers']Order in which language determination process follows. First successful method returns requested language.
throw404booleantrueWhether to throw 404 not found if locale is not found. Does not apply path parameters, it always throws 404.
getterstring | null"i18n.getLocale"Getter method in request object to get current locale. May be nested object such as 'a.b.c'
setterstring | null"i18n.setLocale"Setter method in request object to set current locale. May be nested object such as 'a.b.c'
attributestring | null"i18n.locale"Key in request object which will be used to store locale name. May be nested path such as 'a.b.c'.
createAccessorsbooleantrueEnables creating getter and setter methods in request object.
onEventstring"onPreAuth"Event on which locale determination process is fired.
368 | 369 | 370 | ## exposed 371 | Exposed functions and attributes are listed under exposed name. 372 | To access those attributes `request.server.plugins['hapi-locale']` can be used. 373 | 374 | **Example** 375 | ```js 376 | var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 377 | ``` 378 | 379 | * [exposed](#module_exposed) 380 | * [~getLocales()](#module_exposed..getLocales) ⇒ Array.<string> 381 | * [~getDefaultLocale()](#module_exposed..getDefaultLocale) ⇒ string 382 | * [~getLocale(request)](#module_exposed..getLocale) ⇒ string 383 | 384 | 385 | ### exposed~getLocales() ⇒ Array.<string> 386 | Returns all available locales as an array. 387 | 388 | **Kind**: inner method of [exposed](#module_exposed) 389 | **Returns**: Array.<string> - - Array of locales. 390 | **Example** 391 | ```js 392 | var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc. 393 | ``` 394 | 395 | ### exposed~getDefaultLocale() ⇒ string 396 | Returns default locale. 397 | 398 | **Kind**: inner method of [exposed](#module_exposed) 399 | **Returns**: string - - Default locale 400 | 401 | ### exposed~getLocale(request) ⇒ string 402 | Returns requested language. 403 | 404 | **Kind**: inner method of [exposed](#module_exposed) 405 | **Returns**: string - Locale 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 416 | 417 |
ParamTypeDescription
requestObject

Hapi.js request object

415 |
418 | 419 | 420 | --------------------------------------- 421 | 422 | History & Notes 423 | ================ 424 | Note: Simple documentation updates are not listed here. 425 | 426 | #### 1.0.0 / 2015-10-16 427 | * Changed: node.js 4 (ES6) is used. 428 | * Some changes are incompatible with 0.x versions. 429 | * Changed: Internal structure of the plugin is completly changed. It is class based now. 430 | * Added: JOI validations for plugin options. 431 | * Changed: Code cleaned up to make it easily understandable. 432 | * Changed: Documentation is updated. 433 | * Changed: Option names are simplified. 434 | * Changed: `options.throw404` only affects URL parameter now. 435 | * Changed: `options.callback` is not used anymore. `options.setter` is used both as a setter and as a callback. 436 | * Changed: Best match algorithm now tries every method until requested language is one of the available ones. 437 | 438 | #### 0.4.4 / 2015-10-14 439 | * Fixed: Accept language header parsed wrong. 440 | 441 | #### 0.4.3 / 2015-10-13 442 | * Changed: Tests ported from Mocha/Chai to Lab/Code. 443 | * Fixed: Created setter function does not work. 444 | 445 | #### 0.4.0 / 2015-10-09 446 | 447 | * Changed: `options.createGetterOn` and `options.createSetterOn` are renamed as `options.getter` and `options.setter`. 448 | * Added: `options.createAccessorsIfNotExists` added. 449 | * Fixed: Wrong path parameter caused reply called twice. Fixed. 450 | 451 | #### 0.3.0 / 2015-10-07 452 | * Added: Cookie support. `options.order` to change order of process to determine locale. It is possible to proritize query etc. over url parameters now. 453 | 454 | #### 0.2.1 / 2015-10-07 455 | * Changed: `options.createGetter` and `options.createSetter` are renamed as `options.createGetterOn` and `options.createSetterOn` 456 | 457 | #### 0.2.0 / 2015-10-07 458 | * Added: `getDefaultLocale()` exposed function. 459 | 460 | #### 0.1.0 / 2015-10-07 461 | * Added: `getLocale()` and `getLocales()` exposed functions. 462 | 463 | #### 0.0.1 / 2015-10-06 464 | * Initial version. 465 | 466 | LICENSE 467 | ======= 468 | 469 | The MIT License (MIT) 470 | 471 | Copyright (c) 2015 Özüm Eldoğan 472 | 473 | Permission is hereby granted, free of charge, to any person obtaining a copy 474 | of this software and associated documentation files (the "Software"), to deal 475 | in the Software without restriction, including without limitation the rights 476 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 477 | copies of the Software, and to permit persons to whom the Software is 478 | furnished to do so, subject to the following conditions: 479 | 480 | The above copyright notice and this permission notice shall be included in all 481 | copies or substantial portions of the Software. 482 | 483 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 484 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 485 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 486 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 487 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 488 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 489 | SOFTWARE. 490 | -------------------------------------------------------------------------------- /doc/module-_hapi-locale_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Module: 'hapi-locale' 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Module: 'hapi-locale'

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 |
39 | 40 | 41 |

Configurable plugin for determine request language in hapi.js applications.

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
Source:
89 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |

Methods

134 | 135 | 136 | 137 | 138 | 139 | 140 |

(static) register(server, options, next)

141 | 142 | 143 | 144 | 145 | 146 |
147 |

Hapi plugin function which adds i18n support to request and response objects.

148 |
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
Parameters:
159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 |
NameTypeDescription
server 187 | 188 | 189 | Object 190 | 191 | 192 | 193 |

Hapi server object

options 210 | 211 | 212 | PluginOptions 213 | 214 | 215 | 216 |

Plugin configuration options.

next 233 | 234 | 235 | function 236 | 237 | 238 | 239 |

Callback function.

251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 |
Source:
285 |
288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 |
296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 |

Type Definitions

317 | 318 | 319 | 320 |

PluginOptions

321 | 322 | 323 | 324 | 325 |
326 |

Plugin configuration options.

327 |
328 | 329 | 330 | 331 |
Type:
332 |
    333 |
  • 334 | 335 | Object 336 | 337 | 338 |
  • 339 |
340 | 341 | 342 | 343 | 344 | 345 |
Properties:
346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 386 | 387 | 388 | 395 | 396 | 397 | 398 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 426 | 427 | 428 | 435 | 436 | 437 | 438 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 466 | 467 | 468 | 475 | 476 | 477 | 478 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 506 | 507 | 508 | 515 | 516 | 517 | 518 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 543 | 544 | 545 | 552 | 553 | 554 | 555 | 558 | 559 | 560 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 758 | 759 | 760 | 767 | 768 | 769 | 770 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 798 | 799 | 800 | 807 | 808 | 809 | 810 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 838 | 839 | 840 | 847 | 848 | 849 | 850 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 878 | 879 | 880 | 887 | 888 | 889 | 890 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 918 | 919 | 920 | 927 | 928 | 929 | 930 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 955 | 956 | 957 | 964 | 965 | 966 | 967 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 992 | 993 | 994 | 1001 | 1002 | 1003 | 1004 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1032 | 1033 | 1034 | 1041 | 1042 | 1043 | 1044 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1072 | 1073 | 1074 | 1081 | 1082 | 1083 | 1084 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1112 | 1113 | 1114 | 1121 | 1122 | 1123 | 1124 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1149 | 1150 | 1151 | 1158 | 1159 | 1160 | 1161 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1186 | 1187 | 1188 | 1195 | 1196 | 1197 | 1198 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 |
NameTypeAttributesDefaultDescription
locales 379 | 380 | 381 | Array.<string> 382 | 383 | 384 | 385 | 389 | 390 | <optional>
391 | 392 | 393 | 394 |
399 | 400 | [] 401 | 402 |

List of locales to use in application.

default 416 | 417 | 418 | string 419 | | 420 | 421 | null 422 | 423 | 424 | 425 | 429 | 430 | <optional>
431 | 432 | 433 | 434 |
439 | 440 | 1st Locale 441 | 442 |

Default locale to use if no locale is given.

configFile 456 | 457 | 458 | string 459 | | 460 | 461 | null 462 | 463 | 464 | 465 | 469 | 470 | <optional>
471 | 472 | 473 | 474 |
479 | 480 | package.json 481 | 482 |

Configuration file to get available locales.

configKey 496 | 497 | 498 | string 499 | | 500 | 501 | null 502 | 503 | 504 | 505 | 509 | 510 | <optional>
511 | 512 | 513 | 514 |
519 | 520 | locales 521 | 522 |

Key to look in configuration file to get available locales. May be nested key such as 'a.b.c'.

scan 536 | 537 | 538 | Object 539 | 540 | 541 | 542 | 546 | 547 | <optional>
548 | 549 | 550 | 551 |
556 | 557 |

Scanning options to get available locales

561 |
Properties
562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 600 | 601 | 602 | 609 | 610 | 611 | 612 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 637 | 638 | 639 | 646 | 647 | 648 | 649 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 674 | 675 | 676 | 683 | 684 | 685 | 686 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 711 | 712 | 713 | 720 | 721 | 722 | 723 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 |
NameTypeAttributesDefaultDescription
path 593 | 594 | 595 | string 596 | 597 | 598 | 599 | 603 | 604 | <optional>
605 | 606 | 607 | 608 |
613 | 614 | locale 615 | 616 |

Path or paths to scan locale files to get available locales.

fileTypes 630 | 631 | 632 | string 633 | 634 | 635 | 636 | 640 | 641 | <optional>
642 | 643 | 644 | 645 |
650 | 651 | json 652 | 653 |

File types to scan. ie. "json" for en_US.json, tr_TR.json

directories 667 | 668 | 669 | boolean 670 | 671 | 672 | 673 | 677 | 678 | <optional>
679 | 680 | 681 | 682 |
687 | 688 | true 689 | 690 |

whether to scan directory names to get available locales.

exclude 704 | 705 | 706 | Array.<string> 707 | 708 | 709 | 710 | 714 | 715 | <optional>
716 | 717 | 718 | 719 |
724 | 725 | [templates] 726 | 727 |

Directory or file names to exclude from scan results.

736 | 737 |
param 748 | 749 | 750 | string 751 | | 752 | 753 | null 754 | 755 | 756 | 757 | 761 | 762 | <optional>
763 | 764 | 765 | 766 |
771 | 772 | lang 773 | 774 |

Name of the path parameter to determine language. ie. /{lang}/account

query 788 | 789 | 790 | string 791 | | 792 | 793 | null 794 | 795 | 796 | 797 | 801 | 802 | <optional>
803 | 804 | 805 | 806 |
811 | 812 | lang 813 | 814 |

Name of the query parameter to determine language. ie. /account?lang=tr_TR

cookie 828 | 829 | 830 | string 831 | | 832 | 833 | null 834 | 835 | 836 | 837 | 841 | 842 | <optional>
843 | 844 | 845 | 846 |
851 | 852 | lang 853 | 854 |

Name of the cookie to determine language.

cookieKey 868 | 869 | 870 | string 871 | | 872 | 873 | null 874 | 875 | 876 | 877 | 881 | 882 | <optional>
883 | 884 | 885 | 886 |
891 | 892 | lang 893 | 894 |

Name of the key to look inside cookie to determine language. May be nested key such as 'a.b.c'.

header 908 | 909 | 910 | string 911 | | 912 | 913 | null 914 | 915 | 916 | 917 | 921 | 922 | <optional>
923 | 924 | 925 | 926 |
931 | 932 | accept-language 933 | 934 |

Name of the header parameter to determine language.

order 948 | 949 | 950 | Array.<string> 951 | 952 | 953 | 954 | 958 | 959 | <optional>
960 | 961 | 962 | 963 |
968 | 969 | ['params', 'cookie', 'query', 'headers'] 970 | 971 |

Order in which language determination process follows. First successful method returns requested language.

throw404 985 | 986 | 987 | boolean 988 | 989 | 990 | 991 | 995 | 996 | <optional>
997 | 998 | 999 | 1000 |
1005 | 1006 | true 1007 | 1008 |

Whether to throw 404 not found if locale is not found. Does not apply path parameters, it always throws 404.

getter 1022 | 1023 | 1024 | string 1025 | | 1026 | 1027 | null 1028 | 1029 | 1030 | 1031 | 1035 | 1036 | <optional>
1037 | 1038 | 1039 | 1040 |
1045 | 1046 | i18n.getLocale 1047 | 1048 |

Getter method in request object to get current locale. May be nested object such as 'a.b.c'

setter 1062 | 1063 | 1064 | string 1065 | | 1066 | 1067 | null 1068 | 1069 | 1070 | 1071 | 1075 | 1076 | <optional>
1077 | 1078 | 1079 | 1080 |
1085 | 1086 | i18n.setLocale 1087 | 1088 |

Setter method in request object to set current locale. May be nested object such as 'a.b.c'

attribute 1102 | 1103 | 1104 | string 1105 | | 1106 | 1107 | null 1108 | 1109 | 1110 | 1111 | 1115 | 1116 | <optional>
1117 | 1118 | 1119 | 1120 |
1125 | 1126 | i18n.locale 1127 | 1128 |

Key in request object which will be used to store locale name. May be nested path such as 'a.b.c'.

createAccessors 1142 | 1143 | 1144 | boolean 1145 | 1146 | 1147 | 1148 | 1152 | 1153 | <optional>
1154 | 1155 | 1156 | 1157 |
1162 | 1163 | true 1164 | 1165 |

Enables creating getter and setter methods in request object.

onEvent 1179 | 1180 | 1181 | string 1182 | 1183 | 1184 | 1185 | 1189 | 1190 | <optional>
1191 | 1192 | 1193 | 1194 |
1199 | 1200 | onPreAuth 1201 | 1202 |

Event on which locale determination process is fired.

1211 | 1212 | 1213 | 1214 | 1215 |
1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 |
Source:
1243 |
1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 |
1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 |
1265 | 1266 |
1267 | 1268 | 1269 | 1270 | 1271 |
1272 | 1273 | 1276 | 1277 |
1278 | 1279 |
1280 | Documentation generated by JSDoc 3.4.0-dev on Fri Oct 16 2015 16:30:26 GMT+0300 (EEST) 1281 |
1282 | 1283 | 1284 | 1285 | 1286 | --------------------------------------------------------------------------------