# App Store Localization Gap Finder (`conceivable_extension/app-store-localization-gap-finder`) Actor

Only ~12% of top-100 apps properly localize their App Store/Play Store listings. Pulls a listing across country storefronts and flags which got a real localization pass vs. a reused default — Japan, South Korea, and Germany are typically the biggest gaps.

- **URL**: https://apify.com/conceivable\_extension/app-store-localization-gap-finder.md
- **Developed by:** [joseph fadero](https://apify.com/conceivable_extension) (community)
- **Categories:** Developer tools, Integrations, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 market fully localizeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## App Store Localization Gap Finder

**Only ~12% of top-100 apps properly localize their App Store/Play Store listings beyond basic translation.** Most apps ship the same screenshots and near-identical copy to every country storefront — Japan, South Korea, and Germany are consistently the highest-opportunity underserved markets, the countries most likely to be getting a reused-default listing instead of a real localization pass. This actor finds out which of those gaps exist for a specific app, yours or a competitor's.

### What it does

Pulls an app's listing (title, description, screenshots) across every country storefront you give it, diffs each one against the default (`us`) listing, and classifies each country:

- **`identical_to_default`** — reused default listing, no real localization done (the opportunity)
- **`partial`** — some elements localized (e.g. description translated, screenshots not)
- **`fully_localized`** — title, description, and screenshots all genuinely differ from default

Also rolls up a per-app summary: X of N countries localized, with the highest-opportunity countries surfaced first.

### Verified, not guessed

Unlike some of the platform-specific scraping in this actor set, both fetchers here were tested against real, live data while building this actor — not assumed to work:

- **iOS** uses Apple's iTunes Lookup API (`itunes.apple.com/lookup?id=...&country=...`) — a genuinely public, keyless, documented endpoint. Confirmed live that `country=us` vs. `country=jp` returns real, independently-translated description text for the same app.
- **Android** has no public listing API for third-party apps (Google's official Play Developer API only exposes the calling developer's own apps), so this reads the same embedded JSON state block (`AF_initDataCallback({key: 'ds:5', ...})`) the Play Store page itself hydrates from — no JS execution needed, it's present in the plain HTML response. The exact index path used (`data[1][2][0][0]` = title, `data[1][2][72][0][1]` = description, `data[1][2][78][0]` = screenshots) was verified live against two unrelated real apps (Facebook, Spotify) and confirmed to return genuinely translated content across `en/us`, `ja/jp`, and `de/de` locales.
- **Screenshot hashing** (average-hash/aHash) was verified by decoding two different real App Store screenshots and confirming distinct hashes, then re-decoding the same screenshot twice and confirming identical hashes.
- A full end-to-end run was executed against real data for both platforms during development (Facebook on iOS across us/jp/de, Spotify on Android across us/jp/de/kr) and produced correct, sensible localization classifications.

**One caveat that doesn't go away with testing:** the Android path reads undocumented Google page-render internals, not a stable public contract. It can drift if Google changes how the page renders, unlike the iOS fetcher's official API. If Android checks start returning `no listing returned`, that's the first thing to check.

### Neither platform needs a browser

Both fetchers work over plain HTTP with realistic headers — no Playwright, no JS execution, no anti-bot cat-and-mouse. This runs on the lightweight `apify/actor-node:20` base image, not the heavier Playwright/Chrome image most of this portfolio uses.

### The keyword limitation — read this before relying on `keywords`

**`keywords` is not the real ASO keywords field, for either platform, for any app — including your own.** Apple's keyword field is private to the app's own developer via App Store Connect and isn't exposed by any public API. Google Play has no public keywords field at all. What this actor outputs instead is a simple word-frequency inference from the visible title+description text — genuinely useful as a rough signal, but not authoritative ASO data. Two specific limitations:

- **Non-Latin scripts** (Japanese, Korean, Chinese) don't use spaces between words, so whitespace-based extraction degrades to near-useless there — expected, not a bug.
- **Stopword filtering is English-only.** German, French, Spanish, etc. keyword lists will include foreign function words ("und", "deine", "mit") since there's no per-language stopword list. Treat non-English `keywords` output as a rougher signal than the English default.

### Input schema

```json
{
  "appId": "284882215",
  "platform": "ios",
  "targetCountries": ["us", "gb", "jp", "kr", "de"],
  "compareKeywords": true,
  "compareScreenshots": true
}
```

`appId` — iOS: numeric App Store track ID. Android: Play Store package name (e.g. `com.spotify.music`). The `us` listing is always fetched as the comparison baseline even if you don't list it in `targetCountries`.

### Output schema

Per-country rows (`recordType: "country"`):

```json
{
  "appId": "string",
  "country": "string",
  "title": "string | null",
  "description": "string | null",
  "keywords": ["array — inferred, see limitation above"],
  "screenshotUrls": ["array"],
  "isLocalized": "boolean",
  "localizationGapType": "identical_to_default | partial | fully_localized",
  "titleIdentical": "boolean",
  "descriptionIdentical": "boolean",
  "screenshotsIdenticalCount": "number",
  "screenshotsTotalCount": "number",
  "checkedAt": "ISO timestamp",
  "status": "success | failed"
}
```

Plus one summary row per run (`recordType: "summary"`) with `countriesChecked`, `countriesLocalized`, `localizationScorePercent`, and `topOpportunityCountries`.

### Pricing

| Event | Price |
|---|---|
| Run started | £0.05 |
| Country checked (localized) | £0.02 |
| Gap detected (identical to default) | £0.03 |
| Country check failed | free |

### n8n integration

- **Workflow A (trigger):** scheduled run against the user's own app(s) across all target countries.
- **Workflow B (processing):** generate a localization scorecard (X% localized) from the summary row, flagging Japan/South Korea/Germany first if they show up in `topOpportunityCountries` — the underserved-market pattern this actor is built around.

# Actor input Schema

## `appId` (type: `string`):

iOS: numeric App Store track ID (e.g. '284882215'). Android: Play Store package name (e.g. 'com.facebook.katana').

## `platform` (type: `string`):

Which storefront to check.

## `targetCountries` (type: `array`):

Lowercase 2-letter country codes to check, e.g. 'us', 'gb', 'jp', 'kr', 'de'. The 'us' listing is always fetched as the default/baseline for comparison even if you don't list it here.

## `compareKeywords` (type: `boolean`):

Infer likely keywords from title+description text. This is NOT the real App Store Connect keywords field — that field is private to an app's own developer and isn't exposed by any public API for any app, including your own. Treat this as an approximation, and expect it to be unreliable for non-Latin-script languages (Japanese, Korean) since it splits on whitespace.

## `compareScreenshots` (type: `boolean`):

Perceptually hash each country's screenshots against the default listing's same-position screenshot, to distinguish exact-reused vs. near-duplicate vs. genuinely different images.

## Actor input object example

```json
{
  "appId": "284882215",
  "platform": "ios",
  "targetCountries": [
    "us",
    "gb",
    "jp",
    "kr",
    "de"
  ],
  "compareKeywords": true,
  "compareScreenshots": true
}
```

# Actor output Schema

## `resultsDatasetUrl` (type: `string`):

Per-country App Store/Play Store listing diffs against the default listing, classified as identical, partial, or fully localized, produced by this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "appId": "284882215",
    "targetCountries": [
        "us",
        "gb",
        "jp",
        "kr",
        "de"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("conceivable_extension/app-store-localization-gap-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "appId": "284882215",
    "targetCountries": [
        "us",
        "gb",
        "jp",
        "kr",
        "de",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("conceivable_extension/app-store-localization-gap-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "appId": "284882215",
  "targetCountries": [
    "us",
    "gb",
    "jp",
    "kr",
    "de"
  ]
}' |
apify call conceivable_extension/app-store-localization-gap-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conceivable_extension/app-store-localization-gap-finder"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7EZAJkcLBJl0Upig9/builds/T9lurafi20cCoQCmI/openapi.json
