|
Knowledgebase
EnterprisePlatform
PlatformAPIReact (MCP)CLIIntegrationsReact (Lingo Compiler)
Alpha
Guides
Changelog

Localization

  • Overview
  • Translation API
  • Web App
  • Mobile App
  • iOS with String Catalogs
  • Android with strings.xml
  • Emails
  • Static Content (e.g. .md, .json)

Workflows

  • Engine Setup with MCP
  • CI/CD

iOS App Localization with Xcode String Catalogs

Max PrilutskiyMax Prilutskiy·Updated 1 day ago·5 min read

The Lingo.dev CLI translates Xcode String Catalogs (.xcstrings) through a configured localization engine. String Catalogs are Apple's modern localization format, introduced in Xcode 15, that stores all languages in a single JSON file. The CLI mutates this file in place - no per-locale directories needed.

This guide walks through localizing an iOS app end-to-end: configuring the CLI, translating locally, and automating with GitHub Actions so translations ship on every push.

Demo repository

Clone or fork lingodotdev/ios-app-localization-example to follow along. The repository contains a working Xcode project with String Catalogs, a Lingo.dev CLI configuration, and a GitHub Actions workflow.

How String Catalogs Work#

Before Xcode 15, iOS localization required managing separate .strings and .stringsdict files across [locale].lproj/ directories. String Catalogs replace this with a single Localizable.xcstrings file that Xcode maintains automatically.

When you mark a string as localizable in SwiftUI or UIKit, Xcode detects it during build and adds an entry to the String Catalog. Each entry tracks the source string, its translations for every configured locale, and an optional comment field that provides context to translators.

AspectLegacy .stringsString Catalogs .xcstrings
File countOne per locale per tableOne file, all locales
FormatKey-value textStructured JSON
Plural supportSeparate .stringsdict fileBuilt-in plural rules
Xcode integrationManual export/importAutomatic detection
Translator notesNot supportedComment field per entry

The CLI's xcode-xcstrings bucket type parses this JSON structure, translates each entry through the localization engine, and writes translations back into the same file - preserving comments, plural rules, and metadata.

Prerequisites#

1

Create a localization engine

Every CLI run sends content through a localization engine - the configuration that determines which LLM model, glossary, brand voice, and instructions apply. Create one in the Lingo.dev dashboard and generate an API key.

2

Verify Node.js

The CLI requires Node.js 18 or higher:

bash
node -v
3

Enable localization in Xcode

In your Xcode project, go to Project Settings > Info > Localizations and add your target languages. Xcode creates the String Catalog entries for each locale you add. See Apple's localization documentation for details.

Configure the CLI#

Create an i18n.json file in your project root. The xcode-xcstrings bucket tells the CLI to parse the String Catalog format and mutate the file in place:

json
{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.15",
  "locale": {
    "source": "en",
    "targets": ["es", "fr", "de", "ja"]
  },
  "buckets": {
    "xcode-xcstrings": {
      "include": ["MyApp/Localizable.xcstrings"]
    }
  }
}

Because String Catalogs store all locales in a single file, no [locale] placeholder is needed in the include pattern. The CLI reads the source language entries, translates them, and writes all target languages back into the same .xcstrings file.

Multiple String Catalogs

If your project uses multiple String Catalog files (for example, one per framework target), list them all in the include array:

json
{
  "buckets": {
    "xcode-xcstrings": {
      "include": [
        "MyApp/Localizable.xcstrings",
        "MyAppWidgets/Localizable.xcstrings"
      ]
    }
  }
}

Translate Locally#

Set your API key and run the CLI:

bash
export LINGO_API_KEY="your-api-key"
npx lingo.dev@latest run

The CLI reads your String Catalog, identifies untranslated entries using the lockfile, translates the delta through your localization engine, and writes results back into the .xcstrings file. Open the file in Xcode to see translations populated for each configured locale.

To target a specific locale during development:

bash
npx lingo.dev@latest run --target-locale es

Translator Notes#

String Catalogs support a comment field per entry that the CLI includes in translation requests. These comments provide context to the localization engine - disambiguating terms, specifying tone, or describing where a string appears in the UI.

In Xcode, select a string in the String Catalog editor and add a comment in the inspector panel. The comment is stored in the .xcstrings JSON:

json
{
  "sourceLanguage": "en",
  "strings": {
    "Set": {
      "comment": "Refers to a collection of items, not the verb",
      "localizations": { }
    }
  }
}

The CLI sends this comment alongside the string, steering the model toward the correct interpretation. "Set" without context could become a verb in many languages - the comment eliminates that ambiguity. See Translator Notes for more patterns.

Plurals#

String Catalogs handle plural forms natively using CLDR plural rules. When you define a plural variation in Xcode, the String Catalog stores rules for each plural category (zero, one, two, few, many, other) that the target language requires.

The CLI preserves this structure during translation and generates the correct plural categories for each target locale. English uses two categories (one and other), but Arabic needs six, Polish needs four, and Japanese needs one. The localization engine handles these differences automatically.

Automate with GitHub Actions#

Add a workflow file at .github/workflows/translate.yml to translate on every push:

Translations commit directly to main - zero friction, ideal for small teams:

yaml
name: Translate
on:
  push:
    branches: [main]
permissions:
  contents: write
jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Lingo.dev
        uses: lingodotdev/lingo.dev@main
        with:
          api-key: ${{ secrets.LINGODOTDEV_API_KEY }}

Store your API key as LINGODOTDEV_API_KEY in Settings > Secrets and variables > Actions in your GitHub repository.

Verify Before Deploy#

Use the --frozen flag as a deployment gate to ensure no untranslated strings ship to production. The CLI exits with a non-zero status if any entries need translation:

bash
npx lingo.dev@latest run --frozen

Add this as a separate CI step before your build:

yaml
- name: Verify translations
  run: npx lingo.dev@latest run --frozen

Next Steps#

Mobile App Localization
Overview of all mobile platforms - iOS, Android, Flutter, React Native
CI/CD Workflows
GitHub Actions, GitLab CI, Bitbucket Pipelines patterns
Glossaries
Lock brand names and technical terms from translation
Translator Notes
Provide context to improve translation accuracy

Was this page helpful?