From 23abb990f10270f7992429ef83f86b83c91f75ea Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Fri, 8 Mar 2013 15:37:56 -0800 Subject: chore($ngLocale): refactor i18n closure slurper logic and parse extended datetime symbols --- i18n/run-tests.sh | 5 + i18n/spec/closureI18nExtractorSpec.js | 250 ++++++++++++++++++++++++++++++++++ i18n/spec/parserSpec.js | 2 + i18n/src/closureI18nExtractor.js | 175 ++++++++++++++++++++++++ i18n/src/closureSlurper.js | 155 ++++++++------------- i18n/src/parser.js | 4 +- i18n/update-closure.sh | 5 + 7 files changed, 494 insertions(+), 102 deletions(-) create mode 100755 i18n/run-tests.sh create mode 100644 i18n/spec/closureI18nExtractorSpec.js create mode 100644 i18n/src/closureI18nExtractor.js (limited to 'i18n') diff --git a/i18n/run-tests.sh b/i18n/run-tests.sh new file mode 100755 index 00000000..68b13401 --- /dev/null +++ b/i18n/run-tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e +PARENT_DIR="$(dirname "$0")" +jasmine-node "$PARENT_DIR"/spec/ diff --git a/i18n/spec/closureI18nExtractorSpec.js b/i18n/spec/closureI18nExtractorSpec.js new file mode 100644 index 00000000..f4f190d5 --- /dev/null +++ b/i18n/spec/closureI18nExtractorSpec.js @@ -0,0 +1,250 @@ +var closureI18nExtractor = require('../src/closureI18nExtractor.js'); +var converter = require('../src/converter.js'); +findLocaleId = closureI18nExtractor.findLocaleId; +extractNumberSymbols = closureI18nExtractor.extractNumberSymbols; +extractCurrencySymbols = closureI18nExtractor.extractCurrencySymbols; +extractDateTimeSymbols = closureI18nExtractor.extractDateTimeSymbols; + + +function newTestLocaleInfo() { + return { fr_CA: { + DATETIME_FORMATS: { + MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', + 'octobre', 'novembre', 'décembre'], + SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', + 'nov.', 'déc.'], + DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + AMPMS: ['AM', 'PM'], + medium: 'yyyy-MM-dd HH:mm:ss', + short: 'yy-MM-dd HH:mm', + fullDate: 'EEEE d MMMM y', + longDate: 'd MMMM y', + mediumDate: 'yyyy-MM-dd', + shortDate: 'yy-MM-dd', + mediumTime: 'HH:mm:ss', + shortTime: 'HH:mm' + }, + NUMBER_FORMATS: { + "DECIMAL_SEP": ".", + "GROUP_SEP": ",", + "PATTERNS": [{ + "minInt": 1, + "minFrac": 0, + "macFrac": 0, + "posPre": "", + "posSuf": "", + "negPre": "-", + "negSuf": "", + "gSize": 3, + "lgSize": 3, + "maxFrac": 3 + }, { + "minInt": 1, + "minFrac": 2, + "macFrac": 0, + "posPre": "¤", + "posSuf": "", + "negPre": "¤-", + "negSuf": "", + "gSize": 3, + "lgSize": 3, + "maxFrac": 2 + }], + "CURRENCY_SYM": "£" + }}}; +} + + +describe("findLocaleId", function () { + it("should find the id from numbers", function() { + expect(findLocaleId("NumberFormatSymbols_en_GB", "num")).toEqual("en_GB"); + }); + + + it("should find the id from datetime", function() { + expect(findLocaleId("DateTimeSymbols_en_ISO", "datetime")).toEqual("en_ISO"); + }); + + + it("should throw an error otherwise", function() { + expect(function() { + findLocaleId("str", "otherwise") + }).toThrow("unknown type in findLocaleId: otherwise"); + }); +}); + +describe("extractNumberSymbols", function () { + it("should extract number data", function() { + var CONTENT = [ + "goog.provide('goog.i18n.NumberFormatSymbols_en_GB');", + "goog.i18n.NumberFormatSymbols_en_GB = {", + "DECIMAL_SEP: '.',", + "GROUP_SEP: ',',", + "PERCENT: '%',", + "ZERO_DIGIT: '0',", + "PLUS_SIGN: '+',", + "MINUS_SIGN: '-',", + "EXP_SYMBOL: 'E',", + "PERMILL: '\u2030',", + "INFINITY: '\u221E',", + "NAN: 'NaN',", + "DECIMAL_PATTERN: '#,##0.###',", + "SCIENTIFIC_PATTERN: '#E0',", + "PERCENT_PATTERN: '#,##0%',", + "CURRENCY_PATTERN: '\u00A4#,##0.00',", + "DEF_CURRENCY_CODE: 'GBP' };" + ].join('\n'); + + var currencySymbols = {'GBP':[2, '£', 'GB£']}; + + var expectedNumberFormats = converter.convertNumberData( + { + DECIMAL_SEP:'.', + GROUP_SEP:',', + DECIMAL_PATTERN:'#,##0.###', + CURRENCY_PATTERN:'\u00A4#,##0.00', + DEF_CURRENCY_CODE: 'GBP' + }, currencySymbols + ); + + var localeInfo = {}; + extractNumberSymbols(CONTENT, localeInfo, currencySymbols); + + expect(localeInfo).toEqual({ + 'en_GB': { NUMBER_FORMATS: expectedNumberFormats } + }); + }) +}); + +describe("extractCurrencySymbols", function () { + it("should extract currency data", function() { + var CONTENT = [ + "goog.i18n.currency.CurrencyInfo = {", + " 'GBP':[2, '£', 'GB£'],", + "};", + "goog.i18n.currency.CurrencyInfoTier2 = {", + " 'AOA':[2, 'Kz', 'Kz'],", + "};" + ].join('\n'); + + var localeInfo = {}; + expect(extractCurrencySymbols(CONTENT)).toEqual({ + 'GBP':[2, '£', 'GB£'], + 'AOA':[2, 'Kz', 'Kz'] + }); + }); +}); + + +describe("extractDateTimeSymbols", function () { + it("should extract date time data", function() { + var CONTENT = [ +"goog.i18n.DateTimeSymbols_fr_CA = {", +" ERAS: ['av. J.-C.', 'ap. J.-C.'],", +" ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],", +" NARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],", +" STANDALONENARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O',", +" 'N', 'D'],", +" MONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',", +" 'août', 'septembre', 'octobre', 'novembre', 'décembre'],", +" STANDALONEMONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',", +" 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],", +" SHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.',", +" 'août', 'sept.', 'oct.', 'nov.', 'déc.'],", +" STANDALONESHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',", +" 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],", +" WEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi',", +" 'samedi'],", +" STANDALONEWEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi',", +" 'vendredi', 'samedi'],", +" SHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],", +" STANDALONESHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.',", +" 'sam.'],", +" NARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],", +" STANDALONENARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],", +" SHORTQUARTERS: ['T1', 'T2', 'T3', 'T4'],", +" QUARTERS: ['1er trimestre', '2e trimestre', '3e trimestre', '4e trimestre'],", +" AMPMS: ['AM', 'PM'],", +" DATEFORMATS: ['EEEE d MMMM y', 'd MMMM y', 'yyyy-MM-dd', 'yy-MM-dd'],", +" TIMEFORMATS: ['HH \\'h\\' mm \\'min\\' ss \\'s\\' zzzz', 'HH:mm:ss z',", +" 'HH:mm:ss', 'HH:mm'],", +" FIRSTDAYOFWEEK: 6,", +" WEEKENDRANGE: [5, 6],", +" FIRSTWEEKCUTOFFDAY: 2", +"};" + ].join('\n'); + var localeInfo = {}; + var expectedLocaleInfo = { + fr_CA: { + DATETIME_FORMATS: { + MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', + 'octobre', 'novembre', 'décembre'], + SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', + 'nov.', 'déc.'], + DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + AMPMS: ['AM', 'PM'], + medium: 'yyyy-MM-dd HH:mm:ss', + short: 'yy-MM-dd HH:mm', + fullDate: 'EEEE d MMMM y', + longDate: 'd MMMM y', + mediumDate: 'yyyy-MM-dd', + shortDate: 'yy-MM-dd', + mediumTime: 'HH:mm:ss', + shortTime: 'HH:mm' + } + } + }; + extractDateTimeSymbols(CONTENT, localeInfo); + expect(localeInfo).toEqual(expectedLocaleInfo); + }) +}); + +describe("pluralExtractor", function() { + it("should output PLURAL_CAT in the output string code", function() { + var localeIds = ["fr_CA"]; + var content = ( + "goog.provide('goog.i18n.pluralRules');\n" + + "\n" + + "goog.i18n.pluralRules.Keyword = {\n" + + " ZERO: 'zero',\n" + + " ONE: 'one',\n" + + " TWO: 'two',\n" + + " FEW: 'few',\n" + + " MANY: 'many',\n" + + " OTHER: 'other'\n" + + "};\n" + + "\n" + + "goog.i18n.pluralRules.frSelect_ = function(n) {\n" + + " if (n >= 0 && n < 2) {\n" + + " return goog.i18n.pluralRules.Keyword.ONE;\n" + + " }\n" + + " return goog.i18n.pluralRules.Keyword.OTHER;\n" + + "};\n" + + "\n" + + "if (goog.LOCALE == 'fr') {\n" + + " goog.i18n.pluralRules.select = goog.i18n.pluralRules.frSelect_;\n" + + "}" + ); + var localeInfo = newTestLocaleInfo(); + closureI18nExtractor.pluralExtractor(content, localeInfo); + var pluralCat = localeInfo["fr_CA"].pluralCat; + expect(pluralCat).toBeDefined(); + // pluralCat is the source text for the pluralCat and contains @@ + // placeholders that need to be stripped before evaluation. + // Ref: closureI18nExtractor.pluralExtractor. + pluralCat = pluralCat.replace(/^@@|@@$/g, ''); + // pluralCat requires these constants to exist. + var PLURAL_CATEGORY = { + ZERO: "zero", ONE: "one", TWO: "two", + FEW: "few", MANY: "many", OTHER: "other" + }; + // Obtain the function by evaluating the source text. + pluralCat = eval("(" + pluralCat + ")"); + // Confirm some expectations for pluralCat in fr_CA. + expect(pluralCat(0)).toEqual("one"); + expect(pluralCat(3)).toEqual("other"); + }) +}); + diff --git a/i18n/spec/parserSpec.js b/i18n/spec/parserSpec.js index 2904e31c..e9aff2be 100644 --- a/i18n/spec/parserSpec.js +++ b/i18n/spec/parserSpec.js @@ -24,6 +24,8 @@ describe('parsePattern', function() { parseAndExpect('#,##,##0.###', '', '-', '', '', 1, 0, 3, 2, 3); parseAndExpect("#,##0.###;\'\u202A\'-#,##0.###\'\u202C\'", '', '\u202A-', '', '\u202C', 1, 0, 3, 3, 3); + parseAndExpect('#0.###;#0.###-', '', '', '', '-', 1, 0, 3, 0, 0); + }); it('should parse CURRENCY patterns', function() { diff --git a/i18n/src/closureI18nExtractor.js b/i18n/src/closureI18nExtractor.js new file mode 100644 index 00000000..a26f89b7 --- /dev/null +++ b/i18n/src/closureI18nExtractor.js @@ -0,0 +1,175 @@ +'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; + +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") { + return object; + } + var result = {}; + Object.keys(object).sort().forEach(function(key) { + result[key] = object[key]; + }); + return result; +} + +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 = JSON.stringify(localeInfo[localeID], canonicalizeForJsonStringify, ' ') + .replace(/\¤/g, '\\u00A4') + .replace(/"@@|@@"/g, ''); + + return prefix + content + suffix; +} diff --git a/i18n/src/closureSlurper.js b/i18n/src/closureSlurper.js index 2e78ef64..9f236cf2 100755 --- a/i18n/src/closureSlurper.js +++ b/i18n/src/closureSlurper.js @@ -1,130 +1,85 @@ #!/usr/bin/env node 'use strict'; -var Q = require('qq'), +var Q = require('q'), qfs = require('q-fs'), converter = require('./converter.js'), util = require('./util.js'), + closureI18nExtractor = require('./closureI18nExtractor.js'), localeInfo = {}, - localeIds = [], currencySymbols, goog = { provide: function() {}, require: function() {}, i18n: {currency: {}, pluralRules: {}} }; -createFolder('../../src/ngLocale/').then(function() { - var promiseA = Q.defer(), - promiseB = Q.defer(); - qfs.read(__dirname + '/../closure/currencySymbols.js', 'b').then(function(content) { - eval(content.toString()); - currencySymbols = goog.i18n.currency.CurrencyInfo; - currencySymbols.__proto__ = goog.i18n.currency.CurrencyInfoTier2; +var NG_LOCALE_DIR = '../src/ngLocale/'; - qfs.read(__dirname + '/../closure/numberSymbols.js', 'b').then(function(content) { - //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 = util.findLocaleId(propName, 'num'); - if (localeID) { - if (!localeInfo[localeID]) { - localeInfo[localeID] = {}; - localeIds.push(localeID); - } - var convertedData = converter.convertNumberData(goog.i18n[propName], currencySymbols); - localeInfo[localeID].NUMBER_FORMATS = convertedData; - } - } - promiseA.resolve(); - }); - }); - - - qfs.read(__dirname + '/../closure/datetimeSymbols.js', 'b').then(function(content) { - eval(content.toString()); - for (var propName in goog.i18n) { - var localeID = util.findLocaleId(propName, 'datetime'); - if (localeID) { - if (!localeInfo[localeID]) { - localeInfo[localeID] = {}; - localeIds.push(localeID); - } - var convertedData = converter.convertDatetimeData(goog.i18n[propName]); - localeInfo[localeID].DATETIME_FORMATS = convertedData; - } - } - - promiseB.resolve(); - }); +function readSymbols() { + console.log("Processing currency and number symbols ..."); + var numericStagePromise = qfs.read(__dirname + '/../closure/currencySymbols.js', 'b') + .then(function(content) { + var currencySymbols = closureI18nExtractor.extractCurrencySymbols(content); + return qfs.read(__dirname + '/../closure/numberSymbols.js', 'b').then(function(content) { + closureI18nExtractor.extractNumberSymbols(content, localeInfo, currencySymbols); + }); + }); - return Q.join(promiseA.promise, promiseB.promise, noop); -}).then(function() { - var promise = Q.defer(); + console.log("Processing datetime symbols ..."); + var datetimeStagePromise = qfs.read(__dirname + '/../closure/datetimeSymbols.js', 'b') + .then(function(content) { + closureI18nExtractor.extractDateTimeSymbols(content, localeInfo); + return qfs.read(__dirname + '/../closure/datetimeSymbolsExt.js', 'b').then(function(content) { + closureI18nExtractor.extractDateTimeSymbols(content, localeInfo); + }); + }); - qfs.read(__dirname + '/../closure/pluralRules.js').then(function(content) { - 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]; - eval(content); - var temp = goog.i18n.pluralRules.select.toString(). - replace(/goog.i18n.pluralRules.Keyword/g, 'PLURAL_CATEGORY').replace(/\n/g, ''); + return Q.all([numericStagePromise, datetimeStagePromise]); +} - ///@@ is a crazy place holder to be replaced before writing to file - localeInfo[localeIds[i]].pluralCat = "@@" + temp + "@@"; - } - promise.resolve(); +function extractPlurals() { + console.log('Extracting Plurals ...'); + return qfs.read(__dirname + '/../closure/pluralRules.js').then(function(content) { + closureI18nExtractor.pluralExtractor(content, localeInfo); }); +} - return promise.promise; -}).then(function() { +function writeLocaleFiles() { + console.log('Final stage: Writing angular locale files to directory: %j', NG_LOCALE_DIR); + var writePromises = []; + var localeIds = Object.keys(localeInfo); + var num_files = 0; localeIds.forEach(function(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; - } - - if (!localeObj.DATETIME_FORMATS) { - localeObj.DATETIME_FORMATS = fallBackObj.DATETIME_FORMATS; - } - - // e.g. from zh_CN to zh-CN, from en_US to en-US - var correctedLocaleId = localeID.replace(/_/g, '-').toLowerCase(); - localeObj.id = correctedLocaleId; - - 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}]);'; - - var content = JSON.stringify(localeInfo[localeID]).replace(/\¤/g,'\\u00A4'). - replace(/"@@|@@"/g, ''); - - var toWrite = prefix + content + suffix; - qfs.write(__dirname + '/../locale/' + 'angular-locale_' + correctedLocaleId + '.js', toWrite); + var content = closureI18nExtractor.outputLocale(localeInfo, localeID); + if (!content) return; + var correctedLocaleId = closureI18nExtractor.correctedLocaleId(localeID); + var filename = NG_LOCALE_DIR + 'angular-locale_' + correctedLocaleId + '.js' + writePromises.push( + qfs.write(filename, content) + .then(function () { + console.log('Wrote ' + filename); + ++num_files; + })); + console.log('Writing ' + filename); }); - console.log('Generated ' + localeIds.length + ' locale files!'); -}).end(); - -function noop() {}; + console.log('Generated %j locale files.', localeIds.length); + return Q.all(writePromises).then(function() { return num_files }); +} /** * Make a folder under current directory. * @param folder {string} name of the folder to be made */ function createFolder(folder) { - return qfs.isDirectory(__dirname + '/' + folder).then(function(isDir) { - if (!isDir) return qfs.makeDirectory(__dirname + '/' + folder); + return qfs.isDirectory(folder).then(function(isDir) { + if (!isDir) return qfs.makeDirectory(folder).then(function() { + console.log('Created directory %j', folder); }); }); } + +createFolder(NG_LOCALE_DIR) + .then(readSymbols) + .then(extractPlurals) + .then(writeLocaleFiles) + .done(function(num_files) { console.log("Wrote %j files.\nAll Done!", num_files); }); diff --git a/i18n/src/parser.js b/i18n/src/parser.js index db31aeef..31dea510 100644 --- a/i18n/src/parser.js +++ b/i18n/src/parser.js @@ -45,8 +45,8 @@ function parsePattern(pattern) { } var groups = integer.split(GROUP_SEP); - p.gSize = groups[1].length; - p.lgSize = (groups[2] || groups[1]).length; + p.gSize = groups[1] ? groups[1].length : 0; + p.lgSize = (groups[2] || groups[1]) ? (groups[2] || groups[1]).length : 0; if (negative) { var trunkLen = positive.length - p.posPre.length - p.posSuf.length, diff --git a/i18n/update-closure.sh b/i18n/update-closure.sh index 80337f13..2ddf8892 100755 --- a/i18n/update-closure.sh +++ b/i18n/update-closure.sh @@ -1,9 +1,14 @@ #!/bin/bash +set -e # Exit on error. + BASE_DIR=`dirname $0` cd $BASE_DIR +set -x # Trace commands as they're executed. + curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/currency.js > closure/currencySymbols.js curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/datetimesymbols.js > closure/datetimeSymbols.js +curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/datetimesymbolsext.js > closure/datetimeSymbolsExt.js curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/numberformatsymbols.js > closure/numberSymbols.js curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/pluralrules.js > closure/pluralRules.js -- cgit v1.2.3