Skip to content

Microapps Architecture: Resources and Localization (Swift)

A SwiftUI guide for coding agents. Also covers bundle module property, spm resources copy process, localizable strings package, defaultlocalization, asset catalog in package, cross module localization.

Part three of the microapps series moves resources and translations into the modules that use them. The compiler generates a bundle per module with resources, reachable as Bundle.module, and forgetting the bundle parameter is the classic missing-image bug.

Module Bundles

Image("image") looks in the running target's bundle, so a resource that lives in a package module needs .module spelled out. Xcode auto-bundles asset catalogs, Core Data models, Storyboards, NIBs, and localization files; Bundle.module exists only for modules that contain resources. Wrap the lookup in a type-safe factory so feature modules cannot mistype names:

// DesignSystem module
extension UIImage {
  public enum Icon: String {
    case trash
    case star
    case plus
  }

  public static func get(_ icon: Icon) -> UIImage {
    .init(named: icon.rawValue, in: .module, compatibleWith: .current)!
  }
}

Other file types must be declared in Package.swift. process lets Xcode apply platform-specific compression; copy keeps files and sub-directory structure verbatim.

.target(
  name: "DesignSystem",
  resources: [
    .copy("Resources/Fonts"),
    .process("Resources/PNGs")
  ]
)

Localization per Feature Module

Each feature module carries its own Localizable.strings, so it can build and run as a standalone microapp. Set defaultLocalization: "en" on the package, add en.lproj and other <locale>.lproj folders inside the module, and always pass the bundle when reading a string:

Text("helloMessage", bundle: .module)

A shared view that displays localized text from other modules must accept the bundle in its public initializer, because otherwise it can only see its own strings:

// DesignSystem module
public struct PlaceholderView: View {
  let text: LocalizedStringKey
  let bundle: Bundle?
  let systemImage: String

  public init(_ text: LocalizedStringKey, bundle: Bundle? = nil, systemImage: String) {
    self.text = text
    self.bundle = bundle
    self.systemImage = systemImage
  }

  public var body: some View {
    Label {
      Text(text, bundle: bundle)
    } icon: {
      Image(systemName: systemImage)
    }
  }
}

// Search feature module
PlaceholderView("emptySearchPlaceholder", bundle: .module, systemImage: "magnifyingglass")

Two rules keep the separation clean: a resource lives only in the module that uses it, and shared resources get a type-safe public API.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-microapps-architecture-in-swift-resources-and-localization" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems