Skip to content

Commit

Permalink
simplify script to only generate index.ts and translation.ts files
Browse files Browse the repository at this point in the history
  • Loading branch information
Platonn committed Dec 20, 2024
1 parent 354ae1d commit 7ec287c
Showing 1 changed file with 26 additions and 261 deletions.
287 changes: 26 additions & 261 deletions scripts/update-translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ interface TranslationFile {
importPath: string;
}

interface TranslationChunksConfig {
[chunkName: string]: string[];
}

interface ValidationError {
file: string;
chunkName: string;
suggestedName: string;
fullPath: string;
}

interface ModifiedFiles {
indexFiles: Set<string>;
translationFiles: Set<string>;
Expand Down Expand Up @@ -45,71 +34,13 @@ function getJsonFiles(languageDir: string): TranslationFile[] {
}));
}

function extractPrimaryKeys(filePath: string): string[] {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const json = JSON.parse(content);
return Object.keys(json);
} catch (error) {
console.error(`Error reading/parsing JSON file ${filePath}:`, error);
return [];
}
}

function generateChunksConfig(translationDir: string): TranslationChunksConfig {
const enDir = path.join(translationDir, 'en');
if (!fs.existsSync(enDir)) {
console.warn(`Warning: 'en' directory not found in ${translationDir}`);
return {};
}

const config: TranslationChunksConfig = {};
const jsonFiles = glob.sync('*.json', { cwd: enDir });

for (const file of jsonFiles) {
const chunkName = path.basename(file, '.json');
const primaryKeys = extractPrimaryKeys(path.join(enDir, file));
if (primaryKeys.length > 0) {
if (!isCamelOrPascalCase(chunkName)) {
const camelCaseName = chunkName
.split(/[-_\s]+/)
.map((word, index) =>
index === 0
? word.toLowerCase()
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join('');

const currentFile = path.relative(
process.cwd(),
path.join(enDir, file)
);
const targetFile = path.relative(
process.cwd(),
path.join(enDir, `${camelCaseName}.json`)
);

throw new Error(
`Invalid chunk name '${chunkName}' found in file '${currentFile}'\n` +
'Error: Chunk names should be in camelCase\n' +
'To fix this:\n' +
`1. Rename the file using this command:\n` +
` mv "${currentFile}" "${targetFile}"\n` +
`2. Update all imports and references to use '${camelCaseName}'\n` +
'Rules for valid names:\n' +
'- Must start with a letter (a-z, A-Z)\n' +
'- Can contain only letters and numbers\n' +
'- Cannot be a JavaScript reserved word\n' +
'- Cannot contain hyphens, underscores or special characters\n' +
'Examples:\n' +
'- camelCase: myAccount, userProfile, commonElements'
);
}
config[chunkName] = primaryKeys;
}
}

return config;
function getLicenseHeader(): string {
const currentYear = new Date().getFullYear();
return `/*
* SPDX-FileCopyrightText: ${currentYear} SAP Spartacus team <spartacus-team@sap.com>
*
* SPDX-License-Identifier: Apache-2.0
*/\n\n`;
}

function generateLanguageIndex(
Expand All @@ -125,81 +56,15 @@ function generateLanguageIndex(
languageDir
)} = {\n${translationFiles.map((file) => ` ${file.name},`).join('\n')}\n};\n`;

const indexContent = imports + exportObject;
const indexContent = getLicenseHeader() + imports + exportObject;
const indexPath = path.join(languageDir, 'index.ts');
fs.writeFileSync(indexPath, indexContent);
modifiedFiles.indexFiles.add(indexPath);
}

const RESERVED_WORDS = new Set([
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'null',
'package',
'private',
'protected',
'public',
'return',
'super',
'switch',
'static',
'this',
'throw',
'try',
'true',
'typeof',
'var',
'void',
'while',
'with',
'yield',
]);

function isCamelOrPascalCase(str: string): boolean {
return /^[a-zA-Z][a-zA-Z0-9]*$/.test(str) && !RESERVED_WORDS.has(str);
}

function formatChunksConfig(config: TranslationChunksConfig): string {
const lines = [
'export const translationChunksConfig: TranslationChunksConfig = {',
];

for (const [chunk, keys] of Object.entries(config)) {
lines.push(` ${chunk}: [${keys.map((key) => `'${key}'`).join(', ')}],`);
}

lines.push('};');
return lines.join('\n');
}

function generateMainTranslations(
translationDir: string,
languages: string[],
chunksConfig: TranslationChunksConfig,
modifiedFiles: ModifiedFiles
): void {
const languageExports = languages
Expand All @@ -209,14 +74,11 @@ function generateMainTranslations(
})
.join('\n');

const configContent =
`import { TranslationChunksConfig } from '@spartacus/core';\n\n` +
`${formatChunksConfig(chunksConfig)}\n\n` +
languageExports +
'\n';

const translationsPath = path.join(translationDir, 'translations.ts');
fs.writeFileSync(translationsPath, configContent);
fs.writeFileSync(
translationsPath,
getLicenseHeader() + languageExports + '\n'
);
modifiedFiles.translationFiles.add(translationsPath);
}

Expand All @@ -227,104 +89,14 @@ function processTranslationDir(
console.log(`Processing translation directory: ${translationDir}`);

const languages = getLanguageDirs(translationDir);
const chunksConfig = generateChunksConfig(translationDir);

for (const lang of languages) {
const langDir = path.join(translationDir, lang);
const translationFiles = getJsonFiles(langDir);
generateLanguageIndex(langDir, translationFiles, modifiedFiles);
}

generateMainTranslations(
translationDir,
languages,
chunksConfig,
modifiedFiles
);
}

function validateChunkNames(translationDir: string): ValidationError[] {
const errors: ValidationError[] = [];
const languages = getLanguageDirs(translationDir);

for (const lang of languages) {
const langDir = path.join(translationDir, lang);
const jsonFiles = glob.sync('*.json', { cwd: langDir });

for (const file of jsonFiles) {
const chunkName = path.basename(file, '.json');
if (!isCamelOrPascalCase(chunkName)) {
const camelCaseName = chunkName
.split(/[-_\s]+/)
.map((word, index) =>
index === 0
? word.toLowerCase()
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join('');

errors.push({
file,
chunkName,
suggestedName: camelCaseName,
fullPath: path.relative(process.cwd(), path.join(langDir, file)),
});
}
}
}

return errors;
}

function formatValidationErrors(errors: ValidationError[]): string {
// Group errors by chunk name to avoid duplicate suggestions
const errorsByChunk = new Map<string, ValidationError[]>();

errors.forEach((error) => {
const existing = errorsByChunk.get(error.chunkName) || [];
existing.push(error);
errorsByChunk.set(error.chunkName, existing);
});

const messages = ['Found invalid chunk names that need to be fixed:'];

// First list all the issues, grouped by chunk name
for (const [chunkName, chunkErrors] of errorsByChunk) {
messages.push(
`\nChunk name '${chunkName}' is invalid and appears in these filenames:`
);

chunkErrors.forEach(({ fullPath }) => {
messages.push(`- ${fullPath}`);
});

messages.push(`Suggested name: ${chunkErrors[0].suggestedName}`);
}

// Then list all commands in a batch
messages.push(
'\nCommands to fix all files (copy-paste the whole block):',
'```bash'
);

// Use Set to deduplicate commands by target path
const commands = new Set<string>();
errors.forEach(({ fullPath, suggestedName }) => {
commands.add(
`mv "${fullPath}" "${path.join(path.dirname(fullPath), suggestedName + '.json')}"`
);
});

messages.push(...Array.from(commands));

messages.push(
'```',
'\nAfter fixing:',
'1. Update any imports or references in your code',
'2. Run this script again'
);

return messages.join('\n');
generateMainTranslations(translationDir, languages, modifiedFiles);
}

