├── public ├── css │ └── app.less └── templates │ ├── index.dust │ ├── errors │ ├── 404.dust │ ├── 500.dust │ └── 503.dust │ └── layouts │ └── master.dust ├── .gitignore ├── locales ├── DE │ └── de │ │ ├── index.properties │ │ └── errors │ │ ├── 404.properties │ │ ├── 500.properties │ │ └── 503.properties ├── ES │ └── es │ │ ├── index.properties │ │ └── errors │ │ ├── 404.properties │ │ ├── 503.properties │ │ └── 500.properties └── US │ └── en │ ├── index.properties │ └── errors │ ├── 404.properties │ ├── 503.properties │ └── 500.properties ├── models └── index.js ├── routes.js ├── tasks ├── i18n.js ├── clean.js ├── jshint.js ├── mochacli.js ├── less.js ├── copyto.js └── dustjs.js ├── lib └── locale.js ├── controllers └── index.js ├── Gruntfile.js ├── index.js ├── test └── index.js ├── package.json ├── config ├── development.json └── config.json ├── .jshintrc └── README.md /public/css/app.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .build 3 | .idea -------------------------------------------------------------------------------- /locales/DE/de/index.properties: -------------------------------------------------------------------------------- 1 | greeting=Hallo, {name}! 2 | message=Zeit ist kostbar... -------------------------------------------------------------------------------- /locales/ES/es/index.properties: -------------------------------------------------------------------------------- 1 | greeting=Hola, {name}! 2 | message=El tiempo es precioso... -------------------------------------------------------------------------------- /locales/US/en/index.properties: -------------------------------------------------------------------------------- 1 | greeting=Hello, {name}! 2 | message=Time is precious... 3 | -------------------------------------------------------------------------------- /locales/US/en/errors/404.properties: -------------------------------------------------------------------------------- 1 | header=File not found 2 | description=The URL {url} did not resolve to a route. -------------------------------------------------------------------------------- /locales/DE/de/errors/404.properties: -------------------------------------------------------------------------------- 1 | header=Datei nicht gefunden 2 | description=Die URL {url} nicht auf eine Route zu beheben. -------------------------------------------------------------------------------- /locales/ES/es/errors/404.properties: -------------------------------------------------------------------------------- 1 | header=Archivo no encontrado 2 | description=La url {url} no resolvió de una rutahe URL. -------------------------------------------------------------------------------- /locales/US/en/errors/503.properties: -------------------------------------------------------------------------------- 1 | header=Service unavailable 2 | description=The service is unavailable. Please try back shortly. 3 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function IndexModel() { 5 | return { 6 | name: 'index' 7 | }; 8 | }; -------------------------------------------------------------------------------- /locales/DE/de/errors/500.properties: -------------------------------------------------------------------------------- 1 | header=Internal server error 2 | description=Die URL {url} hatte die folgende Fehler {err}. -------------------------------------------------------------------------------- /locales/ES/es/errors/503.properties: -------------------------------------------------------------------------------- 1 | header=Servicio unvailable 2 | description=El servicio no está disponible. Por favor, inténtelo de nuevo pronto. 3 | -------------------------------------------------------------------------------- /locales/US/en/errors/500.properties: -------------------------------------------------------------------------------- 1 | header=Internal server error 2 | description=The URL {url} had the following error {err}. -------------------------------------------------------------------------------- /locales/DE/de/errors/503.properties: -------------------------------------------------------------------------------- 1 | header=Service-Unavailable 2 | description=Der Service ist nicht verfügbar. Bitte versuchen Sie es in Kürze wieder. 3 | -------------------------------------------------------------------------------- /locales/ES/es/errors/500.properties: -------------------------------------------------------------------------------- 1 | header=Error interno del servidor 2 | description=La url {url} tenía el siguiente error {err}. -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | var controllers = require('./controllers'); 2 | 3 | module.exports = function (router) { 4 | router.get('/', controllers.index); 5 | router.get('/setLocale/:locale', controllers.setLocale); 6 | }; -------------------------------------------------------------------------------- /public/templates/index.dust: -------------------------------------------------------------------------------- 1 | {@useContent bundle="index.properties"} 2 | {>"layouts/master" /} 3 | 4 | {{@pre type="content" key="greeting"/} 6 |

{@pre type="content" key="message"/}

7 | {/body} 8 | {/useContent} -------------------------------------------------------------------------------- /tasks/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function clean(grunt) { 5 | // Load task 6 | grunt.registerTask('i18n', [ 'clean', 'localizr', 'dustjs', 'clean:tmp' ]); 7 | 8 | // Options 9 | return {}; 10 | }; 11 | -------------------------------------------------------------------------------- /public/templates/errors/404.dust: -------------------------------------------------------------------------------- 1 | {@useContent bundle="errors/404.properties"} 2 | {>"layouts/master" /} 3 | 4 | {{@pre type="content" key="header"/} 6 |

{@pre type="content" key="description"/}

7 | {/body} 8 | {/useContent} -------------------------------------------------------------------------------- /public/templates/errors/500.dust: -------------------------------------------------------------------------------- 1 | {@useContent bundle="errors/500.properties"} 2 | {>"layouts/master" /} 3 | 4 | {{@pre type="content" key="header"/} 6 |

{@pre type="content" key="description"/}

7 | {/body} 8 | {/useContent} -------------------------------------------------------------------------------- /public/templates/errors/503.dust: -------------------------------------------------------------------------------- 1 | {@useContent bundle="errors/503.properties"} 2 | {>"layouts/master" /} 3 | 4 | {{@pre type="content" key="header"/} 6 |

{@pre type="content" key="description"/}

7 | {/body} 8 | {/useContent} -------------------------------------------------------------------------------- /tasks/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function clean(grunt) { 5 | // Load task 6 | grunt.loadNpmTasks('grunt-contrib-clean'); 7 | 8 | // Options 9 | return { 10 | tmp: 'tmp', 11 | build: '.build/templates' 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/locale.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function () { 3 | return function (req, res, next) { 4 | var locale = req.cookies && req.cookies.locale; 5 | //Set the locality for this response. The template will pick the appropriate bundle 6 | res.locals.locale = locale; 7 | next(); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /tasks/jshint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function jshint(grunt) { 5 | // Load task 6 | grunt.loadNpmTasks('grunt-contrib-jshint'); 7 | 8 | // Options 9 | return { 10 | files: ['controllers/**/*.js', 'lib/**/*.js', 'models/**/*.js'], 11 | options: { 12 | jshintrc: '.jshintrc' 13 | } 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var IndexModel = require('../models/index'); 4 | 5 | exports.index = function (req, res) { 6 | var model = new IndexModel(); 7 | res.render('index', model); 8 | }; 9 | 10 | exports.setLocale = function (req, res) { 11 | res.cookie('locale', req.params.locale); 12 | res.redirect('/'); 13 | }; 14 | -------------------------------------------------------------------------------- /tasks/mochacli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function mochacli(grunt) { 5 | // Load task 6 | grunt.loadNpmTasks('grunt-mocha-cli'); 7 | 8 | // Options 9 | return { 10 | src: ['test/*.js'], 11 | options: { 12 | timeout: 6000, 13 | ignoreLeaks: false, 14 | ui: 'bdd', 15 | reporter: 'spec' 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /public/templates/layouts/master.dust: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {+title /} 6 | 7 | 8 | 9 |
10 | {+body /} 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tasks/less.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function less(grunt) { 5 | // Load task 6 | grunt.loadNpmTasks('grunt-contrib-less'); 7 | 8 | // Options 9 | return { 10 | build: { 11 | options: { 12 | cleancss: false 13 | }, 14 | files: [{ 15 | expand: true, 16 | cwd: 'public/css', 17 | src: ['**/*.less'], 18 | dest: '.build/css/', 19 | ext: '.css' 20 | }] 21 | } 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/copyto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function copyto(grunt) { 5 | // Load task 6 | grunt.loadNpmTasks('grunt-copy-to'); 7 | 8 | // Options 9 | return { 10 | build: { 11 | files: [{ 12 | cwd: 'public', 13 | src: ['**/*'], 14 | dest: '.build/' 15 | }], 16 | options: { 17 | ignore: [ 18 | 'public/css/**/*', 19 | 'public/templates/**/*' 20 | ] 21 | } 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = function (grunt) { 5 | 6 | // Load the project's grunt tasks from a directory 7 | require('grunt-config-dir')(grunt, { 8 | configDir: require('path').resolve('tasks') 9 | }); 10 | 11 | 12 | 13 | // Register group tasks 14 | grunt.registerTask('build', ['jshint', 'dustjs', 'less', 'copyto']); 15 | 16 | grunt.registerTask('test', [ 'jshint', 'mochacli' ]); 17 | 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | var kraken = require('kraken-js'), 5 | app = require('express')(), 6 | options = { 7 | onconfig: function (config, next) { 8 | //any config overriders here 9 | next(null, config); 10 | } 11 | }, 12 | port = process.env.PORT || 8000; 13 | 14 | 15 | app.use(kraken(options)); 16 | 17 | app.listen(port, function (err) { 18 | console.log('[%s] Listening on http://localhost:%d', app.settings.env, port); 19 | }); -------------------------------------------------------------------------------- /tasks/dustjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = function dustjs(grunt) { 6 | // Load task 7 | grunt.loadNpmTasks('grunt-dustjs'); 8 | 9 | // Options 10 | return { 11 | build: { 12 | files: [ 13 | { 14 | expand: true, 15 | 16 | cwd: 'public/templates/', 17 | 18 | src: '**/*.dust', 19 | dest: '.build/templates', 20 | ext: '.js' 21 | } 22 | ], 23 | options: { 24 | 25 | fullname: function (filepath) { 26 | return path.relative('public/templates/', filepath).replace(/[.]dust$/, ''); 27 | } 28 | 29 | } 30 | } 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false, beforeEach:false, afterEach:false*/ 2 | 3 | 'use strict'; 4 | 5 | 6 | var kraken = require('kraken-js'), 7 | express = require('express'), 8 | path = require('path'), 9 | request = require('supertest'); 10 | 11 | 12 | describe('index', function () { 13 | 14 | var app, mock; 15 | 16 | 17 | beforeEach(function (done) { 18 | app = express(); 19 | app.on('start', done); 20 | app.use(kraken({ 21 | basedir: path.resolve(__dirname, '..') 22 | })); 23 | 24 | mock = app.listen(1337); 25 | 26 | }); 27 | 28 | 29 | afterEach(function (done) { 30 | mock.close(done); 31 | }); 32 | 33 | 34 | it('should say "hello"', function (done) { 35 | request(mock) 36 | .get('/') 37 | .expect(200) 38 | .expect('Content-Type', /html/) 39 | 40 | .expect(/Hello, /) 41 | 42 | .end(function (err, res) { 43 | done(err); 44 | }); 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kraken-example-with-i18n", 3 | "version": "0.1.0", 4 | "description": "Example applications built with kraken", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/krakenjs/kraken-example-with-i18n" 8 | }, 9 | "keywords": [ 10 | "express", 11 | "kraken", 12 | "kraken-js", 13 | "krakenjs", 14 | "examples" 15 | ], 16 | "author": "Jeff Harrell ", 17 | "contributors": [ 18 | { 19 | "name": "Poornima Venkatakrishnan", 20 | "email": "pvenkatakrishnan@paypal.com" 21 | }, 22 | { 23 | "name": "Matt Edelman", 24 | "email": "medelman@paypal.com" 25 | } 26 | ], 27 | "main": "index.js", 28 | "scripts": { 29 | "start": "node index.js", 30 | "test": "grunt test", 31 | "build": "grunt build", 32 | "all": "npm run build && npm run test" 33 | }, 34 | "license": "Apache 2.0", 35 | "homepage": "http://krakenjs.com/", 36 | "dependencies": { 37 | "construx": "^1.0.0", 38 | "construx-copier": "^1.0.0", 39 | "construx-dustjs": "^1.1.0", 40 | "construx-less": "^1.0.0", 41 | "dust-makara-helpers": "^4.1.2", 42 | "express": "^4.12.2", 43 | "kraken-js": "^1.0.3", 44 | "makara": "^2.0.3" 45 | }, 46 | "devDependencies": { 47 | "grunt": "^0.4.5", 48 | "grunt-cli": "^0.1.13", 49 | "grunt-config-dir": "^0.3.2", 50 | "grunt-contrib-clean": "^0.6.0", 51 | "grunt-contrib-jshint": "^0.10.0", 52 | "grunt-contrib-less": "^1.0.1", 53 | "grunt-copy-to": "0.0.10", 54 | "grunt-dustjs": "^1.2.1", 55 | "grunt-mocha-cli": "^1.14.0", 56 | "mocha": "^1.18.0", 57 | "supertest": "^1.1.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "view cache": false, 4 | "view engine": "dust", 5 | "views": "path:./public/templates" 6 | }, 7 | 8 | 9 | "view engines": { 10 | "dust": { 11 | "module": "makara", 12 | "renderer": { 13 | "method": "dust", 14 | "arguments": [ 15 | { 16 | "cache": false, 17 | "helpers": "config:dust.helpers", 18 | "whitespace": true 19 | } 20 | ] 21 | } 22 | } 23 | }, 24 | "middleware": { 25 | 26 | "devtools": { 27 | "enabled": true, 28 | "priority": 35, 29 | "module": { 30 | "name": "construx", 31 | "arguments": [ 32 | "path:./public", 33 | "path:./.build", 34 | { 35 | 36 | "template": { 37 | "module": "construx-dustjs", 38 | "files": "/templates/**/*.js", 39 | "base": "templates" 40 | }, 41 | 42 | 43 | "css": { 44 | "module": "construx-less", 45 | "files": "/css/**/*.css" 46 | }, 47 | 48 | "copier": { 49 | "module": "construx-copier", 50 | "files": "**/*" 51 | } 52 | } 53 | ] 54 | } 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "view cache": false, 4 | "view engine": "dust", 5 | "views": "path:./public/templates" 6 | }, 7 | "view engines": { 8 | "dust": { 9 | "module": "makara", 10 | "renderer": { 11 | "method": "dust", 12 | "arguments": [ 13 | { "cache": true, "helpers": "config:dust.helpers" } 14 | ] 15 | } 16 | } 17 | }, 18 | "dust": { 19 | "helpers": [ 20 | { 21 | "name": "dust-makara-helpers", 22 | "arguments": { "autoloadTemplateContent": false } 23 | } 24 | ] 25 | }, 26 | "i18n": { 27 | "contentPath": "path:./locales", 28 | "fallback": "en-US" 29 | }, 30 | 31 | 32 | "middleware": { 33 | 34 | "static": { 35 | "module": { 36 | "arguments": [ "path:./.build" ] 37 | } 38 | }, 39 | 40 | "router": { 41 | "module": { 42 | "arguments": [{ "index": "path:./routes" }] 43 | } 44 | }, 45 | 46 | "makara": { 47 | "priority": 100, 48 | "enabled": true, 49 | "module": { 50 | "name": "makara", 51 | "arguments": [ 52 | { 53 | "i18n": "config:i18n" 54 | } 55 | ] 56 | } 57 | }, 58 | 59 | "fileNotFound": { 60 | "enabled": true, 61 | "priority": 130, 62 | "module": { 63 | "name": "kraken-js/middleware/404", 64 | "arguments": [ "errors/404" ] 65 | } 66 | }, 67 | 68 | "serverError": { 69 | "enabled": true, 70 | "priority": 140, 71 | "module": { 72 | "name" : "kraken-js/middleware/500", 73 | "arguments": [ "errors/500" ] 74 | } 75 | }, 76 | 77 | "locale": { 78 | "priority": 95, 79 | "enabled": true, 80 | "module": { 81 | "name": "path:./lib/locale" 82 | } 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Whether the scan should stop on first error. 3 | "passfail": false, 4 | // Maximum errors before stopping. 5 | "maxerr": 100, 6 | 7 | 8 | // Predefined globals 9 | 10 | // Whether the standard browser globals should be predefined. 11 | "browser": false, 12 | // Whether the Node.js environment globals should be predefined. 13 | "node": true, 14 | // Whether the Rhino environment globals should be predefined. 15 | "rhino": false, 16 | // Whether CouchDB globals should be predefined. 17 | "couch": false, 18 | // Whether the Windows Scripting Host environment globals should be predefined. 19 | "wsh": false, 20 | 21 | // Whether jQuery globals should be predefined. 22 | "jquery": false, 23 | // Whether Prototype and Scriptaculous globals should be predefined. 24 | "prototypejs": false, 25 | // Whether MooTools globals should be predefined. 26 | "mootools": false, 27 | // Whether Dojo Toolkit globals should be predefined. 28 | "dojo": false, 29 | 30 | // Custom predefined globals. 31 | "predef": [], 32 | 33 | // Development 34 | 35 | // Whether debugger statements should be allowed. 36 | "debug": false, 37 | // Whether logging globals should be predefined (console, alert, etc.). 38 | "devel": false, 39 | 40 | 41 | // ECMAScript 5 42 | 43 | // Whether the "use strict"; pragma should be required. 44 | "strict": true, 45 | // Whether global "use strict"; should be allowed (also enables strict). 46 | "globalstrict": true, 47 | 48 | 49 | // The Good Parts 50 | 51 | // Whether automatic semicolon insertion should be allowed. 52 | "asi": false, 53 | // Whether line breaks should not be checked, e.g. `return [\n] x`. 54 | "laxbreak": false, 55 | // Whether bitwise operators (&, |, ^, etc.) should be forbidden. 56 | "bitwise": false, 57 | // Whether assignments inside `if`, `for` and `while` should be allowed. Usually 58 | // conditions and loops are for comparison, not assignments. 59 | "boss": true, 60 | // Whether curly braces around all blocks should be required. 61 | "curly": true, 62 | // Whether `===` and `!==` should be required (instead of `==` and `!=`). 63 | "eqeqeq": true, 64 | // Whether `== null` comparisons should be allowed, even if `eqeqeq` is `true`. 65 | "eqnull": false, 66 | // Whether `eval` should be allowed. 67 | "evil": false, 68 | // Whether ExpressionStatement should be allowed as Programs. 69 | "expr": true, 70 | // Whether `for in` loops must filter with `hasOwnPrototype`. 71 | "forin": false, 72 | // Whether immediate invocations must be wrapped in parens, e.g. 73 | // `( function(){}() );`. 74 | "immed": true, 75 | // Whether use before define should be forbidden. 76 | "latedef": false, 77 | // Whether functions should be allowed to be defined within loops. 78 | "loopfunc": false, 79 | // Whether arguments.caller and arguments.callee should be forbidden. 80 | "noarg": false, 81 | // Whether `.` should be forbidden in regexp literals. 82 | "regexp": false, 83 | // Whether unescaped first/last dash (-) inside brackets in regexps should be allowed. 84 | "regexdash": false, 85 | // Whether script-targeted URLs should be allowed. 86 | "scripturl": false, 87 | // Whether variable shadowing should be allowed. 88 | "shadow": false, 89 | // Whether `new function () { ... };` and `new Object;` should be allowed. 90 | "supernew": false, 91 | // Whether variables must be declared before used. 92 | "undef": true, 93 | // Whether `this` inside a non-constructor function should be allowed. 94 | "validthis": false, 95 | // Whether smarttabs should be allowed 96 | // (http://www.emacswiki.org/emacs/SmartTabs). 97 | "smarttabs": true, 98 | // Whether the `__proto__` property should be allowed. 99 | "proto": false, 100 | // Whether one-case switch statements should be allowed. 101 | "onecase": false, 102 | // Whether non-standard (but widely adopted) globals should be predefined. 103 | "nonstandard": false, 104 | // Allow multiline strings. 105 | "multistr": false, 106 | // Whether line breaks should not be checked around commas. 107 | "laxcomma": false, 108 | // Whether semicolons may be ommitted for the trailing statements inside of a 109 | // one-line blocks. 110 | "lastsemic": false, 111 | // Whether the `__iterator__` property should be allowed. 112 | "iterator": false, 113 | // Whether only function scope should be used for scope tests. 114 | "funcscope": false, 115 | // Whether es.next specific syntax should be allowed. 116 | "esnext": false, 117 | 118 | 119 | // Style preferences 120 | 121 | // Whether constructor names must be capitalized. 122 | "newcap": false, 123 | // Whether empty blocks should be forbidden. 124 | "noempty": false, 125 | // Whether using `new` for side-effects should be forbidden. 126 | "nonew": false, 127 | // Whether names should be checked for leading or trailing underscores 128 | // (object._attribute would be forbidden). 129 | "nomen": false, 130 | // Whether only one var statement per function should be allowed. 131 | "onevar": false, 132 | // Whether increment and decrement (`++` and `--`) should be forbidden. 133 | "plusplus": false, 134 | // Whether all forms of subscript notation are allowed. 135 | "sub": false, 136 | // Whether trailing whitespace rules apply. 137 | "trailing": false, 138 | // Specify indentation. 139 | "indent": 4, 140 | // Whether strict whitespace rules apply. 141 | "white": false, 142 | // Quote formatting 143 | "quotmark": true, 144 | 145 | // Complexity 146 | 147 | // Maximum number of function parameters. 148 | "maxparams": 5, 149 | // Maximum block nesting depth. 150 | "maxdepth": 3, 151 | // Maximum number of statements per function. 152 | "maxstatements": 25, 153 | // Maximum cyclomatic complexity. 154 | "maxcomplexity": 6 155 | } 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kraken example with.i18n 2 | 3 | An example kraken 1.0 app with i18n (internationalization). 4 | 5 | # What is i18n/internationalization 6 | 7 | Internationalization is the designing and re-engineering of a product so that it can be localized easily for global markets. 8 | In the context of kraken-1.0, including i18n would mean, to optionally decorate an express app to consume pre-localized templates (production mode), or localize templates on-the-fly (dev mode). 9 | By adding the appropriate i18n and view engine configs for your kraken 1.0 app, content bundles will be automatically loaded from a specific location and templates that require translation will get localized. 10 | Currently i18n is only supported for dustjs templates. 11 | 12 | # What does the example demonstrate ? 13 | 14 | The sample app demonstrates how to enable i18n in your app. It has simple support for localization in 3 different languages. 15 | 16 | * `en-US` 17 | * `es-ES` 18 | * `de-DE` 19 | 20 | # How to setup the app with i18n from scratch by using generator-kraken ? 21 | 22 | ### Create a simple scaffolded app using generator-kraken 23 | 24 | * Install Generator 25 | ``` 26 | $ npm install -g generator-kraken 27 | 28 | ``` 29 | 30 | * Create an app using the generator 31 | 32 | ``` 33 | $ yo kraken 34 | 35 | ,'""`. 36 | hh / _ _ \ 37 | |(@)(@)| Release the Kraken! 38 | ) __ ( 39 | /,'))((`.\ 40 | (( (( )) )) 41 | `\ `)(' /' 42 | 43 | Tell me a bit about your application: 44 | 45 | [?] Name: foo 46 | [?] Description: bar 47 | [?] Author: foobar 48 | [?] Template library? Dust 49 | [?] CSS preprocessor library? LESS 50 | [?] JavaScript library? None 51 | 52 | ``` 53 | 54 | ### Setting up the right configs for i18n in the app 55 | 56 | * Check the config/config.json file. You must see the following config for i18n. 57 | 58 | ``` 59 | "i18n": { 60 | "contentPath": "path:./locales", 61 | "fallback": "en-US" 62 | } 63 | ``` 64 | 65 | `contentPath` refers to the default path where the localization files reside, `fallback` is the default locale in case there is no specific locale for a request. 66 | 67 | * Check the view engines setup. You must see the following config for view engines. 68 | 69 | ``` 70 | "view engines": { 71 | "js": { 72 | "module": "engine-munger", 73 | "renderer": { 74 | "method": "js", 75 | "arguments": [ 76 | { "cache": true }, 77 | { 78 | "views": "config:express.views", 79 | "view engine": "config:express.view engine", 80 | "i18n": "config:i18n" 81 | } 82 | ] 83 | } 84 | } 85 | } 86 | ``` 87 | The above config tells the express app to use the function returned by the method `js` provided by the module `engine-munger`, as its engine function. `arguments` array gets passed while invoking `js`. 88 | `engine-munger` module helps add i18n into the render work-flow for your views. You can read more about `engine-munger` [here](https://github.com/krakenjs/engine-munger). 89 | 90 | Similarly in your development.json, make sure you have a config like the following: 91 | 92 | ``` 93 | "view engines": { 94 | "dust": { 95 | "module": "engine-munger", 96 | "renderer": { 97 | "method": "dust", 98 | "arguments": [ 99 | { "cache": false }, 100 | { 101 | "views": "config:express.views", 102 | "view engine": "config:express.view engine", 103 | "i18n": "config:i18n" 104 | } 105 | ] 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | ### Localizing your templates 112 | 113 | Lets demo adding a greeting and message localization in your index.dust 114 | 115 | * Adding `@pre` tags to your `index.dust` to specify what to translate in your view. 116 | 117 | ``` 118 | {{@pre type="content" key="greeting"/} 120 |

{@pre type="content" key="message"/}

121 | {/body} 122 | ``` 123 | 124 | * Adding localization aka `.properties` files 125 | 126 | In kraken 1.0 projects, the localized files have the extension `.properties` and translations are expressed as simple key value pairs, eg.`foo=bar`. 127 | To demonstrate greeting and message translation for your index.dust for three different locales: `en-US`, `es-ES` and `de-DE`: 128 | 129 | * create `locales/US/en/index.properties` and add: 130 | 131 | ``` 132 | greeting=Hello, {name}! 133 | message=Time is precious... 134 | ``` 135 | 136 | * create `locales/ES/es/index.properties` and add: 137 | 138 | ``` 139 | greeting=Hola, {name}! 140 | message=El tiempo es precioso... 141 | ``` 142 | 143 | * create `locales/DE/de/index.properties` and add: 144 | 145 | ``` 146 | greeting=Hallo, {name}! 147 | message=Zeit ist kostbar... 148 | ``` 149 | You may have already noticed that we add the `.properies` files to `locales/` folder because we set the `contentPath` in the i18n config above as `locales/`. 150 | 151 | ### Checking the default locale translation in your app 152 | 153 | Now when you start the app by doing `$ node .` and point your browser to `localhost:8000` you will see `index.dust` rendered in the fallback locale `en-US` per our `i18n` config. 154 | 155 | ### Adding a hook to set the locale on the fly 156 | 157 | * In your `routes.js` add the following route and controller code 158 | 159 | ``` 160 | router.get('/setLocale/:locale', function (req, res) { 161 | res.cookie('locale', req.params.locale); 162 | res.redirect('/'); 163 | }); 164 | ``` 165 | 166 | 167 | * In your `config.json` add the following for setting the locale in the `res.locals`of your express app by reading it from cookie. 168 | ``` 169 | "locale": { 170 | "priority": 95, 171 | "enabled": true, 172 | "module": { 173 | "name": "path:./lib/locale" 174 | } 175 | } 176 | ``` 177 | 178 | The reason above middleware has a priority of `95` is it needs to happen after the cookie parse middleware has executed ([which inside kraken-js has a priority of `90`](https://github.com/krakenjs/kraken-js/blob/master/config/config.json#L90).) 179 | 180 | * Set up the middleware in the path specified in the above config to read locale from cookie, and setting it in the res.locals. So add the file `lib/locale.js` and the following snippet into the file: 181 | 182 | ``` 183 | 'use strict'; 184 | module.exports = function () { 185 | return function (req, res, next) { 186 | var locale = req.cookies && req.cookies.locale; 187 | //Set the locality for this response. The template will pick the appropriate bundle 188 | res.locals.context = { 189 | locality: locale 190 | }; 191 | next(); 192 | }; 193 | }; 194 | ```` 195 | 196 | That is it!!! You are done! 197 | 198 | ## Installation of this demo 199 | 200 | Clone, install and run. 201 | 202 | ```shell 203 | git clone git@github.com:krakenjs/kraken-example-with-i18n.git 204 | cd kraken-examples-with-i18n 205 | npm install 206 | npm start 207 | ``` 208 | 209 | ### See it working with different locales: 210 | 211 | In your browser, visit [`http://localhost:8000/setLocale/en-US`](http://localhost:8000/setLocale/en-US) or [`http://localhost:8000/setLocale/es-ES`](http://localhost:8000/setLocale/es-ES) or [`http://localhost:8000/setLocale/de-DE`](http://localhost:8000/setLocale/de-DE) 212 | 213 | This will automatically set the locale and redirect to the index page in the right locale. 214 | 215 | Now if you would like to see it work in `production` mode with compiled templates: 216 | 217 | ```shell 218 | grunt build 219 | NODE_ENV=production node . 220 | ``` 221 | 222 | And repeat 1 and 2. 223 | --------------------------------------------------------------------------------