API Documentation for: 1.0.5
Show:

File:Translate.js

/**
*  @module cloudkid
*/
(function(window, $, undefined){

	/**
	*  Internationalization/translation object with convenient jquery plugin
	*  @class Translate
	*  @static
	*/
	var Translate = {};

	/**
	*  The current version of the plugin
	*  @property {string} VERSION
	*  @static
	*  @readOnly
	*/
	Translate.VERSION = "${version}";

	/**
	*  The full language dictionary containing all languages as keys
	*  @property {object} _dict
	*  @private
	*  @static
	*/
	var _dict = null,

	/**
	*  The current set of translations to use
	*  @property {object} _current
	*  @private
	*  @static
	*/
	_current = null,

	/**
	*  The currently selected locale
	*  @property {String|Array} _locale
	*  @private
	*  @static
	*/
	_locale = null,

	/**
	*  The fallback locale if no translation is found
	*  @property {String} _fallbackLocale
	*  @private
	*  @static
	*/
	_fallbackLocale = null,

	/**
	*  Reference to the slice method
	*  @property {function} _slice
	*  @private
	*  @static
	*/
	_slice = Array.prototype.slice;

	/**
	*  Load the full dictionary containing all translations, this can also be used to load
	*  separate JSON files which contain the translation. Each JSON file can contain keys
	*  matching the locale which to use, or a single locale:
	*
	*	// Load external json for all locales
	*	Translate.load("lang.json", function(){ 
	*		// Finished loading
	*	});
	*	
	*	// Or load all locales directly
	*	var dict = {
	*		"en" : {
	*			"title" : "My Site"
	*		}
	*	};
	*	Translate.load(dict);
	*	
	*	// Or loading a single local externally
	*	Translate.load("locale/en/lang.json", "en", function(){
	*		// Finished loading
	*	});
	*	
	*	// Or load a single local directly
	*	var dict = {
	*		"title" : "My Site"
	*	};
	*	Translate.load(dict, "en");
	*
	*  @method load
	*  @static
	*  @param {object|String} dict The translation dictionary or file path to the translation dictionary
	*  @param {String|Function} [langOrCallback] Either the language code or the function callback for file loading
	*  @param {function} [callback] The methond to callback if we're loading a file
	*  @return {Translate} The Translate object for chaining
	*/
	Translate.load = function(dict, langOrCallback, callback)
	{
		var lang = null;

		if (typeof langOrCallback == "function")
		{
			callback = langOrCallback;
		}
		else if (typeof langOrCallback == "string")
		{
			lang = langOrCallback;
		}

		// Load the file
		if (typeof dict == "string")
		{
			var onLoaded = function(data)
			{
				Translate.load(data, lang);
				if (callback)
					callback();
			};
			// Load the JSON
			$.get(dict, onLoaded, "json");
		}
		else
		{
			_dict = _dict || {};

			// Set the specific language
			if (lang)
			{
				_dict[lang] = dict;
				_locale = lang;
			}
			// merge with the existing
			else
			{
				$.extend(_dict, dict);
			}
			refresh();
		}
		return Translate;
	};

	/**
	*  The separator between the file name and the locale
	*  if using the filename translation. For instance, "myfile.png" becomes "myfile_en-US.png"
	*  joining "myfile" with the separator, locale then file extension.
	*  @property {String} fileSeparator
	*  @static
	*  @default "_"
	*/
	Translate.fileSeparator = "_";

	/**
	*  Remove all of the current dictionaries stored and the saved locale
	*  @method reset
	*  @static
	*  @return {Translate} The Translate object for chaining
	*/
	Translate.reset = function()
	{
		_dict = 
		_locale = 
		_fallbackLocale = 
		_current = null;

		return Translate;
	};

	/**
	*  Auto detect the locale based on the window navigator object
	*  @method autoDetect
	*  @static
	*  @param {Boolean} [useCountryLocale=true] If we should use the country locale (e.g., "en-US")
	*         as well as the language. 
	*  @return {Array|String} The new locale selected, returns a string if `useCountryLocale` is false.
	*/
	Translate.autoDetect = function(useCountryLocale)
	{
		var lang = (window.navigator.userLanguage || window.navigator.language);
		useCountryLocale = (useCountryLocale === undefined) ? true : useCountryLocale;
		var langOnly = lang.substr(0,2);
		_locale = useCountryLocale ? [lang, langOnly] : langOnly;
		refresh();
		return _locale;
	};

	/**
	*  The optional fallback locale to use in all cases
	*  @property {String} fallbackLocale
	*  @static
	*/
	Object.defineProperty(Translate, "fallbackLocale", 
	{
		set: function(locale)
		{
			_fallbackLocale = locale;
			refresh();
		},
		get: function()
		{
			return _fallbackLocale;
		}
	});

	/**
	*  The current ISO-639-1 two-letter language locale
	*  @property {String|Array} locale
	*  @static
	*/
	Object.defineProperty(Translate, "locale", 
	{
		set: function(locale)
		{
			_locale = locale;
			refresh();
		},
		get: function()
		{
			return _locale;
		}
	});

	/**
	*  Rebuild the current dictionary of translations to use
	*  @method refresh
	*  @static
	*  @private
	*/
	var refresh = function()
	{
		// Ignore if no locale or dictionary is set
		if (!_locale || !_dict) return;

		_current = {};

		// Select the locale
		var locales = getLocales();

		// Add the locale starting at the end first
		for (var i = locales.length - 1; i >= 0; i--)
		{
			var lang = locales[i];
			$.extend(_current, _dict[lang] || {});
		}

		// Do the automatic localizations
		$("[data-localize]")._t();
		$("[data-localize-file]")._f();
	};

	/**
	*  Get the locales with the fallback
	*  @method getLocales
	*  @static
	*  @private
	*  @return {Array} The collection of locales where the first index is the highest priority
	*/
	var getLocales = function()
	{
		// Select the locale
		var locales = (typeof _locale == "string") ? [_locale] : _locale.slice(0);

		// Add the fallback
		if (_fallbackLocale && locales.indexOf(_fallbackLocale) == -1)
		{
			locales.push(_fallbackLocale);
		}
		return locales;
	};

	/**
	*  Looks the given string up in the dictionary and returns the translation if
	*  one exists. If a translation is not found, returns the original word.
	*  @method translateString
	*  @static
	*  @private
	*  @param {string} key The translation key look-up to translate.
	*  @param {object} params.. params for using printf() on the string.
	*  @return {string} Translated word.
	*/
	var translateString = function(key)
	{
		if (!_current)
		{
			throw 'Must call Translate.load() before getting the translation';
		}

		if (_current.hasOwnProperty(key))
		{
			key = _current[key];
		}
		else
		{
			throw "No translation string found matching '" + key + "'";
		}

		args = _slice.call(arguments);
		args[0] = key;

		// Substitute any params
		return printf.apply(null, args);
	};

	/**
	*  Converts a file to a localized version using the first locale. If no locale
	*  is set, returns the original file.
	*  @method translateFile
	*  @private
	*  @param {string} file The file path
	*  @return {string} The updated file path containing local
	*/
	var translateFile = function(file)
	{
		// Bail out
		if (!_locale) return file;
		
		var locales = getLocales(),
			index = file.lastIndexOf("."),
			http = new XMLHttpRequest(),
			url,
			lang;

		// Add the locale starting at the end first
		for (var i = 0, len = locales.length; i < len; i++)
		{
			lang = locales[i];
			url = file.substring(0, index) + Translate.fileSeparator + lang + file.substring(index, file.length);
			
			http.open('HEAD', url, false);
			http.send();

			// If the file exists, return
			if (http.status != 404)
			{
				return url;
			}
		}
		// No language or fallback, then return the original file
		return file; 
	};

	/**
	*  Substitutes %s with parameters given in list. %%s is used to escape %s.
	*  @method printf
	*  @private
	*  @param {string} str String to perform printf on.
	*  @param {string} args	Array of arguments for printf.
	*  @return {string} Substituted string
	*/
	var printf = function(str, args)
	{
		if (arguments.length < 2) return str;
		args = $.isArray(args) ? args : _slice.call(arguments, 1);

		return str.replace(
			/([^%]|^)%(?:(\d+)\$)?s/g, 
			function(p0, p, position)
			{
				if (position)
				{
					return p + args[parseInt(position)-1];
				}
				return p + args.shift();
			}
		).replace(/%%s/g, '%s');
	};

	/**
	*  Allows you to translate a jQuery selector. See window._t for more information.
	*
	*	$('h1')._t('some text')
	*
	*  @method $.fn._t
	*  @static
	*  @param {string} key The string to translate .
	*  @param {mixed} [params] Params for using printf() on the string.
	*  @return {element} Chained and translated element(s).
	*/
	$.fn._t = function()
	{
		// Capture the arguments
		var args = arguments;
		
		return this.each(function(){

			var self = $(this);
			var localArgs = _slice.call(args, 0);

			if (localArgs.length === 0)
			{
				var key = self.data('localize');
				var values = self.data('localize-values');

				if (!key)
				{
					throw "Must either pass in a key to localize or use the data-localize attribute";
				}
				Array.prototype.push.call(localArgs, key);

				if (values)
				{
					localArgs = localArgs.concat(values.split(","));
				}
			}
			return self.html(translateString.apply(null, localArgs));
		});		
	};

	/**
	*  Allows you to swap an localized file path with jQuery selector. See window._f for more information.
	*
	*	$('img#example')._f()
	*
	*  @method $.fn._f
	*  @static
	*  @param {string} [attr="src"] The attribute to change the file for
	*  @return {element} Chained and translated element(s).
	*/
	$.fn._f = function(attr)
	{
		var self = $(this);
		
		if (self.length === 0) return;

		var file = self.data('localize-file') || self.attr('src');
		self.data('localize-file', file);

		return self.attr(attr || "src", translateFile(file));
	};

	/**
	*  Looks the given string up in the dictionary and returns the translation if
	*  one exists. If a translation is not found, returns the original word.
	*  @method window._t
	*  @static
	*  @param {string} str The string to translate.
	*  @param {mixed} [params*] params for using printf() on the string.
	*  @return {string} Translated word.
	*/
	window._t = translateString;

	/**
	*  Converts a file to a localized version using the locale preferences. If no locale
	*  is set or no valid files are found, returns the original file.
	*  @method window._f
	*  @static
	*  @param {string} file The file path     
	*  @return {string} The updated file path containing local
	*/
	window._f = translateFile;

	// Assign to namespace
	namespace('cloudkid').Translate = Translate;

})(window, jQuery);