Skip to content

Commit 4b00275

Browse files
committed
Initialze release
0 parents  commit 4b00275

File tree

12 files changed

+430
-0
lines changed

12 files changed

+430
-0
lines changed

app-express.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
var express = require('express');
2+
var app = express();
3+
4+
app.get('/', function(req, res){
5+
res.send('Hello World');
6+
});
7+
8+
app.listen(3000);
9+
10+
console.log('Server running at http://127.0.0.1:3000/');

app-localized.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var express = require( "express" ),
2+
nunjucks = require( "nunjucks" ),
3+
path = require( "path" ),
4+
i18n = require( "./lib/i18n.js" ),
5+
app = express(),
6+
nunjucksEnv = new nunjucks.Environment( new nunjucks.FileSystemLoader( path.join( __dirname, '/public' )));
7+
8+
nunjucksEnv.express( app );
9+
10+
// Setup locales with i18n
11+
app.use( i18n.abide({
12+
supported_languages: [
13+
"en_US", "th_TH"
14+
],
15+
default_lang: "en_US",
16+
translation_directory: "locale",
17+
localeOnUrl: true
18+
}));
19+
20+
app.get('/', function(req, res){
21+
res.render( "localized.html" );
22+
});
23+
24+
app.listen(3000);
25+
26+
console.log('Server running at http://127.0.0.1:3000/');

app-normal.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var http = require('http');
2+
3+
http.createServer(function (req, res) {
4+
res.writeHead(200, {'Content-Type': 'text/plain'});
5+
res.end('Hello World\n');
6+
}).listen(8000);
7+
8+
console.log('Server running at http://127.0.0.1:8000/');

app-render.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
var express = require( "express" ),
2+
nunjucks = require( "nunjucks" ),
3+
path = require( "path" ),
4+
app = express(),
5+
nunjucksEnv = new nunjucks.Environment( new nunjucks.FileSystemLoader( path.join( __dirname, '/public' )));
6+
7+
nunjucksEnv.express( app );
8+
9+
app.get('/', function(req, res){
10+
res.render( "nunjucks.html", { helloworld: "Hello World"});
11+
});
12+
13+
app.listen(3000);
14+
15+
console.log('Server running at http://127.0.0.1:3000/');

app-static.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var express = require('express');
2+
var app = express();
3+
4+
app.use(express.static(__dirname + '/public'));
5+
6+
app.listen(3000);
7+
8+
console.log('Server running at http://127.0.0.1:3000/');