async function main(): Promise<void> {
Expand All @@ -335,19 +107,7 @@ async function main(): Promise<void> {
return;
}

// Validation phase
const allErrors: ValidationError[] = [];
for (const dir of translationDirs) {
const errors = validateChunkNames(dir);
allErrors.push(...errors);
}

if (allErrors.length > 0) {
throw new Error(formatValidationErrors(allErrors));
}

// If validation passes, proceed with updates
console.log('All chunk names are valid, proceeding with updates...\n');
console.log('Processing translation directories...\n');

const modifiedFiles: ModifiedFiles = {
indexFiles: new Set<string>(),
Expand All @@ -366,17 +126,22 @@ async function main(): Promise<void> {
if (allModifiedFiles.length > 0) {
console.log('\nRunning prettier on modified files...');
const { exec } = require('child_process');
const filePaths = allModifiedFiles.join(' ');
// const filePaths = allModifiedFiles.join(',');

try {
await new Promise((resolve, reject) => {
exec(`npm run prettier:fix -- ${filePaths}`, (error: Error | null) => {
if (error) {
reject(error);
} else {
resolve(undefined);
exec(
// `prettier --config ./.prettierrc --write --list-different "${filePaths}"`
// `npm run prettier:fix`,
'echo "hello"',
(error: Error | null) => {
if (error) {
reject(error);
} else {
resolve(undefined);
}
}
});
);
});
console.log('Prettier formatting complete.');
} catch (error) {
Expand Down

0 comments on commit 7ec287c

Please sign in to comment.