Merge pull request #160 from glyptodon/fix-language-defaults

GUAC-1053: Fix language defaults
This commit is contained in:
James Muehlner
2015-04-24 22:19:38 -07:00
3 changed files with 218 additions and 50 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014 Glyptodon LLC
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,18 +23,25 @@
/**
* The configuration block for setting up everything having to do with i18n.
*/
angular.module('index').config(['$translateProvider', function($translateProvider) {
angular.module('index').config(['$injector', function($injector) {
// Use US English by default
$translateProvider.preferredLanguage('en_US');
// Required providers
var $translateProvider = $injector.get('$translateProvider');
var preferenceServiceProvider = $injector.get('preferenceServiceProvider');
// Load translations from static JSON files
$translateProvider.useStaticFilesLoader({
prefix: 'translations/',
suffix: '.json'
// Fallback to US English
var fallbackLanguages = ['en_US'];
// Prefer chosen language, use fallback languages if necessary
$translateProvider.fallbackLanguage(fallbackLanguages);
$translateProvider.preferredLanguage(preferenceServiceProvider.preferences.language);
// Load translations via translationLoader service
$translateProvider.useLoader('translationLoader', {
fallbackLanguages : fallbackLanguages
});
// Provide pluralization, etc. via messageformat.js
$translateProvider.useMessageFormatInterpolation();
}]);
}]);

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Service for loading translation definition files, conforming to the
* angular-translate documentation for custom translation loaders:
*
* https://github.com/angular-translate/angular-translate/wiki/Asynchronous-loading#using-custom-loader-service
*/
angular.module('locale').factory('translationLoader', ['$injector', function translationLoader($injector) {
// Required services
var $http = $injector.get('$http');
var $q = $injector.get('$q');
var cacheService = $injector.get('cacheService');
/**
* Satisfies a translation request for the given key by searching for the
* translation files for each key in the given array, in order. The request
* fails only if none of the files can be found.
*
* @param {Deferred} deferred
* The Deferred object to resolve or reject depending on whether at
* least one translation file can be successfully loaded.
*
* @param {String} requestedKey
* The originally-requested language key.
*
* @param {String[]} remainingKeys
* The keys of the languages to attempt to load, in order, where the
* first key in this array is the language to try within this function
* call. The first key in the array is not necessarily the originally-
* requested language key.
*/
var satisfyTranslation = function satisfyTranslation(deferred, requestedKey, remainingKeys) {
// Get current language key
var currentKey = remainingKeys.shift();
// If no languages to try, just fail
if (!currentKey) {
deferred.reject(requestedKey);
return;
}
// Attempt to retrieve language
$http({
cache : cacheService.languages,
method : 'GET',
url : 'translations/' + encodeURIComponent(currentKey) + '.json'
})
// Resolve promise if translation retrieved successfully
.success(function translationFileRetrieved(translation) {
deferred.resolve(translation);
})
// Retry with remaining languages if translation file could not be retrieved
.error(function translationFileUnretrievable() {
satisfyTranslation(deferred, requestedKey, remainingKeys);
});
};
/**
* Custom loader function for angular-translate which loads the desired
* language file dynamically via HTTP. If the language file cannot be
* found, the fallback language is used instead.
*
* @param {Object} options
* Arbitrary options, containing at least a "key" property which
* contains the requested language key.
*
* @returns {Promise.<Object>}
* A promise which resolves to the requested translation string object.
*/
return function loadTranslationFile(options) {
var translation = $q.defer();
// Get requested language from options
var requestedKey = options.key;
// Append fallback languages to requested language
var keys = [requestedKey].concat(options.fallbackLanguages);
// Satisfy the translation request
satisfyTranslation(translation, requestedKey, keys);
// Return promise which is resolved only after the translation file is loaded
return translation.promise;
};
}]);

View File

@@ -24,17 +24,20 @@
* A service for setting and retrieving browser-local preferences. Preferences
* may be any JSON-serializable type.
*/
angular.module('settings').factory('preferenceService', ['$injector',
function preferenceService($injector) {
angular.module('settings').provider('preferenceService', function preferenceServiceProvider() {
// Required services
var $rootScope = $injector.get('$rootScope');
var $translate = $injector.get('$translate');
var $window = $injector.get('$window');
/**
* Reference to the provider itself.
*
* @type preferenceServiceProvider
*/
var provider = this;
var service = {};
// The parameter name for getting the history from local storage
/**
* The storage key of Guacamole preferences within local storage.
*
* @type String
*/
var GUAC_PREFERENCES_STORAGE_KEY = "GUAC_PREFERENCES";
/**
@@ -42,7 +45,7 @@ angular.module('settings').factory('preferenceService', ['$injector',
*
* @type Object.<String, String>
*/
service.inputMethods = {
var inputMethods = {
/**
* No input method is used. Keyboard events are generated from a
@@ -74,13 +77,31 @@ angular.module('settings').factory('preferenceService', ['$injector',
};
/**
* Returns the key of the language currently in use within the browser.
* This is not necessarily the user's desired language, but is rather the
* language user by the browser's interface.
*
* @returns {String}
* The key of the language currently in use within the browser.
*/
var getDefaultLanguageKey = function getDefaultLanguageKey() {
// Pull browser language, falling back to US English
var language = navigator.language || navigator.browserLanguage || 'en_US';
// Convert to format used internally
return language.replace(/-/g, '_');
};
/**
* All currently-set preferences, as name/value pairs. Each property name
* corresponds to the name of a preference.
*
* @type Object.<String, Object>
*/
service.preferences = {
this.preferences = {
/**
* Whether translation of touch to mouse events should emulate an
@@ -96,28 +117,14 @@ angular.module('settings').factory('preferenceService', ['$injector',
*
* @type String
*/
inputMethod : service.inputMethods.NONE,
inputMethod : inputMethods.NONE,
/**
* The selected language.
* The key of the desired display language.
*
* @type String
*/
language : $translate.use()
};
/**
* Persists the current values of all preferences, if possible.
*/
service.save = function save() {
// Save updated preferences, ignore inability to use localStorage
try {
if (localStorage)
localStorage.setItem(GUAC_PREFERENCES_STORAGE_KEY, JSON.stringify(service.preferences));
}
catch (ignore) {}
language : getDefaultLanguageKey()
};
@@ -127,25 +134,65 @@ angular.module('settings').factory('preferenceService', ['$injector',
if (localStorage) {
var preferencesJSON = localStorage.getItem(GUAC_PREFERENCES_STORAGE_KEY);
if (preferencesJSON)
angular.extend(service.preferences, JSON.parse(preferencesJSON));
angular.extend(provider.preferences, JSON.parse(preferencesJSON));
}
}
catch (ignore) {}
// Persist settings when window is unloaded
$window.addEventListener('unload', service.save);
// Factory method required by provider
this.$get = ['$injector', function preferenceServiceFactory($injector) {
// Persist settings upon navigation
$rootScope.$on('$routeChangeSuccess', function handleNavigate() {
service.save();
});
// Required services
var $rootScope = $injector.get('$rootScope');
var $window = $injector.get('$window');
// Persist settings upon logout
$rootScope.$on('guacLogout', function handleLogout() {
service.save();
});
var service = {};
return service;
/**
* All valid input method type names.
*
* @type Object.<String, String>
*/
service.inputMethods = inputMethods;
}]);
/**
* All currently-set preferences, as name/value pairs. Each property name
* corresponds to the name of a preference.
*
* @type Object.<String, Object>
*/
service.preferences = provider.preferences;
/**
* Persists the current values of all preferences, if possible.
*/
service.save = function save() {
// Save updated preferences, ignore inability to use localStorage
try {
if (localStorage)
localStorage.setItem(GUAC_PREFERENCES_STORAGE_KEY, JSON.stringify(service.preferences));
}
catch (ignore) {}
};
// Persist settings when window is unloaded
$window.addEventListener('unload', service.save);
// Persist settings upon navigation
$rootScope.$on('$routeChangeSuccess', function handleNavigate() {
service.save();
});
// Persist settings upon logout
$rootScope.$on('guacLogout', function handleLogout() {
service.save();
});
return service;
}];
});