diff --git a/.github/actions/translation-tracker/index.js b/.github/actions/translation-tracker/index.js new file mode 100644 index 0000000000..fe1ba04807 --- /dev/null +++ b/.github/actions/translation-tracker/index.js @@ -0,0 +1,239 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const SUPPORTED_LANGUAGES = ['es', 'hi', 'ko', 'zh-Hans']; + + +function getChangedFiles(testFiles = null) { + // Allow passing test files for local development + if (testFiles) { + console.log('๐Ÿงช Using provided test files for local testing'); + return testFiles.filter(file => + file.startsWith('src/content/examples/en') && file.endsWith('.mdx') + ); + } + + try { + const gitCommand = process.env.GITHUB_EVENT_NAME === 'pull_request' + ? 'git diff --name-only HEAD~1 HEAD' + : 'git diff --name-only HEAD~1 HEAD'; + + const changedFilesOutput = execSync(gitCommand, { encoding: 'utf8' }); + const allChangedFiles = changedFilesOutput.trim().split('\n').filter(file => file.length > 0); + + const changedExampleFiles = allChangedFiles.filter(file => + file.startsWith('src/content/examples/en') && file.endsWith('.mdx') + ); + + console.log(`๐Ÿ“Š Total changed files: ${allChangedFiles.length}`); + console.log(`๐Ÿ“– Changed English example files: ${changedExampleFiles.length}`); + + if (changedExampleFiles.length > 0) { + console.log('๐Ÿ“„ Changed English example files:'); + changedExampleFiles.forEach(file => console.log(` - ${file}`)); + } + + return changedExampleFiles; + } catch (error) { + console.error('โŒ Error getting changed files:', error.message); + return []; + } +} + +/** + * Check if a file exists + */ +function fileExists(filePath) { + try { + return fs.existsSync(filePath); + } catch (error) { + return false; + } +} + + +function getFileModTime(filePath) { + try { + return fs.statSync(filePath).mtime; + } catch (error) { + return null; + } +} + + +function checkTranslationStatus(changedExampleFiles) { + const translationStatus = { + needsUpdate: [], + missing: [], + upToDate: [] + }; + + changedExampleFiles.forEach(englishFile => { + console.log(`\n๐Ÿ“ Checking translations for: ${englishFile}`); + + const englishModTime = getFileModTime(englishFile); + if (!englishModTime) { + console.log(`โš ๏ธ Could not get modification time for English file`); + return; + } + + SUPPORTED_LANGUAGES.forEach(language => { + const translationPath = englishFile.replace('/en/', `/${language}/`); + const exists = fileExists(translationPath); + + if (!exists) { + console.log(` โŒ ${language}: Missing translation`); + translationStatus.missing.push({ + englishFile, + language, + translationPath, + status: 'missing' + }); + } else { + const translationModTime = getFileModTime(translationPath); + const isOutdated = translationModTime < englishModTime; + + if (isOutdated) { + console.log(` ๐Ÿ”„ ${language}: Needs update (English: ${englishModTime.toISOString()}, Translation: ${translationModTime.toISOString()})`); + translationStatus.needsUpdate.push({ + englishFile, + language, + translationPath, + status: 'outdated', + englishModTime, + translationModTime + }); + } else { + console.log(` โœ… ${language}: Up to date`); + translationStatus.upToDate.push({ + englishFile, + language, + translationPath, + status: 'up-to-date' + }); + } + } + }); + }); + + return translationStatus; +} + + +function displaySummary(translationStatus) { + console.log('\n๐Ÿ“Š TRANSLATION STATUS SUMMARY'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + + console.log(`๐Ÿ†• Missing translations: ${translationStatus.missing.length}`); + if (translationStatus.missing.length > 0) { + translationStatus.missing.forEach(item => { + console.log(` - ${item.language}: ${item.englishFile}`); + }); + } + + console.log(`๐Ÿ”„ Outdated translations: ${translationStatus.needsUpdate.length}`); + if (translationStatus.needsUpdate.length > 0) { + translationStatus.needsUpdate.forEach(item => { + console.log(` - ${item.language}: ${item.englishFile}`); + }); + } + + console.log(`โœ… Up-to-date translations: ${translationStatus.upToDate.length}`); + + +} + +/** + * Explore repository structure (focusing on examples as requested) + */ +function exploreRepoStructure() { + console.log('\n๐Ÿ” REPOSITORY STRUCTURE ANALYSIS'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + + try { + const examplesPath = 'src/content/examples'; + console.log(`๐Ÿ“ Examples path: ${examplesPath}`); + + if (fs.existsSync(examplesPath)) { + const languages = fs.readdirSync(examplesPath) + .filter(item => fs.statSync(path.join(examplesPath, item)).isDirectory()) + .filter(item => !item.startsWith('.') && item !== 'images'); + + console.log(`๐ŸŒ Available languages: ${languages.join(', ')}`); + + // Count example files in each language + languages.forEach(lang => { + const langPath = path.join(examplesPath, lang); + try { + let totalFiles = 0; + const categories = fs.readdirSync(langPath) + .filter(item => fs.statSync(path.join(langPath, item)).isDirectory()); + + categories.forEach(category => { + const categoryPath = path.join(langPath, category); + const countFilesRecursively = (dir) => { + const items = fs.readdirSync(dir); + let count = 0; + items.forEach(item => { + const itemPath = path.join(dir, item); + if (fs.statSync(itemPath).isDirectory()) { + count += countFilesRecursively(itemPath); + } else if (item.endsWith('.mdx')) { + count++; + } + }); + return count; + }; + totalFiles += countFilesRecursively(categoryPath); + }); + + console.log(` ${lang}: ${totalFiles} example files across ${categories.length} categories`); + } catch (error) { + console.log(` ${lang}: Error reading directory - ${error.message}`); + } + }); + } else { + console.log(`โŒ Examples path does not exist: ${examplesPath}`); + } + } catch (error) { + console.error('โŒ Error exploring repository structure:', error.message); + } +} + + +function main(testFiles = null) { + console.log('๐ŸŽฏ p5.js Example Translation Tracker - Week 1 Prototype'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log(`๐Ÿ“… Event: ${process.env.GITHUB_EVENT_NAME || 'local'}`); + console.log(`๐Ÿ  Working directory: ${process.cwd()}`); + console.log(`๐ŸŒ Tracking languages: ${SUPPORTED_LANGUAGES.join(', ')}`); + + exploreRepoStructure(); + + // Get changed files (supports both git and test files) + const changedExampleFiles = getChangedFiles(testFiles); + + if (changedExampleFiles.length === 0) { + console.log('\nโœจ No changes detected in English example files.'); + console.log('๐Ÿ“ Nothing to track for translations in this commit!'); + return; + } + + const translationStatus = checkTranslationStatus(changedExampleFiles); + + displaySummary(translationStatus); +} + +// Export for testing +module.exports = { + main, + getChangedFiles, + checkTranslationStatus, + exploreRepoStructure +}; + + +if (require.main === module) { + main(); +} diff --git a/.github/actions/translation-tracker/package.json b/.github/actions/translation-tracker/package.json new file mode 100644 index 0000000000..7284cba02a --- /dev/null +++ b/.github/actions/translation-tracker/package.json @@ -0,0 +1,26 @@ +{ + "name": "p5js-translation-tracker", + "version": "0.1.1", + "description": "GitHub Action to track translation status for p5.js examples and documentation", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "node index.js" + }, + "keywords": [ + "p5.js", + "translation", + "documentation", + "github-actions", + "automation" + ], + "author": "Divyansh Srivastava", + "license": "LGPL-2.1", + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/github": "^5.1.1" + } +} \ No newline at end of file diff --git a/.github/actions/translation-tracker/test-local.js b/.github/actions/translation-tracker/test-local.js new file mode 100755 index 0000000000..79cd71a5e0 --- /dev/null +++ b/.github/actions/translation-tracker/test-local.js @@ -0,0 +1,20 @@ + + +const { main } = require('./index.js'); + +// Test scenarios using actual example files that exist +const testFiles = [ + 'src/content/examples/en/01_Shapes_And_Color/00_Shape_Primitives/description.mdx', + 'src/content/examples/en/02_Animation_And_Variables/00_Drawing_Lines/description.mdx', + 'src/content/examples/en/03_Imported_Media/00_Words/description.mdx' +]; + +console.log('๐Ÿงช Testing Example Translation Tracker Locally'); +console.log('===============================================\n'); + +// Run the main function with test files +main(testFiles); + +console.log('\n๐Ÿ’ก This demonstrates local testing capability as requested by mentor'); +console.log('๐Ÿ”ง The git logic is now separated and modular for easier testing'); +console.log('๐Ÿ“– Now tracking examples instead of tutorials as requested'); \ No newline at end of file diff --git a/.github/workflows/translation-sync.yml b/.github/workflows/translation-sync.yml new file mode 100644 index 0000000000..b3bbe43440 --- /dev/null +++ b/.github/workflows/translation-sync.yml @@ -0,0 +1,34 @@ +name: Translation Sync Tracker + +on: + push: + branches: [main] + paths: + - 'src/content/reference/en/**' + pull_request: + branches: [main] + paths: + - 'src/content/reference/en/**' + +jobs: + track-translation-changes: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 2 # Fetch previous commit to compare changes + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Run translation tracker + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node .github/actions/translation-tracker/index.js \ No newline at end of file diff --git a/src/content/examples/en/01_Shapes_And_Color/00_Shape_Primitives/description.mdx b/src/content/examples/en/01_Shapes_And_Color/00_Shape_Primitives/description.mdx index 428f348709..6d2b99d0e7 100644 --- a/src/content/examples/en/01_Shapes_And_Color/00_Shape_Primitives/description.mdx +++ b/src/content/examples/en/01_Shapes_And_Color/00_Shape_Primitives/description.mdx @@ -9,7 +9,7 @@ relatedReference: --- This program demonstrates the use of the basic shape -primitive functions +primitive functions (sample change for testing) square(), rect(), ellipse(),