Files
openclaw/.github/workflows/control-ui-locale-refresh.yml
2026-07-13 12:12:51 +01:00

324 lines
12 KiB
YAML

name: Control UI Locale Refresh
on:
push:
branches:
- main
paths:
- ui/src/i18n/locales/en.ts
- ui/src/i18n/locales/*.ts
- ui/src/i18n/.i18n/*
- ui/src/i18n/lib/types.ts
- ui/src/i18n/lib/registry.ts
- scripts/control-ui-i18n.ts
- .github/actions/create-generated-pr-tokens/action.yml
- .github/actions/publish-generated-pr/action.yml
- .github/workflows/control-ui-locale-refresh.yml
release:
types:
- published
schedule:
- cron: "23 4 * * *"
workflow_dispatch:
inputs:
token_preflight_only:
description: Verify generated PR App permissions without running locale generation.
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: >-
${{ github.event_name == 'workflow_dispatch' && inputs.token_preflight_only &&
format('control-ui-locale-token-preflight-{0}', github.ref) ||
'control-ui-locale-refresh' }}
# Full refreshes stay serialized; cheap App-permission probes get a ref-scoped group.
# Publisher overlap checks defer stale generated paths to the queued full reconciliation run.
cancel-in-progress: false
jobs:
resolve-base:
if: >-
github.repository == 'openclaw/openclaw' &&
(github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main')
runs-on: ubuntu-latest
outputs:
sha: ${{ steps.base.outputs.sha }}
steps:
- name: Resolve source commit
id: base
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
TOKEN_PREFLIGHT_ONLY: ${{ inputs.token_preflight_only || false }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
if [[ "${TOKEN_PREFLIGHT_ONLY}" == "true" ]]; then
sha="${WORKFLOW_SHA}"
else
sha="$(
timeout --signal=TERM --kill-after=10s 60s \
gh api --method GET "repos/${REPOSITORY}/commits/${DEFAULT_BRANCH}" --jq .sha
)"
fi
if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then
echo "Unable to resolve the locale refresh source to an exact commit." >&2
exit 1
fi
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
publisher-preflight:
name: Verify generated PR App permissions
needs: resolve-base
if: needs.resolve-base.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.resolve-base.outputs.sha }}
persist-credentials: false
submodules: false
- name: Create generated PR tokens
uses: ./.github/actions/create-generated-pr-tokens
with:
contents-client-id: Iv23liOECG0slfuhz093
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
refresh:
needs: [resolve-base, publisher-preflight]
if: >-
needs.resolve-base.result == 'success' &&
needs.publisher-preflight.result == 'success' &&
!(github.event_name == 'workflow_dispatch' && inputs.token_preflight_only)
strategy:
fail-fast: false
max-parallel: 4
matrix:
locale:
[
zh-CN,
zh-TW,
pt-BR,
de,
es,
ja-JP,
ko,
fr,
hi,
ar,
it,
tr,
uk,
id,
pl,
th,
vi,
nl,
fa,
ru,
]
runs-on: ubuntu-latest
name: Refresh ${{ matrix.locale }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.resolve-base.outputs.sha }}
persist-credentials: false
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Ensure translation provider secrets exist
env:
OPENCLAW_DOCS_I18N_OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
set -euo pipefail
if [ -z "${OPENCLAW_DOCS_I18N_OPENAI_API_KEY:-}" ] && [ -z "${OPENAI_API_KEY:-}" ] && [ -z "${ANTHROPIC_API_KEY:-}" ]; then
echo "Missing OPENCLAW_DOCS_I18N_OPENAI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY secret."
exit 1
fi
- name: Refresh control UI locale files
env:
OPENCLAW_DOCS_I18N_OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_MODEL: claude-opus-4-8
OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE || 'gpt-5.6-sol' }}
OPENCLAW_CONTROL_UI_I18N_THINKING: low
OPENCLAW_CONTROL_UI_I18N_AUTH_OPTIONAL: "0"
LOCALE: ${{ matrix.locale }}
run: |
set -euo pipefail
run_refresh() {
local provider="$1"
local model="$2"
local openai_api_key="${3-}"
if [ "$provider" = "openai" ]; then
OPENAI_API_KEY="$openai_api_key" \
OPENCLAW_CONTROL_UI_I18N_PROVIDER="$provider" \
OPENCLAW_CONTROL_UI_I18N_MODEL="$model" \
node --import tsx scripts/control-ui-i18n.ts sync --locale "${LOCALE}" --write
return
fi
OPENCLAW_CONTROL_UI_I18N_PROVIDER="$provider" \
OPENCLAW_CONTROL_UI_I18N_MODEL="$model" \
node --import tsx scripts/control-ui-i18n.ts sync --locale "${LOCALE}" --write
}
run_openai_refresh() {
local status=1
if [ -n "${OPENCLAW_DOCS_I18N_OPENAI_API_KEY:-}" ]; then
set +e
run_refresh openai "${OPENAI_MODEL}" "${OPENCLAW_DOCS_I18N_OPENAI_API_KEY}"
status="$?"
set -e
if [ "$status" -eq 0 ]; then
return 0
fi
if [ -z "${OPENAI_API_KEY:-}" ] || [ "${OPENAI_API_KEY}" = "${OPENCLAW_DOCS_I18N_OPENAI_API_KEY}" ]; then
return "$status"
fi
echo "::warning::Docs OpenAI control UI locale refresh key failed for ${LOCALE}; retrying with repository OpenAI key."
fi
if [ -z "${OPENAI_API_KEY:-}" ]; then
return "$status"
fi
run_refresh openai "${OPENAI_MODEL}" "${OPENAI_API_KEY}"
}
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
set +e
run_refresh anthropic "${ANTHROPIC_MODEL}"
status="$?"
set -e
if [ "$status" -eq 0 ]; then
exit 0
fi
if [ -z "${OPENCLAW_DOCS_I18N_OPENAI_API_KEY:-}" ] && [ -z "${OPENAI_API_KEY:-}" ]; then
exit "$status"
fi
echo "::warning::Anthropic control UI locale refresh failed for ${LOCALE}; retrying with OpenAI."
fi
run_openai_refresh
- name: Prepare locale artifact
env:
LOCALE: ${{ matrix.locale }}
run: |
set -euo pipefail
artifact_dir="${RUNNER_TEMP}/control-ui-locale-${LOCALE}"
mkdir -p "${artifact_dir}"
git add -A ui/src/i18n
git diff --cached --binary --full-index -- ui/src/i18n > "${artifact_dir}/${LOCALE}.patch"
- name: Upload locale artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: control-ui-locale-${{ matrix.locale }}
path: ${{ runner.temp }}/control-ui-locale-${{ matrix.locale }}/${{ matrix.locale }}.patch
if-no-files-found: error
retention-days: 1
finalize:
name: Commit control UI locale refresh
needs: [resolve-base, publisher-preflight, refresh]
if: >-
needs.resolve-base.result == 'success' &&
needs.publisher-preflight.result == 'success' &&
needs.refresh.result == 'success' &&
!(github.event_name == 'workflow_dispatch' && inputs.token_preflight_only)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.resolve-base.outputs.sha }}
fetch-depth: 0
persist-credentials: false
submodules: false
- name: Download locale artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: control-ui-locale-*
path: ${{ runner.temp }}/control-ui-locale-artifacts
merge-multiple: true
- name: Apply locale artifacts
run: |
set -euo pipefail
while IFS= read -r patch; do
if [ -s "${patch}" ]; then
git apply "${patch}"
fi
done < <(find "${RUNNER_TEMP}/control-ui-locale-artifacts" -type f -name '*.patch' | sort)
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Finalize control UI generated artifacts
run: node --import tsx scripts/control-ui-i18n.ts sync --write
- name: Validate control UI locale refresh
run: node --import tsx scripts/control-ui-i18n.ts check
- name: Open or update generated locale PR
uses: ./.github/actions/publish-generated-pr
with:
contents-client-id: Iv23liOECG0slfuhz093
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
base-branch: ${{ github.event.repository.default_branch }}
head-branch: automation/control-ui-locale-refresh
commit-message: "chore(ui): refresh control ui locales"
pr-title: "chore(ui): refresh control ui locales"
generated-paths: ui/src/i18n
invalidation-paths: |
ui/src/i18n/locales/en.ts
ui/src/i18n/.i18n/glossary.*.json
ui/src/i18n/.i18n/raw-copy-baseline.json
ui/src/i18n/lib/types.ts
ui/src/i18n/lib/registry.ts
scripts/control-ui-i18n.ts
.github/actions/create-generated-pr-tokens/action.yml
.github/actions/publish-generated-pr/action.yml
.github/workflows/control-ui-locale-refresh.yml
pr-body: |
## What Problem This Solves
Keeps generated Control UI locales synchronized without bypassing protected-branch checks.
## Why This Change Was Made
The Control UI Locale Refresh workflow generated this update from `${{ needs.resolve-base.outputs.sha }}` and published it through a reviewable automation branch.
## User Impact
No direct user-facing change beyond refreshed translations.
## Evidence
- [Locale refresh run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
- `node --import tsx scripts/control-ui-i18n.ts check`