ngx-translate | Cell 0 | | Search

This code analyzes translation keys to identify misplaced, unused, and missing keys, helping ensure consistency and completeness across different language versions of a project.

Run example

npm run import -- "Find unused/misplaced translation strings"

Find unused/misplaced translation strings

var path = require('path');
var fs = require('fs');

var cwd = '/Users/briancullinan/Documents/portal/src/';
//var cwd = 'C:\\Users\\brian.cullinan\\Documents\\portal\\src\\';

function findMisplaced(obj, parentKey) {
    for (var k in obj) {
        if (typeof obj[k] == 'object' && obj[k] !== null) {
            var newParent = typeof parentKey !== 'undefined' ? (parentKey + '.' + k) : k;
            findMisplaced(obj[k], newParent);
        } else if (typeof obj[k] == 'string') {
            if (obj[k].substr(0, parentKey.length) != parentKey) {
                console.log('Misplaced key: ' + obj[k] + ' in ' + parentKey);
            }
        }
    }
}

findMisplaced(translationKeys);

// get a long list of existing keys from en.js
var enJson = JSON.parse(fs.readFileSync(path.join(cwd, 'assets', 'i18n', 'en.json')).toString());


function flattenAllKeys(obj, parentKey) {
    var result = [];
    for (var k in obj) {
        if (typeof obj[k] == 'object' && obj[k] !== null) {
            var newParent = typeof parentKey !== 'undefined' ? (parentKey + '.' + k) : k;
            result = result.concat(flattenAllKeys(obj[k], newParent));
        } else if (typeof obj[k] == 'string'
            && parentKey.indexOf('TEMPDEMOPAGES') === -1) {
            result[result.length] = parentKey + '.' + k;
        }
    }
    return result;
}

var allENKeys = flattenAllKeys(enJson);

// find unused keys
function getUnused() {
    allENKeys.forEach(k => {
        var parentKey = k.split('.');
        parentKey.pop();
        parentKey = parentKey.join('.');
        if (typeof translationKeys[parentKey] === 'undefined' ||
            translationKeys[parentKey].indexOf(k) === -1) {
            console.log('Unused key: ' + k);
        }
    });
}

function getMissing() {
    for (var k in translationKeys) {
        if (typeof translationKeys[k] !== 'undefined') {
            translationKeys[k].forEach(i => {
                if (allENKeys.indexOf(i) === -1) {
                    console.log('Missing key: ' + i);
                }
            });
        }
    }
}

getMissing();
getUnused();


// find strings of text in html files
var htmlWordRegex = (/>[^><]*?(\b[^><]*\b)+[^><]*?</ig)
var attrWordRegex = (/(placeholder|title|alt)\s*=\s*["]([^"]*)["]/ig);
var needTranslations = files.map((f, i) => {
    var html = fs.readFileSync(path.join(cwd, f)).toString();
    let r;
    var needTranslations = [];
    while ((r = htmlWordRegex.exec(html)) !== null) {
        if (r[1].trim() !== '' && r[1].match(/\|\s*translate/ig) === null) {
            needTranslations[needTranslations.length] = r[1].trim();
        }
    }
    while ((r = attrWordRegex.exec(html)) !== null) {
        if (r[2].match(/\|\s*translate/ig) === null) {
            needTranslations[needTranslations.length] = r[2].trim();
        }
    }
    return {file: f, texts: needTranslations};
}).filter(t => t.texts.length > 0);

needTranslations.forEach(t => {
    console.log('Needs translating: ' + t.texts + ' in ' + t.file);
});


What the code could have been:

// Import required modules
const fs = require('fs');
const path = require('path');

// Define constants for the current working directory and file extensions
const cwd = '/Users/briancullinan/Documents/portal/src/';
const fileExtension = '.js';

// Define a function to find misplaced keys in objects
function findMisplaced(obj, parentKey) {
    /**
     * Recursively finds misplaced keys in objects.
     *
     * @param {object} obj - The object to search in.
     * @param {string} parentKey - The parent key to check against.
     */
    for (const key in obj) {
        if (typeof obj[key] === 'object' && obj[key]!== null) {
            const newParent = parentKey? `${parentKey}.${key}` : key;
            findMisplaced(obj[key], newParent);
        } else if (typeof obj[key] ==='string') {
            if (!obj[key].startsWith(parentKey)) {
                console.log(`Misplaced key: ${obj[key]} in ${parentKey}`);
            }
        }
    }
}

// Define a function to flatten all keys in objects
function flattenAllKeys(obj, parentKey = '') {
    /**
     * Recursively flattens all keys in objects.
     *
     * @param {object} obj - The object to search in.
     * @param {string} parentKey - The parent key to append to.
     * @returns {string[]} - An array of flattened keys.
     */
    const result = [];
    for (const key in obj) {
        if (typeof obj[key] === 'object' && obj[key]!== null) {
            const newParent = parentKey? `${parentKey}.${key}` : key;
            result.push(...flattenAllKeys(obj[key], newParent));
        } else if (typeof obj[key] ==='string' &&!parentKey.includes('TEMPDEMOPAGES')) {
            result.push(`${parentKey}.${key}`);
        }
    }
    return result;
}

// Load the translation keys and English JSON file
const translationKeys = require(path.join(cwd, 'translation.js'));
const enJson = JSON.parse(fs.readFileSync(path.join(cwd, 'assets', 'i18n', 'en.json')).toString());

// Get all English keys
const allENKeys = flattenAllKeys(enJson);

// Find unused keys
function getUnused() {
    allENKeys.forEach((key) => {
        const parentKey = key.split('.').slice(0, -1).join('.');
        if (!translationKeys[parentKey] ||!translationKeys[parentKey].includes(key)) {
            console.log(`Unused key: ${key}`);
        }
    });
}

// Find missing keys
function getMissing() {
    for (const key in translationKeys) {
        if (translationKeys[key]) {
            translationKeys[key].forEach((i) => {
                if (allENKeys.indexOf(i) === -1) {
                    console.log(`Missing key: ${i}`);
                }
            });
        }
    }
}

// Find strings of text in HTML files
const htmlWordRegex = />[^><]*?(\b[^><]*\b)+[^><]*?<\/ig;
const attrWordRegex = /(placeholder|title|alt)\s*=\s*["]([^"]*)["]/ig;
const files = ['file1.html', 'file2.html'];

const needTranslations = files.map((f, i) => {
    const html = fs.readFileSync(path.join(cwd, f)).toString();
    let r;
    const needTranslations = [];
    while ((r = htmlWordRegex.exec(html))!== null) {
        if (r[1].trim()!== '' &&!r[1].match(/\|\s*translate/ig)) {
            needTranslations.push(r[1].trim());
        }
    }
    while ((r = attrWordRegex.exec(html))!== null) {
        if (!r[2].match(/\|\s*translate/ig)) {
            needTranslations.push(r[2].trim());
        }
    }
    return { file: f, texts: needTranslations };
}).filter((t) => t.texts.length > 0);

needTranslations.forEach((t) => {
    console.log(`Needs translating: ${t.texts.join(', ')} in ${t.file}`);
});

// Call the functions
findMisplaced(translationKeys);
getMissing();
getUnused();

This code snippet analyzes translation keys in a JSON file to identify misplaced, unused, and missing keys.

Here's a breakdown:

1. Setup:

2. Initial Key Analysis:

3. Flattening Keys:

4. Key Comparison and Reporting:

In essence, this code helps identify potential issues with translation keys, ensuring consistency and completeness across different language versions.