diff --git a/source/Ox/js/Format.js b/source/Ox/js/Format.js index 21e79c9b..5a42915d 100644 --- a/source/Ox/js/Format.js +++ b/source/Ox/js/Format.js @@ -26,8 +26,9 @@ Ox.formatCount Returns a string like "2 items", "1 item" or "no items". '1,000 cities' @*/ Ox.formatCount = function(number, singular, plural) { - return (number === 0 ? 'no' : Ox.formatNumber(number)) + ' ' - + (number === 1 ? singular : plural || singular + 's'); + plural = (plural || singular + 's') + (number === 2 ? '{2}' : ''); + return (number === 0 ? Ox._('no') : Ox.formatNumber(number)) + + ' ' + Ox._(number === 1 ? singular : plural); }; /*@ @@ -44,7 +45,8 @@ Ox.formatDate Formats a date according to a format string See strftime and ISO 8601. - '%Q' (quarter) and '%X'/'%x' (year with 'BC'/'AD') are non-standard + 'E*%' (localized date and time), %Q' (quarter) and '%X'/'%x' + (year with 'BC'/'AD') are non-standard. (string) -> formatted date (date, string) -> formatted date (date, string, utc) -> formatted date @@ -71,6 +73,26 @@ Ox.formatDate Formats a date according to a format string '01/02/05' > Ox.formatDate(Ox.test.date, '%d') // Zero-padded day of the month '02' + > Ox.formatDate(Ox.test.date, '%ED') // Localized date and time with seconds + '01/02/2005 00:03:04' + > Ox.formatDate(Ox.test.date, '%Ed') // Localized date and time without seconds + '01/02/2005 00:03 + > Ox.formatDate(Ox.test.date, '%EL') // Long localized date with weekday + 'Sunday, January 2, 2005' + > Ox.formatDate(Ox.test.date, '%El') // Long localized date without weekday + 'January 2, 2005' + > Ox.formatDate(Ox.test.date, '%EM') // Medium localized date with weekday + 'Sun, Jan 2, 2005' + > Ox.formatDate(Ox.test.date, '%Em') // Medium localized date without weekday + 'Jan 2, 2005' + > Ox.formatDate(Ox.test.date, '%ES') // Short localized date with century + '01/02/2005' + > Ox.formatDate(Ox.test.date, '%Es') // Short localized date without century + '01/02/05' + > Ox.formatDate(Ox.test.date, '%ET') // Localized time with seconds + '12:03:04 AM' + > Ox.formatDate(Ox.test.date, '%Et') // Localized time without seconds + '12:03 AM' > Ox.formatDate(Ox.test.date, '%e') // Space-padded day of the month ' 2' > Ox.formatDate(Ox.test.date, '%F') // Date @@ -155,6 +177,36 @@ Ox.formatDate Formats a date according to a format string ['D', function() { return '%m/%d/%y'; }], + ['ED', function() { + return '%ES %T'; + }], + ['Ed', function() { + return '%ES %R'; + }], + ['EL', function() { + return Ox._('%A, %B %e, %Y'); + }], + ['El', function() { + return Ox._('%B %e, %Y'); + }], + ['EM', function() { + return Ox._('%a, %b %e, %Y'); + }], + ['Em', function() { + return Ox._('%b %e, %Y'); + }], + ['ES', function() { + return Ox._('%m/%d/%Y'); + }], + ['Es', function() { + return Ox._('%m/%d/%y'); + }], + ['ET', function() { + return Ox._('%I:%M:%S %p'); + }], + ['Et', function() { + return Ox._('%I:%M %p'); + }], ['F', function() { return '%Y-%m-%d'; }], @@ -177,16 +229,16 @@ Ox.formatDate Formats a date according to a format string return '%a %b %e %H:%M:%S %Z %Y'; }], ['A', function(date, utc) { - return Ox.WEEKDAYS[(Ox.getDay(date, utc) + 6) % 7]; + return Ox._(Ox.WEEKDAYS[(Ox.getDay(date, utc) + 6) % 7]); }], ['a', function(date, utc) { - return Ox.SHORT_WEEKDAYS[(Ox.getDay(date, utc) + 6) % 7]; + return Ox._(Ox.SHORT_WEEKDAYS[(Ox.getDay(date, utc) + 6) % 7]); }], ['B', function(date, utc) { - return Ox.MONTHS[Ox.getMonth(date, utc)]; + return Ox._(Ox.MONTHS[Ox.getMonth(date, utc)]); }], ['b', function(date, utc) { - return Ox.SHORT_MONTHS[Ox.getMonth(date, utc)]; + return Ox._(Ox.SHORT_MONTHS[Ox.getMonth(date, utc)]); }], ['C', function(date, utc) { return Math.floor(Ox.getFullYear(date, utc) / 100).toString(); @@ -225,7 +277,7 @@ Ox.formatDate Formats a date according to a format string return Ox.pad((Ox.getMonth(date, utc) + 1), 2); }], ['p', function(date, utc) { - return Ox.AMPM[Math.floor(Ox.getHours(date, utc) / 12)]; + return Ox._(Ox.AMPM[Math.floor(Ox.getHours(date, utc) / 12)]); }], ['Q', function(date, utc) { return Math.floor(Ox.getMonth(date, utc) / 4) + 1; @@ -256,11 +308,13 @@ Ox.formatDate Formats a date according to a format string }], ['X', function(date, utc) { var y = Ox.getFullYear(date, utc); - return Math.abs(y) + ' ' + Ox.BCAD[y < 0 ? 0 : 1]; + return Math.abs(y) + ' ' + Ox._(Ox.BCAD[y < 0 ? 0 : 1]); }], ['x', function(date, utc) { var y = Ox.getFullYear(date, utc); - return Math.abs(y) + (y < 1000 ? ' ' + Ox.BCAD[y < 0 ? 0 : 1] : ''); + return Math.abs(y) + ( + y < 1000 ? ' ' + Ox._(Ox.BCAD[y < 0 ? 0 : 1]) : '' + ); }], ['Y', function(date, utc) { return Ox.getFullYear(date, utc); @@ -569,8 +623,11 @@ Ox.formatDuration = function(seconds/*, decimals, format*/) { if (format == 'none') { ret = Ox.pad(value, 'left', pad[index], '0'); } else if (Ox.isNumber(value) ? value : parseFloat(value)) { - ret = value + (format == 'long' ? ' ' : '') + string[index] - + (format == 'long' && value != 1 ? 's' : ''); + ret = value + (format == 'long' ? ' ' : '') + Ox._(string[index] + ( + format == 'long' + ? (value == 1 ? '' : value == 2 ? 's{2}' : 's') + : '' + )); } else { ret = ''; } @@ -598,8 +655,8 @@ Ox.formatNumber = function(number, decimals) { array.unshift(split[0].slice(-3)); split[0] = split[0].slice(0, -3); } - split[0] = array.join(','); - return (number < 0 ? '-' : '') + split.join('.'); + split[0] = array.join(Ox._(',')); + return (number < 0 ? '-' : '') + split.join(Ox._('.')); }; /*@ @@ -623,15 +680,18 @@ Ox.formatOrdinal = function(number) { var string = Ox.formatNumber(number), length = string.length, last = string[length - 1], - ten = length > 1 && string[length - 2] == '1'; + ten = length > 1 && string[length - 2] == '1', + twenty = length > 1 && !ten; if (last == '1' && !ten) { - string += 'st'; + string += Ox._('st' + (twenty ? '{21}' : '')); } else if (last == '2' && !ten) { - string += 'nd'; + string += Ox._('nd' + (twenty ? '{22}' : '')); } else if (last == '3' && !ten) { - string += 'rd'; + string += Ox._('rd' + (twenty ? '{23}' : '')); } else { - string += 'th'; + string += Ox._( + 'th' + (Ox.contains('123', last) && ten ? '{1' + last + '}' : '') + ); } return string; }; @@ -642,7 +702,7 @@ Ox.formatPercent Formats the relation of two numbers as a percentage "0.10%" @*/ Ox.formatPercent = function(number, total, decimals) { - return Ox.formatNumber(number / total * 100, decimals) + '%' + return Ox.formatNumber(number / total * 100, decimals) + Ox._('%'); }; /*@ @@ -686,18 +746,29 @@ Ox.formatString Basic string formatting 'foobar' > Ox.formatString('{a\\.b}', {'a.b': 'foobar'}) 'foobar' + > Ox.formatString('{1}', ['foobar']) + '{1}' + > Ox.formatString('{b}', {a: 'foobar'}, true) + '' @*/ -Ox.formatString = function(string, collection) { +Ox.formatString = function(string, collection, removeUnmatched) { return string.replace(/\{([^}]+)\}/g, function(string, match) { // make sure to not split at escaped dots ('\.') - var keys = match.replace(/\\\./g, '\n').split('.').map(function(key) { - return key.replace(/\n/g, '.') + var key, + keys = match.replace(/\\\./g, '\n').split('.').map(function(key) { + return key.replace(/\n/g, '.'); }), - value = collection; + value = collection || {}; while (keys.length) { - value = value[keys.shift()]; + key = keys.shift(); + if (value[key]) { + value = value[key]; + } else { + value = null; + break; + } } - return value; + return value || (removeUnmatched ? '' : '{' + match + '}'); }); }; diff --git a/source/Ox/js/Locale.js b/source/Ox/js/Locale.js new file mode 100644 index 00000000..690a164a --- /dev/null +++ b/source/Ox/js/Locale.js @@ -0,0 +1,73 @@ +'use strict'; + +(function() { + + var log, translations = {}; + + /*@ + Ox.setLocale Sets locale + (locale[, url], callback) + locale Locale (like 'de' or 'fr') + url URL of JSON file with additional translations + callback Callback function + success If true, locale has been set + @*/ + Ox.setLocale = function(locale, url, callback) { + var isValidLocale = Ox.contains(Object.keys(Ox.LOCALES), locale); + if (arguments.length == 2) { + callback = arguments[1]; + url = null; + } + if (locale != Ox.LOCALE && isValidLocale) { + Ox.LOCALE = locale; + if (locale == 'en') { + translations = {}; + callback(true); + } else { + Ox.getJSON( + Ox.PATH + 'Ox/json/locale.' + locale + '.json', + function(data) { + translations = data; + if (url) { + Ox.getJSON(url, function(data) { + Ox.extend(translations, data); + callback(true); + }); + } else { + callback(true); + } + } + ); + } + } else { + callback(isValidLocale); + } + }; + + /*@ + Ox._ Localizes a string + (string[, options]) -> Localized string + string English string + options Options passed to Ox.formatString + @*/ + Ox._ = function(value, options) { + var translation = translations[value]; + log && log(value, translation); + translation = translation || value; + return options + ? Ox.formatString(translation, options, true) + : translation + }; + + /*@ + Ox._.log Registers a logging function + (callback) -> undefined + callback Callback function + english English string + translation Translated string + @*/ + Ox._.log = function(callback) { + log = callback; + }; + +})(); \ No newline at end of file