<commit_before>module.exports = class WebpackI18nCoveragePlugin {
	constructor( grunt, { appDir, defaultLocale, localesDir, exclude = [] } = {} ) {
		this.grunt = grunt;
		this.appDir = appDir;
		this.defaultLocale = defaultLocale;
		this.localesDir = localesDir;
		this.exclude = exclude;

		this._BabelParser = require( "@babel/parser" );
		this._BabelTraverse = require( "@babel/traverse" ).default;
		this._Glimmer = require( "@glimmer/syntax" );

		this.localeData = new Map();
		this.translationKeys = new Set();
	}

	apply( compiler ) {
		compiler.hooks.compilation.tap( "i18n-coverage", compilation => {
			// don't emit any files
			compiler.hooks.shouldEmit.tap( "i18n-coverage", () => false );
			// find translation keys in JS and HBS files and get locale data from YML files
			compilation.hooks.succeedModule.tap( "i18n-coverage", this._onModule.bind( this ) );
		});

		compiler.hooks.done.tap( "i18n-coverage", this._onDone.bind( this ) );
	}

	_onDone() {
		const diff = require( "lodash/difference" );
		const diffWith = require( "lodash/differenceWith" );

		const { grunt, localeData, translationKeys, defaultLocale, exclude } = this;

		const output = ( header, callback ) => {
			grunt.log.writeln();
			grunt.log.ok( header );
			const list = callback();
			if ( !list.length ) {
				grunt.log.ok( "Success" );
			} else {
				list.forEach( item => grunt.log.error( item.toString() ) );
			}
		};

		// found translation keys (eg. "foo" or "foo.*.bar")
		const keys = Array.from( translationKeys.values() ).sort();
		// translation keys of default locale
		const main = localeData.get( defaultLocale ).sort();
		localeData.delete( defaultLocale );

		// list of translation keys to be ignored
		const reExclude = exclude.map( str => this._strToRegExp( str ) );
		const fnExclude = ( a, b ) => b.test( a );

		// remove ignored keys from both key lists
		const mainFiltered = diffWith( main, reExclude, fnExclude );
		const keysFiltered = diffWith( keys, reExclude, fnExclude )
			// and turn found keys list into {RegExp[]}
			.map( str => this._strToRegExp( str ) );

		grunt.log.writeln();
		output( "Checking for missing translation keys in locales", () => {
			const missing = [];
			for ( const [ locale, data ] of localeData ) {
				// remove ignored keys from current locale's data
				const dataFiltered = diffWith( data.sort(), reExclude, fnExclude );
				// compare with default locale and add diff to missing list
				missing.push(
					...diff( mainFiltered, dataFiltered )
						.map( item => `${locale}: ${item}` )<commit_msg>i18n-coverage: fix order of missing keys list<commit_after>module.exports = class WebpackI18nCoveragePlugin {
	constructor( grunt, { appDir, defaultLocale, localesDir, exclude = [] } = {} ) {
		this.grunt = grunt;
		this.appDir = appDir;
		this.defaultLocale = defaultLocale;
		this.localesDir = localesDir;
		this.exclude = exclude;

		this._BabelParser = require( "@babel/parser" );
		this._BabelTraverse = require( "@babel/traverse" ).default;
		this._Glimmer = require( "@glimmer/syntax" );

		this.localeData = new Map();
		this.translationKeys = new Set();
	}

	apply( compiler ) {
		compiler.hooks.compilation.tap( "i18n-coverage", compilation => {
			// don't emit any files
			compiler.hooks.shouldEmit.tap( "i18n-coverage", () => false );
			// find translation keys in JS and HBS files and get locale data from YML files
			compilation.hooks.succeedModule.tap( "i18n-coverage", this._onModule.bind( this ) );
		});

		compiler.hooks.done.tap( "i18n-coverage", this._onDone.bind( this ) );
	}

	_onDone() {
		const diff = require( "lodash/difference" );
		const diffWith = require( "lodash/differenceWith" );

		const { grunt, localeData, translationKeys, defaultLocale, exclude } = this;

		const output = ( header, callback ) => {
			grunt.log.writeln();
			grunt.log.ok( header );
			const list = callback();
			if ( !list.length ) {
				grunt.log.ok( "Success" );
			} else {
				list.forEach( item => grunt.log.error( item.toString() ) );
			}
		};

		// found translation keys (eg. "foo" or "foo.*.bar")
		const keys = Array.from( translationKeys.values() ).sort();
		// translation keys of default locale
		const main = localeData.get( defaultLocale ).sort();
		localeData.delete( defaultLocale );

		// list of translation keys to be ignored
		const reExclude = exclude.map( str => this._strToRegExp( str ) );
		const fnExclude = ( a, b ) => b.test( a );

		// remove ignored keys from both key lists
		const mainFiltered = diffWith( main, reExclude, fnExclude );
		const keysFiltered = diffWith( keys, reExclude, fnExclude )
			// and turn found keys list into {RegExp[]}
			.map( str => this._strToRegExp( str ) );

		grunt.log.writeln();
		output( "Checking for missing translation keys in locales", () => {
			const missing = [];
			for ( const locale of Array.from( localeData.keys() ).sort() ) {
				const data = localeData.get( locale );
				// remove ignored keys from current locale's data
				const dataFiltered = diffWith( data.sort(), reExclude, fnExclude );
				// compare with default locale and add diff to missing list
				missing.push(
					...diff( mainFiltered, dataFiltered )
						.map( item => `${locale}: ${item}` )
{'fraction_non_alphanumeric': 0.14159800728108832, 'fraction_numerical': 0.00421536692853037, 'mean_word_length': 3.473007712082262, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1074527', 'n_tokens_mistral': 1875, 'n_tokens_neox': 1650, 'n_words': 463}