i18nUtil = {}; i18nUtil.setup = function(/*Object*/kwArgs){ //summary: loads dojo so we can use it for i18n bundle flattening. //Do the setup only if it has not already been done before. if(typeof djConfig == "undefined" || !(typeof dojo != "undefined" && dojo["i18n"])){ djConfig={ locale: 'xx', extraLocale: kwArgs.localeList, baseUrl: "../../dojo/" }; load('../../dojo/dojo.js'); //Now set baseUrl so it is current directory, since all the prefixes //will be relative to the release dir from this directory. dojo.baseUrl = "./"; //Also be sure we register the right paths for module prefixes. buildUtil.configPrefixes(kwArgs.profileProperties.dependencies.prefixes); dojo.require("dojo.i18n"); } } i18nUtil.flattenLayerFileBundles = function(/*String*/fileName, /*String*/fileContents, /*Object*/kwArgs){ //summary: // This little utility is invoked by the build to flatten all of the JSON resource bundles used // by dojo.requireLocalization(), much like the main build itself, to optimize so that multiple // web hits will not be necessary to load these resources. Normally, a request for a particular // bundle in a locale like "en-us" would result in three web hits: one looking for en_us/ another // for en/ and another for ROOT/. All of this multiplied by the number of bundles used can result // in a lot of web hits and latency. This script uses Dojo to actually load the resources into // memory, then flatten the object and spit it out using dojo.toJson. The bootstrap // will be modified to download exactly one of these files, whichever is closest to the user's // locale. //fileName: // The name of the file to process (like dojo.js). This function will use // it to determine the best resource name to give the flattened bundle. //fileContents: // The contents of the file to process (like dojo.js). This function will look in // the contents for dojo.requireLocation() calls. //kwArgs: // The build's kwArgs. var destDirName = fileName.replace(/\/[^\/]+$/, "/") + "nls"; var nlsNamePrefix = fileName.replace(/\.js$/, ""); nlsNamePrefix = nlsNamePrefix.substring(nlsNamePrefix.lastIndexOf("/") + 1, nlsNamePrefix.length); i18nUtil.setup(kwArgs); var djLoadedBundles = []; //TODO: register plain function handler (output source) in jsonRegistry? var drl = dojo.requireLocalization; dojo.requireLocalization = function(modulename, bundlename, locale){ drl(modulename, bundlename, locale); //TODO: avoid dups? djLoadedBundles.push({modulename: modulename, module: eval(modulename), bundlename: bundlename}); }; var requireStatements = fileContents.match(/dojo\.requireLocalization\(.*\)\;/g); if(requireStatements){ eval(requireStatements.join(";")); //print("loaded bundles: "+djLoadedBundles.length); var djBundlesByLocale = {}; var jsLocale, entry, bundle; for (var i = 0; i < djLoadedBundles.length; i++){ entry = djLoadedBundles[i]; bundle = entry.module.nls[entry.bundlename]; for (jsLocale in bundle){ if (!djBundlesByLocale[jsLocale]){djBundlesByLocale[jsLocale]=[];} djBundlesByLocale[jsLocale].push(entry); } } localeList = []; //Save flattened bundles used by dojo.js. var mkdir = false; var dir = new java.io.File(destDirName); var modulePrefix = buildUtil.mapPathToResourceName(fileName, kwArgs.profileProperties.dependencies.prefixes); //Adjust modulePrefix to include the nls part before the last segment. var lastDot = modulePrefix.lastIndexOf("."); if(lastDot != -1){ modulePrefix = modulePrefix.substring(0, lastDot + 1) + "nls." + modulePrefix.substring(lastDot + 1, modulePrefix.length); }else{ throw "Invalid module prefix for flattened bundle: " + modulePrefix; } for (jsLocale in djBundlesByLocale){ var locale = jsLocale.replace(/\_/g, '-'); if(!mkdir){ dir.mkdir(); mkdir = true; } var outFile = new java.io.File(dir, nlsNamePrefix + "_" + locale + ".js"); //Make sure we can create the final file. var parentDir = outFile.getParentFile(); if(!parentDir.exists()){ if(!parentDir.mkdirs()){ throw "Could not create directory: " + parentDir.getAbsolutePath(); } } var os = new java.io.BufferedWriter( new java.io.OutputStreamWriter(new java.io.FileOutputStream(outFile), "utf-8")); try{ os.write("dojo.provide(\""+modulePrefix+"_"+locale+"\");"); for (var j = 0; j < djLoadedBundles.length; j++){ entry = djLoadedBundles[j]; var bundlePkg = [entry.modulename,"nls",entry.bundlename].join("."); var translationPkg = [bundlePkg,jsLocale].join("."); bundle = entry.module.nls[entry.bundlename]; if(bundle[jsLocale]){ //FIXME:redundant check? os.write("dojo.provide(\""+bundlePkg+"\");"); os.write(bundlePkg+"._built=true;"); os.write("dojo.provide(\""+translationPkg+"\");"); os.write(translationPkg+"="+dojo.toJson(bundle[jsLocale])+";"); } } }finally{ os.close(); } localeList.push(locale); } //Remove dojo.requireLocalization calls from the file. fileContents = fileContents.replace(/dojo\.requireLocalization\(.*\)\;/g, ""); var preloadCall = '\ndojo.i18n._preloadLocalizations("' + modulePrefix + '", ' + dojo.toJson(localeList.sort()) + ');\n'; //Inject the dojo._preloadLocalizations call into the file. //Do this at the end of the file, since we need to make sure dojo.i18n has been loaded. //The assumption is that if dojo.i18n is not in this layer file, dojo.i18n is //in one of the layer files this layer file depends on. //Allow call to be inserted in the dojo.js closure, if that is in play. i18nUtil.preloadInsertionRegExp.lastIndex = 0; if(fileContents.match(i18nUtil.preloadInsertionRegExp)){ i18nUtil.preloadInsertionRegExp.lastIndex = 0; fileContents = fileContents.replace(i18nUtil.preloadInsertionRegExp, preloadCall); }else{ fileContents += preloadCall; } } return fileContents; //String } i18nUtil.preloadInsertionRegExp = /\/\/INSERT dojo.i18n._preloadLocalizations HERE/; i18nUtil.flattenDirBundles = function(/*String*/prefixName, /*String*/prefixDir, /*Object*/kwArgs, /*RegExp*/nlsIgnoreRegExp){ //summary: Flattens the i18n bundles inside a directory so that only request //is needed per bundle. Does not handle resource flattening for dojo.js or //layered build files. i18nUtil.setup(kwArgs); var fileList = fileUtil.getFilteredFileList(prefixDir, /\.js$/, true); var prefixes = kwArgs.profileProperties.dependencies.prefixes; for(var i= 0; i < fileList.length; i++){ //Use new String so we get a JS string and not a Java string. var jsFileName = String(fileList[i]); var fileContents = null; //Files in nls directories, except for layer bundles that already have been processed. if(jsFileName.match(/\/nls\//) && !jsFileName.match(nlsIgnoreRegExp)){ fileContents = "(" + i18nUtil.makeFlatBundleContents(prefixName, prefixDir, jsFileName) + ")"; }else{ fileContents = i18nUtil.modifyRequireLocalization(readText(jsFileName), prefixes); } if(fileContents){ fileUtil.saveUtf8File(jsFileName, fileContents); } } } i18nUtil.modifyRequireLocalization = function(/*String*/fileContents, /*Array*/prefixes){ //summary: Modifies any dojo.requireLocalization calls in the fileContents to have the //list of supported locales as part of the call. This allows the i18n loading functions //to only make request(s) for locales that actually exist on disk. var dependencies = []; //Make sure we have a JS string, and not a Java string. fileContents = String(fileContents); var modifiedContents = fileContents; if(fileContents.match(buildUtil.globalRequireLocalizationRegExp)){ modifiedContents = fileContents.replace(buildUtil.globalRequireLocalizationRegExp, function(matchString){ var replacement = matchString; var partMatches = matchString.match(buildUtil.requireLocalizationRegExp); var depCall = partMatches[1]; var depArgs = partMatches[2]; if(depCall == "requireLocalization"){ //Need to find out what locales are available so the dojo loader //only has to do one script request for the closest matching locale. var reqArgs = i18nUtil.getRequireLocalizationArgsFromString(depArgs); if(reqArgs.moduleName){ //Find the list of locales supported by looking at the path names. var locales = i18nUtil.getLocalesForBundle(reqArgs.moduleName, reqArgs.bundleName, prefixes); //Add the supported locales to the requireLocalization arguments. if(!reqArgs.localeName){ depArgs += ", null"; } depArgs += ', "' + locales.join(",") + '"'; replacement = "dojo." + depCall + "(" + depArgs + ")"; } } return replacement; }); } return modifiedContents; } i18nUtil.makeFlatBundleContents = function(prefix, prefixPath, srcFileName){ //summary: Given a nls file name, flatten the bundles from parent locales into the nls bundle. var bundleParts = i18nUtil.getBundlePartsFromFileName(prefix, prefixPath, srcFileName); if(!bundleParts){ return null; } var moduleName = bundleParts.moduleName; var bundleName = bundleParts.bundleName; var localeName = bundleParts.localeName; dojo.requireLocalization(moduleName, bundleName, localeName); //Get the generated, flattened bundle. var module = dojo.getObject(moduleName); var bundleLocale = localeName ? localeName.replace(/-/g, "_") : "ROOT"; var flattenedBundle = module.nls[bundleName][bundleLocale]; if(!flattenedBundle){ throw "Cannot create flattened bundle for src file: " + srcFileName; } return dojo.toJson(flattenedBundle); } //Given a module and bundle name, find all the supported locales. i18nUtil.getLocalesForBundle = function(moduleName, bundleName, prefixes){ //Build a path to the bundle directory and ask for all files that match //the bundle name. var filePath = buildUtil.mapResourceToPath(moduleName, prefixes); var bundleRegExp = new RegExp("nls[/]?([\\w\\-]*)/" + bundleName + ".js$"); var bundleFiles = fileUtil.getFilteredFileList(filePath + "nls/", bundleRegExp, true); //Find the list of locales supported by looking at the path names. var locales = []; for(var j = 0; j < bundleFiles.length; j++){ var bundleParts = bundleFiles[j].match(bundleRegExp); if(bundleParts && bundleParts[1]){ locales.push(bundleParts[1]); }else{ locales.push("ROOT"); } } return locales.sort(); } i18nUtil.getRequireLocalizationArgsFromString = function(argString){ //summary: Given a string of the arguments to a dojo.requireLocalization //call, separate the string into individual arguments. var argResult = { moduleName: null, bundleName: null, localeName: null }; var l10nMatches = argString.split(/\,\s*/); if(l10nMatches && l10nMatches.length > 1){ argResult.moduleName = l10nMatches[0] ? l10nMatches[0].replace(/\"/g, "") : null; argResult.bundleName = l10nMatches[1] ? l10nMatches[1].replace(/\"/g, "") : null; argResult.localeName = l10nMatches[2]; } return argResult; } i18nUtil.getBundlePartsFromFileName = function(prefix, prefixPath, srcFileName){ //Pull off any ../ values from prefix path to make matching easier. var prefixPath = prefixPath.replace(/\.\.\//g, ""); //Strip off the prefix path so we can find the real resource and bundle names. var prefixStartIndex = srcFileName.lastIndexOf(prefixPath); if(prefixStartIndex != -1){ var startIndex = prefixStartIndex + prefixPath.length; //Need to add one if the prefiPath does not include an ending /. Otherwise, //We'll get extra dots in our bundleName. if(prefixPath.charAt(prefixPath.length) != "/"){ startIndex += 1; } srcFileName = srcFileName.substring(startIndex, srcFileName.length); } //var srcIndex = srcFileName.indexOf("src/"); //srcFileName = srcFileName.substring(srcIndex + 4, srcFileName.length); var parts = srcFileName.split("/"); //Split up the srcFileName into arguments that can be used for dojo.requireLocalization() var moduleParts = [prefix]; for(var i = 0; parts[i] != "nls"; i++){ moduleParts.push(parts[i]); } var moduleName = moduleParts.join("."); if(parts[i+1].match(/\.js$/)){ var localeName = ""; var bundleName = parts[i+1]; }else{ var localeName = parts[i+1]; var bundleName = parts[i+2]; } if(!bundleName || bundleName.indexOf(".js") == -1){ //Not a valid bundle. Could be something like a README file. return null; }else{ bundleName = bundleName.replace(/\.js/, ""); } return {moduleName: moduleName, bundleName: bundleName, localeName: localeName}; }