Install
openclaw skills install @rafalzawadzki/localization-pushLocalizes the user's own App Store / Google Play listing into new languages through App Store Connect or Play Console - researches local keywords with AppGoat, writes localized metadata within store limits, and pushes each locale as a dry run the user approves.
openclaw skills install @rafalzawadzki/localization-pushTurns one good listing into many: for each target locale it researches local search demand,
writes metadata that targets the terms people search for there (not a word-for-word translation),
and writes it to App Store Connect or Play Console after the user has seen the diff. Every write is
a propose_action dry run first (free), applied only on approval.
aso-keywords-metadata-rewrite first, then this).app (required): a linked app (App Store id or Play package; a store URL or name works for the
research tools, then match it to the linked app).locales (optional): comma list such as de-DE,fr-FR,ja,es-MX. Default: suggest 3–5 from step 2
and ask the user to confirm.Ask the user for the base locale (default en-US), the brand name that stays untranslated, terms
that must not change (product names), and whether a native speaker will review the copy.
Limits per run: at most 5 locales.
Costs in AppGoat credits. Without the MCP, use the REST equivalents in reference/api.md.
list_connections {} - 0. Find the asc or play_console connection with the app linked
(status: active; mode: read_only can only dry-run). None: send the user to
https://dash.appgoat.ai/connections and stop.get_app {app, include: ["markets"]} - 1: the countries
where the app is available with ratings per market, and the languages it already supports.
Suggest locales where the app has ratings but no localization, or large markets in its
category (de-DE, fr-FR, ja, ko, es-MX, pt-BR, it, zh-Hans). Optionally check demand with
keyword_research {terms: 2–3 translated core terms, store, country} - 1 per term per
candidate country: popularity ≥ 20 for the core term means real search demand.read_connection {id, what: "listing", app, locale: } - 1. Note the
fields, their lengths, editable, the version and its state.read_connection {id, what: "listing", app, locale} - 1. Shows what exists already
(an empty listing will be created by the push) and what is editable now.keyword_research {terms: seeds, store, country: <the locale's main storefront>, include: ["related"]} - 5 per seed.keyword_research {terms: ≤ 25 candidates, store, country, app} - 1 per term: popularity,
difficulty and the app's current rank there. Re-call pending terms after a minute.playbook: "localization-push" - 0:
propose_action {type: "listing.update", connection, params: {app, locale, fields:
{name, subtitle, promotional_text, description?}}} and propose_action {type: "keywords.set",
connection, params: {app, locale, keywords: "a,b,c", mode: "replace"}}.propose_action {type: "listing.update", connection, params: {app, locale, fields:
{title, short_description, full_description}}}.
The dry run checks the limits and editability: a BLOCKS check means fix it (shorten) or, for
"cannot be edited right now", see the ASC note below.apply_action {id} per approved action - 1 per applied action. pending_approval → give
the user the approve_url; "needs approval by a person" → hand over the link, don't retry;
"account changed" → show the new diff and ask before force: true. reject_action the declined ones - 0.Budget: ~35–60 credits per locale (research dominates) + 1 per applied action.
App Store Connect: name, subtitle, description, keywords and what's new can only change on a
version in Prepare for Submission; promotional text can change on the live version. If the
listing says these fields are not editable, either push only promotional_text now or ask the user
to create the next version in App Store Connect, then run step 5 again. The localized metadata
goes live with that version after App Review. Play changes publish after Google's review (hours).
# Localization push: <app> (<store>) - <locales>
## de-DE (Germany) · version 2.5 (PREPARE_FOR_SUBMISSION)
| Field | Now | Proposed | Length |
|---|---|---|---|
| Name | (none) | Streaks: Gewohnheiten Tracker | 29/30 |
| Subtitle | (none) | Routinen, Ziele & Tagebuch | 26/30 |
| Keywords | (none) | gewohnheit,routine,ziel,… | 98/100 bytes |
| Promotional text | (none) | … | 142/170 |
Target terms: gewohnheiten tracker (pop 41, diff 55), routine (pop 38, …), …
Guardrail: passed 0.93 · actions act_…, act_…
## ja (Japan) - skipped: core terms below popularity 10.
Reply "push all", "push de-DE only", or edit a field.
Run report: pushed locales, pending approvals (links), what to do in the console next ("submit version 2.5 for review"). Credits used: .
Localization is a one-off push per release, not a schedule. Re-run it when a new version is in Prepare for Submission so the localized keyword fields follow the base listing.
GitHub Action (.github/workflows/appgoat-localization.yml, run by hand; secrets ANTHROPIC_API_KEY, APPGOAT_API_KEY).
It submits the localized listing for approval in the AppGoat dashboard; nothing is written until
a person approves it at https://dash.appgoat.ai/approvals.
name: AppGoat localization push
on:
workflow_dispatch:
inputs:
locales:
description: Comma list of locales, e.g. de-DE,fr-FR
required: true
permissions:
contents: read
jobs:
localize:
runs-on: ubuntu-latest
steps:
- name: Install AppGoat playbooks and MCP config
env:
APPGOAT_API_KEY: ${{ secrets.APPGOAT_API_KEY }}
run: |
git clone --depth 1 https://github.com/appgoat/skills /tmp/appgoat-skills
mkdir -p .claude/skills
cp -r /tmp/appgoat-skills/skills/* .claude/skills/
# Hosted AppGoat MCP server; the key goes into a file, not the command line.
jq -n --arg key "$APPGOAT_API_KEY" \
'{mcpServers: {appgoat: {type: "http", url: "https://api.appgoat.ai/mcp", headers: {"x-api-key": $key}}}}' \
> "$RUNNER_TEMP/appgoat-mcp.json"
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Use the localization-push skill for app <app id> with locales ${{ inputs.locales }} and base locale en-US.
This is an unattended run: instead of asking in step 6, call apply_action once for every action without
a blocking check (this submits it for approval in the dashboard). Never call apply_action with force.
Print the per-locale tables as the job summary.
claude_args: |
--mcp-config ${{ runner.temp }}/appgoat-mcp.json
--allowedTools mcp__appgoat__list_connections,mcp__appgoat__read_connection,mcp__appgoat__get_app,mcp__appgoat__keyword_research,mcp__appgoat__get_job,mcp__appgoat__propose_action,mcp__appgoat__apply_action,mcp__appgoat__reject_action,mcp__appgoat__get_action
Claude Code routine (one-off reminder after you create a version):
/schedule once tomorrow 09:00 - Run the localization-push playbook for <app id> into de-DE, fr-FR and es-MX, submit the actions for approval and send me the per-locale tables.