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/workflows/control-ui-locale-refresh.yml release: types: - published schedule: - cron: "23 4 * * *" workflow_dispatch: permissions: contents: write concurrency: group: control-ui-locale-refresh-${{ github.event_name == 'push' && github.ref || github.event_name == 'workflow_dispatch' && format('manual-{0}', github.run_id) || github.event_name == 'release' && format('release-{0}', github.event.release.tag_name) || format('{0}-{1}', github.event_name, github.run_id) }} cancel-in-progress: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.actor != 'github-actions[bot]' }} jobs: plan: if: github.repository == 'openclaw/openclaw' && (github.event_name != 'push' || github.actor != 'github-actions[bot]') runs-on: ubuntu-latest outputs: has_locales: ${{ steps.plan.outputs.has_locales }} locales_json: ${{ steps.plan.outputs.locales_json }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 persist-credentials: false submodules: false - name: Plan locale matrix id: plan env: BEFORE_SHA: ${{ github.event.before }} EVENT_NAME: ${{ github.event_name }} run: | set -euo pipefail all_locales_json='["zh-CN","zh-TW","pt-BR","de","es","ja-JP","ko","fr","ar","it","tr","uk","id","pl","th","vi","nl","fa"]' if [ "$EVENT_NAME" != "push" ]; then echo "has_locales=true" >> "$GITHUB_OUTPUT" echo "locales_json=$all_locales_json" >> "$GITHUB_OUTPUT" exit 0 fi before_ref="$BEFORE_SHA" if [ -z "$before_ref" ] || [ "$before_ref" = "0000000000000000000000000000000000000000" ]; then before_ref="$(git rev-parse HEAD^)" fi changed_files="$(git diff --name-only "$before_ref" HEAD)" echo "changed files:" printf '%s\n' "$changed_files" if printf '%s\n' "$changed_files" | grep -Eq '^(ui/src/i18n/locales/en\.ts|ui/src/i18n/lib/types\.ts|ui/src/i18n/lib/registry\.ts|scripts/control-ui-i18n\.ts|\.github/workflows/control-ui-locale-refresh\.yml)$'; then echo "has_locales=true" >> "$GITHUB_OUTPUT" echo "locales_json=$all_locales_json" >> "$GITHUB_OUTPUT" exit 0 fi locales_json="$(printf '%s\n' "$changed_files" | node <<'EOF' const fs = require("node:fs"); const changed = fs.readFileSync(0, "utf8").split(/\r?\n/).filter(Boolean); const locales = new Set(); for (const file of changed) { let match = file.match(/^ui\/src\/i18n\/locales\/(.+)\.ts$/); if (match && match[1] !== "en") { locales.add(match[1]); continue; } match = file.match(/^ui\/src\/i18n\/\.i18n\/(.+)\.(?:meta\.json|tm\.jsonl)$/); if (match) { locales.add(match[1]); } } process.stdout.write(JSON.stringify([...locales])); EOF )" if [ "$locales_json" = "[]" ]; then echo "has_locales=false" >> "$GITHUB_OUTPUT" echo "locales_json=[]" >> "$GITHUB_OUTPUT" exit 0 fi echo "has_locales=true" >> "$GITHUB_OUTPUT" echo "locales_json=$locales_json" >> "$GITHUB_OUTPUT" refresh: needs: plan if: github.repository == 'openclaw/openclaw' && needs.plan.outputs.has_locales == 'true' strategy: fail-fast: false max-parallel: 4 matrix: locale: ${{ fromJson(needs.plan.outputs.locales_json) }} runs-on: ubuntu-latest name: Refresh ${{ matrix.locale }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: true 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.5' }} 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: refresh if: needs.refresh.result == 'success' runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 persist-credentials: true 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: Validate control UI locale refresh run: node --import tsx scripts/control-ui-i18n.ts check - name: Commit and push aggregate locale refresh env: TARGET_BRANCH: ${{ github.event.repository.default_branch }} run: | set -euo pipefail if ! git status --porcelain -- ui/src/i18n | grep -q .; then echo "No control UI locale changes." exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add -A ui/src/i18n git commit --no-verify -m "chore(ui): refresh control ui locales" for attempt in 1 2 3 4 5; do git fetch origin "${TARGET_BRANCH}" git rebase "origin/${TARGET_BRANCH}" if git push origin HEAD:"${TARGET_BRANCH}"; then exit 0 fi git rebase --abort >/dev/null 2>&1 || true echo "Aggregate push attempt ${attempt} failed; retrying." sleep $((attempt * 2)) done echo "Failed to push aggregate control UI locale update after retries." exit 1