Skip to content

Commit f802528

Browse files
rsesehectorsector
andauthored
Localization: duplicate translation workflow for testing with msft repos (github#27790)
* duplicate translation workflow for msft repos * use duplicate script for creating translation pr * comment out unsued functions Co-authored-by: Hector Alfaro <hectorsector@github.com>
1 parent bc320bb commit f802528

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'fs'
4+
import github from '@actions/github'
5+
6+
const OPTIONS = Object.fromEntries(
7+
['BASE', 'BODY_FILE', 'GITHUB_TOKEN', 'HEAD', 'LANGUAGE', 'TITLE', 'GITHUB_REPOSITORY'].map(
8+
(envVarName) => {
9+
const envVarValue = process.env[envVarName]
10+
if (!envVarValue) {
11+
throw new Error(`You must supply a ${envVarName} environment variable`)
12+
}
13+
return [envVarName, envVarValue]
14+
}
15+
)
16+
)
17+
18+
if (!process.env.GITHUB_REPOSITORY) {
19+
throw new Error('GITHUB_REPOSITORY environment variable not set')
20+
}
21+
22+
const RETRY_STATUSES = [
23+
422, // Retry the operation if the PR already exists
24+
502, // Retry the operation if the API responds with a `502 Bad Gateway` error.
25+
]
26+
const RETRY_ATTEMPTS = 3
27+
const {
28+
// One of the default environment variables provided by Actions.
29+
GITHUB_REPOSITORY,
30+
31+
// These are passed in from the step in the workflow file.
32+
TITLE,
33+
BASE,
34+
HEAD,
35+
LANGUAGE,
36+
BODY_FILE,
37+
GITHUB_TOKEN,
38+
} = OPTIONS
39+
const [OWNER, REPO] = GITHUB_REPOSITORY.split('/')
40+
41+
const octokit = github.getOctokit(GITHUB_TOKEN)
42+
43+
/**
44+
* @param {object} config Configuration options for finding the PR.
45+
* @returns {Promise<number | undefined>} The PR number.
46+
*/
47+
async function findPullRequestNumber(config) {
48+
// Get a list of PRs and see if one already exists.
49+
const { data: listOfPullRequests } = await octokit.rest.pulls.list({
50+
owner: config.owner,
51+
repo: config.repo,
52+
head: `${config.owner}:${config.head}`,
53+
})
54+
55+
return listOfPullRequests[0]?.number
56+
}
57+
58+
/**
59+
* When this file was first created, we only introduced support for creating a pull request for some translation batch.
60+
* However, some of our first workflow runs failed during the pull request creation due to a timeout error.
61+
* There have been cases where, despite the timeout error, the pull request gets created _anyway_.
62+
* To accommodate this reality, we created this function to look for an existing pull request before a new one is created.
63+
* Although the "find" check is redundant in the first "cycle", it's designed this way to recursively call the function again via its retry mechanism should that be necessary.
64+
*
65+
* @param {object} config Configuration options for creating the pull request.
66+
* @returns {Promise<number>} The PR number.
67+
*/
68+
async function findOrCreatePullRequest(config) {
69+
const found = await findPullRequestNumber(config)
70+
71+
if (found) {
72+
return found
73+
}
74+
75+
try {
76+
const { data: pullRequest } = await octokit.rest.pulls.create({
77+
owner: config.owner,
78+
repo: config.repo,
79+
base: config.base,
80+
head: config.head,
81+
title: config.title,
82+
body: config.body,
83+
draft: false,
84+
})
85+
86+
return pullRequest.number
87+
} catch (error) {
88+
if (!error.response || !config.retryCount) {
89+
throw error
90+
}
91+
92+
if (!config.retryStatuses.includes(error.response.status)) {
93+
throw error
94+
}
95+
96+
console.error(`Error creating pull request: ${error.message}`)
97+
console.warn(`Retrying in 5 seconds...`)
98+
await new Promise((resolve) => setTimeout(resolve, 5000))
99+
100+
config.retryCount -= 1
101+
102+
return findOrCreatePullRequest(config)
103+
}
104+
}
105+
106+
/**
107+
* @param {object} config Configuration options for labeling the PR
108+
* @returns {Promise<undefined>}
109+
*/
110+
// async function labelPullRequest(config) {
111+
// await octokit.rest.issues.update({
112+
// owner: config.owner,
113+
// repo: config.repo,
114+
// issue_number: config.issue_number,
115+
// labels: config.labels,
116+
// })
117+
// }
118+
119+
async function main() {
120+
const options = {
121+
title: TITLE,
122+
base: BASE,
123+
head: HEAD,
124+
body: fs.readFileSync(BODY_FILE, 'utf8'),
125+
labels: ['translation-batch', `translation-batch-${LANGUAGE}`],
126+
owner: OWNER,
127+
repo: REPO,
128+
retryStatuses: RETRY_STATUSES,
129+
retryCount: RETRY_ATTEMPTS,
130+
}
131+
132+
options.issue_number = await findOrCreatePullRequest(options)
133+
const pr = `${GITHUB_REPOSITORY}#${options.issue_number}`
134+
console.log(`Created PR ${pr}`)
135+
136+
// metadata parameters aren't currently available in `github.rest.pulls.create`,
137+
// but they are in `github.rest.issues.update`.
138+
// await labelPullRequest(options)
139+
// console.log(`Updated ${pr} with these labels: ${options.labels.join(', ')}`)
140+
}
141+
142+
main()
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
name: Create translation Batch Pull Request
2+
3+
# **What it does**:
4+
# - Creates one pull request per language after running a series of automated checks,
5+
# removing translations that are broken in any known way
6+
# **Why we have it**:
7+
# - To deploy translations
8+
# **Who does it impact**: It automates what would otherwise be manual work,
9+
# helping docs engineering focus on higher value work
10+
11+
on:
12+
workflow_dispatch:
13+
# TODO: bring schedule back in
14+
# schedule:
15+
# - cron: '02 17 * * *' # Once a day at 17:02 UTC / 9:02 PST
16+
17+
permissions:
18+
contents: write
19+
20+
jobs:
21+
create-translation-batch:
22+
name: Create translation batch
23+
if: github.repository == 'github/docs-internal'
24+
runs-on: ubuntu-latest
25+
# A sync's average run time is ~3.2 hours.
26+
# This sets a maximum execution time of 300 minutes (5 hours) to prevent the workflow from running longer than necessary.
27+
timeout-minutes: 300
28+
strategy:
29+
fail-fast: false
30+
max-parallel: 1
31+
matrix:
32+
include:
33+
# TODO: replace language_repos with actual repos once created
34+
# - language: pt
35+
# crowdin_language: pt-BR
36+
# language_dir: translations/pt-BR
37+
# language_repo: github/docs-translations-pt-br
38+
39+
- language: es
40+
crowdin_language: es-ES
41+
language_dir: translations/es-ES
42+
language_repo: github/docs-localization-test-es-es
43+
44+
# - language: cn
45+
# crowdin_language: zh-CN
46+
# language_dir: translations/zh-CN
47+
# language_repo: github/docs-translations-zh-cn
48+
49+
# - language: ja
50+
# crowdin_language: ja
51+
# language_dir: translations/ja-JP
52+
# language_repo: github/docs-translations-ja-jp
53+
54+
# TODO: replace the branch name
55+
steps:
56+
- name: Set branch name
57+
id: set-branch
58+
run: |
59+
echo "::set-output name=BRANCH_NAME::msft-translation-batch-${{ matrix.language }}-$(date +%Y-%m-%d__%H-%M)"
60+
61+
- run: git config --global user.name "docubot"
62+
- run: git config --global user.email "67483024+docubot@users.noreply.github.com"
63+
64+
- name: Checkout the docs-internal repo
65+
uses: actions/checkout@dcd71f646680f2efd8db4afa5ad64fdcba30e748
66+
with:
67+
fetch-depth: 0
68+
lfs: true
69+
70+
- name: Create a branch for the current language
71+
run: git checkout -b ${{ steps.set-branch.outputs.BRANCH_NAME }}
72+
73+
- name: Remove unwanted git hooks
74+
run: rm .git/hooks/post-checkout
75+
76+
- name: Remove all language translations
77+
run: |
78+
git rm -rf --quiet ${{ matrix.language_dir }}/content
79+
git rm -rf --quiet ${{ matrix.language_dir }}/data
80+
81+
- name: Checkout the language-specific repo
82+
uses: actions/checkout@dcd71f646680f2efd8db4afa5ad64fdcba30e748
83+
with:
84+
repository: ${{ matrix.language_repo }}
85+
token: ${{ secrets.DOCUBOT_READORG_REPO_WORKFLOW_SCOPES }}
86+
path: ${{ matrix.language_dir }}
87+
88+
- name: Remove .git from the language-specific repo
89+
run: rm -rf ${{ matrix.language_dir }}/.git
90+
91+
# TODO: Rename this step
92+
- name: Commit crowdin sync
93+
run: |
94+
git add ${{ matrix.language_dir }}
95+
git commit -m "Add crowdin translations" || echo "Nothing to commit"
96+
97+
- name: 'Setup node'
98+
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561
99+
with:
100+
node-version: 16.14.x
101+
102+
- run: npm ci
103+
104+
# step 6 in docs-engineering/crowdin.md
105+
- name: Homogenize frontmatter
106+
run: |
107+
node script/i18n/homogenize-frontmatter.js
108+
git add ${{ matrix.language_dir }} && git commit -m "Run script/i18n/homogenize-frontmatter.js" || echo "Nothing to commit"
109+
110+
# step 7 in docs-engineering/crowdin.md
111+
- name: Fix translation errors
112+
run: |
113+
node script/i18n/fix-translation-errors.js
114+
git add ${{ matrix.language_dir }} && git commit -m "Run script/i18n/fix-translation-errors.js" || echo "Nothing to commit"
115+
116+
# step 8a in docs-engineering/crowdin.md
117+
- name: Check parsing
118+
run: |
119+
node script/i18n/lint-translation-files.js --check parsing | tee -a /tmp/batch.log | cat
120+
git add ${{ matrix.language_dir }} && git commit -m "Run script/i18n/lint-translation-files.js --check parsing" || echo "Nothing to commit"
121+
122+
# step 8b in docs-engineering/crowdin.md
123+
- name: Check rendering
124+
run: |
125+
node script/i18n/lint-translation-files.js --check rendering | tee -a /tmp/batch.log | cat
126+
git add ${{ matrix.language_dir }} && git commit -m "Run script/i18n/lint-translation-files.js --check rendering" || echo "Nothing to commit"
127+
128+
- name: Reset files with broken liquid tags
129+
run: |
130+
node script/i18n/reset-files-with-broken-liquid-tags.js --language=${{ matrix.language }} | tee -a /tmp/batch.log | cat
131+
git add ${{ matrix.language_dir }} && git commit -m "run script/i18n/reset-files-with-broken-liquid-tags.js --language=${{ matrix.language }}" || echo "Nothing to commit"
132+
133+
# step 5 in docs-engineering/crowdin.md using script from docs-internal#22709
134+
- name: Reset known broken files
135+
run: |
136+
node script/i18n/reset-known-broken-translation-files.js | tee -a /tmp/batch.log | cat
137+
git add ${{ matrix.language_dir }} && git commit -m "run script/i18n/reset-known-broken-translation-files.js" || echo "Nothing to commit"
138+
env:
139+
GITHUB_TOKEN: ${{ secrets.DOCUBOT_REPO_PAT }}
140+
141+
- name: Check in CSV report
142+
run: |
143+
mkdir -p translations/log
144+
csvFile=translations/log/${{ matrix.language }}-resets.csv
145+
script/i18n/report-reset-files.js --report-type=csv --language=${{ matrix.language }} --log-file=/tmp/batch.log > $csvFile
146+
git add -f $csvFile && git commit -m "Check in ${{ matrix.language }} CSV report" || echo "Nothing to commit"
147+
148+
- name: Write the reported files that were reset to /tmp/pr-body.txt
149+
run: script/i18n/report-reset-files.js --report-type=pull-request-body --language=${{ matrix.language }} --log-file=/tmp/batch.log > /tmp/pr-body.txt
150+
151+
- name: Push filtered translations
152+
run: git push origin ${{ steps.set-branch.outputs.BRANCH_NAME }}
153+
154+
# TODO: bring this step back
155+
# - name: Close existing stale batches
156+
# uses: lee-dohm/close-matching-issues@e9e43aad2fa6f06a058cedfd8fb975fd93b56d8f
157+
# with:
158+
# token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
159+
# query: 'type:pr label:translation-batch-${{ matrix.language }}'
160+
161+
# TODO: bring labels back into the PR script
162+
- name: Create translation batch pull request
163+
env:
164+
GITHUB_TOKEN: ${{ secrets.DOCUBOT_REPO_PAT }}
165+
TITLE: '[DO NOT MERGE] Msft: New translation batch for ${{ matrix.language }}'
166+
BASE: 'main'
167+
HEAD: ${{ steps.set-branch.outputs.BRANCH_NAME }}
168+
LANGUAGE: ${{ matrix.language }}
169+
BODY_FILE: '/tmp/pr-body.txt'
170+
run: .github/actions-scripts/msft-create-translation-batch-pr.js
171+
172+
# TODO: bring back these steps
173+
# - name: Approve PR
174+
# if: github.ref_name == 'main'
175+
# env:
176+
# GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
177+
# run: gh pr review --approve || echo "Nothing to approve"
178+
179+
# - name: Set auto-merge
180+
# if: github.ref_name == 'main'
181+
# env:
182+
# GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
183+
# run: gh pr merge ${{ steps.set-branch.outputs.BRANCH_NAME }} --auto --squash || echo "Nothing to merge"
184+
185+
# # When the maximum execution time is reached for this job, Actions cancels the workflow run.
186+
# # This emits a notification for the first responder to triage.
187+
# - name: Send Slack notification if workflow is cancelled
188+
# uses: someimportantcompany/github-actions-slack-message@f8d28715e7b8a4717047d23f48c39827cacad340
189+
# if: cancelled()
190+
# with:
191+
# channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
192+
# bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
193+
# color: failure
194+
# text: 'The new translation batch for ${{ matrix.language }} was cancelled.'
195+
196+
# # Emit a notification for the first responder to triage if the workflow failed.
197+
# - name: Send Slack notification if workflow failed
198+
# uses: someimportantcompany/github-actions-slack-message@f8d28715e7b8a4717047d23f48c39827cacad340
199+
# if: failure()
200+
# with:
201+
# channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
202+
# bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
203+
# color: failure
204+
# text: 'The new translation batch for ${{ matrix.language }} failed.'

0 commit comments

Comments
 (0)