lib/i18n.js

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
/*
6+
* i18n-abide
7+
*
8+
* This module abides by the user's language preferences and makes it
9+
* available throughout the app.
10+
*
11+
* This module abides by the Mozilla L10n way of doing things.
12+
*
13+
* The module abides.
14+
*
15+
* See docs/I18N.md for details.
16+
*/
17+
18+
var fs = require('fs'),
19+
gobbledygook = require('gobbledygook'),
20+
plist = require('plist'),
21+
path = require('path'),
22+
util = require('util');
23+
24+
const DAVID_B_LABYRN = 'db-LB';
25+
const BIDI_RTL_LANGS = ['ar', DAVID_B_LABYRN, 'fa', 'he'];
26+
27+
// Number of characters before and after valid JSON in messages.json files
28+
const JS_PRE_LEN = 24;
29+
const JS_POST_LEN = 3;
30+
31+
var translations = {},
32+
logger;
33+
34+
// forward references
35+
var localeFrom, parseAcceptLanguage, bestLanguage, format;
36+
37+
/**
38+
* Connect middleware which is i18n aware.
39+
*
40+
* Usage:
41+
app.use(i18n.abide({
42+
supported_languages: ['en-US', 'fr', 'pl'],
43+
default_lang: 'en-US',
44+
translation_directory: 'locale'
45+
}));
46+
*
47+
* Other valid options: gettext_alias, debug_lang, disable_locale_check,
48+
* or logger
49+
*/
50+
exports.abide = function(options) {
51+
options = options || {};
52+
options.gettext_alias = options.gettext_alias || 'gettext';
53+
options.supported_languages = options.supported_languages || [ 'en-US' ];
54+
options.default_lang = options.default_lang || 'en-US';
55+
options.debug_lang = options.debug_lang || 'it-CH';
56+
options.disable_locale_check = options.disable_locale_check === true;
57+
options.translation_directory = options.translation_directory || 'locale';
58+
options.localeOnUrl = options.localeOnUrl === true;
59+
options.logger = options.logger || console;
60+
logger = options.logger;
61+
62+
var existsSync = fs.existsSync || path.existsSync,
63+
debug_locale = localeFrom(options.debug_lang);
64+
65+
function resolveLocalePath(locale) {
66+
return path.resolve(path.join(__dirname, '..'),
67+
options.translation_directory,
68+
locale + '.plist');
69+
}
70+
71+
72+
options.supported_languages.forEach(function(lang, i) {
73+
var l = localeFrom(lang);
74+
75+
// populate the in-memory translation cache with client.json, which contains
76+
// strings relevant on the server
77+
try {
78+
translations[l] = plist.parseFileSync(resolveLocalePath(l));
79+
} catch (e) {
80+
// an exception here means that there was a problem with the translation
81+
// files for this locale!
82+
if (options.default_lang === lang || options.debug_lang === lang) return;
83+
84+
var msg = util.format(
85+
'Bad locale=[%s] missing .plist file in [%s]. See locale/README (%s)',
86+
l, resolveLocalePath(l), e);
87+
if (!options.disable_locale_check) {
88+
logger.warn(msg);
89+
} else {
90+
logger.error(msg);
91+
throw msg;
92+
}
93+
}
94+
});
95+
96+
function checkUrlLocale( req ) {
97+
if (!options.localeOnUrl) {
98+
return;
99+
}
100+
101+
// Look for /en/ or /en-US/ or /en_US/ on the URL
102+
var matches = req.url.match( /^\/([a-zA-Z]{2,3}([-_][a-zA-Z]{2})?)(\/|$)/ );
103+
104+
// Swap out the accept-language so that we respect what the user
105+
// used on the URL, and strip the extra locale info from the URL
106+
// so our routes still work.
107+
if ( matches ) {
108+
req.url = req.url.replace( matches[ 0 ], '/' );
109+
// Normalize use of dash to underscore internally
110+
req.headers[ 'accept-language' ] = matches[ 1 ].replace( '-', '_' );
111+
}
112+
}
113+
114+
return function(req, resp, next) {
115+
checkUrlLocale( req );
116+
117+
var langs = parseAcceptLanguage(req.headers['accept-language']),
118+
lang_dir,
119+
lang = bestLanguage(langs, options.supported_languages,
120+
options.default_lang),
121+
debug_lang = options.debug_lang.toLowerCase(),
122+
locale,
123+
locals = {},
124+
gt;
125+
126+
if (lang && lang.toLowerCase && lang.toLowerCase() === debug_lang) {
127+
// What? http://www.youtube.com/watch?v=rJLnGjhPT1Q
128+
lang = DAVID_B_LABYRN;
129+
}
130+
// Express 2 support
131+
if (!! resp.local) {
132+
resp.locals = function(args, orValue) {
133+
if ('string' === typeof args) {
134+
resp.local(args, orValue);
135+
} else {
136+
Object.keys(args).forEach(function(key, i) {
137+
resp.local(key, args[key]);
138+
});
139+
}
140+
};
141+
}
142+
143+
locals.lang = lang;
144+
145+
// BIDI support, which direction does text flow?
146+
lang_dir = BIDI_RTL_LANGS.indexOf(lang) >= 0 ? 'rtl' : 'ltr';
147+
locals.lang_dir = lang_dir;
148+
req.lang = lang;
149+
150+
locale = localeFrom(lang);
151+
152+
locals.locale = locale;
153+
req.locale = locale;
154+
155+
locals.format = format;
156+
req.format = format;
157+
158+
if (lang.toLowerCase() === DAVID_B_LABYRN.toLowerCase()) {
159+
gt = gobbledygook;
160+
locals.lang = DAVID_B_LABYRN;
161+
} else if (translations[locale]) {
162+
gt = function(sid) {
163+
if (translations[locale][sid] && translations[locale][sid].length) {
164+
return translations[locale][sid];
165+
}
166+
// If we didn't find it, use the default lang's string
167+
var defaultLocale = options.default_lang;
168+
if (translations[defaultLocale][sid] && translations[defaultLocale][sid].length) {
169+
return translations[defaultLocale][sid];
170+
}
171+
// Otherwise just return the string key
172+
return sid;
173+
};
174+
} else {
175+
// default lang in a non gettext environment... fake it
176+
gt = function(a, b) { return a; };
177+
}
178+
locals[options.gettext_alias] = gt;
179+
req.gettext = gt;
180+
181+
// resp.locals(string, value) doesn't seem to work with EJS
182+
resp.locals(locals);
183+
184+
next();
185+
};
186+
};
187+
188+
function qualityCmp(a, b) {
189+
if (a.quality === b.quality) {
190+
return 0;
191+
} else if (a.quality < b.quality) {
192+
return 1;
193+
} else {
194+
return -1;
195+
}
196+
}
197+
198+
/**
199+
* Parses the HTTP accept-language header and returns a
200+
* sorted array of objects. Example object:
201+
* {
202+
* lang: 'pl', quality: 0.7
203+
* }
204+
*/
205+
exports.parseAcceptLanguage = parseAcceptLanguage = function(header) {
206+
// pl,fr-FR;q=0.3,en-US;q=0.1
207+
if (! header || ! header.split) {
208+
return [];
209+
}
210+
var raw_langs = header.split(',');
211+
var langs = raw_langs.map(function(raw_lang) {
212+
var parts = raw_lang.split(';');
213+
var q = 1;
214+
if (parts.length > 1 && parts[1].indexOf('q=') === 0) {
215+
var qval = parseFloat(parts[1].split('=')[1]);
216+
if (isNaN(qval) === false) {
217+
q = qval;
218+
}
219+
}
220+
return { lang: parts[0].trim(), quality: q };
221+
});
222+
langs.sort(qualityCmp);
223+
return langs;
224+
};
225+
226+
227+
// Given the user's prefered languages and a list of currently
228+
// supported languages, returns the best match or a default language.
229+
//
230+
// languages must be a sorted list, the first match is returned.
231+
exports.bestLanguage = bestLanguage = function(languages, supported_languages, defaultLanguage) {
232+
var lower = supported_languages.map(function(l) { return l.toLowerCase(); });
233+
for(var i=0; i < languages.length; i++) {
234+
var lq = languages[i];
235+
if (lower.indexOf(lq.lang.toLowerCase()) !== -1) {
236+
return lq.lang;
237+
// Issue#1128 match locale, even if region isn't supported
238+
} else if (lower.indexOf(lq.lang.split('-')[0].toLowerCase()) !== -1) {
239+
return lq.lang.split('-')[0];
240+
}
241+
}
242+
return defaultLanguage;
243+
};
244+
245+
/**
246+
* Given a language code, return a locale code the OS understands.
247+
*
248+
* language: en-US
249+
* locale: en_US
250+
*/
251+
exports.localeFrom = localeFrom = function(language) {
252+
if (! language || ! language.split) {
253+
return "";
254+
}
255+
var parts = language.split('-');
256+
if (parts.length === 1) {
257+
return parts[0].toLowerCase();
258+
} else if (parts.length === 2) {
259+
return util.format('%s_%s', parts[0].toLowerCase(), parts[1].toUpperCase());
260+
} else if (parts.length === 3) {
261+
// sr-Cyrl-RS should be sr_RS
262+
return util.format('%s_%s', parts[0].toLowerCase(), parts[2].toUpperCase());
263+
} else {
264+
logger.error(
265+
util.format("Unable to map a local from language code [%s]", language));
266+
return language;
267+
}
268+
};
269+
270+
/**
271+
* Given a locale code, return a language code
272+
*/
273+
exports.languageFrom = function(locale) {
274+
if (!locale || !locale.split) {
275+
return "";
276+
}
277+
var parts = locale.split('_');
278+
if (parts.length === 1) {
279+
return parts[0].toLowerCase();
280+
} else if (parts.length === 2) {
281+
return util.format('%s-%s', parts[0].toLowerCase(), parts[1].toUpperCase());
282+
} else if (parts.length === 3) {
283+
// sr_RS should be sr-RS
284+
return util.format('%s-%s', parts[0].toLowerCase(), parts[2].toUpperCase());
285+
} else {
286+
logger.error(
287+
util.format("Unable to map a language from locale code [%s]", locale));
288+
return locale;
289+
}
290+
};
291+
292+
/**
293+
* format provides string interpolation on the client and server side.
294+
* It can be used with either an object for named variables, or an array
295+
* of values for positional replacement.
296+
*
297+
* Named Example:
298+
* format("%(salutation)s %(place)s", {salutation: "Hello", place: "World"}, true);
299+
* Positional Example:
300+
* format("%s %s", ["Hello", "World"]);
301+
*/
302+
exports.format = format = function(fmt, obj, named) {
303+
if (!fmt) return "";
304+
if (Array.isArray(obj) || named === false) {
305+
return fmt.replace(/%s/g, function(){return String(obj.shift());});
306+
} else if (typeof obj === 'object' || named === true) {
307+
return fmt.replace(/%\(\s*([^)]+)\s*\)s/g, function(m, v){
308+
return String(obj[v.trim()]);
309+
});
310+
} else {
311+
return fmt;
312+
}
313+
};
314+
315+
exports.getStrings = function( locale ) {
316+
return translations[ locale.toLowerCase() ];
317+
};
318+
319+
exports.getLocales = function() {
320+
return Object.keys( translations );
321+
};

0 commit comments

Comments
 (0)