Last active
April 7, 2019 11:10
-
-
Save bill350/421f8c2d5460179d67e43adb2fbfb34c to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#!/usr/bin/env xcrun --sdk macosx swift | |
import Foundation | |
enum PathExtension: String { | |
case lproj | |
case stringsdict | |
} | |
// MARK: Arguments | |
enum InputArgument: String { | |
case output = "--output" | |
case input = "--input" | |
case `public` = "-public" | |
var valueRequired: Bool { | |
switch self { | |
case .output, .input: | |
return true | |
case .public: | |
return false | |
} | |
} | |
static var arguments: [InputArgument: Value] { | |
return CommandLine.arguments.reduce(([:], nil)) { arguments, value -> (value: [InputArgument: Value], last: InputArgument?) in | |
let new = InputArgument(rawValue: value) | |
let last = arguments.last | |
var arguments = arguments.value | |
if let argument = last, argument.valueRequired { | |
arguments[argument] = Value.string(value) | |
} else if let argument = new, !argument.valueRequired { | |
arguments[argument] = Value.none | |
} | |
return (arguments, new) | |
}.value | |
} | |
} | |
extension InputArgument { | |
enum Value { | |
case none | |
case string(String) | |
var string: String? { | |
switch self { | |
case let .string(value): | |
return value | |
default: | |
return nil | |
} | |
} | |
} | |
} | |
// MARK: Helpers | |
extension JSONDecoder { | |
func decode<T: Decodable>(JSONObject: Any, as type: T.Type? = nil) throws -> T { | |
let data = try JSONSerialization.data(withJSONObject: JSONObject, options: []) | |
return try self.decode(T.self, from: data) | |
} | |
} | |
func contentsOfDirectory(_ path: String, withPathExtension pathExtension: PathExtension) -> [String] { | |
do { | |
return try FileManager.default.contentsOfDirectory(atPath: path) | |
.filter { ($0 as NSString).pathExtension == pathExtension.rawValue } | |
.map { (path as NSString).appendingPathComponent($0) } | |
} catch { | |
return [] | |
} | |
} | |
// MARK: Entities | |
struct StringDict { | |
let path: String | |
let items: [PluralItem] | |
var name: String { | |
let path = (self.path as NSString).deletingLastPathComponent | |
let lastPathComponent = (path as NSString).lastPathComponent as NSString | |
return lastPathComponent.deletingPathExtension | |
} | |
init?(path: String) { | |
guard let dictionary = NSDictionary(contentsOfFile: path) as? [String: Any] else { | |
return nil | |
} | |
self.path = path | |
self.items = dictionary.compactMap { | |
try? PluralItem(key: $0.key, value: $0.value) | |
} | |
} | |
private func formatFunction(item: PluralItem, accessModifier: String) -> String { | |
let functionName = item.key.components(separatedBy: "_").reduce("") { name, component -> String in | |
let component = name.isEmpty ? component : component.capitalized | |
return name + component | |
} | |
return """ | |
/// \(item.format.string(1)) | |
/// \(item.format.string(2)) | |
\(accessModifier) static func \(functionName)(_ p1: Int) -> String { | |
return L10n.trPlurals("\(item.key)", count: p1) | |
} | |
""" | |
} | |
func swiftString(publicAccess: Bool) -> String { | |
let accessModifier = publicAccess ? "public" : "internal" | |
let functions = self.items.sorted(by: { $0.key < $1.key }) | |
.map { self.formatFunction(item: $0, accessModifier: accessModifier) } | |
.joined(separator: "\n\n") | |
return """ | |
// Localization | |
// Copyright Β© 2019 Back Market. All rights reserved. | |
import Foundation | |
import Lokalise | |
//swiftlint:disable identifier_name | |
\(accessModifier) extension L10n { | |
\(accessModifier) struct Plurals { | |
\(functions) | |
} | |
} | |
//swiftlint:enable identifier_name | |
\(accessModifier) extension L10n { | |
static func trPlurals(_ key: String, count: Int) -> String { | |
let format = Lokalise.shared.localizedString(forKey: key, value: nil, table: nil) | |
return String(format: format, locale: Locale.current, arguments: [count]) | |
} | |
} | |
""" | |
} | |
} | |
extension StringDict { | |
struct PluralItem { | |
let key: String | |
let format: Format | |
init(key: String, value: Any) throws { | |
self.key = key | |
let formatContainer = try JSONDecoder().decode(JSONObject: value, as: Format.Container.self) | |
self.format = formatContainer.format | |
} | |
} | |
} | |
extension StringDict.PluralItem { | |
struct Format: Decodable { | |
enum CodingKeys: String, CodingKey { | |
case oneFormatter = "one" | |
case otherFormatter = "other" | |
} | |
let oneFormatter: String | |
let otherFormatter: String | |
func string(_ value: Int) -> String { | |
if value > 1 { | |
return String(format: self.otherFormatter, arguments: [value]) | |
} | |
return String(format: self.oneFormatter, arguments: [value]) | |
} | |
} | |
} | |
extension StringDict.PluralItem.Format { | |
struct Container: Decodable { | |
let format: StringDict.PluralItem.Format | |
} | |
} | |
let baseFileName = "Base" | |
let args = InputArgument.arguments | |
guard let languageFolderPath = args[.input]?.string, | |
let outputPath = args[.output]?.string else { | |
print("β - Please provide \(InputArgument.input.rawValue) and \(InputArgument.output.rawValue) args") | |
exit(1) | |
} | |
print("π - Scanning available languages") | |
let stringDicts = contentsOfDirectory(languageFolderPath, withPathExtension: .lproj) | |
.flatMap { | |
contentsOfDirectory($0, withPathExtension: .stringsdict) | |
}.compactMap { | |
StringDict(path: $0) | |
} | |
print("π΅οΈββοΈ - Found: \(stringDicts.map { $0.name }.joined(separator: ", "))") | |
let baseDict = stringDicts.first(where: { $0.name == baseFileName }) ?? stringDicts.first | |
let publicAccess = args[.public] != nil | |
guard let swiftString = baseDict?.swiftString(publicAccess: publicAccess) else { | |
print("β - No languages has been found") | |
exit(1) | |
} | |
print("π - Writing swift file") | |
do { | |
try swiftString.write(toFile: outputPath, atomically: false, encoding: .utf8) | |
} catch { | |
print("β - Cannot write file : \(error)") | |
exit(1) | |
} | |
print("β - Successfully updated \((outputPath as NSString).lastPathComponent)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment