From d1dfa1b240df1fd008e9a741a81f98985cb6dc71 Mon Sep 17 00:00:00 2001 From: Nicolas Camenisch Date: Mon, 19 Oct 2020 16:14:17 +0200 Subject: [PATCH] Add support for .intentDefinition files --- Package.resolved | 9 ++ Package.swift | 2 + .../FileHandling/StringsFilesSearch.swift | 6 + .../OldCommandLine/CommandLineActor.swift | 112 ++++++++++++++++++ .../TaskHandlers/InterfacesTaskHandler.swift | 9 ++ 5 files changed, 138 insertions(+) diff --git a/Package.resolved b/Package.resolved index 25252fd2..a96a6224 100644 --- a/Package.resolved +++ b/Package.resolved @@ -63,6 +63,15 @@ "revision": "c72c4564f8c0a24700a59824880536aca45a4cae", "version": "6.0.1" } + }, + { + "package": "SwiftyXML", + "repositoryURL": "https://github.com/chenyunguiMilook/SwiftyXML.git", + "state": { + "branch": null, + "revision": "30040f38675bc3a1666a0b0f6417adabaf26a3c6", + "version": "3.1.0" + } } ] }, diff --git a/Package.swift b/Package.swift index 0732e8b9..cdc09d3c 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,7 @@ let package = Package( .package(name: "SwiftCLI", url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.1"), .package(name: "Toml", url: "https://github.com/jdfergason/swift-toml.git", .branch("master")), .package(name: "SwiftSyntax", url: "https://github.com/apple/swift-syntax.git", .exact("0.50300.0")), + .package(name: "SwiftyXML", url: "https://github.com/chenyunguiMilook/SwiftyXML.git", from: "3.0.2") ], targets: [ .target( @@ -34,6 +35,7 @@ let package = Package( "SwiftCLI", "SwiftSyntax", "Toml", + "SwiftyXML" ], path: "Sources/BartyCrouchKit" ), diff --git a/Sources/BartyCrouchKit/FileHandling/StringsFilesSearch.swift b/Sources/BartyCrouchKit/FileHandling/StringsFilesSearch.swift index 43dfd47e..ce3e2cfa 100644 --- a/Sources/BartyCrouchKit/FileHandling/StringsFilesSearch.swift +++ b/Sources/BartyCrouchKit/FileHandling/StringsFilesSearch.swift @@ -18,6 +18,12 @@ public final class StringsFilesSearch: FilesSearchable { return self.findAllFilePaths(inDirectoryPath: baseDirectoryPath, matching: ibFileRegex) } + public func findAllIntentDefinitionFiles(within baseDirectoryPath: String, withLocale locale: String = "Base") -> [String] { + // swiftlint:disable:next force_try + let intentsFileRegex = try! NSRegularExpression(pattern: "^(.*\\/)?\(locale).lproj.*\\.intentdefinition\\z", options: .caseInsensitive) + return self.findAllFilePaths(inDirectoryPath: baseDirectoryPath, matching: intentsFileRegex) + } + public func findAllStringsFiles(within baseDirectoryPath: String, withLocale locale: String) -> [String] { // swiftlint:disable:next force_try let stringsFileRegex = try! NSRegularExpression(pattern: "^(.*\\/)?\(locale).lproj.*\\.strings\\z", options: .caseInsensitive) diff --git a/Sources/BartyCrouchKit/OldCommandLine/CommandLineActor.swift b/Sources/BartyCrouchKit/OldCommandLine/CommandLineActor.swift index 3831cbac..fc0cce4c 100644 --- a/Sources/BartyCrouchKit/OldCommandLine/CommandLineActor.swift +++ b/Sources/BartyCrouchKit/OldCommandLine/CommandLineActor.swift @@ -1,6 +1,7 @@ // swiftlint:disable function_parameter_count type_body_length cyclomatic_complexity import Foundation +import SwiftyXML // NOTE: // This file was not refactored as port of the work/big-refactoring branch for version 4.0 to prevent unexpected behavior changes. @@ -75,6 +76,29 @@ public class CommandLineActor { ) } } + + func actOnIntentDefinitions(paths: [String], override: Bool, verbose: Bool, defaultToBase: Bool, unstripped: Bool, ignoreEmptyStrings: Bool) { + let inputFilePaths = paths.flatMap { StringsFilesSearch.shared.findAllIntentDefinitionFiles(within: $0, withLocale: "Base") }.withoutDuplicates() + + guard !inputFilePaths.isEmpty else { print("No input files found.", level: .warning); return } + + for inputFilePath in inputFilePaths { + guard FileManager.default.fileExists(atPath: inputFilePath) else { + print("No file exists at input path '\(inputFilePath)'", level: .error); return + } + + let outputStringsFilePaths = StringsFilesSearch.shared.findAllLocalesForStringsFile(sourceFilePath: inputFilePath).filter { $0 != inputFilePath } + self.incrementalIntentDefinitionUpdate( + inputFilePath, + outputStringsFilePaths, + override: override, + verbose: verbose, + defaultToBase: defaultToBase, + unstripped: unstripped, + ignoreEmptyStrings: ignoreEmptyStrings + ) + } + } func actOnTranslate(paths: [String], override: Bool, verbose: Bool, secret: String, locale: String) { let inputFilePaths = paths.flatMap { StringsFilesSearch.shared.findAllStringsFiles(within: $0, withLocale: locale) }.withoutDuplicates() @@ -315,6 +339,94 @@ public class CommandLineActor { print("Successfully updated strings file(s) of Storyboard or XIB file.", level: .success, file: inputFilePath) } + + private func incrementalIntentDefinitionUpdate( + _ inputFilePath: String, + _ outputStringsFilePaths: [String], + override: Bool, + verbose: Bool, + defaultToBase: Bool, + unstripped: Bool, + ignoreEmptyStrings: Bool + ) { + let extractedStringsFilePath = inputFilePath + ".tmpstrings" + + // Extract translations + var xmlString = "" + do { + xmlString = try String(contentsOfFile: inputFilePath) + } catch { + print("Could not extract string for file at path '\(inputFilePath)'.", level: .error) + return + } + + let xml = XML(string: xmlString) + var translationDict = [String: String]() + + // Traverse the xml structure and search for translatable values + // To check if a value is translatable, it is sufficient to check if the parent dictionary + // contains a key with the same name and the suffix "ID". + func traverse(xml: XML) { + for child in xml.xmlChildren { + traverse(xml: child) + } + + guard xml.xmlName == "dict" else { + return + } + + var xmlDict = [String: String]() + var currentKey = "" + for child in xml.xmlChildren { + if child.xmlName == "key" { + currentKey = child.stringValue + } else if child.xmlName == "string" { + xmlDict[currentKey] = child.stringValue + } + } + + for (key, value) in xmlDict { + guard key.hasSuffix("ID") else { + continue + } + + guard let baseTranslation = xmlDict[String(key.prefix(key.count - 2))] else { + continue + } + + translationDict[value] = baseTranslation.replacingOccurrences(of: "/\\\n", with: "\\n") // Convert linebreaks + } + } + traverse(xml: xml) + + let translationString = translationDict.map({ (key, value) in "\"\(key)\" = \"\(value)\";" }).joined(separator: "\n\n") + try? translationString.write(toFile: extractedStringsFilePath, atomically: true, encoding: .utf8) + + for outputStringsFilePath in outputStringsFilePaths { + guard let stringsFileUpdater = StringsFileUpdater(path: outputStringsFilePath) else { continue } + + stringsFileUpdater.incrementallyUpdateKeys( + withStringsFileAtPath: extractedStringsFilePath, + addNewValuesAsEmpty: !defaultToBase, + override: override, + keepWhitespaceSurroundings: unstripped, + ignoreEmptyStrings: ignoreEmptyStrings + ) + + if verbose { + print("Incrementally updated keys of file '\(outputStringsFilePath)'.", level: .info) + } + } + + do { + try FileManager.default.removeItem(atPath: extractedStringsFilePath) + } catch { + print("Temporary strings file couldn't be deleted at path '\(extractedStringsFilePath)'", level: .error) + return + } + + print("Successfully updated strings file(s) of Intent definition file.", level: .success, file: inputFilePath) + } private func translate(secret: String, _ inputFilePath: String, _ outputStringsFilePaths: [String], override: Bool, verbose: Bool) { var overallTranslatedValuesCount = 0 diff --git a/Sources/BartyCrouchKit/TaskHandlers/InterfacesTaskHandler.swift b/Sources/BartyCrouchKit/TaskHandlers/InterfacesTaskHandler.swift index d509695f..a60787df 100644 --- a/Sources/BartyCrouchKit/TaskHandlers/InterfacesTaskHandler.swift +++ b/Sources/BartyCrouchKit/TaskHandlers/InterfacesTaskHandler.swift @@ -20,6 +20,15 @@ extension InterfacesTaskHandler: TaskHandler { unstripped: options.unstripped, ignoreEmptyStrings: options.ignoreEmptyStrings ) + + CommandLineActor().actOnIntentDefinitions( + paths: options.paths, + override: false, + verbose: GlobalOptions.verbose.value, + defaultToBase: options.defaultToBase, + unstripped: options.unstripped, + ignoreEmptyStrings: options.ignoreEmptyStrings + ) } } }