mirror of
https://github.com/zhigang1992/R.swift.git
synced 2026-04-29 12:45:30 +08:00
Split out format specifiers
This commit is contained in:
@@ -96,6 +96,8 @@
|
||||
D5F97E4C1C1819160066D7C0 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F97E411C1816360066D7C0 /* Image.swift */; };
|
||||
D5F97E4D1C1819160066D7C0 /* ResourceFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F97E431C1816790066D7C0 /* ResourceFile.swift */; };
|
||||
D5F97E4E1C1819160066D7C0 /* Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F97E451C18169E0066D7C0 /* Nib.swift */; };
|
||||
E2156B8E1CC5254A00F341DC /* FormatSpecifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2156B8D1CC5254A00F341DC /* FormatSpecifier.swift */; };
|
||||
E2156B8F1CC5255000F341DC /* FormatSpecifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2156B8D1CC5254A00F341DC /* FormatSpecifier.swift */; };
|
||||
E22D43631C9582CA00692FFF /* ColorGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22D43621C9582CA00692FFF /* ColorGenerator.swift */; };
|
||||
E22D43651C95845200692FFF /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22D43641C95845200692FFF /* ColorPalette.swift */; };
|
||||
E24720CB1C96B6A600DF291D /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22D43641C95845200692FFF /* ColorPalette.swift */; };
|
||||
@@ -151,6 +153,7 @@
|
||||
D5F97E411C1816360066D7C0 /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
D5F97E431C1816790066D7C0 /* ResourceFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceFile.swift; sourceTree = "<group>"; };
|
||||
D5F97E451C18169E0066D7C0 /* Nib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nib.swift; sourceTree = "<group>"; };
|
||||
E2156B8D1CC5254A00F341DC /* FormatSpecifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatSpecifier.swift; sourceTree = "<group>"; };
|
||||
E22D43621C9582CA00692FFF /* ColorGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorGenerator.swift; sourceTree = "<group>"; };
|
||||
E22D43641C95845200692FFF /* ColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -273,6 +276,7 @@
|
||||
D5F97E3F1C1815E70066D7C0 /* AssetFolder.swift */,
|
||||
E22D43641C95845200692FFF /* ColorPalette.swift */,
|
||||
D5F97E391C1812AE0066D7C0 /* Font.swift */,
|
||||
E2156B8D1CC5254A00F341DC /* FormatSpecifier.swift */,
|
||||
D5F97E411C1816360066D7C0 /* Image.swift */,
|
||||
5D997C961C7C2BEE00B2F376 /* LocalizableStrings.swift */,
|
||||
D5F97E451C18169E0066D7C0 /* Nib.swift */,
|
||||
@@ -444,6 +448,7 @@
|
||||
D5B799721C199755009EA901 /* ImageGenerator.swift in Sources */,
|
||||
D5B7997A1C19C1BD009EA901 /* WhiteListedExtensionsResourceType.swift in Sources */,
|
||||
D59F722A1C1963EA0089767C /* Struct.swift in Sources */,
|
||||
E2156B8F1CC5255000F341DC /* FormatSpecifier.swift in Sources */,
|
||||
D5A0A82E1C4793C20089ED2C /* TypePrinter.swift in Sources */,
|
||||
D5A0A82D1C4793C20089ED2C /* SwiftCodeConverible.swift in Sources */,
|
||||
D5B799731C199755009EA901 /* NibGenerator.swift in Sources */,
|
||||
@@ -465,6 +470,7 @@
|
||||
files = (
|
||||
5D997C951C7C291900B2F376 /* StringsGenerator.swift in Sources */,
|
||||
D5B799831C1B8C78009EA901 /* Module.swift in Sources */,
|
||||
E2156B8E1CC5254A00F341DC /* FormatSpecifier.swift in Sources */,
|
||||
D5C5A8EF1BB7196000163E71 /* Core.swift in Sources */,
|
||||
D58672491C21FC9700A760EC /* TypeSequenceProvider.swift in Sources */,
|
||||
D5F97E461C18169E0066D7C0 /* Nib.swift in Sources */,
|
||||
|
||||
@@ -41,7 +41,7 @@ struct StringsGenerator: Generator {
|
||||
|
||||
private static func stringStructFromLocalizableStrings(filename: String, strings: [LocalizableStrings]) -> Struct? {
|
||||
|
||||
var allParams: [String: [Type]] = [:]
|
||||
var allParams: [String: [FormatSpecifier]] = [:]
|
||||
let baseKeys = strings
|
||||
.filter { $0.locale.isBase }
|
||||
.map { Set($0.dictionary.keys) }
|
||||
@@ -91,17 +91,28 @@ struct StringsGenerator: Generator {
|
||||
warn("Strings file \(filenameLocale) is missing translations for keys: \(paddedKeysString)")
|
||||
}
|
||||
|
||||
func includeParam(key: String) -> Bool {
|
||||
if let baseKeys = baseKeys {
|
||||
return baseKeys.contains(key)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return Struct(
|
||||
type: Type(module: .Host, name: sanitizedSwiftName(filename)),
|
||||
implements: [],
|
||||
typealiasses: [],
|
||||
properties: [],
|
||||
functions: allParams.map { ($0.0, $0.1, filename)}.map(StringsGenerator.stringFunction),
|
||||
functions: allParams
|
||||
.filter { includeParam($0.0) }
|
||||
.map { ($0.0, $0.1, filename)}
|
||||
.map(StringsGenerator.stringFunction),
|
||||
structs: []
|
||||
)
|
||||
}
|
||||
|
||||
private static func stringFunction(key: String, params: [Type], tableName: String) -> Function {
|
||||
private static func stringFunction(key: String, params: [FormatSpecifier], tableName: String) -> Function {
|
||||
if params.isEmpty {
|
||||
return stringFunctionNoParams(key, tableName: tableName)
|
||||
}
|
||||
@@ -134,16 +145,16 @@ struct StringsGenerator: Generator {
|
||||
)
|
||||
}
|
||||
|
||||
private static func stringFunctionParams(key: String, params: [Type], tableName: String) -> Function {
|
||||
private static func stringFunctionParams(key: String, params: [FormatSpecifier], tableName: String) -> Function {
|
||||
|
||||
let params = params.enumerate().map { ix, type -> Function.Parameter in
|
||||
let params = params.enumerate().map { ix, formatSpecifier -> Function.Parameter in
|
||||
let name = "value\(ix + 1)"
|
||||
|
||||
if ix == 0 {
|
||||
return Function.Parameter(name: name, type: type)
|
||||
return Function.Parameter(name: name, type: formatSpecifier.type)
|
||||
}
|
||||
else {
|
||||
return Function.Parameter(name: "_", localName: name, type: type)
|
||||
return Function.Parameter(name: "_", localName: name, type: formatSpecifier.type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
146
R.swift/ResourceTypes/FormatSpecifier.swift
Normal file
146
R.swift/ResourceTypes/FormatSpecifier.swift
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// FormatSpecifier.swift
|
||||
// R.swift
|
||||
//
|
||||
// Created by Tom Lokhorst on 2016-04-18.
|
||||
// Copyright © 2016 Mathijs Kadijk. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1
|
||||
enum FormatSpecifier {
|
||||
case Object
|
||||
case Double
|
||||
case Int
|
||||
case UInt
|
||||
case Character
|
||||
case CStringPointer
|
||||
case VoidPointer
|
||||
case TopType
|
||||
|
||||
var type: Type {
|
||||
switch self {
|
||||
case .Object:
|
||||
return Type._String
|
||||
|
||||
case .Double:
|
||||
return Type._Double
|
||||
|
||||
case .Int:
|
||||
return Type._Int
|
||||
|
||||
case .UInt:
|
||||
return Type._UInt
|
||||
|
||||
case .Character:
|
||||
return Type._Character
|
||||
|
||||
case .CStringPointer:
|
||||
return Type._CStringPointer
|
||||
|
||||
case .VoidPointer:
|
||||
return Type._VoidPointer
|
||||
|
||||
case .TopType:
|
||||
return Type._Any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FormatSpecifier {
|
||||
init?(formatChar char: Swift.Character) {
|
||||
let lcChar = Swift.String(char).lowercaseString.characters.first!
|
||||
switch lcChar {
|
||||
case "@":
|
||||
self = .Object
|
||||
case "a", "e", "f", "g":
|
||||
self = .Double
|
||||
case "d", "i":
|
||||
self = .Int
|
||||
case "o", "u", "x":
|
||||
self = .UInt
|
||||
case "c":
|
||||
self = .Character
|
||||
case "s":
|
||||
self = .CStringPointer
|
||||
case "p":
|
||||
self = .VoidPointer
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func formatSpecifiersFromFormatString(formatString: String) -> [FormatSpecifier] {
|
||||
return _formatSpecifiersFromFormatString(formatString)
|
||||
}
|
||||
}
|
||||
|
||||
// Based on StringsFileParser.swift from SwiftGen
|
||||
|
||||
private let formatTypesRegEx: NSRegularExpression = {
|
||||
let pattern_int = "(?:h|hh|l|ll|q|z|t|j)?([dioux])" // %d/%i/%o/%u/%x with their optional length modifiers like in "%lld"
|
||||
let pattern_float = "[aefg]"
|
||||
let position = "([1-9]\\d*\\$)?" // like in "%3$" to make positional specifiers
|
||||
let precision = "[-+]?\\d?(?:\\.\\d)?" // precision like in "%1.2f"
|
||||
do {
|
||||
return try NSRegularExpression(pattern: "(?<!%)%\(position)\(precision)(@|\(pattern_int)|\(pattern_float)|[csp])", options: [.CaseInsensitive])
|
||||
} catch {
|
||||
fatalError("Error building the regular expression used to match string formats")
|
||||
}
|
||||
}()
|
||||
|
||||
// "I give %d apples to %@" --> [.Int, .String]
|
||||
private func _formatSpecifiersFromFormatString(formatString: String) -> [FormatSpecifier] {
|
||||
let nsString = formatString as NSString
|
||||
let range = NSRange(location: 0, length: nsString.length)
|
||||
|
||||
// Extract the list of chars (conversion specifiers) and their optional positional specifier
|
||||
let chars = formatTypesRegEx.matchesInString(formatString, options: [], range: range).map { match -> (String, Int?) in
|
||||
let range: NSRange
|
||||
if match.rangeAtIndex(3).location != NSNotFound {
|
||||
// [dioux] are in range #3 because in #2 there may be length modifiers (like in "lld")
|
||||
range = match.rangeAtIndex(3)
|
||||
} else {
|
||||
// otherwise, no length modifier, the conversion specifier is in #2
|
||||
range = match.rangeAtIndex(2)
|
||||
}
|
||||
let char = nsString.substringWithRange(range)
|
||||
|
||||
let posRange = match.rangeAtIndex(1)
|
||||
if posRange.location == NSNotFound {
|
||||
// No positional specifier
|
||||
return (char, nil)
|
||||
} else {
|
||||
// Remove the "$" at the end of the positional specifier, and convert to Int
|
||||
let posRange1 = NSRange(location: posRange.location, length: posRange.length-1)
|
||||
let pos = nsString.substringWithRange(posRange1)
|
||||
return (char, Int(pos))
|
||||
}
|
||||
}
|
||||
|
||||
// enumerate the conversion specifiers and their optionally forced position and build the array of format specifiers accordingly
|
||||
var list = [FormatSpecifier]()
|
||||
var nextNonPositional = 1
|
||||
for (str, pos) in chars {
|
||||
if let char = str.characters.first, let p = FormatSpecifier(formatChar: char) {
|
||||
let insertionPos: Int
|
||||
if let pos = pos {
|
||||
insertionPos = pos
|
||||
}
|
||||
else {
|
||||
insertionPos = nextNonPositional
|
||||
nextNonPositional += 1
|
||||
}
|
||||
|
||||
if insertionPos > 0 {
|
||||
while list.count <= insertionPos-1 {
|
||||
list.append(FormatSpecifier.TopType)
|
||||
}
|
||||
list[insertionPos-1] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -27,7 +27,7 @@ struct LocalizableStrings: WhiteListedExtensionsResourceType {
|
||||
|
||||
let filename: String
|
||||
let locale: Locale
|
||||
let dictionary: [String : (value: String, params: [Type])]
|
||||
let dictionary: [String : (value: String, params: [FormatSpecifier])]
|
||||
|
||||
init(url: NSURL) throws {
|
||||
try LocalizableStrings.throwIfUnsupportedExtension(url.pathExtension)
|
||||
@@ -55,13 +55,13 @@ struct LocalizableStrings: WhiteListedExtensionsResourceType {
|
||||
}
|
||||
|
||||
// Parse strings from NSDictionary
|
||||
var dictionary: [String : (value: String, params: [Type])] = [:]
|
||||
var dictionary: [String : (value: String, params: [FormatSpecifier])] = [:]
|
||||
for (key, obj) in nsDictionary {
|
||||
if let
|
||||
key = key as? String,
|
||||
val = obj as? String
|
||||
{
|
||||
dictionary[key] = (val, typesFromFormatString(val))
|
||||
dictionary[key] = (val, FormatSpecifier.formatSpecifiersFromFormatString(val))
|
||||
}
|
||||
else {
|
||||
throw ResourceParsingError.ParsingFailed("Non-string value in \(url.absoluteString): \(key) = \(obj)")
|
||||
@@ -73,97 +73,3 @@ struct LocalizableStrings: WhiteListedExtensionsResourceType {
|
||||
self.dictionary = dictionary
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1
|
||||
extension Type {
|
||||
init?(formatChar char: Character) {
|
||||
let lcChar = String(char).lowercaseString.characters.first!
|
||||
switch lcChar {
|
||||
case "@":
|
||||
self = Type._String
|
||||
case "a", "e", "f", "g":
|
||||
self = Type._Double
|
||||
case "d", "i":
|
||||
self = Type._Int
|
||||
case "o", "u", "x":
|
||||
self = Type._UInt
|
||||
case "c":
|
||||
self = Type._Character
|
||||
case "s":
|
||||
self = Type._CStringPointer
|
||||
case "p":
|
||||
self = Type._VoidPointer
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Based on StringsFileParser.swift from SwiftGen
|
||||
|
||||
private let formatTypesRegEx: NSRegularExpression = {
|
||||
let pattern_int = "(?:h|hh|l|ll|q|z|t|j)?([dioux])" // %d/%i/%o/%u/%x with their optional length modifiers like in "%lld"
|
||||
let pattern_float = "[aefg]"
|
||||
let position = "([1-9]\\d*\\$)?" // like in "%3$" to make positional specifiers
|
||||
let precision = "[-+]?\\d?(?:\\.\\d)?" // precision like in "%1.2f"
|
||||
do {
|
||||
return try NSRegularExpression(pattern: "(?<!%)%\(position)\(precision)(@|\(pattern_int)|\(pattern_float)|[csp])", options: [.CaseInsensitive])
|
||||
} catch {
|
||||
fatalError("Error building the regular expression used to match string formats")
|
||||
}
|
||||
}()
|
||||
|
||||
// "I give %d apples to %@" --> [.Int, .String]
|
||||
private func typesFromFormatString(formatString: String) -> [Type] {
|
||||
let range = NSRange(location: 0, length: (formatString as NSString).length)
|
||||
|
||||
// Extract the list of chars (conversion specifiers) and their optional positional specifier
|
||||
let chars = formatTypesRegEx.matchesInString(formatString, options: [], range: range).map { match -> (String, Int?) in
|
||||
let range: NSRange
|
||||
if match.rangeAtIndex(3).location != NSNotFound {
|
||||
// [dioux] are in range #3 because in #2 there may be length modifiers (like in "lld")
|
||||
range = match.rangeAtIndex(3)
|
||||
} else {
|
||||
// otherwise, no length modifier, the conversion specifier is in #2
|
||||
range = match.rangeAtIndex(2)
|
||||
}
|
||||
let char = (formatString as NSString).substringWithRange(range)
|
||||
|
||||
let posRange = match.rangeAtIndex(1)
|
||||
if posRange.location == NSNotFound {
|
||||
// No positional specifier
|
||||
return (char, nil)
|
||||
} else {
|
||||
// Remove the "$" at the end of the positional specifier, and convert to Int
|
||||
let posRange1 = NSRange(location: posRange.location, length: posRange.length-1)
|
||||
let pos = (formatString as NSString).substringWithRange(posRange1)
|
||||
return (char, Int(pos))
|
||||
}
|
||||
}
|
||||
|
||||
// enumerate the conversion specifiers and their optionally forced position and build the array of Types accordingly
|
||||
var list = [Type]()
|
||||
var nextNonPositional = 1
|
||||
for (str, pos) in chars {
|
||||
if let char = str.characters.first, let p = Type(formatChar: char) {
|
||||
let insertionPos: Int
|
||||
if let pos = pos {
|
||||
insertionPos = pos
|
||||
}
|
||||
else {
|
||||
insertionPos = nextNonPositional
|
||||
nextNonPositional += 1
|
||||
}
|
||||
|
||||
if insertionPos > 0 {
|
||||
while list.count <= insertionPos-1 {
|
||||
list.append(Type._Any)
|
||||
}
|
||||
list[insertionPos-1] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
|
||||
let defaults = NSUserDefaults.standardUserDefaults()
|
||||
defaults.setValue(["es"], forKey: "AppleLanguages")
|
||||
defaults.setValue(["en"], forKey: "AppleLanguages")
|
||||
|
||||
print(R.string.settings.notTranslated())
|
||||
print(R.string.settings.onlyDutch())
|
||||
print(R.string.settings.formatSpecifiers1(11, 22, "str"))
|
||||
print(R.string.settings.formatSpecifiers2(11, 22, "str"))
|
||||
print(R.string.settings.formatSpecifiers3())
|
||||
print(R.string.settings.formatSpecifiers4(11))
|
||||
print(R.string.settings.formatSpecifiers5(11, "str"))
|
||||
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
|
||||
@@ -10,4 +10,13 @@
|
||||
|
||||
"Not translated" = "Base language; Not translated";
|
||||
|
||||
"Multiline" = "ABC
|
||||
DEF
|
||||
GHI";
|
||||
"Copy.Progress" = "%1$d of %2$i files copied, %3$f.2%% completed.";
|
||||
|
||||
"FormatSpecifiers1" = "number 1: %d, number 2: %i, string 3: %@";
|
||||
"FormatSpecifiers2" = "string 3: %3$@, number 2: %2$d, number 1: %1$i";
|
||||
"FormatSpecifiers3" = "Nothing";
|
||||
"FormatSpecifiers4" = "number 1: %1$d";
|
||||
"FormatSpecifiers5" = "number 1: %d, string 3: %@";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Copyright © 2016 Nolan Warner. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
one = Uno;
|
||||
two = 2;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Copyright © 2016 Nolan Warner. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
one = "一つ";
|
||||
two = 2;
|
||||
|
||||
|
||||
@@ -10,4 +10,13 @@
|
||||
|
||||
"Only Dutch" = "Alleen Nederlands. Doesn't apepar in Base translation";
|
||||
|
||||
"Multiline" = "ABC
|
||||
DEF
|
||||
GHI";
|
||||
"Copy.Progress" = "Van de %2$d bestanden zijn er %1$d gekopieerd, %3$.2f%% compleet.";
|
||||
|
||||
"FormatSpecifiers1" = "number 1: %d, number 2: %i";
|
||||
"FormatSpecifiers2" = "string 3: %3$@, number 1: %1$i";
|
||||
"FormatSpecifiers3" = "number 2: %2$d, string 3: %3$@, number 1: %1$i";
|
||||
"FormatSpecifiers4" = "number 1: %d, number 2: %i, string 3: %@";
|
||||
"FormatSpecifiers5" = "number 1: %d, number 2: %i, string 3: %@";
|
||||
|
||||
@@ -13,8 +13,14 @@ import XCTest
|
||||
class StringsTests: XCTestCase {
|
||||
|
||||
func testNoNilStrings() {
|
||||
XCTAssertNotNil(R.string.localizable.one())
|
||||
XCTAssertNotNil(R.string.generic.loremipsum())
|
||||
XCTAssertNotNil(R.string.localizable.japaneseOnly())
|
||||
XCTAssertNotNil(R.string.localizable.one())
|
||||
XCTAssertNotNil(R.string.localizable.quote(4))
|
||||
XCTAssertNotNil(R.string.localizable.two())
|
||||
XCTAssertNotNil(R.string.settings.copyProgress(2, 4, 50.0))
|
||||
XCTAssertNotNil(R.string.settings.multiline())
|
||||
XCTAssertNotNil(R.string.settings.notTranslated())
|
||||
XCTAssertNotNil(R.string.settings.title())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user