From 904f10571e3e39d6b3d04085920036d3e92ebf1c Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Tue, 16 Jun 2026 21:16:43 -0400 Subject: [PATCH 1/6] Add daily translation-sync job (Buildkite + fastlane) New update_translations lane downloads the latest WordPress & Jetpack translations from GlotPress and opens/refreshes a single rolling translations/daily-update PR via release-toolkit's find_or_create_pull_request. A new scheduled Buildkite pipeline runs it daily. Temporarily points wpmreleasetoolkit at the release-toolkit branch carrying that action; revert to the released version once it ships. --- .buildkite/commands/download-translations.sh | 10 +++++ .../schedules/download-translations.yml | 20 +++++++++ Gemfile | 6 ++- Gemfile.lock | 44 +++++++++++-------- fastlane/lanes/localization.rb | 43 ++++++++++++++++++ 5 files changed, 102 insertions(+), 21 deletions(-) create mode 100755 .buildkite/commands/download-translations.sh create mode 100644 .buildkite/schedules/download-translations.yml diff --git a/.buildkite/commands/download-translations.sh b/.buildkite/commands/download-translations.sh new file mode 100755 index 000000000000..571bc4cd71d4 --- /dev/null +++ b/.buildkite/commands/download-translations.sh @@ -0,0 +1,10 @@ +#!/bin/bash -eu + +echo "--- :rubygems: Setting up Gems" +install_gems + +echo "--- :closed_lock_with_key: Installing Secrets" +bundle exec fastlane run configure_apply + +echo "--- :globe_with_meridians: Downloading translations and updating the PR" +bundle exec fastlane update_translations diff --git a/.buildkite/schedules/download-translations.yml b/.buildkite/schedules/download-translations.yml new file mode 100644 index 000000000000..d70a8c0f063a --- /dev/null +++ b/.buildkite/schedules/download-translations.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +# Runs on a daily schedule (configured in the Buildkite UI, ~06:00 UTC) to sync the latest +# translations from GlotPress into a single rolling `translations/daily-update` PR. + +agents: + queue: "android" + +steps: + - label: ":globe_with_meridians: Download Translations" + command: .buildkite/commands/download-translations.sh + plugins: [$CI_TOOLKIT] + +notify: + - slack: + channels: + - "#android-core-notifs" + message: "Failed to sync translations from GlotPress." + if: build.state == "failed" diff --git a/Gemfile b/Gemfile index 40dfbdd2cfbd..b1bb7f7b6d55 100644 --- a/Gemfile +++ b/Gemfile @@ -9,9 +9,11 @@ gem 'fastlane', '~> 2' gem 'fastlane-plugin-firebase_app_distribution', '~> 1.0' gem 'fastlane-plugin-sentry' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.7' +# TEMPORARY: point at the release-toolkit branch to validate the new `find_or_create_pull_request` +# action used by the `update_translations` lane. Revert to `~> 14.7` once it ships in a tagged release. +# gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.7' +gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', branch: 'add/find-or-create-pull-request' # gem 'fastlane-plugin-wpmreleasetoolkit', path: '../../release-toolkit' -# gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', branch: '' ### Gems needed only for generating Promo Screenshots group :screenshots, optional: true do diff --git a/Gemfile.lock b/Gemfile.lock index ab185a260b27..a94080d4fa52 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,27 @@ +GIT + remote: https://github.com/wordpress-mobile/release-toolkit + revision: 77cdeac5098b460ac6faba189f7e6a4f3d642c25 + branch: add/find-or-create-pull-request + specs: + fastlane-plugin-wpmreleasetoolkit (14.7.0) + buildkit (~> 1.5) + chroma (= 0.2.0) + diffy (~> 3.3) + dotenv (~> 2.8) + fastlane (~> 2.235) + gettext (~> 3.5) + git (~> 1.3) + google-cloud-storage (~> 1.31) + java-properties (~> 0.3.0) + nokogiri (~> 1.19, >= 1.19.3) + octokit (~> 6.1) + parallel (~> 1.14) + plist (~> 3.1) + progress_bar (~> 1.3) + rake (>= 12.3, < 14.0) + rake-compiler (~> 1.0) + xcodeproj (~> 1.22) + GEM remote: https://rubygems.org/ specs: @@ -167,24 +191,6 @@ GEM google-apis-firebaseappdistribution_v1alpha (>= 0.12.0) fastlane-plugin-sentry (1.29.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (14.7.0) - buildkit (~> 1.5) - chroma (= 0.2.0) - diffy (~> 3.3) - dotenv (~> 2.8) - fastlane (~> 2.235) - gettext (~> 3.5) - git (~> 1.3) - google-cloud-storage (~> 1.31) - java-properties (~> 0.3.0) - nokogiri (~> 1.19, >= 1.19.3) - octokit (~> 6.1) - parallel (~> 1.14) - plist (~> 3.1) - progress_bar (~> 1.3) - rake (>= 12.3, < 14.0) - rake-compiler (~> 1.0) - xcodeproj (~> 1.22) fastlane-sirp (1.1.0) fiddle (1.1.8) forwardable (1.4.0) @@ -378,7 +384,7 @@ DEPENDENCIES fastlane (~> 2) fastlane-plugin-firebase_app_distribution (~> 1.0) fastlane-plugin-sentry - fastlane-plugin-wpmreleasetoolkit (~> 14.7) + fastlane-plugin-wpmreleasetoolkit! rmagick (~> 7.0) BUNDLED WITH diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 528c817646cc..81b60021eef3 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -375,6 +375,49 @@ ) end + TRANSLATIONS_SYNC_BRANCH = 'translations/daily-update' + + ##################################################################################### + # update_translations + # ----------------------------------------------------------------------------------- + # Downloads the latest WordPress & Jetpack translations from GlotPress and opens (or + # refreshes) a single rolling Pull Request, so `trunk` stays continuously localized. + # Intended to run on a daily schedule. + # + # Each run resets `translations/daily-update` to `trunk` and re-downloads, so the PR + # always shows the complete current translation delta against `trunk` (no accumulation). + # If GlotPress has nothing new, no commit is made and the lane exits without a PR. + # ----------------------------------------------------------------------------------- + # Usage: + # bundle exec fastlane update_translations + ##################################################################################### + lane :update_translations do + Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) + sh('git', 'checkout', '-B', TRANSLATIONS_SYNC_BRANCH) + + download_translations + + new_commits = sh('git', 'rev-list', '--count', "origin/#{DEFAULT_BRANCH}..HEAD").strip + if new_commits == '0' + UI.important('No new translations from GlotPress today; nothing to sync.') + next + end + + sh('git', 'push', '--force', 'origin', TRANSLATIONS_SYNC_BRANCH) + + # `find_or_create_pull_request` resolves the GitHub token the standard way (GITHUB_TOKEN) and only + # opens a PR when none is already open; the force-push above already refreshed any existing one. + pr_url = find_or_create_pull_request( + repository: GITHUB_REPO, + title: 'Update translations', + body: 'Automated daily translation sync from GlotPress. Opened by the `download-translations` scheduled pipeline.', + head: TRANSLATIONS_SYNC_BRANCH, + base: DEFAULT_BRANCH, + labels: ['Localization'] + ) + UI.success("Translations PR: #{pr_url}") + end + # Updates the `.po` file at the given `po_path` using the content of the `sources` files, # interpolating `release_version` where appropriate. # Internally, this calls the `gp_update_metadata_source` release toolkit action and adds Git management to it. From bfa5ed755eecebcf8f0962334c546225e3df3d3b Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Wed, 17 Jun 2026 12:56:11 -0400 Subject: [PATCH 2/6] Fix translation-sync git identity and use proper git helpers - download-translations.sh: 'source use-bot-for-git' (like the release committing lanes) so the agent has a git identity/auth; drop configure_apply (not needed for translations). - update_translations lane: use GitHelper.create_branch / point_to_same_commit? and push_to_git_remote instead of raw git sh calls, mirroring release.rb. --- .buildkite/commands/download-translations.sh | 10 +++++----- fastlane/lanes/localization.rb | 12 ++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.buildkite/commands/download-translations.sh b/.buildkite/commands/download-translations.sh index 571bc4cd71d4..7f873e516251 100755 --- a/.buildkite/commands/download-translations.sh +++ b/.buildkite/commands/download-translations.sh @@ -1,10 +1,10 @@ #!/bin/bash -eu -echo "--- :rubygems: Setting up Gems" -install_gems +echo "--- :robot_face: Use bot for git operations" +source use-bot-for-git -echo "--- :closed_lock_with_key: Installing Secrets" -bundle exec fastlane run configure_apply +echo "--- :ruby: Setup Ruby Tools" +install_gems -echo "--- :globe_with_meridians: Downloading translations and updating the PR" +echo "--- :globe_with_meridians: Download translations and open/update the PR" bundle exec fastlane update_translations diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 81b60021eef3..0a63d9aaf517 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -393,17 +393,21 @@ ##################################################################################### lane :update_translations do Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) - sh('git', 'checkout', '-B', TRANSLATIONS_SYNC_BRANCH) + + # Reset the rolling branch to the tip of `trunk` so each run produces a clean delta against it. + Fastlane::Helper::GitHelper.delete_local_branch_if_exists!(TRANSLATIONS_SYNC_BRANCH) + Fastlane::Helper::GitHelper.create_branch(TRANSLATIONS_SYNC_BRANCH, from: DEFAULT_BRANCH) download_translations - new_commits = sh('git', 'rev-list', '--count', "origin/#{DEFAULT_BRANCH}..HEAD").strip - if new_commits == '0' + # `download_translations` commits when GlotPress has updates; if nothing changed, HEAD still + # points at `trunk` and there is nothing to open a PR for. + if Fastlane::Helper::GitHelper.point_to_same_commit?(DEFAULT_BRANCH, 'HEAD') UI.important('No new translations from GlotPress today; nothing to sync.') next end - sh('git', 'push', '--force', 'origin', TRANSLATIONS_SYNC_BRANCH) + push_to_git_remote(remote_branch: TRANSLATIONS_SYNC_BRANCH, tags: false, force: true, set_upstream: true) # `find_or_create_pull_request` resolves the GitHub token the standard way (GITHUB_TOKEN) and only # opens a PR when none is already open; the force-push above already refreshed any existing one. From 98c249126da3fb0668b9bb073459f20a9cdf5633 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Wed, 17 Jun 2026 13:07:46 -0400 Subject: [PATCH 3/6] Run translation-sync on mac-metal (where use-bot-for-git is available) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The job commits, pushes and opens a PR, which needs the bot git identity from use-bot-for-git — only available on mac-metal agents, same as the release code-freeze/finalize lanes. (android only builds, so release-builds.yml stays there.) --- .buildkite/schedules/download-translations.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.buildkite/schedules/download-translations.yml b/.buildkite/schedules/download-translations.yml index d70a8c0f063a..cb3b34e1d507 100644 --- a/.buildkite/schedules/download-translations.yml +++ b/.buildkite/schedules/download-translations.yml @@ -3,9 +3,13 @@ # Runs on a daily schedule (configured in the Buildkite UI, ~06:00 UTC) to sync the latest # translations from GlotPress into a single rolling `translations/daily-update` PR. +# +# Runs on `mac-metal` (like the release code-freeze/finalize lanes) because this job commits, +# pushes and opens a PR, which needs the bot git identity from `use-bot-for-git` — that command +# is only available on those agents. agents: - queue: "android" + queue: "mac-metal" steps: - label: ":globe_with_meridians: Download Translations" From a928520c3a36f79b39a780accd309f20f0f81995 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Wed, 17 Jun 2026 13:46:41 -0400 Subject: [PATCH 4/6] Document why release pipelines run on mac-metal (use-bot-for-git) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a reference comment to the mac-metal release pipelines explaining that the queue choice is driven by the bot git identity from use-bot-for-git being only available there — a hint for future git-writing jobs (e.g. the translation sync). --- .buildkite/code-freeze.yml | 2 ++ .buildkite/complete-code-freeze.yml | 2 ++ .buildkite/finalize-hotfix-release.yml | 2 ++ .buildkite/finalize-release.yml | 2 ++ .buildkite/new-beta-release.yml | 2 ++ .buildkite/new-hotfix-release.yml | 2 ++ .buildkite/publish-release.yml | 2 ++ .buildkite/update-release-notes.yml | 2 ++ 8 files changed, 16 insertions(+) diff --git a/.buildkite/code-freeze.yml b/.buildkite/code-freeze.yml index 1cf78e3c066d..933636906759 100644 --- a/.buildkite/code-freeze.yml +++ b/.buildkite/code-freeze.yml @@ -13,6 +13,8 @@ steps: echo '--- :snowflake: Start Code Freeze' bundle exec fastlane code_freeze version:"${RELEASE_VERSION}" skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: "mac-metal" retry: diff --git a/.buildkite/complete-code-freeze.yml b/.buildkite/complete-code-freeze.yml index 310766f39659..20e16f10e7f0 100644 --- a/.buildkite/complete-code-freeze.yml +++ b/.buildkite/complete-code-freeze.yml @@ -15,6 +15,8 @@ steps: echo '--- :snowflake: Complete Code Freeze' bundle exec fastlane complete_code_freeze skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: "mac-metal" retry: diff --git a/.buildkite/finalize-hotfix-release.yml b/.buildkite/finalize-hotfix-release.yml index 4af22b5700cc..861efa287ac0 100644 --- a/.buildkite/finalize-hotfix-release.yml +++ b/.buildkite/finalize-hotfix-release.yml @@ -15,6 +15,8 @@ steps: echo '--- :shipit: Finalize hotfix' bundle exec fastlane finalize_hotfix_release skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: mac-metal retry: diff --git a/.buildkite/finalize-release.yml b/.buildkite/finalize-release.yml index b541f8def406..b7868fb9fa1b 100644 --- a/.buildkite/finalize-release.yml +++ b/.buildkite/finalize-release.yml @@ -15,6 +15,8 @@ steps: echo '--- :shipit: Finalize Release' bundle exec fastlane finalize_release skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: "mac-metal" retry: diff --git a/.buildkite/new-beta-release.yml b/.buildkite/new-beta-release.yml index c684880bb623..ab56f79c6851 100644 --- a/.buildkite/new-beta-release.yml +++ b/.buildkite/new-beta-release.yml @@ -13,6 +13,8 @@ steps: echo '--- :shipit: New Beta Release' bundle exec fastlane new_beta_release skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: "mac-metal" retry: diff --git a/.buildkite/new-hotfix-release.yml b/.buildkite/new-hotfix-release.yml index 6fea293dc6be..94eb18a41661 100644 --- a/.buildkite/new-hotfix-release.yml +++ b/.buildkite/new-hotfix-release.yml @@ -13,6 +13,8 @@ steps: echo '--- :shipit: Start new hotfix' bundle exec fastlane new_hotfix_release version_name:"$RELEASE_VERSION" build_code:"$BUILD_CODE" skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: mac-metal retry: diff --git a/.buildkite/publish-release.yml b/.buildkite/publish-release.yml index a75faddbd52e..80fdb10d3c3d 100644 --- a/.buildkite/publish-release.yml +++ b/.buildkite/publish-release.yml @@ -15,6 +15,8 @@ steps: echo '--- :package: Publish Release' bundle exec fastlane publish_release skip_confirm:true + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: mac-metal retry: diff --git a/.buildkite/update-release-notes.yml b/.buildkite/update-release-notes.yml index 3b774014a6b8..b79219be244a 100644 --- a/.buildkite/update-release-notes.yml +++ b/.buildkite/update-release-notes.yml @@ -16,6 +16,8 @@ steps: echo '--- :memo: Update Release Notes' bundle exec fastlane update_appstore_strings version:${RELEASE_VERSION} + # Runs on `mac-metal` so it can use the bot git identity from `use-bot-for-git` (only available + # on these agents) for its git write operations (commit/push/PR). agents: queue: "mac-metal" retry: From 54ae2e6f7f4a040eec1d7b910f25cadfead35877 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 18 Jun 2026 11:22:00 -0400 Subject: [PATCH 5/6] Disable translation-sync failure notifications Comment out the notify block; the channel is pre-set to #wpmobile for if/when it is re-enabled. --- .buildkite/schedules/download-translations.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.buildkite/schedules/download-translations.yml b/.buildkite/schedules/download-translations.yml index cb3b34e1d507..76cbbce96557 100644 --- a/.buildkite/schedules/download-translations.yml +++ b/.buildkite/schedules/download-translations.yml @@ -16,9 +16,10 @@ steps: command: .buildkite/commands/download-translations.sh plugins: [$CI_TOOLKIT] -notify: - - slack: - channels: - - "#android-core-notifs" - message: "Failed to sync translations from GlotPress." - if: build.state == "failed" +# Failure notifications are disabled for now. If re-enabled, they go to #wpmobile. +# notify: +# - slack: +# channels: +# - "#wpmobile" +# message: "Failed to sync translations from GlotPress." +# if: build.state == "failed" From a0c6285915858846cac3962c0e39c5fb7b490e34 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 18 Jun 2026 11:54:13 -0400 Subject: [PATCH 6/6] Prune orphaned translations as a separate commit in update_translations After downloading, prune keys no longer in the source strings (main/res vs main; jetpack/res vs main+jetpack) via android_prune_orphaned_translations and commit it separately, so the PR shows exactly what was pruned vs downloaded. --- fastlane/lanes/localization.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 0a63d9aaf517..6cf97a9689a1 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -407,6 +407,18 @@ next end + # Prune translations whose key is no longer in the source strings (GlotPress can still serve them), + # which would otherwise fail Lint's `ExtraTranslation` check. Done as a separate commit on top of the + # download so the PR shows exactly what was pruned vs. what was downloaded. + main_res = File.join('WordPress', 'src', 'main', 'res') + jetpack_res = File.join('WordPress', 'src', 'jetpack', 'res') + android_prune_orphaned_translations(res_dir: main_res) + android_prune_orphaned_translations( + res_dir: jetpack_res, + additional_source_strings_paths: [File.join(main_res, 'values', 'strings.xml')] + ) + Fastlane::Helper::GitHelper.commit(message: 'Prune orphaned translations', files: :all) + push_to_git_remote(remote_branch: TRANSLATIONS_SYNC_BRANCH, tags: false, force: true, set_upstream: true) # `find_or_create_pull_request` resolves the GitHub token the standard way (GITHUB_TOKEN) and only