For a Mac app with framework-owned panels, define fallback at two levels: which language each bundle selects, and what each message returns when its lookup fails. Use an English development localization and explicit English defaults for the messages you own.

This guide implements that contract for a simple desktop window containing the app-owned label Workspace and the framework-owned label Panel ready. The labels are synthetic. The task is to keep those messages readable when a language or translation is missing, while preserving normal localization when translations exist.

What should the fallback contract promise?

Adopt this contract for app-owned and framework-owned messages: use an available localization selected through the system’s language matching; use English as the development localization when no preferred localization matches; and return a message’s explicit English default if its string lookup fails. Each message must identify its owning bundle and string table.

Language selection considers the user’s preferred languages and available localizations. A missing first-choice language does not necessarily mean English appears immediately: another preferred language may match first. Apple’s archived bundle guide describes that selection sequence, followed by the development localization when no preferred language matches. The current documentation defines CFBundleDevelopmentRegion as the bundle’s fallback language identifier. Accessing a Bundle’s Contents, CFBundleDevelopmentRegion.

Use the following decision table when defining expected results. It separates resource selection from message lookup rather than treating every missing translation as the same condition.

Condition Contract for messages you own What to check
A preferred language has an available translation Display that translation Bundle, table, and key
The first preferred language is unavailable, but another preferred language matches Allow system language matching to select the available localization Preference order and shipped resources
No preferred localization matches Use the bundle’s English development resources Development language and English resources
The selected lookup cannot resolve the message Return its explicit English default The message’s defaultValue
A framework lookup accidentally targets the app bundle Correct the bundle argument Resource ownership
A translation exists but contains unsuitable text Correct the translation Catalog content and review

The table combines Apple’s resource-selection rules with the Swift initializer’s documented default-value behavior. It is an implementation policy derived from those APIs, not a separate fallback feature in String Catalogs. A successful lookup containing unsuitable text does not meet the initializer’s documented condition for using its default: a failed lookup. String.init(localized:defaultValue:table:bundle:locale:comment:).

Configure English source resources in every owning target

For this example, use a current Xcode installation, a macOS SwiftUI app targeting macOS 13 or later, and an embedded dynamic Swift framework target named PanelKit. Link and embed that framework in the app. These are example project prerequisites; the framework name does not identify an existing product.

Set English as the development language for both targets, and verify that each built bundle’s CFBundleDevelopmentRegion resolves to en. Supply the corresponding English source resources in both targets. A development-language identifier describes the fallback language; it does not supply missing message content. Apple documents en as the English language identifier. CFBundleDevelopmentRegion.

Create a separate Localizable.xcstrings catalog for each target. In Xcode, choose File > New > File from Template, select String Catalog, and name it Localizable. Assign one catalog to the app target and the other to the framework target. Keeping resource ownership separate makes it possible to verify each lookup independently. Apple documents the catalog creation workflow and supports multiple catalogs. Localizing and varying text with a string catalog.

After adding the code below, choose Product > Build. Xcode extracts recognized localizable calls into the catalogs. Confirm that workspace.title appears in the app catalog and panel.ready appears in the framework catalog, with the intended English source values. Add supported languages using the catalog sidebar’s add button and enter translations for the relevant messages. Localizing and varying text with a string catalog.

Catalog completeness and runtime lookup correctness require separate checks. A translated entry in the framework catalog cannot help a call that searches the app bundle. Apple explicitly instructs code loading strings outside the main bundle to pass the bundle associated with that target. Preparing your app’s text for translation.

Give each message a bundle, key, table, and default

Use stable synthetic keys and readable English defaults. The Swift initializer accepting defaultValue supports a key different from the displayed source text. Its default is returned when looking up the localized string from the bundle fails. The table parameter selects the string table; omitting it selects Localizable. String.init(localized:defaultValue:table:bundle:locale:comment:).

Put framework messages in the framework

Add this complete Swift source file to the dynamic PanelKit framework target. The marker class is defined in that target so Bundle(for:) identifies its bundle. Apple documents this class-based approach as an alternative to the current #bundle macro. Preparing your app’s text for translation.

import Foundation

private final class PanelBundleAnchor: NSObject {}

public enum PanelText {
    public static var resourceBundle: Bundle {
        Bundle(for: PanelBundleAnchor.self)
    }

    public static var ready: String {
        String(
            localized: "panel.ready",
            defaultValue: "Panel ready",
            table: "Localizable",
            bundle: resourceBundle,
            comment: "Status label above the framework panel."
        )
    }
}

The framework resolves its message before returning a String to the app. This establishes a clear ownership boundary: the framework controls the key, source text, table, and resource bundle. The app receives the resulting display text.

Display app and framework messages together

Replace the generated app entry-point file with this complete example. It assumes the linked framework module is named PanelKit and contains the preceding file. Keep only one app entry point in the app target.

