name: Native App Locale Refresh on: push: branches: - main paths: - apps/android/app/src/main/** - apps/ios/** - apps/macos/Sources/** - apps/macos/Package.swift - apps/shared/OpenClawKit/Sources/** - apps/.i18n/native/** - apps/.i18n/native-source.json - scripts/control-ui-i18n.ts - scripts/native-app-i18n.ts - ui/src/i18n/.i18n/glossary.*.json - .github/actions/create-generated-pr-tokens/action.yml - .github/actions/publish-generated-pr/action.yml - .github/workflows/native-app-locale-refresh.yml workflow_dispatch: permissions: contents: read concurrency: group: native-app-locale-refresh # Finish the active full refresh; GitHub retains only the newest pending run for this group. # Publisher overlap checks defer stale generated paths to that queued 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 default branch head id: base env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_TOKEN: ${{ github.token }} REPOSITORY: ${{ github.repository }} run: | set -euo pipefail sha="$( timeout --signal=TERM --kill-after=10s 60s \ gh api --method GET "repos/${REPOSITORY}/commits/${DEFAULT_BRANCH}" --jq .sha )" if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then echo "Unable to resolve ${DEFAULT_BRANCH} 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' strategy: fail-fast: false max-parallel: 2 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, sv, ] runs-on: ubuntu-latest name: Refresh native ${{ 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 native locale artifact 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/native-app-i18n.ts sync --write --locale "${LOCALE}" return fi OPENCLAW_CONTROL_UI_I18N_PROVIDER="$provider" \ OPENCLAW_CONTROL_UI_I18N_MODEL="$model" \ node --import tsx scripts/native-app-i18n.ts sync --write --locale "${LOCALE}" } 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 native 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 native 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}/native-locale-${LOCALE}" mkdir -p "${artifact_dir}" git add -A apps/.i18n/native git diff --cached --binary --full-index -- apps/.i18n/native > "${artifact_dir}/${LOCALE}.patch" - name: Upload locale artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: native-locale-${{ matrix.locale }} path: ${{ runner.temp }}/native-locale-${{ matrix.locale }}/${{ matrix.locale }}.patch if-no-files-found: error retention-days: 1 finalize: name: Commit native locale refresh needs: [resolve-base, publisher-preflight, refresh] if: >- needs.resolve-base.result == 'success' && needs.publisher-preflight.result == 'success' && needs.refresh.result == 'success' 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: native-locale-* path: ${{ runner.temp }}/native-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}/native-locale-artifacts" -type f -name '*.patch' | sort) - name: Setup Node environment uses: ./.github/actions/setup-node-env with: install-bun: "false" # Every worker observes the same source inventory. Generate it once here # so locale patches remain independent and can be applied sequentially. - name: Refresh shared native inventory run: node --import tsx scripts/native-app-i18n.ts sync --write - name: Validate native locale refresh run: node --import tsx scripts/native-app-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/native-app-locale-refresh commit-message: "chore(i18n): refresh native locales" pr-title: "chore(i18n): refresh native locales" generated-paths: | apps/.i18n/native apps/.i18n/native-source.json invalidation-paths: | apps/android/app/src/main apps/ios apps/macos/Sources apps/macos/Package.swift apps/shared/OpenClawKit/Sources scripts/control-ui-i18n.ts scripts/native-app-i18n.ts ui/src/i18n/.i18n/glossary.*.json .github/actions/create-generated-pr-tokens/action.yml .github/actions/publish-generated-pr/action.yml .github/workflows/native-app-locale-refresh.yml pr-body: | ## What Problem This Solves Keeps generated native app locales synchronized without bypassing protected-branch checks. ## Why This Change Was Made The Native App 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/native-app-i18n.ts check`