← All playbooks
Playbook

Localization push

Localizes the user's own App Store / Google Play listing into new languages through their connected App Store Connect or Play Console account – picks target locales from markets with demand, researches the keywords people actually search in each country with AppGoat, writes a localized name, subtitle, keyword field and promotional text (Play title, short and full description) within the store limits, and pushes each locale as a dry run with a before/after diff the user approves.

~35–60 per locale + 1 per applied change credits a run/localization-push <app> [locales]

Turns 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.

Install

The appgoat/skills repository is private until its public release, so the plugin and git commands below are coming soon. Today, run the playbooks as prompts of the AppGoat MCP server (Claude, Cursor, ChatGPT) or download the SKILL.md from this page.

Install the AppGoat plugin (all playbooks plus the AppGoat MCP server), then run /mcp to sign in.

/plugin marketplace add appgoat/skills
/plugin install appgoat-playbooks@appgoat

Run it by asking for it in plain words, or by name:

/localization-push <app> [locales]

When to use

  • The user owns the app and connected App Store Connect and/or Play Console in AppGoat (https://dash.appgoat.ai/connections).
  • Not for rewriting the base-language listing (use aso-keywords-metadata-rewrite first, then this).

Inputs

  • 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.

Steps

Costs in AppGoat credits. Without the MCP, use the REST equivalents in the REST calls below.

  1. 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.
  2. Pick locales (when not given): 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.
  3. Base listing: read_connection {id, what: "listing", app, locale: <base>} - 1. Note the fields, their lengths, editable, the version and its state.
  4. Per target locale:
    • 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.
    • Translate 3–5 seed terms for the app's core job (meaning, not words) and research them: 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.
    • Write the localized fields with the decision rules (no calls).
  5. Propose per locale, with playbook: "localization-push" - 0:
    • App Store: 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"}}.
    • Play: 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.
  6. Show the user, per locale, the diff table and the target keywords (see Output format) and ask which locales to push. Offer to have a native speaker check the copy first.
  7. 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.
  8. Report what was pushed and what the user still has to do in the console (submit the version).

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).

Decision rules

  • Localize, don't translate. Pick the terms locals search for (highest popularity × fit from step 4), then write natural copy around them. A literal translation with no search demand loses.
  • Apple limits: name ≤ 30 characters, subtitle ≤ 30, keyword field ≤ 100 bytes (comma separated, no spaces after commas; CJK and Cyrillic characters take 2–3 bytes each, so count bytes, not characters), promotional text ≤ 170, description ≤ 4000.
  • Play limits: title ≤ 30, short description ≤ 80, full description ≤ 4000; Play indexes the description, so use each top term 2–4 times in natural sentences.
  • Keyword field: no words already in the localized name or subtitle (Apple combines them), no plurals of present words, no competitor brands, no "app"/"free", singular forms, local spelling. Some storefronts also index a second locale (e.g. US: en-US + es-MX; Apple's list): do not repeat terms across those pairs.
  • Brand stays as is (Latin script unless the user has a local brand name); put the top local term next to it in the name.
  • Claims: keep the base listing's facts only; no "#1", "best" or features the app lacks in that locale (e.g. unsupported languages in-app).
  • Skip a locale when no core term has popularity ≥ 10 there: report "low search demand" instead of pushing.
  • Never overwrite an existing localized listing that ranks (the app ranks top 10 for a term with popularity ≥ 30 in that country) without showing the user what would be lost.

Output format

# 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: <sum>.

Automate it

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
        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/
      - 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 '{"mcpServers":{"appgoat":{"command":"npx","args":["-y","@appgoat/mcp"],"env":{"APPGOAT_API_KEY":"${{ secrets.APPGOAT_API_KEY }}"}}}}'
            --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.

Without MCP: the REST calls

Without the AppGoat MCP server, call the API directly. Base URL https://api.appgoat.ai/v1, header x-api-key: $APPGOAT_API_KEY (create a key at https://dash.appgoat.ai). Responses are { "data": …, "meta": { "credits_used", "credits_remaining", … } }; errors are { "error": { "code", "message", "hint", "approve_url"? } } - read hint.

MCP toolREST call
list_connectionsGET /connections (free)
get_appGET /apps/{app}?include=markets
read_connection what=listingGET /connections/{id}/listings?app={app}&locale=de-DE
keyword_researchGET /keywords?qs=term1,term2&store=apple&country=de&include=related (5 credits per seed with related) or &app={app} (1 per term)
propose_actionPOST /actions with "dry_run": true, "source": "playbook:localization-push" (free)
apply_actionPOST /actions/{id}/approve (1 credit when applied)
reject_actionPOST /actions/{id}/reject (free)

Listing: { app, locale, fields: { name, subtitle, description, keywords, promotional_text, whats_new, … }, editable: [...], notes: [...], version, version_state } (Play: title, short_description, full_description, video).

CON=con_… APP=1234567890
curl -s -G -H "x-api-key: $APPGOAT_API_KEY" "https://api.appgoat.ai/v1/connections/$CON/listings" \
  -d app=$APP -d locale=de-DE | jq '.data | {fields, editable, notes}'

curl -s -H "x-api-key: $APPGOAT_API_KEY" -H "content-type: application/json" https://api.appgoat.ai/v1/actions \
  -d '{"type":"keywords.set","connection":"'$CON'","dry_run":true,"source":"playbook:localization-push",
       "params":{"app":"'$APP'","locale":"de-DE","mode":"replace","keywords":"gewohnheit,routine,ziel"}}' \
  | jq '.data | {id, diff, guardrail}'

