Skip to content

Fix localized files leaking into synced folder targets - #1633

Open
AlexNsbmr wants to merge 1 commit into
yonaskolb:masterfrom
AlexNsbmr:fix/synced-folder-localized-exceptions
Open

Fix localized files leaking into synced folder targets#1633
AlexNsbmr wants to merge 1 commit into
yonaskolb:masterfrom
AlexNsbmr:fix/synced-folder-localized-exceptions

Conversation

@AlexNsbmr

@AlexNsbmr AlexNsbmr commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Problem

When a target uses a synced folder and that folder contains localized resources (*.lproj/Foo.strings), the localized files become members of every target syncing the folder, even when a target excludes them via includes:/excludes:.

XcodeGen does write the excluded localized files into the target's PBXFileSystemSynchronizedBuildFileExceptionSet.membershipExceptions, but Xcode ignores them, because they use the per-language path form:

Resources/en.lproj/AppShortcuts.strings

Xcode only honors localized exclusions in the variant-group form (the same thing Xcode writes when you uncheck a localized file's target membership in the File inspector):

/Localized: Resources/AppShortcuts.strings

In practice this leaks e.g. AppShortcuts.strings into app extensions / UI-test targets that have no AppShortcutsProvider, producing build warnings like "This phrase is not used in any App Shortcut or as a Negative Phrase" and "Unable to read App Intents metadata, AppShortcuts phrases will go through basic validation".

Fix

In PBXProjGenerator.configureMembershipExceptions, rewrite exception paths for files inside a *.lproj directory to the /Localized: variant-group form (with the .lproj component removed). The surrounding Set dedupes the per-language entries into one.

Verified

  • New unit test passes.
  • Confirmed on a real multi-target project (app + widget + notification-service + UI-tests): with the fix, the localized app-only strings are excluded from the secondary targets, the app itself keeps them, and the App Intents build warnings are gone with no project.yml change.

Localized .lproj files were excluded from synced folder targets using
per-language membership exception paths (e.g. Resources/en.lproj/Foo.strings),
which Xcode silently ignores, so the files stayed members of every target
syncing the folder.

Emit the variant-group form Xcode honors (/Localized: Resources/Foo.strings),
collapsed across languages, so includes/excludes work for localized resources.
@AlexNsbmr
AlexNsbmr force-pushed the fix/synced-folder-localized-exceptions branch from affcd6c to ad8a745 Compare July 20, 2026 13:40
@AlexNsbmr
AlexNsbmr marked this pull request as ready for review July 20, 2026 13:43
@AlexNsbmr

Copy link
Copy Markdown
Contributor Author

@yonaskolb could you take a look on my PR when you have some time? Thanks

@yonaskolb yonaskolb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @AlexNsbmr.

/Localized: <path without the .lproj folder> matches what Xcode writes for localized files in synced folders (e.g. "/Localized: Resources/Localizable.strings", "/Localized: MainInterface.storyboard" in Xcode-generated projects), so the core fix is correct.

Issue: language-specific selections become all-language exclusions

Xcode sets synced-folder target membership per localized file, not per language, but the generated exceptions come from concrete per-language paths. Rewriting every .lproj path drops the language, so specs that select only some languages now exclude all of them. Output generated from this branch:

Spec Generated membershipExceptions Result
excludes: ["Resources/Base.lproj/Main.storyboard"] /Localized: Resources/Main.storyboard
excludes: ["en.lproj/Root.strings"] (.lproj at the synced root) /Localized: Root.strings
excludes: ["Resources/fr.lproj"] /Localized: Resources/AppShortcuts.strings, /Localized: Resources/Localizable.strings ⚠️ excluded in every language
excludes: ["Resources/fr.lproj/Localizable.strings"] /Localized: Resources/Localizable.strings ⚠️ excluded in every language
includes: ["a.swift", "Resources/en.lproj/*"] /Localized: entries for both files ❌ explicitly included English files are excluded too

The last case is the most serious. Previously the target received extra languages; with this change it silently loses strings that were explicitly included.

Suggested approach

  1. Only write /Localized: X when every language copy of X on disk is excluded (e.g. group exception paths by path-without-.lproj and compare against the .lproj copies that exist).
  2. When only some languages are excluded, keep the per-language paths rather than widening the exclusion, and log a warning that Xcode can't exclude individual languages from a synced folder. Throwing an error would break specs that generate successfully today.
  3. Check how Base.lproj storyboards/xibs pair with their per-language .strings companions (e.g. Base.lproj/Main.storyboard + fr.lproj/Main.strings). The file extensions differ but Xcode treats them as the same localized item, so grouping by exact filename may not be enough.

Tests
Add cases for: excluding a whole language folder, excluding one language's file, includes that pick only some languages, .lproj at the synced root, nested paths, .stringsdict, a Base.lproj storyboard with .strings companions, and a synced Info.plist exception alongside localized ones.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants