Skip to content

Instantly share code, notes, and snippets.

@beccadax
Last active March 6, 2020 16:10
Show Gist options
  • Save beccadax/79fa038c0af0cafb52dd to your computer and use it in GitHub Desktop.
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.
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.")
//
// 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