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.
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@appgoatRun it by asking for it in plain words, or by name:
/localization-push <app> [locales]Add the connector: Settings → Connectors → Add custom connector → https://api.appgoat.ai/mcp.
Pick localization-push from the “+” menu → AppGoat (it is served as a prompt), or upload the skill folder as a zip in Settings → Capabilities → Skills.
Add the MCP server to .cursor/mcp.json:
{ "mcpServers": { "appgoat": { "url": "https://api.appgoat.ai/mcp" } } }Type / in the chat and pick localization-push, or save the SKILL.md as .cursor/rules/localization-push.mdc.
In developer mode (Settings → Apps & Connectors → Advanced), add a connector with https://api.appgoat.ai/mcp and sign in. Then ask:
Use the AppGoat list_playbooks tool and run the localization-push playbook.Copy the skill folder into your agent’s skills directory and add the MCP server:
git clone https://github.com/appgoat/skills /tmp/appgoat-skills
cp -r /tmp/appgoat-skills/skills/localization-push ~/.claude/skills/
claude mcp add --transport http appgoat https://api.appgoat.ai/mcpWhen 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-rewritefirst, 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 asde-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.
list_connections{} - 0. Find theascorplay_consoleconnection with the app linked (status: active;mode: read_onlycan only dry-run). None: send the user to https://dash.appgoat.ai/connections and stop.- 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 withkeyword_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. - Base listing:
read_connection{id, what: "listing", app, locale: <base>} - 1. Note the fields, their lengths,editable, the version and its state. - 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-callpendingterms after a minute.- Write the localized fields with the decision rules (no calls).
- 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?}}} andpropose_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: aBLOCKScheck means fix it (shorten) or, for "cannot be edited right now", see the ASC note below.
- App Store:
- 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.
apply_action{id} per approved action - 1 per applied action.pending_approval→ give the user theapprove_url; "needs approval by a person" → hand over the link, don't retry; "account changed" → show the new diff and ask beforeforce: true.reject_actionthe declined ones - 0.- 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 tool | REST call |
|---|---|
list_connections | GET /connections (free) |
get_app | GET /apps/{app}?include=markets |
read_connection what=listing | GET /connections/{id}/listings?app={app}&locale=de-DE |
keyword_research | GET /keywords?qs=term1,term2&store=apple&country=de&include=related (5 credits per seed with related) or &app={app} (1 per term) |
propose_action | POST /actions with "dry_run": true, "source": "playbook:localization-push" (free) |
apply_action | POST /actions/{id}/approve (1 credit when applied) |
reject_action | POST /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 runApp 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 runFind 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 runASO 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 runCompetitor 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 runReviews → 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 runReply 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 runApple 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.