Last active
March 6, 2020 16:10
-
-
Save beccadax/79fa038c0af0cafb52dd to your computer and use it in GitHub Desktop.
Elegant handling of localizable strings in Swift. Note: This code is in Swift 2 and would need updates to be used in modern Swift.
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
let color = "blue" | |
let num = 42 | |
localized("Colorless green ideas sleep furiously.") | |
localized("Colorless \(color) ideas sleep furiously.") | |
localized("\(num.formatted("%05d")) colorless green ideas sleep furiously.") |
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
// | |
// LocalizableString.swift | |
// Scoreboard | |
// | |
// Created by Brent Royal-Gordon on 10/5/15. | |
// Copyright © 2015 Architechies. All rights reserved. | |
// | |
import Foundation | |
public func localized(str: LocalizableString, withTable table: String? = nil, inBundle bundle: NSBundle = .mainBundle()) -> String { | |
let format = str.formatString | |
let localizedFormat = bundle.localizedStringForKey(format, value: format, table: table) | |
return String(format: localizedFormat, arguments: str.formatArguments) | |
} | |
public extension CVarArgType { | |
func formatted(string: String) -> LocalizableString { | |
return LocalizableString(segments: [.Format (format: string, argument: self)]) | |
} | |
} | |
public struct LocalizableString { | |
private enum Segment { | |
case Literal (String) | |
case Format (format: String, argument: CVarArgType) | |
var format: String { | |
switch self { | |
case let .Literal(str): | |
return str.stringByReplacingOccurrencesOfString("%", withString: "%%") | |
case let .Format(format, _): | |
return format | |
} | |
} | |
var argument: CVarArgType? { | |
if case let .Format(_, arg) = self { | |
return arg | |
} | |
else { | |
return nil | |
} | |
} | |
} | |
private var segments: [Segment] | |
public init() { | |
segments = [] | |
} | |
private init(segments: [Segment]) { | |
self.segments = segments | |
} | |
public init(_ string: String) { | |
self.init(segments: [.Literal(string)]) | |
} | |
public init(object: NSObject) { | |
self.init(segments: [.Format(format: "%@", argument: object)]) | |
} | |
public var formatString: String { | |
return segments.map { $0.format }.reduce("", combine: +) | |
} | |
public var formatArguments: [CVarArgType] { | |
return segments.map { $0.argument }.flatMap { $0 } | |
} | |
} | |
extension LocalizableString: StringLiteralConvertible { | |
public init(stringLiteral value: String) { | |
self.init(value) | |
} | |
public init(unicodeScalarLiteral value: String) { | |
self.init(value) | |
} | |
public init(extendedGraphemeClusterLiteral value: String) { | |
self.init(value) | |
} | |
} | |
extension LocalizableString: StringInterpolationConvertible { | |
public init(stringInterpolation strings: LocalizableString...) { | |
let fixedStrings = strings.enumerate().map { i, str -> LocalizableString in | |
if i % 2 == 0 { | |
// Evens are literals | |
guard str.segments.count == 1, case let .Format (_, realStr) = str.segments.first! else { | |
fatalError("Interpolation sequence violation") | |
} | |
return LocalizableString(realStr as! String) | |
} | |
else { | |
// Odds are interpolations | |
return str | |
} | |
} | |
self.init(segments: fixedStrings.flatMap { $0.segments }) | |
} | |
public init<T>(stringInterpolationSegment expr: T) { | |
switch expr { | |
case let x as NSObject: | |
self.init(object: x) | |
case let x as String: | |
self.init(object: x) | |
case let x as LocalizableString: | |
self.init(segments: x.segments) | |
default: | |
self.init(object: String(expr)) | |
} | |
} | |
} | |
extension LocalizableString: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { | |
public var description: String { | |
return String(format: formatString, arguments: formatArguments) | |
} | |
public var debugDescription: String { | |
return "localized(\"" + segments.map { $0.debugDescription }.reduce("", combine: +) + "\")" | |
} | |
public func customMirror() -> Mirror { | |
return Mirror(self, children: ["formatString": formatString, "formatArguments": formatArguments]) | |
} | |
} | |
extension LocalizableString.Segment: CustomDebugStringConvertible { | |
private var debugDescription: String { | |
switch self { | |
case let .Literal(str): | |
return str.stringByReplacingOccurrencesOfString("\\", withString: "\\\\") | |
case let .Format(format, argument): | |
if format == "%@" { | |
return "\\(" + String(reflecting: argument) + ")" | |
} | |
else { | |
return "\\(" + String(reflecting: argument) + ".formatted(\"\(format)\"))" | |
} | |
} | |
} | |
} | |
extension LocalizableString: Equatable {} | |
public func == (lhs: LocalizableString, rhs: LocalizableString) -> Bool { | |
return lhs.formatString == rhs.formatString | |
} | |
public func += (inout lhs: LocalizableString, rhs: LocalizableString) { | |
lhs.segments += rhs.segments | |
} | |
public func + (var lhs: LocalizableString, rhs: LocalizableString) -> LocalizableString { | |
lhs += rhs | |
return lhs | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment