Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
64a2fa0
chore(ci): add Claude GitHub Actions workflows (#40)
arclabs-studio Jan 28, 2026
62b98c6
chore(sync): sync main v2.2.0 into develop
arclabs-studio Jan 28, 2026
29f397b
Feature/skills review (#44)
arclabs-studio Feb 3, 2026
7a969b7
chore(sync): sync develop with main after v2.3.0 release
arclabs-studio Feb 3, 2026
9ffbe70
feat(skills): add arc-final-review skill for pre-merge quality checks
arclabs-studio Feb 5, 2026
0aadce6
chore(sync): sync develop with main after v2.4.0 release
arclabs-studio Feb 5, 2026
2def45f
docs: add Swift 6 concurrency patterns and Clean Architecture learnings
arclabs-studio Feb 6, 2026
149dbb9
chore(sync): sync develop with main after v2.5.0 release
arclabs-studio Feb 6, 2026
5aa397e
chore(security): add ARC Labs security patterns to .gitignore
arclabs-studio Feb 18, 2026
73815ed
Merge branch 'chore/arc-security-gitignore-patterns' into develop
arclabs-studio Feb 18, 2026
71f5bb7
Feature/audit configuration (#51)
arclabs-studio Feb 19, 2026
486bf5a
chore(sync): sync develop with main after v2.6.0 release
arclabs-studio Feb 19, 2026
97728db
Feature/skills improvement (#54)
arclabs-studio Feb 21, 2026
1af5c99
chore(sync): sync develop with main after v2.7.0 release
arclabs-studio Feb 21, 2026
432467f
Feature/arc xcode cloud (#57)
arclabs-studio Mar 12, 2026
adf8508
chore: sync develop with main after v2.8.0 release
arclabs-studio Mar 12, 2026
93e4cd9
Feature/agents (#60)
arclabs-studio Mar 14, 2026
da5db21
feat(arc-linear-bridge): create branch automatically after scaffolding
arclabs-studio Mar 14, 2026
b777ba2
fix(arc-spm-manager): correct org URL and add ARCPurchasing, ARCAuthe…
arclabs-studio Mar 14, 2026
d5f0c73
feat(agents): add arc-pr-publisher and arc-release-orchestrator
arclabs-studio Mar 14, 2026
86e8930
docs(agents): update index for 8-agent system and package table
arclabs-studio Mar 14, 2026
a933e0b
feat(agents): add arc-testflight distribution agent
arclabs-studio Mar 14, 2026
f2418bf
feat(agents): add arc-aso App Store Optimization agent
arclabs-studio Mar 14, 2026
92287c7
feat(agents): add arc-swiftdata-migration high-risk schema agent
arclabs-studio Mar 14, 2026
cebb9b8
feat(agents): add arc-dependency-auditor read-only audit agent
arclabs-studio Mar 14, 2026
316dd04
docs(agents): update AGENTS.md from 8 to 12 agents
arclabs-studio Mar 14, 2026
0f3b6f8
docs(skills): add 4 new agents to skills-index agents section
arclabs-studio Mar 14, 2026
d655375
chore(sync): sync develop with main after Develop #61 merge
arclabs-studio Mar 14, 2026
c662e61
feat(agents): merge more-agents into develop
arclabs-studio Mar 14, 2026
d47d45e
chore: sync develop with main after v2.9.0 release
arclabs-studio Mar 14, 2026
0423a92
Feature/swift solid pattern (#65)
arclabs-studio Mar 19, 2026
a60801a
chore: sync develop with main after v2.10.0 release
arclabs-studio Mar 19, 2026
ab0cc00
docs(standards): add @Environment DI guidance and fix @MainActor patt…
arclabs-studio Mar 21, 2026
12d21af
chore: sync develop with main after v2.11.0 release
arclabs-studio Mar 21, 2026
13d5575
docs(ARCPK-14): add Monetization/ section and register ARCPurchasing …
arclabs-studio Mar 24, 2026
2664dcb
chore: sync develop with main after v2.12.0 release
arclabs-studio Mar 24, 2026
60e896d
docs(l10n): add localization standard and /arc-localization skill (#75)
arclabs-studio Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .claude/skills/arc-localization/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: arc-localization
description: |
Localization standards for ARC Labs Studio apps and packages.
Covers in-app locale switching, String Catalogs, nameKey pattern for domain entities,
LocalizedStringKey vs String(localized:), navigation title localization, and
LanguageManager pattern. Use when "localizing strings", "adding translations",
"fixing locale switching", "language picker", or "string catalog".
user-invocable: true
metadata:
author: ARC Labs Studio
version: "1.0.0"
---

# ARC Labs Studio - Localization

## Quick Decision Tree

```
What are you localizing?
├── Navigation title → String(localized:locale:) + @Environment(\.locale)
├── Tab label → LocalizedStringKey via ARCTabItem protocol
├── Domain entity name in a View → LocalizedStringKey(entity.nameKey)
├── Domain entity name for logging → entity.name (String(localized:))
├── Package enum title → var title: LocalizedStringKey
├── Package view title → LocalizedStringKey parameter
└── Menu item subtitle → String(localized:locale:) via ViewModel helper
```

## Core Principle

> **Packages are text-agnostic. Apps own all strings.**

Packages use `LocalizedStringKey`. Apps provide translations via `.xcstrings`.

## The Two-System Problem

| API | Resolves Against | Immediate? |
|-----|-----------------|------------|
| `LocalizedStringKey` | `@Environment(\.locale)` | Yes |
| `String(localized:)` | `Locale.current` (process) | No (cold restart) |
| `String(localized:locale:)` | Explicit Locale | Yes (with @Environment) |

## Key Patterns

### Navigation Titles
```swift
@Environment(\.locale) private var locale
// ...
.navigationTitle(String(localized: "Favorites", locale: locale))
```

### Domain Entity nameKey
```swift
// Domain layer (no SwiftUI)
var nameKey: String { "Italian" }

// Presentation layer
Text(LocalizedStringKey(cuisineType.nameKey))
```

### Package Enums
```swift
public var title: LocalizedStringKey {
switch self {
case .system: "System" // App's xcstrings provides translation
}
}
```

## Full Reference

See `Quality/localization.md` for complete standards, LanguageManager pattern, and decision matrix.
211 changes: 211 additions & 0 deletions Quality/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Localization Standards

**Comprehensive localization standards for ARC Labs Studio apps and packages.**

All ARC Labs apps **MUST** implement these standards for in-app locale switching support.

---

## Core Principle

> **Packages are text-agnostic. Apps own all user-facing strings.**

Packages use `LocalizedStringKey` for display text so the consuming app's String Catalog provides translations. Packages never embed their own `.xcstrings` files.

---

## String Resolution: Two Systems

Understanding the difference is critical for in-app locale switching:

| API | Resolves Against | Updates Immediately? | Use In |
|-----|-----------------|---------------------|--------|
| `LocalizedStringKey` | `@Environment(\.locale)` | Yes | SwiftUI `Text`, `Label`, `Button`, `Tab` |
| `String(localized:)` | `Locale.current` (process locale) | No (cold restart only) | Non-SwiftUI contexts, logging, formatters |
| `String(localized:locale:)` | Explicit `Locale` parameter | Yes (when locale comes from `@Environment`) | Navigation titles, any `String` context in views |

### The Problem

When the user changes language in-app, `.environment(\.locale, newLocale)` updates SwiftUI's locale environment. But `Locale.current` (the process locale) doesn't change until cold restart. So:

- `Text("Favorites")` (LocalizedStringKey) -- updates immediately
- `String(localized: "Favorites")` -- does NOT update until restart
- `String(localized: "Favorites", locale: locale)` with `@Environment(\.locale)` -- updates immediately

---

## Pattern: Navigation Titles

Navigation titles backed by UIKit's `UINavigationBar` do not reliably re-resolve `LocalizedStringKey` on locale change. Use explicit `String(localized:locale:)`:

```swift
struct FavoritesView: View {
@Environment(\.locale) private var locale

var body: some View {
content
.navigationTitle(String(localized: "Favorites", locale: locale))
}
}
```

**Rule**: Every view with `.navigationTitle()` must use `String(localized:locale:)` with `@Environment(\.locale)`.

---

## Pattern: Domain Entity Display Names (`nameKey`)

Domain entities must not import SwiftUI. They provide raw localization keys; the Presentation layer converts them to `LocalizedStringKey`.

### Domain Layer

```swift
// Domain/Entities/CuisineType.swift
enum CuisineType: String, Codable, CaseIterable {
case italian, japanese, mexican

/// Localized name (resolves against process locale -- for non-SwiftUI contexts)
var name: String {
switch self {
case .italian: String(localized: "Italian")
case .japanese: String(localized: "Japanese")
case .mexican: String(localized: "Mexican")
}
}

/// Raw localization key for SwiftUI views (responds to environment locale)
var nameKey: String {
switch self {
case .italian: "Italian"
case .japanese: "Japanese"
case .mexican: "Mexican"
}
}
}
```

### Presentation Layer

```swift
// In SwiftUI views -- use nameKey for immediate locale response
Text(LocalizedStringKey(cuisineType.nameKey))
Label(LocalizedStringKey(category.nameKey), systemImage: category.icon)

// In non-SwiftUI contexts (logging, analytics) -- use name
logger.debug("Selected cuisine", metadata: ["type": .public(cuisineType.name)])
```

---

## Pattern: Package Components

Package enums and views use `LocalizedStringKey` so the app's String Catalog resolves them:

### Enum Titles

```swift
// In ARCUIComponents package
public enum ARCAppLanguage: String, CaseIterable {
case system, spanish, english

public var title: LocalizedStringKey {
switch self {
case .system: "System" // App's xcstrings: "Sistema"
case .spanish: "Spanish" // App's xcstrings: "Espanol"
case .english: "English" // App's xcstrings: "Ingles"
}
}
}
```

### Configurable View Titles

Picker views accept `LocalizedStringKey` parameters with sensible defaults:

```swift
public struct ARCMenuLanguagePickerView: View {
public init(
selectedLanguage: Binding<ARCAppLanguage>,
navigationTitle: LocalizedStringKey = "Language",
onDone: (() -> Void)? = nil
) { ... }
}
```

The app passes localized titles when needed:

```swift
ARCMenuLanguagePickerView(
selectedLanguage: $viewModel.selectedLanguage,
navigationTitle: String(localized: "Language", locale: locale),
onDone: { dismiss() }
)
```

---

## Pattern: Protocol Title Types

Navigation protocols use `LocalizedStringKey` for tab titles:

```swift
// ARCTabItem (ARCUIComponents), NavigationTab (ARCNavigation)
public protocol ARCTabItem {
var title: LocalizedStringKey { get } // Not String
var icon: String { get }
}
```

---

## String Catalog Standards

### File Organization
- One `Localizable.xcstrings` per target (app or extension)
- `InfoPlist.xcstrings` for Info.plist localizations
- Source language: English (`en`)
- Keys are English strings (not identifiers like `SCREEN_TITLE`)

### Key Format
```
"Favorites" -- navigation title, tab label
"Bug Report" -- feedback type name
"Price: Low to High" -- sort option
```

### Build Settings
- **Use Compiler to Extract Swift Strings**: Yes
- **Localization Prefers String Catalogs**: Yes
- **SWIFT_EMIT_LOC_STRINGS**: Yes

---

## LanguageManager Pattern

Mirrors `AppearanceManager`. Manages user preference with immediate SwiftUI re-rendering:

```swift
@Observable @MainActor
final class LanguageManager {
var language: ARCAppLanguage { didSet { /* persist + AppleLanguages */ } }
var locale: Locale? { /* maps language to Locale */ }
}

// App root
.environment(\.locale, languageManager.locale ?? Locale.current)
```

---

## Decision Matrix

| Context | Pattern |
|---------|---------|
| Tab labels | `LocalizedStringKey` via protocol (ARCTabItem) |
| Navigation titles | `String(localized:locale:)` + `@Environment(\.locale)` |
| Domain entity names in views | `LocalizedStringKey(entity.nameKey)` |
| Domain entity names for logging | `entity.name` (String(localized:)) |
| Menu item labels | `String(localized:)` in view context |
| Package enum titles | `LocalizedStringKey` property |
| Package view titles | `LocalizedStringKey` parameter |
| Subtitle in `ARCMenuItem` | `String(localized:locale:)` via ViewModel helper |
2 changes: 2 additions & 0 deletions Skills/skills-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
What do you need? → Primary Skill → Backup
──────────────────────────────────────────────────────────────────────
Architecture (ARC Labs projects) → arc-swift-architecture
Localization & locale switching → arc-localization ⭐
SwiftUI general patterns → swiftui-expert-skill ⭐
SwiftUI specific (nav, perf, layout) → axiom-swiftui-*
Swift Concurrency (learning) → swift-concurrency
Expand Down Expand Up @@ -58,6 +59,7 @@ Use for **ARC Labs projects** where consistency with studio standards is require
| `/arc-worktrees-workflow` | Parallel feature development with git worktrees |
| `/arc-memory` | Persistent context across Claude Code sessions |
| `/arc-xcode-cloud` | Xcode Cloud setup, ci_scripts, workflow configuration, hour budgeting |
| `/arc-localization` | In-app locale switching, String Catalogs, nameKey pattern, LanguageManager |

**Characteristics**:
- Prescriptive (imposes specific patterns)
Expand Down
Loading