Files
openclaw/.github/workflows/native-app-locale-refresh.yml
2026-07-13 11:41:15 +01:00

337 lines
12 KiB
YAML

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/android-app-i18n.ts
- scripts/apple-app-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: Refresh Android native resources
run: node --import tsx scripts/android-app-i18n.ts sync
- name: Refresh Apple native resources
run: node --import tsx scripts/apple-app-i18n.ts sync-ios --write
- name: Validate native locale refresh
run: |
node --import tsx scripts/native-app-i18n.ts check
node --import tsx scripts/android-app-i18n.ts check
node --import tsx scripts/apple-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
apps/.i18n/apple-translation-contradictions.json
apps/android/app/src/main/java/ai/openclaw/app/i18n/NativeStringResources.kt
apps/android/app/src/main/res/values*/assistant.xml
apps/android/app/src/main/res/values*/strings.xml
apps/ios/Resources/Localizable.xcstrings
apps/ios/Sources/*.lproj/InfoPlist.strings
apps/ios/WatchApp/*.lproj/InfoPlist.strings
apps/ios/ShareExtension/*.lproj/InfoPlist.strings
apps/ios/ActivityWidget/*.lproj/InfoPlist.strings
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/android-app-i18n.ts
scripts/apple-app-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`
- `node --import tsx scripts/android-app-i18n.ts check`
- `node --import tsx scripts/apple-app-i18n.ts check`