UNPKG

11.3 kBJavaScriptView Raw
1/**
2 * @license
3 * MOST Web Framework 2.0 Codename Blueshift
4 * Copyright (c) 2017, THEMOST LP All rights reserved
5 *
6 * Use of this source code is governed by an BSD-3-Clause license that can be
7 * found in the LICENSE file at https://themost.io/license
8 */
9///
10var AbstractMethodError = require("../common/errors").AbstractMethodError;
11var AbstractClassError = require("../common/errors").AbstractClassError;
12var LangUtils = require('@themost/common/utils').LangUtils;
13var Args = require('@themost/common/utils').Args;
14var TraceUtils = require('@themost/common/utils').TraceUtils;
15var path = require('path');
16var HttpApplicationService = require('./types').HttpApplicationService;
17var _ = require('lodash');
18var Symbol = require('symbol');
19var sprintf = require('sprintf').sprintf;
20var i18n = require('i18n');
21var optionsProperty = Symbol('options');
22/**
23 * @abstract
24 * @class
25 * @constructor
26 * @param {HttpApplication} app
27 * @augments HttpApplicationService
28 */
29function LocalizationStrategy(app) {
30 LocalizationStrategy.super_.bind(this)(app);
31 if (this.constructor === LocalizationStrategy.prototype.constructor) {
32 throw new AbstractClassError();
33 }
34}
35LangUtils.inherits(LocalizationStrategy, HttpApplicationService);
36
37/**
38 * Gets a collection of available cultures
39 * @abstract
40 * @public
41 * @returns {Array.<string>}
42 */
43LocalizationStrategy.prototype.getCultures = function() {
44 throw new AbstractMethodError();
45};
46
47/**
48 * Gets the default culture of an HTTP application
49 * @abstract
50 * @public
51 * @returns {string}
52 */
53LocalizationStrategy.prototype.getDefaultCulture = function() {
54 throw new AbstractMethodError();
55};
56
57/**
58 * Returns true if the given culture exists in available cultures
59 * @param {string} culture
60 * @returns {boolean}
61 */
62LocalizationStrategy.prototype.hasCulture = function(culture) {
63 Args.notString(culture,'Culture');
64 return typeof _.find(this.getCultures(), function(x) {
65 return x===culture;
66 }) === 'string';
67};
68
69/**
70 * Returns the localized string of the given string
71 * @param {string} locale - The target locale
72 * @param {...string} str - The string or key which is going to be localized
73 * @abstract
74 * @returns {*}
75 */
76// eslint-disable-next-line no-unused-vars
77LocalizationStrategy.prototype.getLocaleString = function(locale, str) {
78 throw new AbstractMethodError();
79};
80/**
81 * Sets localization data for the specified locale
82 * @param {string} locale - A string which represents the target locale
83 * @param {Object} data - An object which represents a collection of value-key pairs that are going to be used as localization data
84 * @param {boolean=} shouldMerge - A boolean value which indicates whether the specified localization data will be appended to existing localization data or not.
85 * @abstract
86 */
87// eslint-disable-next-line no-unused-vars
88LocalizationStrategy.prototype.setLocaleString = function(locale, data, shouldMerge) {
89 throw new AbstractMethodError();
90};
91
92var culturesProperty = Symbol('cultures');
93var defaultCultureProperty = Symbol('defaultCulture');
94var librariesProperty = Symbol('libraries');
95var defaultLibProperty = "global";
96
97/**
98 * @class
99 * @constructor
100 * @param {HttpApplication} app
101 * @augments LocalizationStrategy
102 */
103function DefaultLocalizationStrategy(app) {
104 DefaultLocalizationStrategy.super_.bind(this)(app);
105 var configuration = this.getApplication().getConfiguration();
106 this[culturesProperty] = ['en-us'];
107 this[defaultCultureProperty] = 'en-us';
108 this[librariesProperty] = {};
109 if (configuration.settings) {
110 if (configuration.settings && configuration.settings.hasOwnProperty('localization')) {
111 /**
112 * @type {{cultures:Array,default:string}}
113 */
114 var localization = configuration.settings['localization'];
115 if (_.isArray(localization.cultures))
116 this[culturesProperty] = localization.cultures;
117 if (_.isString(localization.default))
118 this[defaultCultureProperty] = localization.default;
119 }
120 }
121
122}
123LangUtils.inherits(DefaultLocalizationStrategy, LocalizationStrategy);
124
125/**
126 * Gets a collection of available cultures
127 * @public
128 * @returns {Array.<string>}
129 */
130DefaultLocalizationStrategy.prototype.getCultures = function() {
131 return this[culturesProperty];
132};
133
134/**
135 * Gets the default culture of an HTTP application
136 * @abstract
137 * @public
138 * @returns {string}
139 */
140DefaultLocalizationStrategy.prototype.getDefaultCulture = function() {
141 return this[defaultCultureProperty];
142};
143/**
144 * Resolves localization file path based on the given locale
145 * @param {string} locale
146 */
147DefaultLocalizationStrategy.prototype.resolveLocalePath = function(locale) {
148 return path.resolve(this.getApplication().getExecutionPath(),_.template('locales/global.${locale}.json')({ locale:locale }));
149};
150
151/**
152 * Returns the localized string of the given string
153 * @param {string} locale - The target locale
154 * @param {...*} str - The string or key which is going to be localized
155 * @abstract
156 * @returns {*}
157 */
158// eslint-disable-next-line no-unused-vars
159DefaultLocalizationStrategy.prototype.getLocaleString = function(locale, str) {
160 var lib = 'global';
161 var libraries = this[librariesProperty];
162 var locLibrary;
163 var locText;
164 var args;
165 if (libraries.hasOwnProperty(lib)) {
166 locLibrary = libraries[lib];
167 if (locLibrary.hasOwnProperty(locale)) {
168 // get localized text
169 locText = locLibrary[locale][str] || str;
170 // get arguments for final text
171 args = Array.prototype.slice.call(arguments, 1);
172 // replace the first arguments with the localized text
173 args[0] = locText;
174 // format text
175 return sprintf.apply(sprintf, args);
176 }
177 }
178 var libraryFile;
179 try {
180 libraryFile = this.resolveLocalePath(locale);
181 }
182 catch(err) {
183 if (err.code === 'ENOENT' || err.code === 'MODULE_NOT_FOUND') {
184 TraceUtils.debug('Cannot find localization module' + libraryFile + '.');
185 return str;
186 }
187 throw err;
188 }
189 try {
190
191 if (libraries.hasOwnProperty(lib))
192 locLibrary = libraries[lib];
193 else
194 locLibrary = libraries[lib] = { };
195 locLibrary[locale] = require(libraryFile);
196 // get localized text
197 locText = locLibrary[locale][str] || str;
198 // get arguments for final text
199 args = Array.prototype.slice.call(arguments, 1);
200 // replace the first arguments with the localized text
201 args[0] = locText;
202 // format text
203 return sprintf.apply(sprintf, args);
204 }
205 catch (err) {
206 if (err.code === 'ENOENT' || err.code === 'MODULE_NOT_FOUND') {
207 TraceUtils.debug('Cannot find localization module' + libraryFile + '.');
208 return str;
209 }
210 throw err;
211 }
212};
213
214/**
215 * Sets localization data for the specified locale
216 * @param {string} locale - A string which represents the target locale
217 * @param {Object} data - An object which represents a collection of value-key pairs that are going to be used as localization data
218 * @param {boolean=} shouldMerge - A boolean value which indicates whether the specified localization data will be appended to existing localization data or not.
219 * If parameter is missing the specified data will be merged.
220 */
221DefaultLocalizationStrategy.prototype.setLocaleString = function(locale, data, shouldMerge) {
222 var libraries = this[librariesProperty];
223 //check if the given locale exists in application locales
224 if (this.getCultures().indexOf(locale)<0) {
225 throw new Error('Invalid locale. The specified locale does not exist in application locales.');
226 }
227 //validate locale data libraries["global"]["en-us"]
228 libraries[defaultLibProperty] = libraries[defaultLibProperty] || {};
229 libraries[defaultLibProperty][locale] = libraries[defaultLibProperty][locale] || {};
230 if (typeof shouldMerge === 'undefined' || shouldMerge) {
231 _.assign(libraries[defaultLibProperty][locale], data);
232 }
233 else {
234 libraries[defaultLibProperty][locale] = data;
235 }
236};
237
238/**
239 * @class
240 * @constructor
241 * @param {HttpApplication} app
242 * @augments LocalizationStrategy
243 */
244function I18nLocalizationStrategy(app) {
245 I18nLocalizationStrategy.super_.bind(this)(app);
246 // get application locales and default locales
247 var configuration = this.getApplication().getConfiguration();
248 // get i18n options
249 var options = _.assign({
250 "locales": [ "en" ],
251 "defaultLocale": "en",
252 "directory": path.resolve(this.getApplication().getExecutionPath(),'i18n'),
253 "autoReload": false
254 }, configuration.getSourceAt('settings/i18n'));
255 // set options
256 this[culturesProperty] = options.locales;
257 this[defaultCultureProperty] = options.defaultLocale;
258 i18n.configure(options);
259}
260LangUtils.inherits(I18nLocalizationStrategy, DefaultLocalizationStrategy);
261
262/**
263 * Returns the localized string of the given string
264 * @param {string} locale - The target locale
265 * @param {...*} str - The string or key which is going to be localized
266 * @abstract
267 * @returns {string}
268 */
269/* eslint-disable-next-line no-unused-vars */
270I18nLocalizationStrategy.prototype.getLocaleString = function(locale, str) {
271 return i18n.__.apply({
272 locale: locale
273 }, Array.prototype.slice.call(arguments, 1));
274};
275
276I18nLocalizationStrategy.prototype.resolveLocalePath = function(locale) {
277 if (typeof locale !== 'string') {
278 throw new Error('Invalid locale parameter. Expected string.');
279 }
280 return path.resolve(this.getApplication().getExecutionPath(),_.template('i18n/${locale}.json')({ locale:locale.substr(0,2) }));
281};
282/**
283 * Gets an array of strings which represents the available cultures
284 * @returns {Array<string>}
285 */
286I18nLocalizationStrategy.prototype.getCultures = function() {
287 return this[culturesProperty];
288};
289
290/**
291 * Gets the default culture of an HTTP application
292 * @abstract
293 * @public
294 * @returns {string}
295 */
296I18nLocalizationStrategy.prototype.getDefaultCulture = function() {
297 return this[defaultCultureProperty];
298};
299
300/**
301 * Sets localization data for the specified locale
302 * @param {string} locale - A string which represents the target locale
303 * @param {Object} data - An object which represents a collection of value-key pairs that are going to be used as localization data
304 * @param {boolean=} shouldMerge - A boolean value which indicates whether the specified localization data will be appended to existing localization data or not.
305 * If parameter is missing the specified data will be merged.
306 */
307I18nLocalizationStrategy.prototype.setLocaleString = function(locale, data, shouldMerge) {
308 // get catalog
309 var catalog = i18n.getCatalog(locale);
310 // if data should be merged
311 if (typeof shouldMerge === 'undefined' || shouldMerge) {
312 _.assign(catalog, data);
313 }
314 else {
315 _.assign(catalog, data);
316 }
317};
318
319if (typeof exports !== 'undefined')
320{
321 module.exports.LocalizationStrategy = LocalizationStrategy;
322 module.exports.DefaultLocalizationStrategy = DefaultLocalizationStrategy;
323 module.exports.I18nLocalizationStrategy = I18nLocalizationStrategy;
324}