More playbooks

/app-audit-lite~25–40 credits a run

App audit (lite)

Runs a quick App Store / Google Play audit of one app with live AppGoat data – listing quality, ratings trend, recent complaints, keyword rankings, competitors and their recent changes – and ends with a prioritized fix list.

/find-app-ideas~150–250 credits a run

Find app ideas

Finds underserved App Store / Google Play niches with live AppGoat data – search demand (keyword popularity) against weak supply (low ratings, abandoned incumbents, few strong apps) – and returns a ranked shortlist of app ideas with evidence and user pain points.

/aso-keywords-metadata-rewrite~40–80 credits a run

ASO keywords → metadata rewrite

Researches App Store / Google Play keywords for one app with live AppGoat data (current ranks, competitor keyword gaps, popularity, difficulty, relevance) and rewrites the title, subtitle, keyword field and description within the store character limits.

/competitor-digest~15–55 credits a run

Competitor digest

Writes a weekly competitor digest for one App Store / Google Play app from an AppGoat competitor watch – what each competitor changed (title, subtitle, description, screenshots, icon), price and in-app purchase changes, new versions with release-note summaries, rating and chart moves, keyword gains and losses against the user's app, keyword gaps and review themes – and ends with 3 recommended actions linked to other playbooks. Sets up or reuses the competitor_set watch.

/reviews-to-github-issues~2–10 credits a run

Reviews → GitHub issues

Turns recent negative App Store / Google Play reviews into deduplicated GitHub issues – one issue per problem topic, with linked and quoted reviews, new reviews added as comments to the existing issue instead of duplicates. Uses AppGoat review data and the gh CLI (or the GitHub MCP server).

/reply-to-every-review~1–3 + 1 per posted reply credits a run

Reply to every review

Drafts a reply to every unanswered App Store and Google Play review of the user's own app, in the app's voice, through their connected App Store Connect / Play Console account – each reply is a dry run with a before/after diff and AppGoat guardrail scores (policy, tone, claims), and only the replies the user approves are posted.

/apple-ads-optimizer~5–8 per campaign + 1 per applied change credits a run

Apple Ads optimizer

Optimizes the user's Apple Ads (Search Ads) campaigns through their connected Apple Ads account – reads the last 14 days of keyword and search term reports, raises bids on keywords that convert under the target CPA (capped +30% per run), lowers or pauses keywords that spend without installs, adds converting search terms as exact keywords and wasteful ones as negatives – every change a dry run with a diff the user approves.