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 |
--------------------------------------------------------------------------------