Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace duplicate string transifex #2559

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

CollinBeczak
Copy link
Collaborator

@CollinBeczak CollinBeczak commented Feb 15, 2025

Resolves: #2380

I ran this script, it finds duplicates in en-us (keeps the first id, removes the rest), then looks threw the Messages.js files and *.test.js files for the removed id's and replaces them.

import path from 'path';
import { fileURLToPath } from 'url';

// Get current directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

function findDuplicatesAndUpdate(enUsPath, srcDir) {
    console.log('Starting script...');
    console.log('Reading translations from:', enUsPath);
    
    // Read the en-US.json file
    const translations = JSON.parse(fs.readFileSync(enUsPath, 'utf8'));
    
    // Create a mapping of message values to their IDs
    const valueToIds = {};
    for (const [id, value] of Object.entries(translations)) {
        if (typeof value === 'string') {  // Only process string values
            if (!(value in valueToIds)) {
                valueToIds[value] = [];
            }
            valueToIds[value].push(id);
        }
    }
    
    // Find duplicates and determine which ID to keep
    const duplicates = Object.entries(valueToIds)
        .filter(([_, ids]) => ids.length > 1)
        .reduce((acc, [value, ids]) => ({ ...acc, [value]: ids }), {});
    
    console.log('\nFound duplicate messages:', Object.keys(duplicates).length);
    
    const idMapping = {};  // Maps duplicate IDs to the ID to keep
    
    // For each set of duplicates, keep the first ID and map others to it
    for (const [value, ids] of Object.entries(duplicates)) {
        const keepId = ids[0];
        for (const duplicateId of ids.slice(1)) {
            idMapping[duplicateId] = keepId;
            delete translations[duplicateId];
            console.log(`Mapping "${duplicateId}" to "${keepId}"`);
        }
    }
    
    // Write updated en-US.json
    fs.writeFileSync(enUsPath, JSON.stringify(translations, null, 2));
    console.log('\nUpdated en-US.json file');
    
    // Update files
    function walkDir(dir) {
        const files = fs.readdirSync(dir);
        files.forEach(file => {
            const filePath = path.join(dir, file);
            const stat = fs.statSync(filePath);
            if (stat.isDirectory()) {
                walkDir(filePath);
            } else if (file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.test.js')) {
                updateFile(filePath, idMapping);
            }
        });
    }
    
    console.log('\nSearching for files to update in:', srcDir);
    walkDir(srcDir);
}

function updateFile(filePath, idMapping) {
    let content = fs.readFileSync(filePath, 'utf8');
    let modified = false;
    
    for (const [oldId, newId] of Object.entries(idMapping)) {
        // Pattern for test files with message IDs in objects
        const testPattern = new RegExp(`(id:\\s*["'])(${escapeRegExp(oldId)})(["'])`, 'g');
        
        // Check if the file contains the old ID
        if (content.includes(oldId)) {
            const originalContent = content;
            content = content.replace(testPattern, `$1${newId}$3`);
            
            if (content !== originalContent) {
                modified = true;
                console.log(`Found and replaced "${oldId}" with "${newId}" in ${filePath}`);
            }
        }
    }
    
    if (modified) {
        fs.writeFileSync(filePath, content);
        console.log(`Updated ${filePath}`);
    }
}

// Helper function to escape special characters in string for RegExp
function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// Usage
const enUsPath = path.join(__dirname, 'lang/en-US.json');
const srcDir = path.join(__dirname, 'src');

console.log('Script configuration:');
console.log('en-US.json path:', enUsPath);
console.log('src directory:', srcDir);

findDuplicatesAndUpdate(enUsPath, srcDir);

@CollinBeczak CollinBeczak marked this pull request as ready for review February 17, 2025 16:03
Copy link
Contributor

@jake-low jake-low left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think this is a good idea and I'm okay with merging this initial implementation, which will avoid making translators translate the same string repeatedly.

But I think that we should follow this up with some refactoring, because it's now somewhat confusing that lots of different Message definitions in various Messages.js files intentionally have colliding IDs - it means that the defaultValue each one specifies doesn't matter (and changing that value in a single Message probably won't have the desired effect).

I think maybe what we should do at some point soon is to move all of these common strings to a single module, and refactor the code that uses the messages to import and use them from that module. This way it's more clear that the various use sites share a single definition of the translated value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use existing translated strings while possible
2 participants