updated migration guide #66
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ------------------------------------------------------------------------------ | |
# Frenglish Translation GitHub Action | |
# | |
# Workflow summary | |
# - PRs - Internal: always translate the diff to the PR base. | |
# • Example 1: Open PR => feature_1 → main | |
# • Example 2: Open PR => feature_1_fix → feature_1 | |
# | |
# - PRs - External: Only translate when external PR is merged | |
# • Example: merge PR => fork → main (from a contributor fork) | |
# | |
# - Default‑branch pushes: translate unless | |
# a) author is github‑actions[bot], or | |
# b) the push includes a commit with “chore(i18n): update translations” | |
# • Example: Hotfix push → main | |
# | |
# - Diff logic: compares to fork-point for PRs, or previous commit on main for direct pushes. | |
# - If changes are found: handles file renames/deletes, runs translation and formatting, | |
# then commits and pushes a single update from the bot. | |
# ------------------------------------------------------------------------------ | |
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 | |
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 == 'push' && | |
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && | |
!contains(github.event.head_commit.author.name, 'github-actions[bot]') && | |
!startsWith(github.event.head_commit.message, 'Merge ') | |
) | |
runs-on: ubuntu-latest | |
permissions: | |
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 | |
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" | |
else | |
echo "skip=false" >> "$GITHUB_OUTPUT" | |
fi | |
- name: Checkout code | |
if: steps.detect.outputs.skip != 'true' | |
uses: actions/checkout@v4 | |
with: | |
token: ${{ secrets.GITHUB_TOKEN }} | |
fetch-depth: 0 | |
- name: Setup Node.js | |
if: steps.detect.outputs.skip != 'true' | |
uses: actions/setup-node@v3 | |
with: | |
node-version: '18' # Or your preferred Node.js version >= 16 | |
- 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 | |
- 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]" | |
- name: Get Language Configuration | |
if: steps.detect.outputs.skip != 'true' | |
id: get_lang_config | |
run: node .github/scripts/fetch-frenglish-configuration.js | |
env: | |
FRENGLISH_API_KEY: ${{ secrets.FRENGLISH_API_KEY }} | |
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 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- 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 }} | |
run: node .github/scripts/translate.js | |
- name: Stage ALL changes (new, modified, deleted, renamed) | |
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 | |
- 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 | |
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 | |
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} |