From 6b3d996e19e2965dacbe353f26e925d2070bee8d Mon Sep 17 00:00:00 2001 From: vivian Date: Tue, 6 May 2025 23:32:30 +0800 Subject: [PATCH 1/3] update github actions --- .github/scripts/translate.js | 111 ++++------ .github/workflows/frenglish-translations.yaml | 198 +++--------------- 2 files changed, 75 insertions(+), 234 deletions(-) diff --git a/.github/scripts/translate.js b/.github/scripts/translate.js index 1a8bd4ca..08e38cb9 100644 --- a/.github/scripts/translate.js +++ b/.github/scripts/translate.js @@ -7,7 +7,7 @@ const path = require('path'); // ================================================================================================== // Path to your original language files (e.g., English source content) -const ORIGIN_LANGUAGE_DIR = path.resolve('.'); +const ORIGIN_LANGUAGE_DIR = path.resolve('.'); // Path where translated files will be saved (Base directory) const TRANSLATION_OUTPUT_DIR = path.resolve('.'); @@ -20,15 +20,13 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do // ============================================================ (async () => { - // Dynamically import the ESM-only module const sdkModule = await import('@frenglish/sdk'); const FrenglishSDK = sdkModule.FrenglishSDK; if (!FrenglishSDK) throw new Error('FrenglishSDK not found in module exports.'); - // Retrieve the API key from environment variable const FRENGLISH_API_KEY = process.env.FRENGLISH_API_KEY; if (!FRENGLISH_API_KEY) { - console.error('Error: FRENGLISH_API_KEY environment variable not set.'); + console.error('❌ FRENGLISH_API_KEY environment variable not set. Aborting action.'); process.exit(1); } const frenglish = FrenglishSDK(FRENGLISH_API_KEY); @@ -44,102 +42,86 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do const data = await response.json(); return data.default_branch; } catch (error) { - console.log('Error getting default branch:', error.message); + console.error(`❌ Failed to retrieve default branch: ${error.message}`); return 'main'; } } async function isSupportedFile(filePath) { try { - // Ensure the file is within the intended source directory structure for relative path logic const relativeToOrigin = path.relative(ORIGIN_LANGUAGE_DIR, path.resolve(filePath)); if (relativeToOrigin.startsWith('..') || relativeToOrigin === '') { - return false; + return false; } - // Check against general excluded files/paths if (EXCLUDED_FILES.some(excluded => filePath.includes(excluded))) { - console.log(`File ${filePath} is explicitly excluded.`); + console.log(`⏭️ Skipping (excluded): ${filePath}`); return false; } - // Get the configuration to check origin language const config = await frenglish.getDefaultConfiguration(); const languageCodes = await frenglish.getSupportedLanguages(); const originLanguage = config.originLanguage.toLowerCase(); - // Check if the file is in a translated language directory const pathParts = filePath.split(path.sep); - const languageDirIndex = pathParts.findIndex(part => - part.toLowerCase() === originLanguage || + const languageDirIndex = pathParts.findIndex(part => + part.toLowerCase() === originLanguage || languageCodes.some(lang => lang.toLowerCase() === part.toLowerCase()) ); - // If the file is in a language directory and it's not the origin language, exclude it if (languageDirIndex !== -1 && pathParts[languageDirIndex].toLowerCase() !== originLanguage) { - console.log(`File ${filePath} is in a translated language directory. Excluding.`); + console.log(`⏭️ Skipping (translated dir): ${filePath}`); return false; } - // Check extension against Frenglish supported types const supportedFileTypes = await frenglish.getSupportedFileTypes(); const validFileTypes = supportedFileTypes.filter(type => type && type.length > 0); const ext = path.extname(filePath).toLowerCase().replace('.', ''); const isSupported = ext && validFileTypes.includes(ext); return isSupported; - } catch (error) { - console.error(`Error checking file support for ${filePath}:`, error.message); + console.error(`❌ Error checking file support for ${filePath}: ${error.message}`); return false; } } - // Compares files changed in a PR (or files changed with a commit directly to default branch) async function getChangedFiles() { try { const isPR = !!process.env.GITHUB_BASE_REF; - const currentBranch = process.env.GITHUB_HEAD_REF // PR - || process.env.GITHUB_REF.replace('refs/heads/', ''); // Push to default branch + const currentBranch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF.replace('refs/heads/', ''); const defaultBranch = await getDefaultBranch(); - - // Skip non‑PR pushes that are NOT on default branch + if (!isPR && currentBranch !== defaultBranch) { - return []; + return []; } - + // Figure out what we’re diffing against const baseBranch = isPR ? process.env.GITHUB_BASE_REF : defaultBranch; let baseSha; if (isPR) { - // bring in the base branch locally execSync(`git fetch --depth=1 origin ${baseBranch}:${baseBranch}`); - - // HEAD is already at the PR tip baseSha = execSync(`git merge-base ${baseBranch} HEAD`).toString().trim(); } else { - // push on default branch: compare to the previous commit on that branch baseSha = process.env.GITHUB_EVENT_BEFORE || execSync('git rev-parse HEAD^').toString().trim(); } - - console.log(`Diff base: ${baseBranch} @ ${baseSha}`); - console.log(`Head : ${currentBranch} @ HEAD`); - - const output = execSync( - `git diff --diff-filter=ACM --name-only ${baseSha} HEAD` - ).toString().trim(); - - const changedFiles = output ? output.split('\n') : []; + + console.log(`🔀 Diff base: ${baseBranch} @ ${baseSha}`); + console.log(`🔝 Head : ${currentBranch} @ HEAD`); + + const output = execSync(`git diff --diff-filter=ACM --name-only ${baseSha} HEAD`).toString().trim(); + const changedFiles = output ? output.split('\n') : []; const supportedFiles = []; - + for (const file of changedFiles) { if (await isSupportedFile(file)) supportedFiles.push(file); } - console.log('Files sent for translation:', supportedFiles); + + console.log(`📦 Files queued for translation (${supportedFiles.length}): ${supportedFiles.join(', ') || 'None'}`); return supportedFiles; } catch (error) { - console.error('Error getting changed files:', error.message); + console.error(`❌ Error getting changed files: ${error.message}`); return []; } } @@ -150,12 +132,11 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do const originLanguage = config.originLanguage.toLowerCase(); const filesToTranslate = await getChangedFiles(); - if (!filesToTranslate || filesToTranslate.length === 0) { - console.log('No supported files found in the diff within the origin language directory to translate.'); - return; // Exit gracefully if no files need translation + if (!filesToTranslate.length) { + console.log('ℹ️ No eligible files found for translation. Exiting.'); + return; } - // Prepare content for translation const fileContents = await Promise.all(filesToTranslate.map(async (file) => { try { const content = await fs.readFile(file, 'utf-8'); @@ -163,31 +144,29 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do const fileId = path.relative(ORIGIN_LANGUAGE_DIR, file); return { fileId: fileId, content: content }; } catch (readError) { - console.error(`Error reading file ${file}:`, readError.message); + console.error(`❌ Error reading file ${file}:`, readError.message); return null; } })); - // Filter out any nulls from read errors const validFileContents = fileContents.filter(fc => fc !== null); if (validFileContents.length === 0) { - console.log('No valid file contents could be read for translation.'); + console.log('⚠️ No readable file contents detected. Exiting.'); return; } const filenames = validFileContents.map(file => file.fileId); const contents = validFileContents.map(file => file.content); - console.log(`Initiating translation for ${filenames.length} files:`, filenames); + console.log(`🚀 Initiating translation for ${filenames.length} file(s).`); const translation = await frenglish.translate(contents, false, filenames); - console.log(`Translation requested with ID: ${translation.translationId}`); + console.log(`📤 Translation request submitted. ID: ${translation.translationId}`); - // Process translated content for (const languageData of translation.content) { const language = languageData.language; // Skip writing files for the origin language if they are returned if (language === originLanguage) { - console.log(`Skipping write for origin language: ${language}`); + console.log(`⏩ Skipping origin language (${language}).`); continue; } @@ -195,47 +174,43 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do try { await fs.mkdir(languageOutputDir, { recursive: true }); } catch (mkdirError) { - console.error(`Error creating directory ${languageOutputDir}:`, mkdirError.message); - continue; // Skip this language if directory creation fails + console.error(`❌ Unable to create directory ${languageOutputDir}: ${mkdirError.message}`); + continue; } for (const translatedFile of languageData.files) { - // Construct the full path for the translated file const translatedFilePath = path.join(languageOutputDir, translatedFile.fileId); - // Ensure the subdirectory for the file exists (e.g., for nested structures) try { await fs.mkdir(path.dirname(translatedFilePath), { recursive: true }); } catch (mkdirError) { - console.error(`Error creating subdirectory ${path.dirname(translatedFilePath)}:`, mkdirError.message); - continue; // Skip this file if subdirectory creation fails + console.error(`❌ Unable to create subdirectory ${path.dirname(translatedFilePath)}: ${mkdirError.message}`); + continue; } - + // Write the file content if not empty if (translatedFile.content && translatedFile.content.length > 0) { try { await fs.writeFile(translatedFilePath, translatedFile.content, 'utf8'); - console.log(`Translated file written: ${translatedFilePath}`); + console.log(`✅ Written: ${translatedFilePath}`); } catch (writeError) { - console.error(`Error writing translated file ${translatedFilePath}:`, writeError.message); + console.error(`❌ Error writing ${translatedFilePath}: ${writeError.message}`); } } else { - console.warn(`Empty content for translated file: ${translatedFile.fileId} in language ${language}. Skipping write.`); + console.warn(`⚠️ Empty content for ${translatedFile.fileId} (${language}). Skipping.`); } } } - console.log("Translation file writing complete. Git operations will be handled by the workflow."); - + console.log('🏁 Translation workflow complete. Git operations will be handled by the Action.'); } catch (error) { - console.error('Error during translation and file writing process:', error); - if (error.response && error.response.data) { - console.error('Frenglish API Error Details:', error.response.data); + console.error('❌ Translation process failed:', error); + if (error.response?.data) { + console.error('🔍 Frenglish API details:', error.response.data); } process.exit(1); } } - // Run the modified translation process translateAndWriteFiles(); })(); \ No newline at end of file diff --git a/.github/workflows/frenglish-translations.yaml b/.github/workflows/frenglish-translations.yaml index 532bcdbf..fdcf5c20 100644 --- a/.github/workflows/frenglish-translations.yaml +++ b/.github/workflows/frenglish-translations.yaml @@ -20,24 +20,23 @@ # ------------------------------------------------------------------------------ name: Frenglish Translation + on: - # Run once per pull‑request (feature → any target) pull_request: types: [opened, synchronize, reopened] - - # Run again only when commits land on the default branch (e.g. master/main) push: - branches: - - '**' # We filter below + branches: ['**'] permissions: contents: read jobs: translate_and_format: - # Run if (a) it’s a PR OR (b) it’s a push *and* the ref equals the repo’s default branch if: >- - github.event_name == 'pull_request' || + ( + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository + ) || ( github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && @@ -49,16 +48,20 @@ jobs: contents: write pull-requests: write steps: - # We check if commit message include `chore(i18n): update translations` and assume it's been fully translated if so - - name: Detect translation commit in push range + - name: Detect prior translation commit id: detect - if: github.event_name == 'push' # PRs always run - run: | - echo "Looking for 'chore(i18n): update translations' between ${{ github.event.before }}..${{ github.sha }}" - if git log --format=%B ${{ github.event.before }}..${{ github.sha }} | grep -qF 'chore(i18n): update translations'; then - echo "skip=true" >> "$GITHUB_OUTPUT" + run: | + if [ "${{ github.event_name }}" = "push" ]; then + RANGE="${{ github.event.before }}..${{ github.sha }}" else - echo "skip=false" >> "$GITHUB_OUTPUT" + BASE="${{ github.event.pull_request.base.ref }}" + git fetch --depth=1 origin "$BASE":"$BASE" + RANGE="$(git merge-base $BASE HEAD)..HEAD" + fi + if git log --format=%B $RANGE | grep -qF 'chore(i18n): update translations'; then + echo "skip=true" >>"$GITHUB_OUTPUT" + else + echo "skip=false" >>"$GITHUB_OUTPUT" fi - name: Checkout code @@ -72,20 +75,17 @@ jobs: if: steps.detect.outputs.skip != 'true' uses: actions/setup-node@v3 with: - node-version: '18' # Or your preferred Node.js version >= 16 + node-version: '18' - name: Install dependencies if: steps.detect.outputs.skip != 'true' - run: | - # Ensure you have a package.json and package-lock.json - # Add @frenglish/sdk to your package.json: npm install @frenglish/sdk --save - npm install + run: npm install - name: Setup Git User if: steps.detect.outputs.skip != 'true' run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" + git config --global user.name "github-actions[bot]" - name: Get Language Configuration if: steps.detect.outputs.skip != 'true' @@ -93,174 +93,40 @@ jobs: run: node .github/scripts/fetch-frenglish-configuration.js env: FRENGLISH_API_KEY: ${{ secrets.FRENGLISH_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # --- Step to Handle Renamed/Deleted Files --- - name: Handle Renamed and Deleted Source Files if: steps.detect.outputs.skip != 'true' id: handle_changes - run: | - set -e # Exit immediately if a command exits with a non-zero status. - - # Get target languages and commit SHAs - # SOURCE_DIR_RAW from get_lang_config is now IGNORED for path determination - TARGET_DIRS_STRING="${{ steps.get_lang_config.outputs.target_langs }}" - BEFORE_SHA="${{ github.event.before }}" - CURRENT_SHA="${{ github.sha }}" - - # --- Define the source path CONSISTENTLY with translate.js --- - # ORIGIN_LANGUAGE_DIR in translate.js is path.resolve('.'), so we use '.' here. - EFFECTIVE_SOURCE_PATH="." - echo "Source file location for rename/delete check: Root Directory (.)" - - # --- Validate Target Languages --- - if [ -z "$TARGET_DIRS_STRING" ]; then - echo "::warning::No target languages determined. Rename/delete actions for target directories will be skipped." - echo "processed_changes=false" >> $GITHUB_OUTPUT - exit 0 - fi - read -r -a TARGET_DIRS <<< "$TARGET_DIRS_STRING" - if [ ${#TARGET_DIRS[@]} -eq 0 ]; then - echo "::warning::No target languages parsed. Rename/delete actions for target directories will be skipped." - echo "processed_changes=false" >> $GITHUB_OUTPUT - exit 0 - fi - - # --- List of top-level files/dirs to EXCLUDE from rename/delete handling --- - # Add any other known non-locale files/folders residing in your root directory - # Use trailing slash for directories to avoid matching files starting with the same name - EXCLUDED_PATTERNS=( - 'package.json' - 'package-lock.json' - 'node_modules/' - 'frenglish.config.json' - '.github/' - '.git/' - '.gitignore' - 'README.md' - # Add other files/dirs like 'vite.config.js', 'tsconfig.json', etc. if they exist in root - ) - echo "Excluding patterns: ${EXCLUDED_PATTERNS[*]}" - - - # --- Check for Renamed/Deleted Files in the Root Directory --- - echo "Checking for renamed/deleted files in '$EFFECTIVE_SOURCE_PATH' between $BEFORE_SHA and $CURRENT_SHA..." - processed_any_change=false - - # Use NUL delimiters, check within the root directory (.) - git diff --name-status --find-renames -z $BEFORE_SHA $CURRENT_SHA -- "$EFFECTIVE_SOURCE_PATH" | while IFS= read -r -d $'\0' status && IFS= read -r -d $'\0' old_path && IFS= read -r -d $'\0' new_path; do - # Handle cases where new_path might not be present (for deletions) - if [ -z "$new_path" ]; then - new_path=$old_path - fi - - # --- Calculate relative paths (already relative to root) --- - relative_old_path="$old_path" - relative_new_path="$new_path" - - # --- Filter out EXCLUDED top-level files/directories --- - is_excluded=false - for pattern in "${EXCLUDED_PATTERNS[@]}"; do - # Check if old_path starts with or exactly matches the pattern - if [[ "$old_path" == "$pattern"* ]]; then - is_excluded=true - echo "Skipping excluded file/path based on pattern '$pattern': $old_path" - break # Exit inner loop once matched - fi - done - if [ "$is_excluded" = true ]; then - continue # Skip to the next file in the diff - fi - # --- End of exclusion filter --- - - # Proceed only if the file wasn't excluded - echo "Detected potentially relevant change: Status=$status, Old Path=$old_path, New Path=$new_path" - - for TARGET_DIR in "${TARGET_DIRS[@]}"; do # Iterate over array elements correctly - # Ensure target *directory* exists (e.g., 'ja', 'es') - if [ ! -d "$TARGET_DIR" ]; then - echo "::warning::Target directory '$TARGET_DIR' not found. Skipping for this language." - continue - fi - - # Construct target paths using the relative path from root - target_old_path="$TARGET_DIR/$relative_old_path" - - if [[ "$status" == D* ]]; then - # Delete corresponding file in target dir IF it exists - if [ -f "$target_old_path" ]; then - echo "Deleting corresponding file: $target_old_path" - git rm "$target_old_path" - processed_any_change=true - else - # It's okay if the target file doesn't exist, don't warn loudly. - echo "Corresponding file for deletion not found (or already deleted): $target_old_path" - fi - elif [[ "$status" == R* ]]; then - # Rename corresponding file in target dir IF it exists - target_new_path="$TARGET_DIR/$relative_new_path" - target_new_path_dir=$(dirname "$target_new_path") - - if [ -f "$target_old_path" ]; then - # Create parent directory for target if needed - if [ ! -d "$target_new_path_dir" ]; then - echo "Creating directory for renamed file: $target_new_path_dir" - mkdir -p "$target_new_path_dir" - fi - echo "Renaming corresponding file: $target_old_path -> $target_new_path" - git mv "$target_old_path" "$target_new_path" - processed_any_change=true - else - # It's okay if the target file doesn't exist, don't warn loudly. - echo "Corresponding file for rename not found: $target_old_path" - fi - fi # End status check (D or R) - done # end loop target dirs - done # end loop git diff - - # Output based on the flag - echo "processed_changes=$processed_any_change" >> $GITHUB_OUTPUT + run: .github/scripts/handle-changes.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Run translation script (Writes/Updates files) + - name: Run translation script if: steps.detect.outputs.skip != 'true' env: FRENGLISH_API_KEY: ${{ secrets.FRENGLISH_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: node .github/scripts/translate.js - - name: Stage ALL changes (new, modified, deleted, renamed) + - name: Stage all changes if: steps.detect.outputs.skip != 'true' - run: | - echo "Staging all tracked changes (adds, modifications, deletes, renames)..." - git add . # This stages all changes in the working directory + run: git add . - name: Commit changes if: steps.detect.outputs.skip != 'true' id: commit run: | - # Check index status after all operations (add, rm, mv) - # Use --cached to check staged changes specifically if git diff --cached --quiet; then - echo "No changes staged for commit." - echo "changes_committed=false" >> $GITHUB_OUTPUT + echo "changes_committed=false" >>"$GITHUB_OUTPUT" else - echo "Committing translation updates, formatting, renames, and deletions..." - # Use the dynamically fetched source language in the commit message - COMMIT_SOURCE_LANG="${{ steps.get_lang_config.outputs.source_lang }}" # Capture output first - git commit -m "chore(i18n): update translations [${COMMIT_SOURCE_LANG:-unknown}]" \ - -m "Sync file structure, format locales. Branch: ${{ github.ref_name }}" - echo "changes_committed=true" >> $GITHUB_OUTPUT - git show --stat # Show commit details + LANG="${{ steps.get_lang_config.outputs.source_lang }}" + git commit -m "chore(i18n): update translations [$LANG]" + echo "changes_committed=true" >>"$GITHUB_OUTPUT" fi - name: Push changes - # run only when we actually committed something if: steps.detect.outputs.skip != 'true' && steps.commit.outputs.changes_committed == 'true' env: - # use head branch for PRs, ref_name for normal pushes TARGET_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} - run: | - echo "Pushing changes to origin/${TARGET_REF}..." - git push origin HEAD:${TARGET_REF} \ No newline at end of file + run: git push origin HEAD:${TARGET_REF} From dccf56f3e9a86f004262e0dfa98743adf680d5d8 Mon Sep 17 00:00:00 2001 From: vivian Date: Tue, 6 May 2025 23:40:46 +0800 Subject: [PATCH 2/3] revert github action --- .github/workflows/frenglish-translations.yaml | 198 +++++++++++++++--- 1 file changed, 166 insertions(+), 32 deletions(-) diff --git a/.github/workflows/frenglish-translations.yaml b/.github/workflows/frenglish-translations.yaml index fdcf5c20..532bcdbf 100644 --- a/.github/workflows/frenglish-translations.yaml +++ b/.github/workflows/frenglish-translations.yaml @@ -20,23 +20,24 @@ # ------------------------------------------------------------------------------ name: Frenglish Translation - on: + # Run once per pull‑request (feature → any target) pull_request: types: [opened, synchronize, reopened] + + # Run again only when commits land on the default branch (e.g. master/main) push: - branches: ['**'] + branches: + - '**' # We filter below permissions: contents: read jobs: translate_and_format: + # Run if (a) it’s a PR OR (b) it’s a push *and* the ref equals the repo’s default branch if: >- - ( - github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - ) || + github.event_name == 'pull_request' || ( github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && @@ -48,20 +49,16 @@ jobs: contents: write pull-requests: write steps: - - name: Detect prior translation commit + # We check if commit message include `chore(i18n): update translations` and assume it's been fully translated if so + - name: Detect translation commit in push range id: detect - run: | - if [ "${{ github.event_name }}" = "push" ]; then - RANGE="${{ github.event.before }}..${{ github.sha }}" + if: github.event_name == 'push' # PRs always run + run: | + echo "Looking for 'chore(i18n): update translations' between ${{ github.event.before }}..${{ github.sha }}" + if git log --format=%B ${{ github.event.before }}..${{ github.sha }} | grep -qF 'chore(i18n): update translations'; then + echo "skip=true" >> "$GITHUB_OUTPUT" else - BASE="${{ github.event.pull_request.base.ref }}" - git fetch --depth=1 origin "$BASE":"$BASE" - RANGE="$(git merge-base $BASE HEAD)..HEAD" - fi - if git log --format=%B $RANGE | grep -qF 'chore(i18n): update translations'; then - echo "skip=true" >>"$GITHUB_OUTPUT" - else - echo "skip=false" >>"$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" fi - name: Checkout code @@ -75,17 +72,20 @@ jobs: if: steps.detect.outputs.skip != 'true' uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '18' # Or your preferred Node.js version >= 16 - name: Install dependencies if: steps.detect.outputs.skip != 'true' - run: npm install + run: | + # Ensure you have a package.json and package-lock.json + # Add @frenglish/sdk to your package.json: npm install @frenglish/sdk --save + npm install - name: Setup Git User if: steps.detect.outputs.skip != 'true' run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" + git config --global user.name "github-actions[bot]" - name: Get Language Configuration if: steps.detect.outputs.skip != 'true' @@ -93,40 +93,174 @@ jobs: run: node .github/scripts/fetch-frenglish-configuration.js env: FRENGLISH_API_KEY: ${{ secrets.FRENGLISH_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # --- Step to Handle Renamed/Deleted Files --- - name: Handle Renamed and Deleted Source Files if: steps.detect.outputs.skip != 'true' id: handle_changes - run: .github/scripts/handle-changes.sh + run: | + set -e # Exit immediately if a command exits with a non-zero status. + + # Get target languages and commit SHAs + # SOURCE_DIR_RAW from get_lang_config is now IGNORED for path determination + TARGET_DIRS_STRING="${{ steps.get_lang_config.outputs.target_langs }}" + BEFORE_SHA="${{ github.event.before }}" + CURRENT_SHA="${{ github.sha }}" + + # --- Define the source path CONSISTENTLY with translate.js --- + # ORIGIN_LANGUAGE_DIR in translate.js is path.resolve('.'), so we use '.' here. + EFFECTIVE_SOURCE_PATH="." + echo "Source file location for rename/delete check: Root Directory (.)" + + # --- Validate Target Languages --- + if [ -z "$TARGET_DIRS_STRING" ]; then + echo "::warning::No target languages determined. Rename/delete actions for target directories will be skipped." + echo "processed_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + read -r -a TARGET_DIRS <<< "$TARGET_DIRS_STRING" + if [ ${#TARGET_DIRS[@]} -eq 0 ]; then + echo "::warning::No target languages parsed. Rename/delete actions for target directories will be skipped." + echo "processed_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # --- List of top-level files/dirs to EXCLUDE from rename/delete handling --- + # Add any other known non-locale files/folders residing in your root directory + # Use trailing slash for directories to avoid matching files starting with the same name + EXCLUDED_PATTERNS=( + 'package.json' + 'package-lock.json' + 'node_modules/' + 'frenglish.config.json' + '.github/' + '.git/' + '.gitignore' + 'README.md' + # Add other files/dirs like 'vite.config.js', 'tsconfig.json', etc. if they exist in root + ) + echo "Excluding patterns: ${EXCLUDED_PATTERNS[*]}" + + + # --- Check for Renamed/Deleted Files in the Root Directory --- + echo "Checking for renamed/deleted files in '$EFFECTIVE_SOURCE_PATH' between $BEFORE_SHA and $CURRENT_SHA..." + processed_any_change=false + + # Use NUL delimiters, check within the root directory (.) + git diff --name-status --find-renames -z $BEFORE_SHA $CURRENT_SHA -- "$EFFECTIVE_SOURCE_PATH" | while IFS= read -r -d $'\0' status && IFS= read -r -d $'\0' old_path && IFS= read -r -d $'\0' new_path; do + # Handle cases where new_path might not be present (for deletions) + if [ -z "$new_path" ]; then + new_path=$old_path + fi + + # --- Calculate relative paths (already relative to root) --- + relative_old_path="$old_path" + relative_new_path="$new_path" + + # --- Filter out EXCLUDED top-level files/directories --- + is_excluded=false + for pattern in "${EXCLUDED_PATTERNS[@]}"; do + # Check if old_path starts with or exactly matches the pattern + if [[ "$old_path" == "$pattern"* ]]; then + is_excluded=true + echo "Skipping excluded file/path based on pattern '$pattern': $old_path" + break # Exit inner loop once matched + fi + done + if [ "$is_excluded" = true ]; then + continue # Skip to the next file in the diff + fi + # --- End of exclusion filter --- + + # Proceed only if the file wasn't excluded + echo "Detected potentially relevant change: Status=$status, Old Path=$old_path, New Path=$new_path" + + for TARGET_DIR in "${TARGET_DIRS[@]}"; do # Iterate over array elements correctly + # Ensure target *directory* exists (e.g., 'ja', 'es') + if [ ! -d "$TARGET_DIR" ]; then + echo "::warning::Target directory '$TARGET_DIR' not found. Skipping for this language." + continue + fi + + # Construct target paths using the relative path from root + target_old_path="$TARGET_DIR/$relative_old_path" + + if [[ "$status" == D* ]]; then + # Delete corresponding file in target dir IF it exists + if [ -f "$target_old_path" ]; then + echo "Deleting corresponding file: $target_old_path" + git rm "$target_old_path" + processed_any_change=true + else + # It's okay if the target file doesn't exist, don't warn loudly. + echo "Corresponding file for deletion not found (or already deleted): $target_old_path" + fi + elif [[ "$status" == R* ]]; then + # Rename corresponding file in target dir IF it exists + target_new_path="$TARGET_DIR/$relative_new_path" + target_new_path_dir=$(dirname "$target_new_path") + + if [ -f "$target_old_path" ]; then + # Create parent directory for target if needed + if [ ! -d "$target_new_path_dir" ]; then + echo "Creating directory for renamed file: $target_new_path_dir" + mkdir -p "$target_new_path_dir" + fi + echo "Renaming corresponding file: $target_old_path -> $target_new_path" + git mv "$target_old_path" "$target_new_path" + processed_any_change=true + else + # It's okay if the target file doesn't exist, don't warn loudly. + echo "Corresponding file for rename not found: $target_old_path" + fi + fi # End status check (D or R) + done # end loop target dirs + done # end loop git diff + + # Output based on the flag + echo "processed_changes=$processed_any_change" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Run translation script + - name: Run translation script (Writes/Updates files) if: steps.detect.outputs.skip != 'true' env: FRENGLISH_API_KEY: ${{ secrets.FRENGLISH_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: node .github/scripts/translate.js - - name: Stage all changes + - name: Stage ALL changes (new, modified, deleted, renamed) if: steps.detect.outputs.skip != 'true' - run: git add . + run: | + echo "Staging all tracked changes (adds, modifications, deletes, renames)..." + git add . # This stages all changes in the working directory - name: Commit changes if: steps.detect.outputs.skip != 'true' id: commit run: | + # Check index status after all operations (add, rm, mv) + # Use --cached to check staged changes specifically if git diff --cached --quiet; then - echo "changes_committed=false" >>"$GITHUB_OUTPUT" + echo "No changes staged for commit." + echo "changes_committed=false" >> $GITHUB_OUTPUT else - LANG="${{ steps.get_lang_config.outputs.source_lang }}" - git commit -m "chore(i18n): update translations [$LANG]" - echo "changes_committed=true" >>"$GITHUB_OUTPUT" + echo "Committing translation updates, formatting, renames, and deletions..." + # Use the dynamically fetched source language in the commit message + COMMIT_SOURCE_LANG="${{ steps.get_lang_config.outputs.source_lang }}" # Capture output first + git commit -m "chore(i18n): update translations [${COMMIT_SOURCE_LANG:-unknown}]" \ + -m "Sync file structure, format locales. Branch: ${{ github.ref_name }}" + echo "changes_committed=true" >> $GITHUB_OUTPUT + git show --stat # Show commit details fi - name: Push changes + # run only when we actually committed something if: steps.detect.outputs.skip != 'true' && steps.commit.outputs.changes_committed == 'true' env: + # use head branch for PRs, ref_name for normal pushes TARGET_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} - run: git push origin HEAD:${TARGET_REF} + run: | + echo "Pushing changes to origin/${TARGET_REF}..." + git push origin HEAD:${TARGET_REF} \ No newline at end of file From f488a0e214189db77110963afc4f493ebad5415e Mon Sep 17 00:00:00 2001 From: vivian Date: Tue, 6 May 2025 23:42:14 +0800 Subject: [PATCH 3/3] readd comment --- .github/scripts/translate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/translate.js b/.github/scripts/translate.js index 08e38cb9..0463b15d 100644 --- a/.github/scripts/translate.js +++ b/.github/scripts/translate.js @@ -86,6 +86,7 @@ const EXCLUDED_FILES = ['package.json', 'package-lock.json', 'node_modules', 'do } } + // Compares files changed in a PR (or files changed with a commit directly to default branch) async function getChangedFiles() { try { const isPR = !!process.env.GITHUB_BASE_REF;