# ASC Locale

A [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code) that automates App Store metadata translation and uploading. Claude handles all translations directly — no third-party translation APIs needed.

## What it does

- **Translates** your App Store listing (name, subtitle, description, keywords, promotional text, release notes, subscription metadata) to 15+ languages
- **Validates** character limits before upload to prevent App Store Connect rejections
- **Uploads** all metadata via Fastlane `deliver`
- **Uploads subscriptions** via raw App Store Connect API (Fastlane doesn't support this)
- **Downloads** existing metadata from App Store Connect
- **Organizes and uploads screenshots** via App Store Connect API

## Requirements

- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
- Node.js 18+ (uses native `fetch` and `crypto`)
- Ruby (for Fastlane)
- An [App Store Connect API key](#get-an-app-store-connect-api-key)

## Installation

### 1. Install the skill

```bash
npx github:AustineA/asc-locale
```

This will:
- Copy the skill to `~/.agents/skills/asc-locale/`
- Automatically symlink it to all detected AI coding agents (Claude Code, Cursor, Windsurf, Antigravity, Trae, Amp)

Other commands:
```bash
npx github:AustineA/asc-locale status      # Check installation status
npx github:AustineA/asc-locale uninstall   # Remove skill and all links
```

### 2. Get an App Store Connect API key

1. Go to [App Store Connect > Users and Access > Integrations > App Store Connect API](https://appstoreconnect.apple.com/access/integrations/api)
2. Click **Generate API Key**
3. Select role: **Admin** or **App Manager**
4. Download the `.p8` key file
5. Note the **Key ID** and **Issuer ID**

Store the key:

```bash
mkdir -p ~/.appstoreconnect
mv ~/Downloads/AuthKey_XXXXXXXXXX.p8 ~/.appstoreconnect/
```

### 3. Set up your project

Open your iOS/Expo project in Claude Code and say:

```
Set up app store metadata for this project
```

Claude will automatically:

1. Create `.asc-config.json` with your API credentials
2. Copy `scripts/asc-metadata.mjs` to your project
3. Create `Gemfile` and `fastlane/Fastfile`
4. Install Fastlane via `bundle install`
5. Download your existing metadata from App Store Connect

#### Manual setup

If you prefer doing it yourself:

```bash
# 1. Create config (fill in your values)
cp ~/.agents/skills/asc-locale/templates/asc-config.example.json .asc-config.json

# 2. Copy the script
mkdir -p scripts
cp ~/.agents/skills/asc-locale/templates/asc-metadata.mjs scripts/asc-metadata.mjs

# 3. Set up Fastlane
cp ~/.agents/skills/asc-locale/templates/Gemfile ./Gemfile
mkdir -p fastlane
cp ~/.agents/skills/asc-locale/templates/Fastfile fastlane/Fastfile
# Edit fastlane/Fastfile — replace YOUR_KEY_ID, YOUR_ISSUER_ID, key path

# 4. Install & download
bundle install
node scripts/asc-metadata.mjs setup

# 5. Gitignore the config
echo ".asc-config.json" >> .gitignore
```

## Usage

### Translate your app listing

Just tell Claude:

```
Translate my app listing
```

Claude reads your `en-US` metadata, translates everything to all target languages, validates character limits, and writes the files. No API keys needed — Claude does it natively.

### Translate specific files

```
Translate just the release notes
Re-translate the description and keywords
```

### Upload to App Store Connect

```
Upload the metadata
```

Or directly:

```bash
node scripts/asc-metadata.mjs upload          # All metadata via Fastlane
node scripts/asc-metadata.mjs upload-subs     # Subscriptions via ASC API
```

### Download existing metadata

```bash
node scripts/asc-metadata.mjs setup
```

### Screenshots

```bash
# Organize from fastlane/Translated/ into fastlane/screenshots/
node scripts/asc-metadata.mjs organize-screenshots [device]

# Upload to App Store Connect
node scripts/asc-metadata.mjs upload-screenshots [--overwrite]
```

Source screenshots should be named: `01 - AppName(de).jpg`

## Configuration

### `.asc-config.json`

```json
{
  "bundleId": "com.example.myapp",
  "keyId": "YOUR_KEY_ID",
  "issuerId": "YOUR_ISSUER_ID",
  "keyPath": "~/.appstoreconnect/AuthKey_YOUR_KEY_ID.p8"
}
```

| Field      | Description                  | Where to find                                                                      |
| ---------- | ---------------------------- | ---------------------------------------------------------------------------------- |
| `bundleId` | Your app's bundle identifier | `app.json`, Xcode project, or App Store Connect                                    |
| `keyId`    | App Store Connect API Key ID | [ASC Integrations page](https://appstoreconnect.apple.com/access/integrations/api) |
| `issuerId` | App Store Connect Issuer ID  | Same page                                                                          |
| `keyPath`  | Path to `.p8` key file       | Where you saved it (`~` supported)                                                 |

> **Add `.asc-config.json` to `.gitignore`** — it contains API credentials.

## Commands

| Command                            | Description                                       |
| ---------------------------------- | ------------------------------------------------- |
| `setup`                            | Download existing metadata from App Store Connect |
| `upload`                           | Upload all metadata via Fastlane deliver          |
| `upload-subs`                      | Upload subscription metadata via ASC API          |
| `organize-screenshots [device]`    | Organize screenshots from `Translated/`           |
| `upload-screenshots [--overwrite]` | Upload screenshots to ASC                         |

### Screenshot device types

`iphone55`, `iphone58`, `iphone61`, `iphone65`, `iphone67` (default), `iphone69`, `ipadpro129`, `ipadpro11`, `ipadpro3gen129`

## App Store Character Limits

| Field                                     | Max Characters |
| ----------------------------------------- | -------------- |
| App name (`name.txt`)                     | 30             |
| Subtitle (`subtitle.txt`)                 | 30             |
| Keywords (`keywords.txt`)                 | 100            |
| Promotional text (`promotional_text.txt`) | 170            |
| Description (`description.txt`)           | 4000           |
| Release notes (`release_notes.txt`)       | 4000           |
| Subscription display name                 | 30             |
| Subscription description                  | 45             |

## Project Structure (after setup)

```
your-project/
  .asc-config.json                 # API credentials (gitignored)
  Gemfile                          # Fastlane dependency
  scripts/
    asc-metadata.mjs               # CLI tool (zero dependencies)
  fastlane/
    Fastfile                       # Upload lane
    metadata/
      en-US/                       # Source locale
        name.txt
        subtitle.txt
        description.txt
        keywords.txt
        promotional_text.txt
        release_notes.txt
        subscription_groups/
          GroupName/
            display_name.txt
            ProductId/
              display_name.txt
              description.txt
      es-ES/                       # Translated (auto-generated)
        ...
      de-DE/
        ...
    screenshots/                   # Organized screenshots
      en-US/
        iphone67/
          1.png
    Translated/                    # Screenshot source files
      01 - AppName(de).jpg
```

## Troubleshooting

| Error                                                   | Cause                           | Fix                                     |
| ------------------------------------------------------- | ------------------------------- | --------------------------------------- |
| `name cannot be longer than 30 characters`              | Translated name too long        | Shorten: keep brand, drop filler words  |
| `Description cannot be longer than 4000 characters`     | Translated description too long | Condense the subscription/legal section |
| `Keywords cannot be longer than 100 characters`         | Translated keywords too long    | Remove trailing keywords                |
| `Promotional text cannot be longer than 170 characters` | Translated promo too long       | Tighten phrasing                        |
| `Cannot edit when in ACTIVE state`                      | App info locked on live version | Non-fatal. Create a new version first   |
| `fastlane finished with errors`                         | Usually character limits        | Run validation, fix, re-upload          |
| `bundle exec fastlane not found`                        | Fastlane not installed          | Run `bundle install`                    |
| `Config file not found`                                 | Missing `.asc-config.json`      | Create it from the example template     |
| `App not found`                                         | Wrong bundle ID                 | Check `bundleId` in `.asc-config.json`  |

## How it works

1. **Setup**: Downloads existing metadata from App Store Connect via the REST API (JWT auth with your `.p8` key)
2. **Translation**: Claude translates directly in the conversation — reads en-US files, translates, validates character limits, writes translated files
3. **Upload (metadata)**: Fastlane `deliver` handles all app metadata upload (description, keywords, name, subtitle, etc.) — it can create new locale combinations that the raw API can't
4. **Upload (subscriptions)**: The Node.js script calls the ASC API directly because Fastlane/Spaceship doesn't support subscription localizations
5. **Screenshots**: The Node.js script handles the multi-step upload (reserve → upload binary chunks → commit with MD5)

## License

MIT
