Fix localized files leaking into synced folder targets - #1633
Conversation
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.
affcd6c to
ad8a745
Compare
|
@yonaskolb could you take a look on my PR when you have some time? Thanks |
yonaskolb
left a comment
There was a problem hiding this comment.
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 |
|
excludes: ["Resources/fr.lproj/Localizable.strings"] |
/Localized: Resources/Localizable.strings |
|
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
- Only write
/Localized: Xwhen every language copy ofXon disk is excluded (e.g. group exception paths by path-without-.lprojand compare against the.lprojcopies that exist). - 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.
- Check how
Base.lprojstoryboards/xibs pair with their per-language.stringscompanions (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.
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 viaincludes:/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: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):
In practice this leaks e.g.
AppShortcuts.stringsinto app extensions / UI-test targets that have noAppShortcutsProvider, 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*.lprojdirectory to the/Localized:variant-group form (with the.lprojcomponent removed). The surroundingSetdedupes the per-language entries into one.Verified
project.ymlchange.