To resolve a framework’s localization key, pass both its resource bundle and its strings table. This guide shows how to make a framework-owned label appear correctly in a Mac app, then check that the result came from the table rather than fallback text.
The task is deliberately small: display the synthetic label Open panel from a framework’s Panel.xcstrings catalog. You’ll need a Mac app project in Xcode and a framework target you control. The main example uses Xcode 26 or later and targets macOS 12 or later; Apple documents the localized-string initializer as available from macOS 12. Swift initializer documentation.
Identify the bundle, table, and key before changing code
Treat a localized message as a lookup with three explicit coordinates. The bundle identifies the resource owner, the table identifies a group of strings within that bundle, and the key identifies an entry within that table. Apple’s localization APIs expose the bundle and table separately because frameworks and app targets can each contain string resources. Preparing your app’s text for translation.
For this example, write down the following contract before opening the catalog. These are illustrative names, not names required by Apple.
| Part | Example value | Purpose |
|---|---|---|
| Resource owner | Your framework target | Contains the catalog |
| Catalog file | Panel.xcstrings |
Stores the table’s source text and translations |
| Table argument | Panel |
Selects that catalog without its extension |
| Key | PANEL_OPEN_ACTION |
Identifies the message |
| English source value | Open panel |
Supplies readable interface text |
| Comment | Label for the action that opens the panel. |
Explains the message’s use |
Passing table: "Panel" selects the table represented by Panel.xcstrings. Leaving the table unset selects Localizable. Similarly, leaving the bundle unset makes the string initializer search the main app bundle. A matching key in a framework catalog therefore needs the framework’s bundle as well as the correct table. Swift localized-string parameters.
This gives you a useful diagnostic order: verify the owner first, the table second, and the entry third. Searching for alternative spellings of the key cannot fix a lookup pointed at the app’s resources when the message belongs to a framework.
Add the string catalog to the framework target
In Xcode, choose File > New > File from Template, select the platform, filter for string, and choose String Catalog. Name the file Panel. Ensure that the catalog belongs to the framework target, alongside the framework source that will reference it. Apple’s catalog workflow allows custom catalog names, and its framework demonstration creates the catalog within the framework target. Localizing and varying text with a string catalog, Apple’s framework localization demonstration.
The example uses a semantic key rather than making the displayed wording serve as the key. With PANEL_OPEN_ACTION as the identifier and Open panel as the default value, the code states both pieces explicitly. Apple supports this arrangement through the initializer that accepts a static key and a separate default localization value. Swift static-key initializer.
After adding the source below, build the framework target. Xcode discovers localizable API calls during builds and updates the relevant catalogs. Open Panel.xcstrings and verify that it contains PANEL_OPEN_ACTION, the English source value Open panel, and the explanatory comment. If the entry is missing, check the source’s target membership and whether the target containing that source was built. Apple also provides Editor > Assistant to inspect the code associated with a catalog entry. Populate and inspect a string catalog.
Resolve the message inside the framework
Place this complete source file in the framework target. It defines the localized string and a small SwiftUI label that the containing Mac app can display. It also includes a diagnostic method used in the next section. The code was checked against Apple’s documented signatures; it was not executed here because a Swift compiler was unavailable.
import Foundation
import SwiftUI
public enum PanelMessages {
public static var openPanel: String {
String(
localized: "PANEL_OPEN_ACTION",
defaultValue: "Open panel",
table: "Panel",
bundle: #bundle,
comment: "Label for the action that opens the panel."
)
}
public static func lookupReport() -> String {
let missingMarker = "__PANEL_LOOKUP_MISSING__"
let result = #bundle.localizedString(
forKey: "PANEL_OPEN_ACTION",
value: missingMarker,
table: "Panel"
)
if result == missingMarker {
return "No entry resolved from the selected bundle and table."
}
return "Resolved table value: \(result)"
}
}
public struct PanelActionLabel: View {
public init() {}
public var body: some View {
Text(verbatim: PanelMessages.openPanel)
}
}
The #bundle expression is evaluated where you write it. Inside this framework source, it supplies the bundle associated with the framework target. If you move the same expression into app-target source, it supplies the app’s bundle instead. For a Swift package target, Apple documents that the macro supplies the target’s resource bundle. Keep the lookup beside the resources it owns. Foundation’s bundle macro.
The declaration returns an ordinary String whose localization has already been resolved. The view uses Text(verbatim:) to display that result explicitly. The public view is only a label; add it to an existing app view where the text needs to appear.
Use a framework class when the bundle macro is unavailable
Apple also supports locating a framework bundle through a class defined in that framework. For a conventional framework that contains its own resources, the following is a complete alternative message provider. Use it instead of the earlier PanelMessages declaration, and keep the catalog and deployment requirements described above.
import Foundation
private final class PanelBundleAnchor: NSObject {}
public enum PanelMessages {
public static var openPanel: String {
String(
localized: "PANEL_OPEN_ACTION",
defaultValue: "Open panel",
table: "Panel",
bundle: Bundle(for: PanelBundleAnchor.self),
comment: "Label for the action that opens the panel."
)
}
}
The anchor class must be compiled into the framework whose resources you want. Apple documents that Bundle(for:) returns the bundle associated with the supplied class, including its framework bundle when applicable. An anchor compiled into the app would identify the app instead. For package resources or resources placed in a separate bundle, select their actual resource bundle; a class’s location alone does not establish ownership of those resources. Bundle class-based initializer, Resource bundle selection with the macro.
Prove that readable text came from a successful lookup
Seeing Open panel on screen is insufficient evidence that the catalog lookup succeeded. The initializer’s defaultValue is returned when lookup fails, so the same visible text can represent either a successful lookup or a fallback. This behavior is useful for readable interface text, but it can conceal an incorrect bundle or table during development. Swift default-value behavior.
The first source file’s lookupReport() method addresses that ambiguity. It performs a lookup with a distinctive fallback marker that must not be used as a real catalog value. Inspect its return value from the running app or an existing debug context. If it reports no resolved entry, investigate the selected resources before editing translations.
This diagnostic follows directly from the documented behavior of Bundle.localizedString(forKey:value:table:): when a key is absent and the supplied fallback is nonempty, the method returns that fallback. With a missing key and a nil or empty fallback, it returns the key instead. The marker makes failure easier to distinguish from legitimate source text. Bundle localized-string return values).
| Observation | Next check |
|---|---|
| The diagnostic returns the missing-entry message | Confirm that the catalog is built into the selected bundle, then check Panel and PANEL_OPEN_ACTION |
| The diagnostic resolves an unexpected value | Inspect that exact entry in the selected table and the language used by the current run |
| The diagnostic resolves correctly but the interface differs | Trace which value the interface actually displays |
| The English source value appears during a translated run | Inspect the entry’s translation and the run configuration; successful lookup alone does not prove the requested translation exists |
The table is a troubleshooting sequence derived from the lookup contract. A successful marker test establishes that the lookup returned an entry, but it does not establish that every supported language has a completed translation. Keep resource resolution and translation completeness as separate checks.
Test the label in the Mac app’s selected language
For an app that supports additional languages, select the relevant language in the catalog sidebar and enter the translation in that entry’s text field. Then test through the containing Mac app. Apple documents direct entry of translations in the catalog editor and updates each entry’s translation state as you work. Add translations to a string catalog.
To control the debug run, choose Product > Scheme > Edit Scheme, select Run, and open Options. Set App Language to a language supported by the project, close the editor, and run the Mac app again. Choose System Language when you want the run to follow the system’s language setting. Testing localizations when running your app.
Do not use the string initializer’s locale: argument as a substitute for this language test. Apple documents that this argument controls localization of interpolated values, such as numbers, and does not change the locale used to look up the localized string. The example omits it because the label contains no interpolated values. Swift localized-string locale parameter.
Keep the final contract small and explicit: the framework owns Panel.xcstrings, the lookup selects Panel, and PANEL_OPEN_ACTION identifies the label. Verify the entry with the diagnostic, then verify its displayed translation in the app’s chosen run language. That sequence lets you correct the resource lookup before spending time on wording that the app may never be reading.
Sources
- Preparing your app’s text for translation — Apple
- Localizing and varying text with a string catalog — Apple
- Code-along: Explore localization with Xcode — Apple
init(localized:defaultValue:table:bundle:locale:comment:)— Appleinit(localized:table:bundle:locale:comment:)— Applebundle()— Appleinit(for:)— ApplelocalizedString(forKey:value:table:)— Apple- Testing localizations when running your app — Apple