1 /*
  2  * 	Copyright (C) 2012-2013 DFKI GmbH
  3  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
  4  * 	German Research Center for Artificial Intelligence
  5  * 	http://www.dfki.de
  6  * 
  7  * 	Permission is hereby granted, free of charge, to any person obtaining a 
  8  * 	copy of this software and associated documentation files (the 
  9  * 	"Software"), to deal in the Software without restriction, including 
 10  * 	without limitation the rights to use, copy, modify, merge, publish, 
 11  * 	distribute, sublicense, and/or sell copies of the Software, and to 
 12  * 	permit persons to whom the Software is furnished to do so, subject to 
 13  * 	the following conditions:
 14  * 
 15  * 	The above copyright notice and this permission notice shall be included 
 16  * 	in all copies or substantial portions of the Software.
 17  * 
 18  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 19  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 20  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 21  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 22  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 23  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 24  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 25  */
 26 
 27 
 28 define(['constants', 'configurationManager', 'commonUtils', 'semanticInterpreter', 'logger', 'module'],
 29 		
 30 	/**
 31 	 * A class for managing the language of the application. <br>
 32 	 * It's purpose is to load the controllers and their views / partials and provide functions to find controllers or
 33 	 * perform actions or helper-actions.
 34 	 * 
 35 	 * This "class" is structured as a singleton - so that only one instance is in use.<br>
 36 	 * 
 37 	 * @name LanguageManager
 38 	 * @memberOf mmir
 39 	 * @class
 40 	 * 
 41 	 * 
 42 	 * @requires mmir.Constants
 43 	 * @requires mmir.CommonUtils
 44 	 * @requires mmir.SemanticInterpreter
 45 	 * 
 46 	 * 
 47      * @requires jQuery.Deferred
 48      * @requires jQuery.ajax
 49      * 
 50 	 */
 51 	function( 
 52 			constants, configurationManager, commonUtils, semanticInterpreter, Logger, module
 53 ){
 54 			//the next comment enables JSDoc2 to map all functions etc. to the correct class description
 55 			/** @scope mmir.LanguageManager.prototype */
 56 			
 57 		    /**
 58 		     * Object containing the instance of the class
 59 		     * {@link LanguageManager}
 60 		     * 
 61 		     * @type Object
 62 		     * @private
 63 		     * 
 64 		     * @memberOf LanguageManager#
 65 		     */
 66 		    var instance = null;
 67 		    
 68 		    /**
 69 		     * @private
 70 		     * @type Logger
 71 		     * @memberOf LanguageManager#
 72 		     */
 73 		    var logger = Logger.create(module);
 74 
 75 		    /**
 76 		     * JSON object containing the contents of a dictionary file - which are
 77 		     * found in 'config/languages/<language>/dictionary.json'.
 78 		     * 
 79 		     * @type JSON
 80 		     * @private
 81 		     * 
 82 		     * @memberOf LanguageManager#
 83 		     */
 84 		    var dictionary = null;
 85 
 86 		    /**
 87 		     * A String holding the currently loaded language, e.g. "en".
 88 		     * 
 89 		     * @type String
 90 		     * @private
 91 		     * 
 92 		     * @memberOf LanguageManager#
 93 		     */
 94 		    var currentLanguage = null;
 95 
 96 		    /**
 97 		     * A JSON-Object holding the speech-configuration for the currently loaded language.
 98 		     * 
 99 		     * @type JSON-Object
100 		     * @private
101 		     * 
102 		     * @memberOf LanguageManager#
103 		     */
104 		    var currentSpeechConfig = null;
105 
106 		    /**
107 		     * An array of all available languages.
108 		     * 
109 		     * @type Array
110 		     * @private
111 		     * 
112 		     * @memberOf LanguageManager#
113 		     */
114 		    var languages = null;
115 
116 		    /**
117 		     * A keyword which can be used in views (ehtml) to display the current
118 		     * language.<br>
119 		     * If this keyword is used inside a view or partial, it is replaced by the
120 		     * current language string.
121 		     * 
122 		     * @type String
123 		     * @private
124 		     * @example @localize('current_language')
125 		     * 
126 		     * @memberOf LanguageManager#
127 		     */
128 		    var keyword_current_language = 'current_language';
129 
130 		    /**
131 		     * Function to set a new language, but only, if the new language is
132 		     * different from the current language.
133 		     * 
134 		     * @function
135 		     * @param {String}
136 		     *            lang The language of the dictionary which should be loaded.
137 		     * @returns {String} The (new) current language
138 		     * @private
139 		     * 
140 		     * @memberOf LanguageManager#
141 		     */
142 		    function setLanguage(lang) {
143 		        if ((lang) && (currentLanguage != lang)) {
144 		            loadDictionary(lang);
145 		            loadSpeechConfig(lang);
146 		            requestGrammar(lang);
147 		        }
148 		        return currentLanguage;
149 		    }
150 
151 		    /**
152 		     * @function
153 		     * @memberOf LanguageManager#
154 		     */
155 		    function doCheckExistsGrammar(lang) {
156 		        var langFiles = null;
157 		        var retValue = false;
158 
159 		        if (lang != null) {
160 		            langFiles = commonUtils.getDirectoryContents(constants.getLanguagePath() + lang);
161 		            if (langFiles != null) {
162 		                if (langFiles.indexOf(constants.getGrammarFileName()) > -1) {
163 		                    retValue = true;
164 		                }
165 		            }
166 		        }
167 		        return retValue;
168 		    }
169 
170 		    /**
171 		     * Request grammar for the provided language.
172 		     * 
173 		     * If there is no grammar available for the requested language, no new
174 		     * grammar is set.
175 		     * 
176 		     * A grammar is available, if at least one of the following is true for the
177 		     * requested language
178 		     * <ul>
179 		     * <li>there exists a JSON grammar file (with correct name and at the
180 		     * 		correct location)</li>
181 		     * <li>there exists a compiled JavaScript grammar file (with correct name
182 		     * 		and at the correct location)</li>
183 		     * </ul>
184 		     * 
185 		     * TODO document location for JSON and JavaScript grammar files
186 		     * 
187 		     * @function
188 		     * @param {String}
189 		     *            lang The language of the grammar which should be loaded.
190 		     * @returns {String} The current grammar language
191 		     * @async
192 		     * @private
193 		     * 
194 		     * @memberOf LanguageManager#
195 		     */
196 		    function requestGrammar(lang, doSetNextBestAlternative) {
197 
198 		        if (semanticInterpreter.hasGrammar(lang) || doCheckExistsGrammar(lang)) {
199 		            semanticInterpreter.setCurrentGrammar(lang);
200 		            return lang;
201 		        }
202 				else if (doSetNextBestAlternative) {
203 		            // try to find a language, for which a grammar is available
204 		            var grammarLang = null;
205 		            if (languages.length > 0) {
206 		                // first: try to find a language with COMPILED grammar
207 		                for ( var i = 0, size = languages.length; i < size; ++i) {
208 		                    grammarLang = languages[i];
209 		                    if (semanticInterpreter.hasGrammar(grammarLang)) {
210 		                        break;
211 		                    }
212 							else {
213 		                        grammarLang = null;
214 		                    }
215 		                }
216 
217 		                // ... otherwise: try to find a language with JSON grammar:
218 		                if (!grammarLang) {
219 		                    for ( var i = 0, size = languages.length; i < size; ++i) {
220 		                        grammarLang = languages[i];
221 		                        if (doCheckExistsGrammar(grammarLang)) {
222 		                            break;
223 		                        }
224 								else {
225 		                            grammarLang = null;
226 		                        }
227 		                    }
228 		                }
229 		            }
230 
231 		            if (grammarLang) {
232 		                logger.warn('Could not find grammar for selected language ' + lang + ', using grammar for language ' + grammarLang + ' instead.');
233 		                semanticInterpreter.setCurrentGrammar(grammarLang);
234 		            }
235 					else {
236 		                logger.warn('Could not find any grammar for one of [' + languages.join(', ') + '], disabling SemanticInterpret.');
237 		                semanticInterpreter.setEnabled(false);
238 		            }
239 		        }
240 
241 		        return semanticInterpreter.getCurrentGrammar();
242 		    }
243 		    /**
244 		     * Loads the speech-configuration for the provided language and updates the current
245 		     * language.
246 		     * 
247 		     * @function
248 		     * @param {String} lang
249 		     *            The language of the speech-configuration which should be loaded.
250 		     * @returns {String} The (new) current language
251 		     * @async
252 		     * @private
253 		     * 
254 		     * @memberOf LanguageManager#
255 		     */
256 		    function loadSpeechConfig(lang) {
257 
258 		        if (lang && currentLanguage != lang) {
259 		            currentLanguage = lang;
260 		        }
261 		        var path = constants.getLanguagePath() + lang + "/" + constants.getSpeechConfigFileName();
262 		        $.ajax({
263 		            async : false,
264 		            dataType : "json",
265 		            url : path,
266 		            success : function(data) {
267 		                
268 		            	if(logger.isVerbose()) logger.v("[LanguageManager] Success. " + data);
269 		                
270 		            	currentSpeechConfig = data;// jQuery.parseJSON(data);
271 		                
272 		                if(logger.isVerbose()) logger.v("[LanguageManager] " + JSON.stringify(dictionary));
273 		            },
274 		            error : function(xhr, statusStr, error) {
275 		                logger.error("[LanguageManager] Error loading speech configuration from \""+path+"\": " + error? error.stack? error.stack : error : ''); // error
276 		            }
277 		        });
278 		        return currentLanguage;
279 		    }
280 
281 		    /**
282 		     * Loads the dictionary for the provided language and updates the current
283 		     * language.
284 		     * 
285 		     * @function
286 		     * @param {String}
287 		     *            lang The language of the dictionary which should be loaded.
288 		     * @returns {String} The (new) current language
289 		     * @async
290 		     * @private
291 		     * 
292 		     * @memberOf LanguageManager#
293 		     */
294 		    function loadDictionary(lang) {
295 
296 		        if (lang && currentLanguage != lang) {
297 		            currentLanguage = lang;
298 		        }
299 		        var path = constants.getLanguagePath() + lang + "/" + constants.getDictionaryFileName();
300 		        $.ajax({
301 		            async : false,
302 		            dataType : "json",
303 		            url : path,
304 		            success : function(data) {
305 		                dictionary = data;
306 		            },
307 		            error : function(xhr, statusStr, error) {
308 		                logger.error("[LanguageManager] Error loading language dictionary from \""+path+"\": " + error? error.stack? error.stack : error : ''); // error
309 		            }
310 		        });
311 		        return currentLanguage;
312 		    }
313 		    /**
314 		     * Translates a keyword using the current dictionary and returns the
315 		     * translation.
316 		     * 
317 		     * @function
318 		     * @param {String}
319 		     *            textVarName The keyword which should be looked up
320 		     * @returns {String} the translation of the keyword
321 		     * @private
322 		     * 
323 		     * @memberOf LanguageManager#
324 		     */
325 		    function internalGetText(textVarName) {
326 		        var translated = "";
327 		        if (dictionary[textVarName] && dictionary[textVarName].length > 0) {
328 		            translated = dictionary[textVarName];
329 		        }
330 				else if (textVarName === keyword_current_language){
331 		            translated = currentLanguage;
332 		        }
333 				else {
334 		            translated = "undefined";
335 		            logger.warn("[Dictionary] '" + textVarName + "' not found in " + JSON.stringify(dictionary));
336 		        }
337 		        return translated;
338 		    }
339 
340 		    /**
341 		     * Constructor-Method of Singleton mmir.LanguageManager.<br>
342 		     * 
343 		     * @constructs LanguageManager
344 		     * @memberOf LanguageManager#
345 		     * @private
346 		     * @ignore
347 		     * 
348 		     */
349 		    function constructor() {
350 		    			    	
351 		        var _isInitialized = false;
352 		        
353 		        /** @lends LanguageManager.prototype */
354 		        return {
355 		        	
356 		        	/**
357 		        	 * @param {String} [lang] OPTIONAL
358 		        	 * 				a language code for setting the current language and
359 		        	 * 				selecting the corresponding language resources
360 		        	 * 
361 		        	 * @memberOf LanguageManager.prototype
362 		        	 */
363 		        	init: function(lang){
364 		        		
365 		        		if (!lang && !currentLanguage) {
366 
367 		        			//try to retrieve language from configuration:
368 				            var appLang = configurationManager.get("language");
369 				            if (appLang) {
370 				            	
371 				                lang = appLang;
372 				                logger.info("[LanguageManager] No language argument specified: using language from configuration '" + appLang + "'.");
373 				                
374 				            }
375 				            else {
376 				            	
377 					            appLang = constants.getLanguage();
378 					            
379 				            	if (appLang) {
380 				            
381 					                lang = appLang;
382 					                logger.info("[LanguageManager] No language argument specified: using language from mmir.constants '" + appLang + "'.");
383 					            }
384 				            	else {
385 					            	
386 					                if (languages.length > 0) {
387 					                	
388 					                	appLang = this.determineLanguage(lang);
389 					                	if(appLang){
390 					                		
391 					                		lang = appLang;
392 					                		
393 					                		logger.info("[LanguageManager] No language argument specified: used determinLanguage() for selecting language '" + appLang + "'.");
394 					                	}
395 					                }
396 					                
397 					            }//END: else(consts::lang)
398 				            	
399 				            }//END: else(config::lang)
400 				        
401 			        		if(!lang){
402 			        			logger.warn("[LanguageManager.init] No language specified. And no language could be read from directory '" + constants.getLanguagePath() + "'.");
403 			        		}
404 			        		
405 				        }//END: if(!lang && !currentLanguage)
406 		        		
407 
408 				        // get all the languages/dictionaries by name
409 				        languages = commonUtils.getDirectoryContents(constants.getLanguagePath());
410 
411 				        if (logger.isDebug()) logger.debug("[LanguageManager] Found dictionaries for: " + JSON.stringify(languages));// debug
412 
413 				        loadDictionary(lang);   
414 				        loadSpeechConfig(lang);
415 				        requestGrammar(lang, true);//2nd argument TRUE: if no grammar is available for lang, try to find/set any available grammar
416 				        
417 				        _isInitialized = true;
418 				        
419 				        return this;
420 		        	},
421 		        	/**
422 		        	 * Initializes the LanguageManager instance if necessary, and sets the Language to lang.
423 		        	 * 
424 				     * If no language is supplied as parameter, then the property *language*
425 				     * from {@link mmir.Configuration} is used or the first language found
426 				     * in the language directory.
427 				     * 
428 				     * @deprecated instead use LanguageManager directly (NOTE: before starting to use LanguageManager, init() has to be invoked once)
429 				     * 
430 				     * @param {String} [lang] OPTIONAL
431 				     *            The language which should be used throughout the
432 				     *            application.
433 		        	 */
434 		        	getInstance: function(lang){
435 		        		
436 		        		if(_isInitialized === false){
437 		        			_isInitialized = true;
438 		        			this.init(lang);
439 		        		}
440 		        		else if(lang) {
441 		        			setLanguage(lang);
442 		        		}
443 		        		
444 		        		return this;
445 		        	},
446 		        	
447 		            /**
448 		             * Returns the dictionary of the currently used language.
449 		             * 
450 		             * @function
451 		             * @returns {Object} The JSON object for the dictionary of the
452 		             *          currently used language
453 		             * @public
454 		             */
455 		            getDictionary : function() {
456 		                return dictionary;
457 		            },
458 
459 		            /**
460 		             * If a dictionary exists for the given language, 'true' is
461 		             * returned. Else the method returns 'false'.
462 		             * 
463 		             * @function
464 		             * @returns {Boolean} True if a dictionary exists for given
465 		             *          language.
466 		             * @param {String}
467 		             *            Language String, i.e.: en, de
468 		             * @public
469 		             */
470 		            existsDictionary : function(lang) {
471 		                var langFiles = null;
472 		                var retValue = false;
473 
474 		                if (lang != null) {
475 		                    langFiles = commonUtils.getDirectoryContents(constants.getLanguagePath() + lang);
476 		                    if (langFiles != null) {
477 		                        if (langFiles.indexOf(constants.getDictionaryFileName()) > -1) {
478 		                            retValue = true;
479 		                        }
480 		                    }
481 		                }
482 		                return retValue;
483 		            },
484 
485 		            /**
486 		             * If a speech-configuration (file) exists for the given language.
487 		             * 
488 		             * @function
489 		             * @returns {Boolean}
490 		             * 				<code>true</code>if a speech-configuration exists for given language.
491 		             * 				Otherwise <code>false</code>.
492 		             * 
493 		             * @param {String} lang
494 		             *            the language for which existence of the configuration should be checked, e.g. en, de
495 		             *            
496 		             * @public
497 		             */
498 		            existsSpeechConfig : function(lang) {
499 		                var langFiles = null;
500 		                var retValue = false;
501 
502 		                if (lang != null) {
503 		                    langFiles = commonUtils.getDirectoryContents(constants.getLanguagePath() + lang);
504 		                    if (langFiles != null) {
505 		                        if (langFiles.indexOf(constants.getSpeechConfigFileName()) > -1) {
506 		                            retValue = true;
507 		                        }
508 		                    }
509 		                }
510 		                return retValue;
511 		            },
512 
513 		            /**
514 		             * If a JSON grammar file exists for the given language, 'true' is
515 		             * returned. Else the method returns 'false'.
516 		             * 
517 		             * @function
518 		             * @returns {Boolean} True if a grammar exists for given language.
519 		             * @param {String}
520 		             *            Language String, i.e.: en, de
521 		             * @public
522 		             */
523 		            existsGrammar : doCheckExistsGrammar,
524 
525 		            /**
526 		             * Chooses a language for the application.
527 		             * 
528 		             * <p>
529 		             * The language selection is done as follows:
530 		             * <ol>
531 		             * <li>check if a default language exists<br>
532 		             * if it does and if both (!) grammar and dictionary exist for this
533 		             * language, return this language </li>
534 		             * <li>walk through all languages alphabetically
535 		             * <ol>
536 		             * <li>if for a language both (!) grammar and dictionary exist,
537 		             * return this language memorize the first language with a grammar
538 		             * (do not care, if a dictionary exists) </li>
539 		             * </ol>
540 		             * <li>test if a grammar exists for the default language - do not
541 		             * care about dictionaries - if it does, return the default language
542 		             * </li>
543 		             * <li>If a language was found (in Step 2.1) return this language
544 		             * </li>
545 		             * <li>If still no language is returned take the default language
546 		             * if it has a dictionary </li>
547 		             * <li>If a language exists, take it (the first one) </li>
548 		             * <li>Take the default language - no matter what </li>
549 		             * </ol>
550 		             * 
551 		             * @function
552 		             * @returns {String} The determined language
553 		             * @public
554 		             */
555 		            determineLanguage : function(lang) {
556 		                var tempLanguage = lang;
557 		                var firstLanguageWithGrammar = null;
558 
559 		                // first check, if language - given in parameter - exists
560 		                if (tempLanguage != null) {
561 		                    // check if both grammar and dictionary exist for given
562 		                    // language
563 		                    if (instance.existsGrammar(tempLanguage) && instance.existsDictionary(tempLanguage)) {
564 		                        return tempLanguage;
565 		                    }
566 		                }
567 
568 		                tempLanguage = constants.getLanguage();
569 		                // then check, if default language exists
570 		                if (tempLanguage != null) {
571 		                    // check if both grammar and dictionary exist for default
572 		                    // language
573 		                    if (instance.existsGrammar(tempLanguage) && instance.existsDictionary(tempLanguage)) {
574 		                        return tempLanguage;
575 		                    }
576 		                }
577 		                // walk through the languages alphabetically
578 		                for ( var i = 0; i < languages.length; i++) {
579 		                    tempLanguage = languages[i];
580 		                    // check if a grammar and dictionary exists for every
581 		                    // language
582 		                    if (instance.existsGrammar(tempLanguage)) {
583 
584 		                        // memorize the first language with a grammar (for
585 		                        // later)
586 		                        if (firstLanguageWithGrammar == null) {
587 		                            firstLanguageWithGrammar = tempLanguage;
588 		                        }
589 
590 		                        if (instance.existsDictionary(tempLanguage)) {
591 		                            return tempLanguage;
592 		                        }
593 		                    }
594 		                }
595 
596 		                // still no language found - take the default language and test
597 		                // if a grammar exists
598 		                tempLanguage = constants.getLanguage();
599 		                if (tempLanguage != null) {
600 		                    // check if both grammar and dictionary exist for default
601 		                    // language
602 		                    if (instance.existsGrammar(tempLanguage)) {
603 		                        return tempLanguage;
604 		                    } else if (firstLanguageWithGrammar != null) {
605 		                        return firstLanguageWithGrammar;
606 		                    } else if (instance.existsDictionary(tempLanguage)) {
607 		                        return tempLanguage;
608 		                    }
609 		                }
610 
611 		                // still no language - take the first one
612 		                tempLanguage = languages[0];
613 		                if (tempLanguage != null) {
614 		                    return tempLanguage;
615 		                }
616 
617 		                return constants.getLanguage();
618 		            },
619 
620 		            /**
621 		             * Sets a new language, but only, if the new language is different
622 		             * from the current language.
623 		             * 
624 		             * @function
625 		             * @returns {String} The (new) current language
626 		             * @public
627 		             */
628 		            setLanguage : function(lang) {
629 		                return setLanguage(lang);
630 		            },
631 
632 		            /**
633 		             * Gets the language currently used for the translation.
634 		             * 
635 		             * @function
636 		             * @returns {String} The current language
637 		             * @public
638 		             */
639 		            getLanguage : function() {
640 		                return currentLanguage;
641 		            },
642 
643 		            /**
644 		             * Gets the default language.
645 		             * 
646 		             * @function
647 		             * @returns {String} The default language
648 		             * @public
649 		             */
650 		            getDefaultLanguage : function() {
651 		                return constants.getLanguage();
652 		            },
653 
654 		            /**
655 		             * Gets an array of all for the translation available languages.<br>
656 		             * 
657 		             * @function
658 		             * @returns {String} An array of all for the translation available
659 		             *          languages
660 		             * @public
661 		             */
662 		            getLanguages : function() {
663 		                return languages;
664 		            },
665 
666 		            /**
667 		             * Cycles through the available languages.
668 		             * 
669 		             * @function
670 		             * @returns {String} The (new) current language
671 		             * @public
672 		             * @deprecated unused
673 		             */
674 		            setNextLanguage : function() {
675 		                var indexCurrentLanguage = languages.indexOf(currentLanguage);
676 
677 		                if (logger.isVerbose()) logger.v("[LanguageManager] Current language is " + currentLanguage);
678 
679 		                if (indexCurrentLanguage > -1) {
680 		                    indexCurrentLanguage = indexCurrentLanguage + 1;
681 		                    if (indexCurrentLanguage > languages.length - 1) {
682 		                        indexCurrentLanguage = 0;
683 		                    }
684 		                    currentLanguage = languages[indexCurrentLanguage];
685 
686 		                    if (logger.isVerbose()) logger.v("[LanguageManager] Next language is " + currentLanguage);
687 		                    
688 		                    loadSpeechConfig(currentLanguage);
689 		                    return loadDictionary(currentLanguage);
690 		                }
691 		                return currentLanguage;
692 		            },
693 
694 		            /**
695 		             * Looks up a keyword in the current dictionary and returns the
696 		             * translation.
697 		             * 
698 		             * @function
699 		             * @param {String}
700 		             *            textVarName The keyword which is to be translated
701 		             * @returns {String} The translation of the keyword
702 		             * @public
703 		             */
704 		            getText : function(textVarName) {
705 		                return internalGetText(textVarName);
706 		            },
707 		            
708 		            /**
709 		             * Get the language code setting for a specific plugin.
710 		             * 
711 		             * Returns the default setting, if no specific setting for the specified plugin was defined.
712 		             * 
713 		             * @public
714 		             * @param {String} pluginId
715 		             * @param {String|Array<String>} [feature] OPTIONAL
716 		             * 				dot-separate path String or "path Array"
717 		             * 				This parameter may be omitted, if no <code>separator</code> parameter
718 		             * 				is used.
719 		             * 				DEFAULT: "language" (the language feature)
720 		             * @param {String} [separator] OPTIONAL
721 		             * 				the speparator-string that should be used for separating
722 		             * 				the country-part and the language-part of the code
723 		             * @returns {String} the language-setting/-code
724 		             */
725 		            getLanguageConfig : function(pluginId, feature, separator) {
726 		                
727 		                //if nothing is specfied:
728 		            	//	return default language-setting
729 		                if(typeof pluginId === 'undefined'){
730 		                	return currentSpeechConfig.language; /////////// EARLY EXIT ///////////////
731 		                }
732 		                
733 		                //ASSERT pluginId is defined
734 		                
735 		                //default feature is language
736 		                if(typeof feature === 'undefined'){
737 		                	feature = 'language';
738 		                }
739 
740 		                var value;
741 		                if(currentSpeechConfig.plugins && currentSpeechConfig.plugins[pluginId] && typeof currentSpeechConfig.plugins[pluginId][feature] !== 'undefined'){
742 		                	//if there is a plugin-specific setting for this feature
743 		                	value = currentSpeechConfig.plugins[pluginId][feature];
744 		                }
745 		                else if(feature !== 'plugins' && typeof currentSpeechConfig[feature] !== 'undefined'){
746 		                	//otherwise take the default setting (NOTE: the name "plugins" is not allowed for features!)
747 		                	value = currentSpeechConfig[feature];
748 		                }
749 		                
750 		                //if there is a separator specified: replace default separator '-' with this one
751 		                if(value && typeof separator !== 'undefined'){
752 		                	value = value.replace(/-/, separator);
753 		                }
754 		                
755 		                return value;
756 		            }
757 		            
758 		            /**
759 		             * Set to "backwards compatibility mode" (for pre version 2.0).
760 		             * 
761 		             * This function re-adds deprecated and removed functions and
762 		             * properties to the CommonUtils instance.
763 		             * 
764 		             * NOTE that once set to compatibility mode, it cannot be reset to
765 		             * non-compatibility mode.
766 		             * 
767 		             * @deprecated use only for backward compatability
768 		             * 
769 		             * @public
770 		             * @async
771 				     * @requires jQuery.Deferred
772 				     * @requires mmir.LanguageManager.setToCompatibilityModeExtension
773 				     * 
774 				     * @param {Function} [success]
775 				     * 				a callback function that is invoked, after compatibility mode
776 				     * 				was set (alternatively the returned promise can be used).
777 				     * @returns {Promise}
778 				     * 				a Deffered.promise that is resolved, after compatibility mode
779 				     * 				was set
780 				     * 
781 				     * @see mmir.LanguageManager.setToCompatibilityModeExtension
782 		             */
783 		            , setToCompatibilityMode : function(success) {
784 		            	
785 		            	var defer = $.Deferred();
786 				    	if(success){
787 				    		defer.always(success);
788 				    	}
789 				    	
790 				    	require(['languageManagerCompatibility'],function(setCompatibility){
791 				    		
792 				    		setCompatibility(instance);
793 				    		
794 				    		defer.resolve();
795 				    	});
796 				    	
797 				    	return defer.promise();
798 		                
799 		            }//END: setToCompatibilityMode()
800 		            
801 		        };//END: return{}
802 		        
803 		        
804 		    }//END: construcor = function(){...
805 
806 		    		    
807 		    //FIXME as of now, the LanguageManager needs to be initialized,
808 		    //		either by calling getInstance() or init()
809 		    //		-> should this be done explicitly (async-loading for dictionary and grammar? with returned Deferred.promise?)
810 		    instance = new constructor();
811 		    		    
812 		    return instance;
813 			
814 });
815