UNPKG

5.81 kBJavaScriptView Raw
1var fs = require('fs'),
2 path = require('path');
3
4/**
5 * Initialize localization module
6 */
7module.exports = function i18n(compound, root) {
8 var app = compound.app;
9 root = root || compound.root;
10 var dir = root + '/config/locales';
11
12 if (!app) {
13 return;
14 }
15
16 if (!compound.utils.existsSync(dir)) {
17 app.set('i18n', 'off');
18 }
19
20 if (app.set('locale') === 'off' || app.set('i18n') === 'off') {
21 compound.T = function () {
22 return function (path, defVal) {
23 return defVal;
24 };
25 };
26 compound.t = compound.T();
27 return;
28 } else {
29 compound.t = T(true);
30 compound.T = T;
31 }
32
33 load(dir, compound);
34
35 /**
36 * Global translation helper
37 *
38 * @param {Boolean} global
39 * @public
40 */
41 function T(global) {
42 if (global) {
43 // helper for global scope (models, initializers, etc)
44 // requires two params (locale expected)
45 return function t(path, locale, defaultValue) {
46 if (!locale) {
47 throw new Error('Locale expected');
48 }
49 return translate(path, locale, defaultValue);
50 };
51 } else {
52 // helper for local scope (controllers, views, helpers)
53 // requires one param
54 return function t(path, defaultValue) {
55 return translate(path, t.locale, defaultValue);
56 };
57 }
58
59 function translate(path, locale, defaultValue) {
60 var translation = compound.__localeData[locale], substitute;
61
62 function nextPathItem(token) {
63 return (translation = translation[token]);
64 }
65
66 if (typeof path === 'string') {
67 substitute = false;
68 } else {
69 substitute = path;
70 path = substitute.shift();
71 }
72
73 if (!translation || !path.split('.').every(nextPathItem)) {
74 translation = typeof defaultValue === 'undefined' ?
75 translationMissing(locale, path, defaultValue) :
76 defaultValue;
77 }
78
79 if (translation && substitute && substitute.length) {
80 substitute.forEach(function(substitution) {
81 translation = translation.replace(/%/, substitution.toString().replace(/%/g, ''));
82 });
83 }
84
85 return translation;
86 }
87
88 function translationMissing(locale, path, defaultValue) {
89
90 if (compound.parent) {
91 var translation;
92 translation = compound.parent.t(path, locale, defaultValue);
93 if (translation) {
94 return translation;
95 }
96
97 }
98
99 switch (app.settings.translationMissing) {
100 case 'display':
101 return 'translation missing for ' + locale + '.' + path;
102 case 'default':
103 case undefined:
104 var defLocale = app.settings.defaultLocale;
105 return !defLocale || locale === defLocale ? '' : translate(path, defLocale, defaultValue);
106 }
107 }
108 }
109
110 T.localeSupported = function(localeName) {
111 return !!compound.__localeData[localeName];
112 };
113};
114
115/**
116 * Load localization files from `dir`. Locales can be in yaml, json or coffee
117 * format
118 *
119 * Example locale.yml file:
120 *
121 * en:
122 * key: 'Value'
123 *
124 * @param {String} dir - absolute path to locales directory.
125 */
126function load(dir, compound) {
127 var coffee;
128 fs.readdirSync(dir).forEach(function(file) {
129 if (file.match(/^\./)) return;
130
131 var filename = dir + '/' + file;
132 var code = fs.readFileSync(filename, 'utf8').toString();
133 var obj;
134
135 try {
136 if (file.match(/\.ya?ml$/)) {
137 var yaml = require(['yaml', 'js'].join('-')),
138 obj = yaml.load(code);
139 if (obj.shift) {
140 obj = obj.shift();
141 }
142 } else if (file.match(/\.json/)) {
143 obj = JSON.parse(code);
144 } else if (file.match(/\.coffee/)) {
145 coffee = coffee || require('coffee-script');
146 obj = coffee.eval(code);
147 } else {
148 console.log('Unsupported extension of locale file ' + filename);
149 }
150 } catch (e) {
151 console.log('Parsing file ' + filename);
152 console.log(e);
153 console.log(e.stack);
154 }
155
156 if (obj) {
157 addTranslation(obj, compound);
158 }
159
160 });
161}
162
163/**
164 * Add translation to `lang` to application locales collection
165 */
166function addTranslation(lang, compound) {
167 Object.keys(lang).forEach(function(localeName) {
168 var translations = lang[localeName];
169 if (compound.locales.indexOf(localeName) === -1) {
170 compound.locales.push([localeName, translations.lang && translations.lang.name || localeName]);
171 }
172 compound.__localeData[localeName] = compound.__localeData[localeName] || {};
173 Object.keys(translations).forEach(function(namespace) {
174 if ('object' === typeof compound.__localeData[localeName] && namespace in compound.__localeData[localeName]) {
175 merge(compound.__localeData[localeName][namespace], translations[namespace]);
176 } else {
177 compound.__localeData[localeName][namespace] = translations[namespace];
178 }
179 });
180 });
181
182 function merge(dest, data) {
183 for (var i in data) {
184 if (i in dest && typeof dest[i] === 'object') {
185 merge(dest[i], data[i]);
186 } else {
187 dest[i] = data[i];
188 }
189 }
190 }
191}