From eb28bc1fd640b4be105bc64f41ff4509b47d0fd3 Mon Sep 17 00:00:00 2001 From: fhasse95 <49185957+fhasse95@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:22:08 +0100 Subject: [PATCH 1/5] Added support for traditional *.strings resource files --- .../SkipBuild/Commands/SkipstoneCommand.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index e2b07ee5..74196b15 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -1122,7 +1122,7 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { } } - /// Links resources in "process" mode, flattening the hierarchy and performing special processing for .xcstrings and other files + /// Links resources in "process" mode, flattening the hierarchy and performing special processing for .strings, .xcstrings and other files func linkProcessResources(entry: ResourceEntry, resourcesBasePath: AbsolutePath) throws { for resourceFile in entry.urls.map(\.path).sorted() { let resourceFileCanonical = (resourceFile as NSString).standardizingPath @@ -1148,8 +1148,10 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { trace("skipping resource linking for buildSrc/") } else if isCMakeProject { trace("skipping resource linking for CMake project") - } else if sourcePath.extension == "xcstrings" { + } else if sourcePath.extension == "strings" { try convertStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) + } else if sourcePath.extension == "xcstrings" { + try convertXCStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) //} else if sourcePath.extension == "xcassets" { // TODO: convert various assets into Android res/ folder } else { // non-processed resources are just linked directly from the package @@ -1168,6 +1170,22 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { } func convertStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { + // process the .strings in the same way that Xcode does: read the TXT and use the content to synthesize a LANG.lproj/TABLENAME.strings file + let stringsContent = try Data(contentsOf: resourceSourceURL) + + let localeId = sourcePath.parentDirectory.basenameWithoutExt + let lprojFolder = resourcesBasePath.appending(component: localeId + ".lproj") + let locBase = sourcePath.basenameWithoutExt + + try fs.createDirectory(lprojFolder, recursive: true) + + let localizableStrings = try RelativePath(validating: locBase + ".strings") + let localizableStringsPath = lprojFolder.appending(localizableStrings) + info("create \(localizableStrings.pathString) from \(sourcePath.pathString)", sourceFile: localizableStringsPath.sourceFile) + try writeChanges(tag: localizableStrings.pathString, to: localizableStringsPath, contents: stringsContent, readOnly: false) + } + + func convertXCStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { // process the .xcstrings in the same way that Xcode does: parse the JSON and use the localizations keys to synthesize a LANG.lproj/TABLENAME.strings file let xcstrings = try JSONDecoder().decode(LocalizableStringsDictionary.self, from: Data(contentsOf: resourceSourceURL)) let defaultLanguage = xcstrings.sourceLanguage From 6c78272f2630c1a0791d0ef3150f7d240a6817d3 Mon Sep 17 00:00:00 2001 From: fhasse95 <49185957+fhasse95@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:32:23 +0100 Subject: [PATCH 2/5] Added support for traditional *.stringsdict resource files --- .../SkipBuild/Commands/SkipstoneCommand.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index 74196b15..46a7e177 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -1150,6 +1150,8 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { trace("skipping resource linking for CMake project") } else if sourcePath.extension == "strings" { try convertStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) + } else if sourcePath.extension == "stringsdict" { + try convertStringsDict(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) } else if sourcePath.extension == "xcstrings" { try convertXCStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) //} else if sourcePath.extension == "xcassets" { @@ -1185,6 +1187,22 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { try writeChanges(tag: localizableStrings.pathString, to: localizableStringsPath, contents: stringsContent, readOnly: false) } + func convertStringsDict(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { + // process the .stringsdict in the same way that Xcode does: read the PLIST and use the content to synthesize a LANG.lproj/TABLENAME.stringsdict file + let stringsDictContent = try Data(contentsOf: resourceSourceURL) + + let localeId = sourcePath.parentDirectory.basenameWithoutExt + let lprojFolder = resourcesBasePath.appending(component: localeId + ".lproj") + let locBase = sourcePath.basenameWithoutExt + + try fs.createDirectory(lprojFolder, recursive: true) + + let localizableStringsDict = try RelativePath(validating: locBase + ".stringsdict") + let localizableStringsDictPath = lprojFolder.appending(localizableStringsDict) + info("create \(localizableStringsDict.pathString) from \(sourcePath.pathString)", sourceFile: localizableStringsDictPath.sourceFile) + try writeChanges(tag: localizableStringsDict.pathString, to: localizableStringsDictPath, contents: stringsDictContent, readOnly: false) + } + func convertXCStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { // process the .xcstrings in the same way that Xcode does: parse the JSON and use the localizations keys to synthesize a LANG.lproj/TABLENAME.strings file let xcstrings = try JSONDecoder().decode(LocalizableStringsDictionary.self, from: Data(contentsOf: resourceSourceURL)) From 704737246dc6399d4e79319b755e6c31894d8b88 Mon Sep 17 00:00:00 2001 From: fhasse95 <49185957+fhasse95@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:40:37 +0100 Subject: [PATCH 3/5] Update SkipstoneCommand.swift --- .../SkipBuild/Commands/SkipstoneCommand.swift | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index 46a7e177..ac5999dd 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -1148,10 +1148,8 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { trace("skipping resource linking for buildSrc/") } else if isCMakeProject { trace("skipping resource linking for CMake project") - } else if sourcePath.extension == "strings" { - try convertStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) - } else if sourcePath.extension == "stringsdict" { - try convertStringsDict(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) + } else if sourcePath.extension == "strings" || sourcePath.extension == "stringsdict" { + try copyStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) } else if sourcePath.extension == "xcstrings" { try convertXCStrings(resourceSourceURL: resourceSourceURL, sourcePath: sourcePath) //} else if sourcePath.extension == "xcassets" { @@ -1171,25 +1169,9 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { } } - func convertStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { - // process the .strings in the same way that Xcode does: read the TXT and use the content to synthesize a LANG.lproj/TABLENAME.strings file - let stringsContent = try Data(contentsOf: resourceSourceURL) - - let localeId = sourcePath.parentDirectory.basenameWithoutExt - let lprojFolder = resourcesBasePath.appending(component: localeId + ".lproj") - let locBase = sourcePath.basenameWithoutExt - - try fs.createDirectory(lprojFolder, recursive: true) - - let localizableStrings = try RelativePath(validating: locBase + ".strings") - let localizableStringsPath = lprojFolder.appending(localizableStrings) - info("create \(localizableStrings.pathString) from \(sourcePath.pathString)", sourceFile: localizableStringsPath.sourceFile) - try writeChanges(tag: localizableStrings.pathString, to: localizableStringsPath, contents: stringsContent, readOnly: false) - } - - func convertStringsDict(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { - // process the .stringsdict in the same way that Xcode does: read the PLIST and use the content to synthesize a LANG.lproj/TABLENAME.stringsdict file - let stringsDictContent = try Data(contentsOf: resourceSourceURL) + func copyStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { + // process the .strings / .stringsdict in the same way that Xcode does: parse the FILE and use the content to synthesize a LANG.lproj/TABLENAME.EXTENSION file + let content = try Data(contentsOf: resourceSourceURL) let localeId = sourcePath.parentDirectory.basenameWithoutExt let lprojFolder = resourcesBasePath.appending(component: localeId + ".lproj") @@ -1197,10 +1179,10 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { try fs.createDirectory(lprojFolder, recursive: true) - let localizableStringsDict = try RelativePath(validating: locBase + ".stringsdict") - let localizableStringsDictPath = lprojFolder.appending(localizableStringsDict) - info("create \(localizableStringsDict.pathString) from \(sourcePath.pathString)", sourceFile: localizableStringsDictPath.sourceFile) - try writeChanges(tag: localizableStringsDict.pathString, to: localizableStringsDictPath, contents: stringsDictContent, readOnly: false) + let localizableResource = try RelativePath(validating: locBase + "." + sourcePath.extension!) + let localizableResourcePath = lprojFolder.appending(localizableResource) + info("create \(localizableResource.pathString) from \(sourcePath.pathString)", sourceFile: localizableResourcePath.sourceFile) + try writeChanges(tag: localizableResource.pathString, to: localizableResourcePath, contents: content, readOnly: false) } func convertXCStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { From 7f23cc2d44dccd453f99e11eeec1631ac0a97dd9 Mon Sep 17 00:00:00 2001 From: fhasse95 <49185957+fhasse95@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:41:01 +0100 Subject: [PATCH 4/5] Update SkipstoneCommand.swift --- Sources/SkipBuild/Commands/SkipstoneCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index ac5999dd..59491944 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -1122,7 +1122,7 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { } } - /// Links resources in "process" mode, flattening the hierarchy and performing special processing for .strings, .xcstrings and other files + /// Links resources in "process" mode, flattening the hierarchy and performing special processing for .strings, .stringsdict, .xcstrings and other files func linkProcessResources(entry: ResourceEntry, resourcesBasePath: AbsolutePath) throws { for resourceFile in entry.urls.map(\.path).sorted() { let resourceFileCanonical = (resourceFile as NSString).standardizingPath From b7238f7819a614e8e5ccea8b4c3fcf2a4eba27d5 Mon Sep 17 00:00:00 2001 From: fhasse95 <49185957+fhasse95@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:46:37 +0100 Subject: [PATCH 5/5] Update SkipstoneCommand.swift --- Sources/SkipBuild/Commands/SkipstoneCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index 59491944..7729e6d5 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -1170,7 +1170,7 @@ struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { } func copyStrings(resourceSourceURL: URL, sourcePath: AbsolutePath) throws { - // process the .strings / .stringsdict in the same way that Xcode does: parse the FILE and use the content to synthesize a LANG.lproj/TABLENAME.EXTENSION file + // process the .strings / .stringsdict in the same way that Xcode does: read the FILE and use the content to synthesize a LANG.lproj/TABLENAME.EXTENSION file let content = try Data(contentsOf: resourceSourceURL) let localeId = sourcePath.parentDirectory.basenameWithoutExt