-
-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Meta] Automatic String Organization (#1372)
* Automate String Organization. * Comment the script so it's easier to maintain? Or messier? * Linting post comments * Rename ShellScript -> Alphabetize Strings for tvOS * use swift regex, add error messages, clean up separators * Only search for ./Translations/en.lproj/Localizable.strings * Purge Unused Strings Script * Organize Translation Scripts into a Folder. Update references at the project level. * clean up --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
- Loading branch information
Showing
5 changed files
with
332 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// Swiftfin is subject to the terms of the Mozilla Public | ||
// License, v2.0. If a copy of the MPL was not distributed with this | ||
// file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
// | ||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors | ||
// | ||
|
||
import Foundation | ||
|
||
// Get the English localization file | ||
let fileURL = URL(fileURLWithPath: "./Translations/en.lproj/Localizable.strings") | ||
|
||
// This regular expression pattern matches lines of the format: | ||
// "Key" = "Value"; | ||
let regex = #/^\"(?<key>[^\"]+)\"\s*=\s*\"(?<value>[^\"]+)\";/# | ||
|
||
// Attempt to read the file content. | ||
guard let content = try? String(contentsOf: fileURL, encoding: .utf8) else { | ||
print("Unable to read file: \(fileURL.path)") | ||
exit(1) | ||
} | ||
|
||
// Split file content by newlines to process line by line. | ||
let strings = content.components(separatedBy: .newlines) | ||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } | ||
.filter { !$0.isEmpty && !$0.hasPrefix("//") } | ||
|
||
let entries = strings.reduce(into: [String: String]()) { | ||
if let match = $1.firstMatch(of: regex) { | ||
let key = String(match.output.key) | ||
let value = String(match.output.value) | ||
$0[key] = value | ||
} else { | ||
print("Error: Invalid line format in \(fileURL.path): \($1)") | ||
exit(1) | ||
} | ||
} | ||
|
||
// Sort the keys alphabetically for consistent ordering. | ||
let sortedKeys = entries.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } | ||
let newContent = sortedKeys.map { "/// \(entries[$0]!)\n\"\($0)\" = \"\(entries[$0]!)\";" }.joined(separator: "\n\n") | ||
|
||
// Write the updated, sorted, and commented localizations back to the file. | ||
do { | ||
try newContent.write(to: fileURL, atomically: true, encoding: .utf8) | ||
|
||
if let derivedFileDirectory = ProcessInfo.processInfo.environment["DERIVED_FILE_DIR"] { | ||
try? "".write(toFile: derivedFileDirectory + "/alphabetizeStrings.txt", atomically: true, encoding: .utf8) | ||
} | ||
} catch { | ||
print("Error: Failed to write to \(fileURL.path)") | ||
exit(1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// | ||
// Swiftfin is subject to the terms of the Mozilla Public | ||
// License, v2.0. If a copy of the MPL was not distributed with this | ||
// file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
// | ||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors | ||
// | ||
|
||
import Foundation | ||
|
||
// Path to the English localization file | ||
let localizationFile = "./Translations/en.lproj/Localizable.strings" | ||
|
||
// Directories to scan for Swift files | ||
let directoriesToScan = ["./Shared", "./Swiftfin", "./Swiftfin tvOS"] | ||
|
||
// File to exclude from scanning | ||
let excludedFile = "./Shared/Strings/Strings.swift" | ||
|
||
// Regular expressions to match localization entries and usage in Swift files | ||
// Matches lines like "Key" = "Value"; | ||
let localizationRegex = #/^\"(?<key>[^\"]+)\"\s*=\s*\"(?<value>[^\"]+)\";$/# | ||
|
||
// Matches usage like L10n.key in Swift files | ||
let usageRegex = #/L10n\.(?<key>[a-zA-Z0-9_]+)/# | ||
|
||
// Attempt to load the localization file's content | ||
guard let localizationContent = try? String(contentsOfFile: localizationFile, encoding: .utf8) else { | ||
print("Unable to read localization file at \(localizationFile)") | ||
exit(1) | ||
} | ||
|
||
// Split the file into lines and initialize a dictionary for localization entries | ||
let localizationLines = localizationContent.components(separatedBy: .newlines) | ||
var localizationEntries = [String: String]() | ||
|
||
// Parse each line to extract key-value pairs | ||
for line in localizationLines { | ||
let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) | ||
|
||
// Skip empty lines or comments | ||
if trimmed.isEmpty || trimmed.hasPrefix("//") { continue } | ||
|
||
// Match valid localization entries and add them to the dictionary | ||
if let match = line.firstMatch(of: localizationRegex) { | ||
let key = String(match.output.key) | ||
let value = String(match.output.value) | ||
localizationEntries[key] = value | ||
} | ||
} | ||
|
||
// Set to store all keys found in the codebase | ||
var usedKeys = Set<String>() | ||
|
||
// Function to scan a directory recursively for Swift files | ||
func scanDirectory(_ path: String) { | ||
let fileManager = FileManager.default | ||
guard let enumerator = fileManager.enumerator(atPath: path) else { return } | ||
|
||
for case let file as String in enumerator { | ||
let filePath = "\(path)/\(file)" | ||
|
||
// Skip the excluded file | ||
if filePath == excludedFile { continue } | ||
|
||
// Process only Swift files | ||
if file.hasSuffix(".swift") { | ||
if let fileContent = try? String(contentsOfFile: filePath, encoding: .utf8) { | ||
for line in fileContent.components(separatedBy: .newlines) { | ||
// Find all matches for L10n.key in each line | ||
let matches = line.matches(of: usageRegex) | ||
for match in matches { | ||
let key = String(match.output.key) | ||
usedKeys.insert(key) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Scan all specified directories | ||
for directory in directoriesToScan { | ||
scanDirectory(directory) | ||
} | ||
|
||
// MARK: - Remove Unused Keys | ||
|
||
// Identify keys in the localization file that are not used in the codebase | ||
let unusedKeys = localizationEntries.keys.filter { !usedKeys.contains($0) } | ||
|
||
// Remove unused keys from the dictionary | ||
unusedKeys.forEach { localizationEntries.removeValue(forKey: $0) } | ||
|
||
// MARK: - Write Updated Localizable.strings | ||
|
||
// Sort keys alphabetically for consistent formatting | ||
let sortedKeys = localizationEntries.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } | ||
|
||
// Reconstruct the localization file with sorted and updated entries | ||
let updatedContent = sortedKeys.map { "/// \(localizationEntries[$0]!)\n\"\($0)\" = \"\(localizationEntries[$0]!)\";" } | ||
.joined(separator: "\n\n") | ||
|
||
// Attempt to write the updated content back to the localization file | ||
do { | ||
try updatedContent.write(toFile: localizationFile, atomically: true, encoding: .utf8) | ||
print("Localization file updated. Removed \(unusedKeys.count) unused keys.") | ||
} catch { | ||
print("Error: Failed to write updated localization file.") | ||
exit(1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.