Skip to content

Instantly share code, notes, and snippets.

@bill350
Last active April 7, 2019 11:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bill350/421f8c2d5460179d67e43adb2fbfb34c to your computer and use it in GitHub Desktop.
Save bill350/421f8c2d5460179d67e43adb2fbfb34c to your computer and use it in GitHub Desktop.
#!/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