'use strict'; var converter = require('./converter.js'); exports.extractNumberSymbols = extractNumberSymbols; exports.extractCurrencySymbols = extractCurrencySymbols; exports.extractDateTimeSymbols = extractDateTimeSymbols; exports.pluralExtractor = pluralExtractor; exports.outputLocale = outputLocale; exports.correctedLocaleId = correctedLocaleId; exports.findLocaleId = findLocaleId; exports.serializeContent = serializeContent; var goog = { provide: function() {}, require: function() {}, i18n: {currency: {}, pluralRules: {}} }; function findLocaleId(str, type) { if (type === 'num') { return (str.match(/^NumberFormatSymbols_(.+)$/) || [])[1]; } if (type != 'datetime') { throw new Error('unknown type in findLocaleId: ' + type); } return (str.match(/^DateTimeSymbols_(.+)$/) || [])[1]; } function getInfoForLocale(localeInfo, localeID) { if (!localeInfo[localeID]) { localeInfo[localeID] = {}; //localeIds.push(localeID); } return localeInfo[localeID]; } function extractNumberSymbols(content, localeInfo, currencySymbols) { //eval script in the current context so that we get access to all the symbols eval(content.toString()); for (var propName in goog.i18n) { var localeID = findLocaleId(propName, 'num'); if (localeID) { var info = getInfoForLocale(localeInfo, localeID); info.NUMBER_FORMATS = converter.convertNumberData(goog.i18n[propName], currencySymbols); } } } function extractCurrencySymbols(content) { //eval script in the current context so that we get access to all the symbols eval(content.toString()); var currencySymbols = goog.i18n.currency.CurrencyInfo; currencySymbols.__proto__ = goog.i18n.currency.CurrencyInfoTier2; return currencySymbols; } function extractDateTimeSymbols(content, localeInfo) { //eval script in the current context so that we get access to all the symbols eval(content.toString()); for (var propName in goog.i18n) { var localeID = findLocaleId(propName, 'datetime'); if (localeID) { var info = getInfoForLocale(localeInfo, localeID); localeInfo[localeID].DATETIME_FORMATS = converter.convertDatetimeData(goog.i18n[propName]); } } } function pluralExtractor(content, localeInfo) { var contentText = content.toString(); var localeIds = Object.keys(localeInfo); for (var i = 0; i < localeIds.length; i++) { //We don't need to care about country ID because the plural rules in more specific id are //always the same as those in its language ID. // e.g. plural rules for en_SG is the same as those for en. goog.LOCALE = localeIds[i].match(/[^_]+/)[0]; try { eval(contentText); } catch(e) { console.log("Error in eval(contentText): " + e.stack); } if (!goog.i18n.pluralRules.select) { console.log('No select for lang [' + goog.LOCALE + ']'); continue; } var temp = goog.i18n.pluralRules.select.toString(). replace(/goog.i18n.pluralRules.Keyword/g, 'PLURAL_CATEGORY').replace(/\n/g, ''); ///@@ is a crazy place holder to be replaced before writing to file localeInfo[localeIds[i]].pluralCat = "@@" + temp + "@@"; } } function correctedLocaleId(localeID) { // e.g. from zh_CN to zh-CN, from en_US to en-US return localeID.replace(/_/g, '-').toLowerCase(); } function canonicalizeForJsonStringify(unused_key, object) { // This function is intended to be called as the 2nd argument to // JSON.stringify. The goal here is to ensure that the generated JSON has // objects with their keys in ascending order. Without this, it's much // harder to diff the generated files in src/ngLocale as the order isn't // exactly consistent. We've gotten lucky in the past. // // Iteration order, for string keys, ends up being the same as insertion // order. Refer :- // 1. http://ejohn.org/blog/javascript-in-chrome/ // (search for "for loop order"). // Currently all major browsers loop over the properties of an object // in the order in which they were defined. // - John Resig // 2. https://code.google.com/p/v8/issues/detail?id=164 // ECMA-262 does not specify enumeration order. The de facto standard // is to match insertion order, which V8 also does ... if (typeof object != "object" || Object.prototype.toString.apply(object) === '[object Array]') { return object; } var result = {}; Object.keys(object).sort().forEach(function(key) { result[key] = object[key]; }); return result; } function serializeContent(localeObj) { return JSON.stringify(localeObj, canonicalizeForJsonStringify, ' ') .replace(new RegExp('[\\u007f-\\uffff]', 'g'), function(c) { return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4); }) .replace(/"@@|@@"/g, ''); } function outputLocale(localeInfo, localeID) { var fallBackID = localeID.match(/[A-Za-z]+/)[0], localeObj = localeInfo[localeID], fallBackObj = localeInfo[fallBackID]; // fallBack to language formats when country format is missing // e.g. if NUMBER_FORMATS of en_xyz is not present, use the NUMBER_FORMATS of en instead if (!localeObj.NUMBER_FORMATS) { localeObj.NUMBER_FORMATS = fallBackObj.NUMBER_FORMATS; } // datetimesymbolsext.js provides more top level locales than the other // files. We process datetimesymbolsext.js because we want the country // specific formats that are missing from datetimesymbols.js. However, we // don't want to write locale files that only have dateformat (i.e. missing // number formats.) So we skip them. if (!localeObj.NUMBER_FORMATS) { console.log("Skipping locale %j: Don't have any number formats", localeID); return null; } if (!localeObj.DATETIME_FORMATS) { localeObj.DATETIME_FORMATS = fallBackObj.DATETIME_FORMATS; } localeObj.id = correctedLocaleId(localeID); var prefix = 'angular.module("ngLocale", [], ["$provide", function($provide) {\n' + 'var PLURAL_CATEGORY = {' + 'ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"' + '};\n' + '$provide.value("$locale", '; var suffix = ');\n}]);'; localeObj = { DATETIME_FORMATS: localeObj.DATETIME_FORMATS, NUMBER_FORMATS: localeObj.NUMBER_FORMATS, pluralCat: localeObj.pluralCat, id: localeObj.id }; var content = serializeContent(localeInfo[localeID]); return prefix + content + suffix; }