Skip to content

p5.js Reference Translation Automation Task #863

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions .github/actions/translation-tracker/index.js
Original file line number Diff line number Diff line change
@@ -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();
}
26 changes: 26 additions & 0 deletions .github/actions/translation-tracker/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
20 changes: 20 additions & 0 deletions .github/actions/translation-tracker/test-local.js
Original file line number Diff line number Diff line change
@@ -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');
34 changes: 34 additions & 0 deletions .github/workflows/translation-sync.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ relatedReference:
---

This program demonstrates the use of the basic shape
primitive functions
primitive functions (sample change for testing)
<a href="https://p5js.org/reference/p5/square" target="_blank">square()</a>,
<a href="https://p5js.org/reference/p5/rect" target="_blank">rect()</a>,
<a href="https://p5js.org/reference/p5/ellipse" target="_blank">ellipse()</a>,
Expand Down