import Foundation
import SwiftUI
import PanelKit

@main
struct FallbackDemoApp: App {
    var body: some Scene {
        WindowGroup {
            VStack(alignment: .leading, spacing: 12) {
                Text(verbatim: String(
                    localized: "workspace.title",
                    defaultValue: "Workspace",
                    table: "Localizable",
                    bundle: .main,
                    comment: "Heading for the main workspace."
                ))

                Text(verbatim: PanelText.ready)
            }
            .padding(24)
        }
    }
}

The app lookup explicitly uses .main; the framework lookup uses its own resource bundle. Both provide English defaults. This follows Apple’s documented bundle selection and default-value parameters. Build and run the project in Xcode to validate the complete target configuration; these examples have not been executed here. String.init(localized:defaultValue:table:bundle:locale:comment:).

For an existing implementation using Bundle.localizedString(forKey:value:table:), supply a nonempty readable value when you need a readable missing-key result. Apple documents that a missing key returns the key itself when value is absent or empty, and returns value when it is nonempty. A key such as panel.ready therefore needs an explicit fallback if exposing that identifier would violate the message contract. localizedString(forKey:value:table:).

Decide whether frameworks may select different languages

Passing the correct bundle answers where to search. Mixed-localization configuration affects how localization selection relates to the containing app. Apple exposes the Boolean CFBundleAllowMixedLocalizations, displayed as Localized resources can be mixed, for retrieving localized strings from frameworks. CFBundleAllowMixedLocalizations.

The Swift Forums explanation of this behavior states that enabling the key in the app’s Info.plist allows other bundles to select localizations beyond those supported by the main app. Without that opt-in, the main bundle constrains their selection. This is an older explanatory discussion, useful alongside Apple’s current key reference. Swift package manager localization.

For the example window, prefer matching supported-language coverage across the app and framework, with English development resources in both. Leave independent framework language selection outside the initial contract. If your intended behavior is instead to let a framework use additional translations, set the app’s CFBundleAllowMixedLocalizations Boolean to YES and test that configuration explicitly. Swift package manager localization.

Enabling this key does not provide a translation or a missing-message default. Those responsibilities still belong to the resource files and lookup calls. Likewise, a default supplied to your own call defines that call’s failed-lookup result; it does not establish a universal English fallback for every system control or framework message.

Avoid using the locale argument of this String initializer as a translation-language switch. Apple explicitly documents that it controls localization of interpolated values, such as numbers, and does not change the locale used to look up the localized string. Use the supported language configuration and test controls for this task. String.init(localized:defaultValue:table:bundle:locale:comment:).

Test missing languages separately from missing messages

Run localization checks through Product > Scheme > Edit Scheme. Select Run, open Options, choose an App Language, close the dialog, and run the Mac app again. Apple documents this workflow for exercising localizations without changing the entire system’s settings. Keep region selection unchanged when investigating a language-only issue. Testing localizations when running your app.

Use the following test matrix as a project-specific diagnostic aid. For bundle-routing checks, temporarily use distinct English test markers such as App translation sample and Framework translation sample in a supported non-source localization. These are diagnostic content, not translations or language identifiers.

  1. Verify source behavior. Run with English selected. Expect Workspace and Panel ready.
  2. Verify translated resource routing. Select the supported test localization. Expect the distinct markers from the two owning catalogs.
  3. Verify a missing-message default. In a disposable test edit, change the framework lookup key to panel.ready.probe, leave defaultValue unchanged, and prevent that probe entry from being included in the test catalog. Expect Panel ready when the lookup fails. Restore the original key afterward.
  4. Verify unsupported-language selection. Exercise a preference list with no supported language match. Check that both owned bundles use their English development resources. Also test an unsupported first preference followed by a supported preference, because that case has a different expected outcome.
  5. Verify mixed localization if enabled. Exercise a language available only in the framework. Record the intended behavior and test the app’s mixed-localization setting against it.

The third check isolates a failed lookup; the fourth isolates language matching. Testing only an untranslated catalog row can blur those conditions because the resulting resource configuration also affects lookup. The expected missing-key result comes from the Swift initializer, while preferred-language selection follows the bundle rules. String.init(localized:defaultValue:table:bundle:locale:comment:), Accessing a Bundle’s Contents.

When results differ, inspect the actual owning bundle. developmentLocalization reports the value corresponding to CFBundleDevelopmentRegion; preferredLocalizations reports available localizations ordered according to language preferences. Compare these properties on Bundle.main and PanelText.resourceBundle. They help diagnose configuration, but the displayed messages remain the final check of each lookup. developmentLocalization, preferredLocalizations.

A useful fallback contract makes each result explainable: the selected language comes from bundle matching, the message comes from its owning table, and a failed lookup returns an explicit readable default. Apply those rules to every app and framework message you own, then verify them together in the desktop window where users encounter them.

Sources