/*______________ | ______ | U I Z E J A V A S C R I P T F R A M E W O R K | / / | --------------------------------------------------- | / O / | MODULE : Uize.Services.LocAdapter Class | / / / | | / / / /| | ONLINE : http://uize.com | /____/ /__/_| | COPYRIGHT : (c)2013-2014 UIZE | /___ | LICENSE : Available under MIT License or GNU General Public License |_______________| http://uize.com/license.html */ /* Module Meta Data type: Class importance: 2 codeCompleteness: 80 docCompleteness: 2 */ /*? Introduction The =Uize.Services.LocAdapter= module defines a base class for adapters for the =Uize.Services.Loc= service. *DEVELOPERS:* `Chris van Rensburg` */ Uize.module ({ name:'Uize.Services.LocAdapter', required:[ 'Uize.Services.FileSystem', 'Uize.Json', 'Uize.Data.Flatten', 'Uize.Data.NameValueRecords', 'Uize.Data.Csv', 'Uize.Loc.Xliff', 'Uize.Data.Diff', 'Uize.Loc.Pseudo', 'Uize.Str.Split', 'Uize.Templates.Text.Tables.Breakdown', 'Uize.Templates.Text.Tables.YinYangBreakdown', 'Uize.Templates.Text.Tables.Histogram' ], superclass:'Uize.Service.Adapter', builder:function (_superclass) { 'use strict'; var /*** Variables for Scruncher Optimization ***/ _undefined, _split = Uize.Str.Split.split, _breakdownTable = Uize.Templates.Text.Tables.Breakdown.process, /*** General Variables ***/ _fileSystem = Uize.Services.FileSystem.singleton (), _sacredEmptyArray = [], _pathJsonSerializationOptions = { quoteChar:'"', indentChars:'', linebreakChars:'' } ; /*** Utility Functions ***/ function _twoGroupBreakdownTable (_title,_groupATitle,_groupACount,_groupBTitle,_groupBCount) { return _breakdownTable ({ title:_title, countByCategory:Uize.pairUp ( 'All',_groupACount + _groupBCount, _groupATitle,_groupACount, _groupBTitle,_groupBCount ) }); } function _serializeStringPath (_path) { return Uize.Json.to (_path,_pathJsonSerializationOptions); } /*** Private Instance Methods ***/ function _calculateStringsInfoForLanguage (m,_language,_languageResources,_subFolder) { var _project = m.project, _stringsInfo = [], _infoFilePath = m._workingFolderPath + _subFolder + 'strings-info/' + _language ; Uize.forEach ( _languageResources, function (_resourceFileStrings,_resourceFileSubPath) { var _resourceFileIsBrandSpecific = m.isBrandResourceFile (_resourceFileSubPath), _resourceFileBrand = _resourceFileIsBrandSpecific ? m.getResourceFileBrand (_resourceFileSubPath) : '' ; _processStrings ( _resourceFileStrings, function (_value,_path) { var _isTranslatable = m.isTranslatableString ({ key:_path [_path.length - 1], value:_value }), _stringMetrics = _getStringMetrics (m,_value), _isBrandSpecific = _resourceFileIsBrandSpecific || m.isBrandResourceString (_path,_value) ; /*** check for weak tokens ***/ for ( var _tokens = _stringMetrics.tokens, _tokenNo = _tokens.length, _hasWeakTokens = false ; !_hasWeakTokens && --_tokenNo >= 0; ) { if (m.isTokenWeak (_tokens [_tokenNo])) _hasWeakTokens = true ; } _stringsInfo.push ({ path:[_resourceFileSubPath].concat (_path), value:_value, metrics:_stringMetrics, isBrandSpecific:_isBrandSpecific, brand:_isBrandSpecific ? _resourceFileBrand || m.getStringBrand (_path,_value) : '', hasHtml:m.stringHasHtml (_path,_value), isLong:_isTranslatable && m.isStringLong (_stringMetrics), isKeyValid:m.isStringKeyValid (_path), hasWeakTokens:_hasWeakTokens, isTranslatable:_isTranslatable }); return _value; } ); } ); /*** write the JSON file ***/ _fileSystem.writeFile ({ path:_infoFilePath + '.json', contents:Uize.Json.to (_stringsInfo) }); /*** generate and write a flat CSV file version ***/ _fileSystem.writeFile ({ path:_infoFilePath + '.csv', contents:Uize.Data.Csv.to ( Uize.map ( _stringsInfo, function (_stringInfo) { var _path = _stringInfo.path, _stringMetrics = _stringInfo.metrics ; return [ _path [_path.length - 1], _stringInfo.value, _path [0], _serializeStringPath (_path), _stringInfo.isBrandSpecific, _stringInfo.brand, _stringInfo.hasHtml, _stringInfo.isLong, _stringInfo.isKeyValid, _stringInfo.hasWeakTokens, _stringInfo.isTranslatable, _stringMetrics.words, _stringMetrics.chars, _stringMetrics.tokens.join (',') ]; } ), { hasHeader:true, columns:[ 'Key', 'Value', 'File', 'Path', 'Brand-specific', 'Brand', 'HTML', 'Long', 'Valid Key', 'Waak Tokens', 'Translatable', 'Word Count', 'Char Count', 'Tokens' ] } ) }); return _stringsInfo; } function _calculateMetricsForLanguage (m,_language,_languageResources,_subFolder) { var _project = m.project, _totalResourceFiles = 0, _totalBrandSpecificResourceFiles = 0, _totalResourceFilesPerBrand = {}, _totalResourceStrings = 0, _totalBrandSpecificResourceStrings = 0, _totalResourceStringPerBrand = {}, _totalWordCount = 0, _totalBrandSpecificWordCount = 0, _totalWordCountPerBrand = {}, _totalCharCount = 0, _totalBrandSpecificCharCount = 0, _totalCharCountPerBrand = {}, _totalTokens = 0, _totalTokenizedResourceStrings = 0, _totalHtmlResourceStrings = 0, _totalLongResourceStrings = 0, _totalInvalidKeyResourceStrings = 0, _totalWeakTokenResourceStrings = 0, _totalNonTranslatableResourceStrings = 0, _totalDupedResourceStrings = 0, _valuesLookup = {}, _dupedResourceStringsDetails = {}, _tokenUsage = {}, _tokenHistogram = {}, _wordCountHistogram = {}, _charCountHistogram = {}, _stringsInfo = _calculateStringsInfoForLanguage (m,_language,_languageResources,_subFolder) ; Uize.forEach ( _languageResources, function (_resourceFileStrings,_resourceFileSubPath) { _totalResourceFiles++; if (m.isBrandResourceFile (_resourceFileSubPath)) { _totalBrandSpecificResourceFiles++; var _resourceFileBrand = m.getResourceFileBrand (_resourceFileSubPath); if (_resourceFileBrand) _totalResourceFilesPerBrand [_resourceFileBrand] = (_totalResourceFilesPerBrand [_resourceFileBrand] || 0) + 1 ; } } ); Uize.forEach ( _stringsInfo, function (_stringInfo) { var _path = _stringInfo.path, _value = _stringInfo.value, _stringFullPath = _serializeStringPath (_path) ; /*** update information on duplicates ***/ if (_valuesLookup [_value]) { _totalDupedResourceStrings++; ( _dupedResourceStringsDetails [_value] || (_dupedResourceStringsDetails [_value] = [_valuesLookup [_value]]) ).push (_stringFullPath); } else { _valuesLookup [_value] = _stringFullPath; } /*** get metrics for string ***/ var _stringMetrics = _stringInfo.metrics, _words = _stringMetrics.words, _chars = _stringMetrics.chars, _stringTokens = _stringMetrics.tokens, _stringTokensLength = _stringTokens.length ; _stringInfo.hasHtml && _totalHtmlResourceStrings++; _stringInfo.isLong && _totalLongResourceStrings++; _stringInfo.isKeyValid || _totalInvalidKeyResourceStrings++; _stringInfo.hasWeakTokens && _totalWeakTokenResourceStrings++; _stringInfo.isTranslatable || _totalNonTranslatableResourceStrings++; /*** update general metrics ***/ _totalResourceStrings++; _totalWordCount += _words; _totalCharCount += _chars; if (_stringInfo.isBrandSpecific) { _totalBrandSpecificResourceStrings++; _totalBrandSpecificWordCount += _words; _totalBrandSpecificCharCount += _chars; var _stringBrand = _stringInfo.brand; if (_stringBrand) { _totalResourceStringPerBrand [_stringBrand] = (_totalResourceStringPerBrand [_stringBrand] || 0) + 1 ; _totalWordCountPerBrand [_stringBrand] = (_totalWordCountPerBrand [_stringBrand] || 0) + _words ; _totalCharCountPerBrand [_stringBrand] = (_totalCharCountPerBrand [_stringBrand] || 0) + _chars ; } } _wordCountHistogram [_words] = (_wordCountHistogram [_words] || 0) + 1; _charCountHistogram [_chars] = (_charCountHistogram [_chars] || 0) + 1; /*** update metrics on tokenized strings and token usage ***/ _tokenHistogram [_stringTokensLength] = (_tokenHistogram [_stringTokensLength] || 0) + 1; if (_stringTokensLength) { Uize.forEach ( _stringTokens, function (_tokenName) { (_tokenUsage [_tokenName] || (_tokenUsage [_tokenName] = [])).push ( _stringFullPath ); } ); _totalTokens += _stringTokensLength; _totalTokenizedResourceStrings++; } } ); var _metrics = { resourceFiles:{ all:_totalResourceFiles, brandSpecific:_totalBrandSpecificResourceFiles, perBrand:_totalResourceFilesPerBrand }, resourceStrings:{ all:_totalResourceStrings, brandSpecific:_totalBrandSpecificResourceStrings, tokenized:_totalTokenizedResourceStrings, html:_totalHtmlResourceStrings, long:_totalLongResourceStrings, invalidKey:_totalInvalidKeyResourceStrings, weakTokens:_totalWeakTokenResourceStrings, nonTranslatable:_totalNonTranslatableResourceStrings, duped:_totalDupedResourceStrings, perBrand:_totalResourceStringPerBrand }, wordCount:{ all:_totalWordCount, brandSpecific:_totalBrandSpecificWordCount, perBrand:_totalWordCountPerBrand }, charCount:{ all:_totalCharCount, brandSpecific:_totalBrandSpecificCharCount, perBrand:_totalCharCountPerBrand }, tokens:_totalTokens, dupedResourceStringsDetails:_dupedResourceStringsDetails, tokenUsage:_tokenUsage, tokenHistogram:_tokenHistogram, wordCountHistogram:_wordCountHistogram, charCountHistogram:_charCountHistogram }; _fileSystem.writeFile ({ path:m._workingFolderPath + _subFolder + 'metrics/' + _language + '.json', contents:Uize.Json.to (_metrics) }); return _metrics; } function _pseudoLocalizeResources (m,_primaryLanguageResources) { var _pseudoLocalizedResources = {}, _pseudoLocalizeOptions = Uize.copy (m.project.pseudoLocalization,{wordSplitter:m.wordSplitter}) ; Uize.forEach ( _primaryLanguageResources, function (_resourceFileStrings,_resourceFileSubPath) { _pseudoLocalizedResources [_resourceFileSubPath] = Uize.Data.Diff.diff ( _primaryLanguageResources [_resourceFileSubPath], {}, function (_string) { if (m.isTranslatableString (_string)) _string.value = Uize.Loc.Pseudo.pseudoLocalize (_string.value,_pseudoLocalizeOptions) ; return _string; } ) ; } ); return _pseudoLocalizedResources; } function _languageResourcesFilePath (m,_language) { return m._workingFolderPath + _language + '.json'; } function _readLanguageResourcesFile (m,_language) { var _path = _languageResourcesFilePath (m,_language); return ( _fileSystem.fileExists ({path:_path}) ? Uize.Json.from (_fileSystem.readFile ({path:_path})) : _undefined ); } function _writeLanguageResourcesFile (m,_language,_languageResources) { _fileSystem.writeFile ({ path:_languageResourcesFilePath (m,_language), contents:Uize.Json.to (_languageResources) }); } function _forEachTranslatableLanguage (m,_iterationHandler) { var _project = m.project, _primaryLanguage = _project.primaryLanguage, _pseudoLocale = _project.pseudoLocale ; Uize.forEach ( _project.languages, function (_language) { _language != _primaryLanguage && _language != _pseudoLocale && _iterationHandler (_language); } ); } function _getStringMetrics (m,_sourceStr) { var _chars = 0, _tokens = [], _tokenRegExp = m.tokenRegExp ; if (_tokenRegExp) { var _match, _tokenName, _tokenAdded = {} ; _tokenRegExp.index = 0; while (_match = _tokenRegExp.exec (_sourceStr)) { if (!(_tokenName = _match [1])) { for (var _matchSegmentNo = _match.length; !_tokenName && --_matchSegmentNo >= 0;) _tokenName = _match [_matchSegmentNo] ; } if (!_tokenAdded [_tokenName]) { _tokens.push (_tokenName); _tokenAdded [_tokenName] = 1; } } } for ( var _stringSegmentNo = -2, _stringSegments = _split (_sourceStr,m.wordSplitter), _stringSegmentsLength = _stringSegments.length ; (_stringSegmentNo += 2) < _stringSegmentsLength; ) _chars += _stringSegments [_stringSegmentNo].length ; return { words:(_stringSegmentsLength + 1) / 2, chars:_chars, tokens:_tokens }; } function _processStrings (_strings,_stringProcessor) { function _processSection (_section,_path) { for (var _key in _section) { var _value = _section [_key]; if (Uize.isObject (_value)) { _processSection (_value,_path.concat (_key)); } else if (typeof _value == 'string') { _section [_key] = _stringProcessor (_section [_key],_path.concat (_key)); } } } _processSection (_strings,[]); } return _superclass.subclass ({ instanceMethods:{ distributeResources:function (_resources,_language) { // NOTE: this method can be useful for implementation of the extract method var m = this, _rootFolderPath = m.project.rootFolderPath ; Uize.forEach ( _resources, function (_resourceFileStrings,_resourceFileSubPath) { var _resourceFileFullPath = _rootFolderPath + '/' + m.getLanguageResourcePath (_resourceFileSubPath,_language) ; _fileSystem.writeFile ({ path:_resourceFileFullPath, contents:m.serializeResourceFile (_resourceFileStrings,_language) }); } ); }, prepareToExecuteMethod:function (_totalSteps) { this._methodTotalSteps = _totalSteps; this._methodCompletedSteps = 0; }, stepCompleted:function (_message) { this._log (_message,++this._methodCompletedSteps / this._methodTotalSteps); }, methodExecutionComplete:function (_summary) { this._log (_summary,'summary'); }, gatherResources:function () { var m = this, _resources = {}, _rootFolderPath = m.project.rootFolderPath, _resourceFiles = _fileSystem.getFiles ({ path:_rootFolderPath, pathMatcher:function (_filePath) {return m.isResourceFile (_filePath)}, recursive:true }) ; Uize.forEach ( _resourceFiles, function (_filePath) { try { _resources [_filePath] = m.parseResourceFile ( _fileSystem.readFile ({path:_rootFolderPath + '/' + _filePath}) ); } catch (_error) { console.log ( 'ERROR: problem parsing file ' + _filePath + '\n' + _error ); } } ); return _resources; }, getLanguageResourcePath:function (_enResourcePath,_language) { // this method should be implemented by subclasses }, isBrandResourceFile:function (_filePath) { // this method should be implemented by subclasses return false; }, isBrandResourceString:function (_resourceStringPath,_resourceStringText) { // this method should be implemented by subclasses return false; }, getResourceFileBrand:function (_filePath) { // this method should be implemented by subclasses return ''; }, getStringBrand:function (_resourceStringPath,_resourceStringText) { // this method should be implemented by subclasses return ''; }, stringHasHtml:function (_path,_value) { // this method can be overridden by subclasses return /<[^<]+>/.test (_value); // NOTE: this is not the most robust test, so probably RegExpComposition should be used }, isStringLong:function (_stringMetrics) { // this method can be overridden by subclasses return _stringMetrics.words > 50 || _stringMetrics.chars > 500; }, isStringKeyValid:function (_path) { // this method can be overridden by subclasses return true; }, isTokenWeak:function (_tokenName) { // this method can be overridden by subclasses return _tokenName.length < 3 || /^\d+$/.test (_tokenName); }, isTranslatableString:function (_stringInfo) { // this method should be implemented by subclasses /* NOTE: the _stringInfo argument is an object of the form... ................. { key:keySTR, value:valueSTR } ................. */ return true; }, isResourceFile:function (_filePath) { // this method should be implemented by subclasses }, parseResourceFile:function (_resourceFileText) { // this method should be implemented by subclasses }, serializeResourceFile:function (_strings) { // this method should be implemented by subclasses }, getReferencingCodeFiles:function () { return []; // this method should be implemented by subclasses }, getReferencesFromCodeFile:function (_filePath) { return {}; // this method should be implemented by subclasses }, 'import':function (_params,_callback) { var m = this, _project = m.project, _primaryLanguage = _project.primaryLanguage, _languages = _project.languages ; m.prepareToExecuteMethod ((_languages.length - !_project.importPrimary) * 2); Uize.forEach ( _languages, function (_language) { if (_language != _primaryLanguage || _project.importPrimary) { var _resources = _readLanguageResourcesFile (m,_language); m.stepCompleted (_language + ': read language resources file'); _resources && m.distributeResources (_resources,_language); m.stepCompleted (_language + ': distributed strings to individual resource files'); } } ); _callback (); }, 'export':function (_params,_callback) { var m = this, _project = m.project, _rootFolderPath = _project.rootFolderPath, _primaryLanguageResources = m.gatherResources (), _primaryLanguage = _project.primaryLanguage, _primaryLanguageResourcesLast = _readLanguageResourcesFile (m,_primaryLanguage) || {}, _primaryLanguageResourcesDiff = Uize.Data.Diff.diff ( _primaryLanguageResourcesLast, _primaryLanguageResources ), _resoucesByLanguage = Uize.pairUp (_primaryLanguage,_primaryLanguageResources), _totalLanguages = _project.languages.length, _totalTranslatableLanguages = _totalLanguages - 2 ; m.prepareToExecuteMethod ( _totalTranslatableLanguages * Uize.totalKeys (_primaryLanguageResources) + // total number of resource files to gather, across all translatable languages _totalLanguages // number of language resources files to write ); /*** gather resources for all translatable languages ***/ _forEachTranslatableLanguage ( m, function (_language) { var _languageResources = _resoucesByLanguage [_language] = {}; Uize.forEach ( _primaryLanguageResources, function (_resourceFileStrings,_resourceFileSubPath) { var _resourceFilePath = m.getLanguageResourcePath (_resourceFileSubPath,_language), _resourceFileFullPath = _rootFolderPath + '/' + _resourceFilePath ; _languageResources [_resourceFileSubPath] = Uize.Data.Diff.diff ( _fileSystem.fileExists ({path:_resourceFileFullPath}) ? m.parseResourceFile (_fileSystem.readFile ({path:_resourceFileFullPath})) : {} , _primaryLanguageResourcesDiff [_resourceFileSubPath], function (_gatheredProperty,_propertyDiff) { return ( !_propertyDiff || _propertyDiff.value == 'removed' ? _undefined : { value:_propertyDiff.value == 'modified' ? '' : _gatheredProperty ? _gatheredProperty.value : '' } ); } ); m.stepCompleted ('Gathered resources from file: ' + _resourceFilePath); } ); } ); /*** generate resources for pseudo-locale ***/ _resoucesByLanguage [_project.pseudoLocale] = _pseudoLocalizeResources (m,_primaryLanguageResources); Uize.forEach ( _resoucesByLanguage, function (_languageResources,_language) { _writeLanguageResourcesFile (m,_language,_languageResources); m.stepCompleted ('Created resources file for language: ' + _language); } ); _callback (); }, exportJobs:function (_params,_callback) { var m = this, _project = m.project, _primaryLanguage = _project.primaryLanguage, _primaryLanguageResources = _readLanguageResourcesFile (m,_primaryLanguage), _totalTranslatableLanguages = _project.languages.length - 2 ; m.prepareToExecuteMethod (_totalTranslatableLanguages * 3); _forEachTranslatableLanguage ( m, function (_language) { /*** determine strings that need translation ***/ var _translationJobStrings = Uize.Data.Diff.diff ( _readLanguageResourcesFile (m,_language) || {}, _primaryLanguageResources, function (_languageString,_primaryLanguageString) { return ( !_languageString.value && m.isTranslatableString (_primaryLanguageString) ? _primaryLanguageString : _undefined ); } ), _jobsPath = m._workingFolderPath + 'jobs/' ; m.stepCompleted (_language + ': determined strings that need translation'); /*** calculate metrics for translation job ***/ _calculateMetricsForLanguage (m,_language,_translationJobStrings,'jobs/'); m.stepCompleted (_language + ': calculated translation job metrics'); /*** write translation job file ***/ var _translationJobFileFormat = _project.translationJobFileFormat || 'csv', _translationJobFilePath = _jobsPath + _language + '.' + _translationJobFileFormat ; _fileSystem.writeFile ({ path:_translationJobFilePath, contents:_translationJobFileFormat == 'xliff' ? Uize.Loc.Xliff.to ({ sourceLanguage:_primaryLanguage, targetLanguage:_language, strings:_translationJobStrings }) : Uize.Data.Csv.to ( Uize.Data.NameValueRecords.fromHash ( Uize.Data.Flatten.flatten ( _translationJobStrings, function (_path) {return Uize.Json.to (_path,'mini')} ), 0, 1 ) ) }); m.stepCompleted (_language + ': created translation job file'); } ); _callback (); }, importJobs:function (_params,_callback) { var m = this, _project = m.project, _totalTranslatableLanguages = _project.languages.length - 2, _jobsPath = m._workingFolderPath + 'jobs/' ; m.prepareToExecuteMethod (_totalTranslatableLanguages * 2); _forEachTranslatableLanguage ( m, function (_language) { /*** determine strings that have been translated ***/ var _translationJobFileFormat = _project.translationJobFileFormat || 'csv', _translationJobFilePath = _jobsPath + _language + '.' + _translationJobFileFormat, _translationJobFile = _fileSystem.fileExists ({path:_translationJobFilePath}) ? _fileSystem.readFile ({path:_translationJobFilePath}) : '', _translatedStrings = _translationJobFile ? Uize.Data.Diff.diff ( _translationJobFileFormat == 'xliff' ? Uize.Loc.Xliff.from (_translationJobFile) : Uize.Data.Flatten.unflatten ( Uize.Data.NameValueRecords.toHash (Uize.Data.Csv.from (_translationJobFile),0,1), Uize.Json.from ) , {}, function (_string) {return _string.value ? _string : _undefined} ) : {} ; m.stepCompleted (_language + ': determined strings that have been translated'); /*** update language resources file ***/ if (!Uize.isEmpty (_translatedStrings)) _writeLanguageResourcesFile ( m, _language, Uize.mergeInto (_readLanguageResourcesFile (m,_language),_translatedStrings) ) ; m.stepCompleted (_language + ': updated language resources file'); } ); _callback (); }, extract:function (_params,_callback) { _callback (); }, metrics:function (_params,_callback) { var m = this, _primaryLanguage = m.project.primaryLanguage ; m.prepareToExecuteMethod (2); /*** gather resources for primary language ***/ var _primaryLanguageResources = m.gatherResources (); m.stepCompleted ('gathered resources for primary language'); /*** calculate metrics for primary language ***/ var _metrics = _calculateMetricsForLanguage (m,_primaryLanguage,_primaryLanguageResources,''); m.stepCompleted ('calculated metrics for primary language'); /*** produce summary ***/ /*** compile data for duplicates histogram ***/ var _dupesHistogram = {}; Uize.forEach ( _metrics.dupedResourceStringsDetails, function (_resourceStringDupes) { var _dupeCount = _resourceStringDupes.length - 1; _dupesHistogram [_dupeCount] = (_dupesHistogram [_dupeCount] || 0) + 1; } ); function _brandSpecificBreakdownTable (_title,_qualityMetrics) { var _countByCategory = Uize.pairUp ( 'All',_qualityMetrics.all, 'Non Brand-specific',_qualityMetrics.all - _qualityMetrics.brandSpecific, 'Brand-specific',_qualityMetrics.brandSpecific ); Uize.forEach ( _qualityMetrics.perBrand, function (_count,_brandId) { _countByCategory ['Brand: ' + _brandId] = _count; } ); return _breakdownTable ({ title:_title, countByCategory:_countByCategory }); } m.methodExecutionComplete ( _brandSpecificBreakdownTable ('Resource Files',_metrics.resourceFiles) + '\n' + _brandSpecificBreakdownTable ('Resource Strings',_metrics.resourceStrings) + '\n' + _brandSpecificBreakdownTable ('Word Count',_metrics.wordCount) + '\n' + _brandSpecificBreakdownTable ('Character Count',_metrics.charCount) + '\n' + Uize.Templates.Text.Tables.YinYangBreakdown.process ({ title:'Resource Strings', countByCategory:{ 'All,None':_metrics.resourceStrings.all, 'Brand-specific,Brand-neutral':_metrics.resourceStrings.brandSpecific, 'Tokenized,Non-tokenized':_metrics.resourceStrings.tokenized, 'HTML,Non-HTML':_metrics.resourceStrings.html, 'Long,Normal':_metrics.resourceStrings.long, 'Invalid Keys,Valid Keys':_metrics.resourceStrings.invalidKey, 'Some Weak Tokens,Only Strong Tokens':_metrics.resourceStrings.weakTokens, 'Non-translatable,Translatable':_metrics.resourceStrings.nonTranslatable } }) + '\n' + Uize.Templates.Text.Tables.Histogram.process ({ title:'Histogram of Resource String Duplicates', columnTitles:{ count:'Duplication Count', occurrences:'Occurrences', total:'Total Duplicates' }, occurrencesByValue:_dupesHistogram }) + '\n' + Uize.Templates.Text.Tables.Histogram.process ({ title:'Histogram of Resource String Tokenization', columnTitles:{ count:'Tokens in String', occurrences:'Strings', total:'Total Tokens' }, occurrencesByValue:_metrics.tokenHistogram }) ); _callback (); }, pseudoLocalize:function (_params,_callback) { var m = this; m.prepareToExecuteMethod (3); /*** gather resources for primary language ***/ var _primaryLanguageResources = m.gatherResources (); m.stepCompleted ('gathered resources for primary language'); /**( pseudo-localize resources for primary language ***/ var _pseudoLocalizedResources = _pseudoLocalizeResources (m,_primaryLanguageResources); m.stepCompleted ('pseudo-localized resources for primary language'); /*** distributed pseudo-localized resources to individual resource files ***/ m.distributeResources (_pseudoLocalizedResources,m.project.primaryLanguage); m.stepCompleted ('distributed pseudo-localized resources to individual resource files'); _callback (); }, usage:function (_params,_callback) { var m = this, _allReferencesLookup = {}, _referencingFiles = m.getReferencingCodeFiles () ; m.prepareToExecuteMethod (_referencingFiles.length + 4); /*** build lookup of string references ***/ Uize.forEach ( _referencingFiles, function (_filePath) { Uize.forEach ( m.getReferencesFromCodeFile (_filePath), function (_stringReferences,_stringId) { Uize.push ( _allReferencesLookup [_stringId] || (_allReferencesLookup [_stringId] = []), _stringReferences ); } ); m.stepCompleted ('scanned for resource string references in file: ' + _filePath); } ); /*** create index of resource string references by code file ***/ var _stringsReferencesByCodeFile = {}; Uize.forEach ( _allReferencesLookup, function (_stringReferences,_stringId) { Uize.forEach ( _stringReferences, function (_stringReference) { var _filePath = _stringReference.filePath; ( _stringsReferencesByCodeFile [_filePath] || (_stringsReferencesByCodeFile [_filePath] = []) ).push (_stringId); } ); } ); m.stepCompleted ('created index of resource string references by code file'); /*** gather resources for primary language ***/ var _primaryLanguageResources = m.gatherResources (); m.stepCompleted ('gathered resources for primary language'); /*** analyze resource string usage ***/ var _stringIdLookup = {}, _unreferenced = [], _references = {}, _multiReferenced = {}, _referencesHistogram = {}, _trueValue = {} ; Uize.Data.Flatten.flatten ( _primaryLanguageResources, function (_path) { var _stringId = _path.slice (1).join ('.'), _stringReferences = _allReferencesLookup [_stringId], _stringReferenceCount = _stringReferences ? _stringReferences.length : 0 ; _stringIdLookup [_stringId] = _trueValue; if (_stringReferenceCount) { _references [_stringId] = _stringReferences; if (_stringReferenceCount > 1) _multiReferenced [_stringId] = _stringReferenceCount ; } else { _unreferenced.push (_stringId); } _referencesHistogram [_stringReferenceCount] = (_referencesHistogram [_stringReferenceCount] || 0) + 1 ; } ); m.stepCompleted ('analyzed resource usage'); /*** references to missing resource strings ***/ var _missingStrings = {}; Uize.forEach ( _allReferencesLookup, function (_stringReferences,_stringId) { if (_stringIdLookup [_stringId] != _trueValue) _missingStrings [_stringId] = _stringReferences ; } ); /*** write report file ***/ var _usageReportFilePath = m.workingFolderPath + 'metrics/usage-report.json'; _fileSystem.writeFile ({ path:_usageReportFilePath, contents:Uize.Json.to ({ unreferenced:_unreferenced, multiReferenced:_multiReferenced, references:_references, referencesByCodeFile:_stringsReferencesByCodeFile, referencesHistogram:_referencesHistogram, missingStrings:_missingStrings }) }); m.stepCompleted ('created usage report file: ' + _usageReportFilePath); /*** produce summary ***/ var _referencesValues = Uize.values (_references), _referencesValuesLength = _referencesValues.length, _unreferencedLength = _unreferenced.length ; m.methodExecutionComplete ( _twoGroupBreakdownTable ( 'Resource Strings', 'Referenced',_referencesValuesLength, 'Unreferenced',_unreferencedLength ) + '\n' + Uize.Templates.Text.Tables.Histogram.process ({ title:'Histogram of String References', columnTitles:{ count:'References', occurrences:'Strings', total:'Total References' }, occurrencesByValue:_referencesHistogram }) ); _callback (); }, init:function (_params,_callback) { var m = this; m.project = _params.project; m._workingFolderPath = m.workingFolderPath = _params.workingFolder + '/' + m.project.name + '/'; m._log = _params.log || Uize.nop; _callback (); } }, instanceProperties:{ wordSplitter:null, tokenRegExp:null } }); } });