smithery.ai

app-localization

iOS/macOS app localization management for Tuist-based projects with .strings files. Use when: (1) Adding new translation keys to modules, (2) Validating .strings files for missing/duplicate keys, (3) Syncing translations across languages, (4) AI-powered translation from English to other locales, (5) Checking placeholder consistency (%@, %d), (6) Generating localization reports, (7) Updating Swift code to use localized strings instead of hardcoded text.

First seen Mar 26, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,421 B
  • docs SUMMARY.md 484 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

App Localization

Manage iOS/macOS .strings files in Tuist-based projects.

Project Structure

<ModuleName>/
├── Resources/
│   ├── en.lproj/Localizable.strings       # Primary language (English)
│   ├── <locale>.lproj/Localizable.strings # Additional locales
│   └── ...
├── Derived/
│   └── Sources/
│       └── TuistStrings+<ModuleName>.swift  # Generated by Tuist
└── Sources/
    └── **/*.swift  # Uses <ModuleName>Strings.Section.key

After editing .strings files, run tuist generate to regenerate type-safe accessors.

Complete Localization Workflow

Step 1: Identify Hardcoded Strings

Find hardcoded strings in Swift files:

# Find Text("...") patterns with hardcoded strings
grep -rn 'Text("[A-Z]' <ModuleName>/Sources/
grep -rn 'title: "[A-Z]' <ModuleName>/Sources/
grep -rn 'label: "[A-Z]' <ModuleName>/Sources/
grep -rn 'placeholder: "[A-Z]' <ModuleName>/Sources/

Step 2: Add Translation Keys

Add keys to all language files:

en.lproj/Localizable.strings (primary):

/* Section description */
"section.key.name" = "English value";
"section.key.withParam" = "Value with %@";

Other locales (translate appropriately):

"section.key.name" = "<translated value>";
"section.key.withParam" = "<translated> %@";

Step 3: Generate Type-Safe Accessors

tuist generate

This creates Derived/Sources/TuistStrings+<ModuleName>.swift with accessors:

  • <ModuleName>Strings.Section.keyName (static property)
  • <ModuleName>Strings.Section.keyWithParam(value) (static function for %@ params)

See [references/tuist-strings-patterns.md](references/tuist-strings-patterns.md) for detailed patterns.

Step 4: Update Swift Code

Replace hardcoded strings with generated accessors.

Pattern Mapping

Hardcoded Pattern Localized Pattern
Text("Title") Text(<Module>Strings.Section.title)
Text("Hello, \(name)") Text(<Module>Strings.Section.hello(name))
title: "Submit" title: <Module>Strings.Action.submit
placeholder: "Enter..." placeholder: <Module>Strings.Field.placeholder

Example Transformations

Before:

Text("Settings")
    .font(.headline)

TextField("Enter your name", text: $name)

Button("Submit") { ... }

Text("Hello, \(userName)!")

After:

Text(<Module>Strings.Section.settings)
    .font(.headline)

TextField(<Module>Strings.Field.namePlaceholder, text: $name)

Button(<Module>Strings.Action.submit) { ... }

Text(<Module>Strings.Greeting.hello(userName))

Handling Parameters and Plurals

String with parameter (key: "search.noResults" = "No results for \"%@\""):

// Before
Text("No results for \"\(searchText)\"")

// After
Text(<Module>Strings.Search.noResults(searchText))

Conditional plurals:

// Keys:
// "item.count" = "%d item"
// "item.countPlural" = "%d items"

// Swift:
let label = count == 1
    ? <Module>Strings.Item.count(count)
    : <Module>Strings.Item.countPlural(count)

Multiple parameters (key: "message.detail" = "%@ uploaded %d files"):

Text(<Module>Strings.Message.detail(userName, fileCount))

Step 5: Validate Changes

  1. Build the project to catch missing keys
  2. Run validation script to check consistency:
python scripts/validate_strings.py /path/to/<ModuleName>

AI-Powered Translation

When translating strings to non-English locales:

  1. Read the English source string
  2. Consider context from the key name (e.g., search.noResults = search UI)
  3. Translate appropriately for the target locale:

- zh-Hans: Simplified Chinese, formal but friendly - zh-Hant: Traditional Chinese - ja: Japanese, polite form (desu/masu style) - ko: Korean, polite form (hamnida/yo style) - de/fr/es/etc.: Appropriate regional conventions

  1. Preserve all placeholders exactly (%@, %d, %ld, etc.)

Translation context by UI element:

  • Labels: Keep concise
  • Buttons: Action-oriented verbs
  • Placeholders: Instructive tone
  • Error messages: Helpful and clear
  • Confirmations: Clear consequences

Validation Scripts

Validate .strings Files

python scripts/validate_strings.py /path/to/<ModuleName>

Checks for:

  • Missing keys between languages
  • Duplicate keys
  • Placeholder mismatches (%@, %d, %ld)
  • Untranslated strings (value = English)

Sync Missing Translations

Report missing keys:

python scripts/sync_translations.py /path/to/<ModuleName> --report

Add missing keys as placeholders:

python scripts/sync_translations.py /path/to/<ModuleName> --sync

Key Naming Convention

Pattern: "domain.context.element" → <Module>Strings.Domain.Context.element

Domain-Focused Naming (User Mental Model)

Keys should reflect what the user is doing, not technical UI components:

User Mental Model Key Pattern Generated Accessor
"I'm looking at my profile" "profile.name" Strings.Profile.name
"I'm testing a build" "betaBuild.whatToTest" Strings.BetaBuild.whatToTest
"I'm adding a tester" "testerGroup.addTester" Strings.TesterGroup.addTester
"Something went wrong with sync" "sync.error.failed" Strings.Sync.Error.failed

Good vs Bad Examples

Bad (Technical) Good (Domain-Focused)
button.save profile.save
field.email registration.email
placeholder.search appSelector.searchPlaceholder
error.network sync.connectionFailed
label.title settings.title
alert.confirm build.expireConfirm

Structure by Feature/Screen

Organize keys by the feature or screen where they appear:

/* Profile Section */
"profile.title" = "Profile";
"profile.name" = "Name";
"profile.save" = "Save Changes";
"profile.saveSuccess" = "Profile updated";

/* Beta Builds */
"betaBuild.title" = "Beta Builds";
"betaBuild.whatToTest" = "What to Test";
"betaBuild.submitForReview" = "Submit for Review";
"betaBuild.expireConfirm" = "Expire this build?";

/* Tester Groups */
"testerGroup.create" = "Create Group";
"testerGroup.addTester" = "Add Tester";
"testerGroup.empty" = "No testers yet";

This mirrors how users think: "I'm in Beta Builds, submitting for review" → betaBuild.submitForReview

.strings File Format

/* Comment describing the section */
"key.name" = "Value";
"key.with.parameter" = "Hello, %@!";
"key.with.number" = "%d items";
"key.with.multiple" = "%1$@ has %2$d items";

Rules:

  • Keys must be unique within a file
  • Values are UTF-8 encoded
  • Escape quotes with backslash: \"
  • Line ends with semicolon
  • Use positional parameters (%1$@, %2$d) when order differs between languages