From 6629288c26f3d3eeaea47b56534cb5f6ed7f0c1c Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 18 Jul 2015 23:36:59 +0200 Subject: [PATCH 01/27] Add files with confirmed structure that represent expressions and translatable key-value pairs --- example/base | 38 ++++++++++++++++++++++++++++++++++++++ example/expressions | 9 +++++++++ example/pl | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 example/base create mode 100644 example/expressions create mode 100644 example/pl diff --git a/example/base b/example/base new file mode 100644 index 0000000..a1eb031 --- /dev/null +++ b/example/base @@ -0,0 +1,38 @@ +/* +Displayed at welcome screen +*/ +"welcome": "welcome" + +/* +Displays string with number of cars +1 car or X cars +*/ +"cars" { + "one": "1 car" + "more": "%d cars" +} + +/* +Forgot password sentence displayed on login form +*/ +"forgot-password" { + @100: "Forgot Password? Help." + @200: "Forgot Password? Get password Help." + @300: "Forgotten Your Password? Get password Help." +} + +/* +Sentence about cars +*/ +"car-sentence" { + "one" { + @100: "one car" + @200: "just one car" + @300: "you've got just one car" + } + + "more" { + @100: "%d cars" + @300: "you've got %d cars" + } +} diff --git a/example/expressions b/example/expressions new file mode 100644 index 0000000..68afebc --- /dev/null +++ b/example/expressions @@ -0,0 +1,9 @@ +"base" { + "one": "ie:x=1" + "two": "ie:x=2" + "more": "exp:(^[^1])|(^\\d{2,})" +} + +"pl" { + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" +} diff --git a/example/pl b/example/pl new file mode 100644 index 0000000..ad07916 --- /dev/null +++ b/example/pl @@ -0,0 +1,38 @@ +/* +Displayed at welcome screen +*/ +"welcome": "witaj" + +/* +Displays string with number of cars +1 car or X cars +*/ +"cars" { + "one": "1 samochód" + "more": "%d samochodów (simple)" +} + +/* +Forgot password sentence displayed on login form +*/ +"forgot-password" { + @100: "Zapomniałeś hasła? Pomoc." + @200: "Zapomniałeś hasła? Skorzystaj z Pomocy." + @300: "Zapomniałeś swojego hasła? Skorzystaj z Pomocy." +} + +/* +Sentence about cars +*/ +"car-sentence" { + "one" { + @100: "jeden samochód" + @200: "tylko jeden samochód" + @300: "posiadasz tylko jeden samochód" + } + + "more" { + @100: "%d samochodów (simple)" + @300: "masz %d samochodów (simple)" + } +} \ No newline at end of file From e27e62d4b0d3d0e37c9b9ee040b9161472238b52 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Tue, 21 Jul 2015 19:39:40 +0200 Subject: [PATCH 02/27] Add Playground with files parsing --- ExpressionsLoader.playground/Contents.swift | 11 ++ .../Resources/base.json | 28 +++++ .../Resources/expressions.json | 11 ++ .../Sources/CountryCode.swift | 8 ++ .../Sources/ExpressionType.swift | 9 ++ .../Sources/ExpressionsLoader.swift | 27 +++++ .../Sources/ExpressionsPatternType.swift | 11 ++ .../Sources/JSONFileLoader.swift | 45 ++++++++ .../Sources/LengthVariation.swift | 18 +++ .../Sources/LengthVariationType.swift | 10 ++ .../Sources/Processable.swift | 12 ++ .../Sources/ProcessableExpression.swift | 18 +++ ...ProcessableLengthVariationExpression.swift | 18 +++ .../Sources/ProcessableTranslation.swift | 18 +++ ...TranslationLengthVariationExpression.swift | 18 +++ .../Sources/Regex.swift | 106 ++++++++++++++++++ .../Sources/TranslationLengthVariation.swift | 18 +++ .../Sources/TranslationSimple.swift | 18 +++ .../Sources/TranslationType.swift | 9 ++ .../Sources/TranslationsLoader.swift | 103 +++++++++++++++++ .../contents.xcplayground | 4 + .../contents.xcworkspacedata | 7 ++ .../UserInterfaceState.xcuserstate | Bin 0 -> 26703 bytes .../WorkspaceSettings.xcsettings | 10 ++ .../timeline.xctimeline | 6 + example/base | 38 ------- example/base.json | 28 +++++ example/expressions | 9 -- example/expressions.json | 11 ++ example/pl | 38 ------- example/pl.json | 28 +++++ 31 files changed, 610 insertions(+), 85 deletions(-) create mode 100644 ExpressionsLoader.playground/Contents.swift create mode 100644 ExpressionsLoader.playground/Resources/base.json create mode 100644 ExpressionsLoader.playground/Resources/expressions.json create mode 100644 ExpressionsLoader.playground/Sources/CountryCode.swift create mode 100644 ExpressionsLoader.playground/Sources/ExpressionType.swift create mode 100644 ExpressionsLoader.playground/Sources/ExpressionsLoader.swift create mode 100644 ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift create mode 100644 ExpressionsLoader.playground/Sources/JSONFileLoader.swift create mode 100644 ExpressionsLoader.playground/Sources/LengthVariation.swift create mode 100644 ExpressionsLoader.playground/Sources/LengthVariationType.swift create mode 100644 ExpressionsLoader.playground/Sources/Processable.swift create mode 100644 ExpressionsLoader.playground/Sources/ProcessableExpression.swift create mode 100644 ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift create mode 100644 ExpressionsLoader.playground/Sources/ProcessableTranslation.swift create mode 100644 ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift create mode 100644 ExpressionsLoader.playground/Sources/Regex.swift create mode 100644 ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift create mode 100644 ExpressionsLoader.playground/Sources/TranslationSimple.swift create mode 100644 ExpressionsLoader.playground/Sources/TranslationType.swift create mode 100644 ExpressionsLoader.playground/Sources/TranslationsLoader.swift create mode 100644 ExpressionsLoader.playground/contents.xcplayground create mode 100644 ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata create mode 100644 ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/WorkspaceSettings.xcsettings create mode 100644 ExpressionsLoader.playground/timeline.xctimeline delete mode 100644 example/base create mode 100644 example/base.json delete mode 100644 example/expressions create mode 100644 example/expressions.json delete mode 100644 example/pl create mode 100644 example/pl.json diff --git a/ExpressionsLoader.playground/Contents.swift b/ExpressionsLoader.playground/Contents.swift new file mode 100644 index 0000000..642a8c5 --- /dev/null +++ b/ExpressionsLoader.playground/Contents.swift @@ -0,0 +1,11 @@ +//: Playground - noun: a place where people can play + +import UIKit + +let expsBase = ExpressionsLoader.loadExpressions("base") +let expsPL = ExpressionsLoader.loadExpressions("pl") +let translations = TranslationsLoader.loadTranslations("base") + +for translation in translations { + println(translation.key) +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Resources/base.json b/ExpressionsLoader.playground/Resources/base.json new file mode 100644 index 0000000..e0b4524 --- /dev/null +++ b/ExpressionsLoader.playground/Resources/base.json @@ -0,0 +1,28 @@ +{ + "welcome": "welcome", + + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + }, + + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + }, + + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + } + } +} diff --git a/ExpressionsLoader.playground/Resources/expressions.json b/ExpressionsLoader.playground/Resources/expressions.json new file mode 100644 index 0000000..05f0b5d --- /dev/null +++ b/ExpressionsLoader.playground/Resources/expressions.json @@ -0,0 +1,11 @@ +{ + "base": { + "one": "ie:x=1", + "two": "ie:x=2", + "more": "exp:(^[^1])|(^\\d{2,})" + }, + + "pl": { + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" + } +} diff --git a/ExpressionsLoader.playground/Sources/CountryCode.swift b/ExpressionsLoader.playground/Sources/CountryCode.swift new file mode 100644 index 0000000..05a482c --- /dev/null +++ b/ExpressionsLoader.playground/Sources/CountryCode.swift @@ -0,0 +1,8 @@ +import Foundation + +/** +https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + "base" +Represents country codes in framework. +Country codes are used to get correct user device's language. +*/ +public typealias CountryCode = String diff --git a/ExpressionsLoader.playground/Sources/ExpressionType.swift b/ExpressionsLoader.playground/Sources/ExpressionType.swift new file mode 100644 index 0000000..cb07383 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ExpressionType.swift @@ -0,0 +1,9 @@ +import Foundation + +/** +Represents expressions in the framework. +*/ +public protocol ExpressionType { + /// Identifier of expression. + var identifier: String {get} +} diff --git a/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift b/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift new file mode 100644 index 0000000..da50f07 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift @@ -0,0 +1,27 @@ +import Foundation + +/** +Used to load content from `expressions.json` file for specified language. +*/ +public final class ExpressionsLoader: JSONFileLoader { + + /** + Loads expressions for specified language. + + :param: countryCode A country code + + :returns: array of loaded expressions. + */ + public class func loadExpressions(countryCode: CountryCode) -> [ProcessableExpression] { + var expressions = [ProcessableExpression]() + if let json = self.load("expressions", fileType: "json", bundle: NSBundle.mainBundle()), + let expressionsDict = json[countryCode] as? Dictionary { + for (identifier, pattern) in expressionsDict { + expressions.append(ProcessableExpression(identifier: identifier, pattern: pattern)) + } + } else { + println("expressions.json file structure is incorrect.") + } + return expressions + } +} diff --git a/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift b/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift new file mode 100644 index 0000000..7babb8d --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift @@ -0,0 +1,11 @@ +import Foundation + +/** +Represents objects that have expression pattern. +Example an expression that contains just pattern, without things like length +variations. +*/ +public protocol ExpressionPatternType { + /// A pattern. + var pattern: String {get} +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/JSONFileLoader.swift b/ExpressionsLoader.playground/Sources/JSONFileLoader.swift new file mode 100644 index 0000000..e5e4884 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/JSONFileLoader.swift @@ -0,0 +1,45 @@ +import Foundation + +/** +Simple JSON loader. +*/ +public class JSONFileLoader { + public typealias JSONDictionary = Dictionary + + /** + Load content of file with specified name, type and bundle. + + :param: fileName A name of a file. + :param: fileType A type of a file. + :param: bundle A bundle when file is located. + + :return: JSON or nil. + */ + public final class func load(fileName: String, fileType: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + if let fileURL = bundle.URLForResource(fileName, withExtension: fileType) { + return load(fileURL) + } + println("Cannot find file \(fileName).\(fileType).") + return nil + } + + /** + Loads file for specified URL and try to serialize it. + + :params: fileURL url to JSON file. + + :return: Dictionary with content of JSON file or nil. + */ + private class func load(fileURL: NSURL) -> JSONDictionary? { + if let data = NSData(contentsOfURL: fileURL) { + var error: NSError? + if let dictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? JSONDictionary { + return dictionary + } else { + print("Cannot parse JSON. It might be broken.") + } + } + print("Cannot load content of file.") + return nil + } +} diff --git a/ExpressionsLoader.playground/Sources/LengthVariation.swift b/ExpressionsLoader.playground/Sources/LengthVariation.swift new file mode 100644 index 0000000..beadc32 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/LengthVariation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Struct that represents length variation. +*/ +public struct LengthVariation: LengthVariationType { + /// Length - width of a screen. + public let length: Int + + /// localized string. + public let value: String + + /// Create length variation object. + public init(length: Int, value: String) { + self.length = length + self.value = value + } +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/LengthVariationType.swift b/ExpressionsLoader.playground/Sources/LengthVariationType.swift new file mode 100644 index 0000000..0959ff2 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/LengthVariationType.swift @@ -0,0 +1,10 @@ +import Foundation + +/// Represents length variation. +public protocol LengthVariationType { + /// Length of a screen. + var length: Int {get} + + /// Localized value. + var value: String {get} +} diff --git a/ExpressionsLoader.playground/Sources/Processable.swift b/ExpressionsLoader.playground/Sources/Processable.swift new file mode 100644 index 0000000..4deef2a --- /dev/null +++ b/ExpressionsLoader.playground/Sources/Processable.swift @@ -0,0 +1,12 @@ +import Foundation + +/** +Specifies objects that need processing and are not final objects that can be +used in the framework. An example of such object might be `ProcessableExpression` +that is not ready to be used because it has to be converted into normal kind of +expression that has its fields correctly filled. + +Another good example is `TranslationType` object that contains expression or few +that need to be processed. +*/ +protocol Processable {} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/ProcessableExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableExpression.swift new file mode 100644 index 0000000..8f74695 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ProcessableExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents loaded expression that will be processed later. +*/ +public struct ProcessableExpression: ExpressionType, ExpressionPatternType, Processable { + /// Identifier of expression. + public let identifier: String + + /// Pattern of expression. + public let pattern: String + + /// Creates expression. + public init(identifier: String, pattern: String) { + self.identifier = identifier + self.pattern = pattern + } +} diff --git a/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift new file mode 100644 index 0000000..fb61dfe --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents processable expression with length variations. +*/ +public struct ProcessableLengthVariationExpression: ExpressionType, Processable { + /// Identifier of expression. + public var identifier: String + + /// Array of length variations + var variations: [LengthVariation] + + /// Creates instance of the class. + public init(identifier: String, variations: [LengthVariation]) { + self.identifier = identifier + self.variations = variations + } +} diff --git a/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift b/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift new file mode 100644 index 0000000..6c9725d --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents translation which contains expressions that are not processed yet. +*/ +public struct ProcessableTranslation: TranslationType, Processable { + /// Key that identifies translation. + public let key: String + + /// Array with loaded expressions. + let loadedExpressions: [ProcessableExpression] + + /// Creates instances of the class. + public init(key: String, loadedExpressions: [ProcessableExpression]) { + self.key = key + self.loadedExpressions = loadedExpressions + } +} diff --git a/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift new file mode 100644 index 0000000..092aa1f --- /dev/null +++ b/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents translation which contains expressions with length variations. +*/ +public struct ProcessableTranslationLengthVariationExpression: TranslationType, Processable { + /// Key that identifies translation. + public let key: String + + /// Array with expressions. + let expressions: [ProcessableLengthVariationExpression] + + /// Creates instances of the class. + public init(key: String, expressions: [ProcessableLengthVariationExpression]) { + self.key = key + self.expressions = expressions + } +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/Regex.swift b/ExpressionsLoader.playground/Sources/Regex.swift new file mode 100644 index 0000000..91717ca --- /dev/null +++ b/ExpressionsLoader.playground/Sources/Regex.swift @@ -0,0 +1,106 @@ +// +// Regex.swift +// Swifternalization +// +// Created by Tomasz Szulc on 27/06/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Class uses NSRegularExpression internally and simplifies its usability. +*/ +public class Regex { + + /** + Return match in a string. Optionally with index of capturing group + + :param: str A string that will be matched. + :param: pattern A regex pattern. + + :returns: `String` that matches pattern or nil. + */ + public class func matchInString(str: String, pattern: String, capturingGroupIdx: Int?) -> String? { + var resultString: String? + + let range = NSMakeRange(0, count(str)) + regexp(pattern)?.enumerateMatchesInString(str, options: nil, range: range, usingBlock: { result, flags, stop in + if let result = result { + if let capturingGroupIdx = capturingGroupIdx where result.numberOfRanges > capturingGroupIdx { + resultString = self.substring(str, range: result.rangeAtIndex(capturingGroupIdx)) + } else { + resultString = self.substring(str, range: result.range) + } + } + }) + + return resultString + } + + + /** + Return first match in a string. + + :param: str A string that will be matched. + :param: pattern A regexp pattern. + + :returns: `String` that matches pattern or nil. + */ + class func firstMatchInString(str: String, pattern: String) -> String? { + if let result = regexp(pattern)?.firstMatchInString(str, options: .ReportCompletion, range: NSMakeRange(0, count(str))) { + return substring(str, range: result.range) + } + return nil + } + + /** + Return all matches in a string. + + :param: str A string that will be matched. + :param: pattern A regexp pattern. + + :returns: Array of `Strings`s. If nothing found empty array is returned. + */ + class func matchesInString(str: String, pattern: String) -> [String] { + var matches = [String]() + if let results = regexp(pattern)?.matchesInString(str, options: .ReportCompletion, range: NSMakeRange(0, count(str))) as? [NSTextCheckingResult] { + for result in results { + matches.append(substring(str, range: result.range)) + } + } + + return matches + } + + /** + Returns new `NSRegularExpression` object. + + :param: pattern A regexp pattern. + + :returns: `NSRegularExpression` object or nil if it cannot be created. + */ + private class func regexp(pattern: String) -> NSRegularExpression? { + var error: NSError? = nil + var regexp = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error) + if error != nil { + println(error!) + } + return regexp + } + + /** + Method that substring string with passed range. + + :param: str A string that is source of substraction. + :param: range A range that tells which part of `str` will be substracted. + + :returns: A string contained in `range`. + */ + private class func substring(str: String, range: NSRange) -> String { + let startRange = advance(str.startIndex, range.location) + let endRange = advance(startRange, range.length) + + return str.substringWithRange(Range(start: startRange, end: endRange)) + } +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift b/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift new file mode 100644 index 0000000..95688c1 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Class represents translation which contains key and length variations. +*/ +public struct TranslationLengthVariation: TranslationType { + /// Key that identifies this translation. + public let key: String + + /// list of variations. + let variations: [LengthVariation] + + /// Creates translation object + public init(key: String, variations: [LengthVariation]) { + self.key = key + self.variations = variations + } +} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/TranslationSimple.swift b/ExpressionsLoader.playground/Sources/TranslationSimple.swift new file mode 100644 index 0000000..83305cd --- /dev/null +++ b/ExpressionsLoader.playground/Sources/TranslationSimple.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents simple key-value translation. +*/ +public struct TranslationSimple: TranslationType { + /// Key that identifies translation. + public let key: String + + /// Localized string. + public let value: String + + /// Creates instance of the class. + public init(key: String, value: String) { + self.key = key + self.value = value + } +} diff --git a/ExpressionsLoader.playground/Sources/TranslationType.swift b/ExpressionsLoader.playground/Sources/TranslationType.swift new file mode 100644 index 0000000..653d598 --- /dev/null +++ b/ExpressionsLoader.playground/Sources/TranslationType.swift @@ -0,0 +1,9 @@ +import Foundation + +/** +Represents translation. +*/ +public protocol TranslationType { + /// Key that identifies translation. + var key: String {get} +} diff --git a/ExpressionsLoader.playground/Sources/TranslationsLoader.swift b/ExpressionsLoader.playground/Sources/TranslationsLoader.swift new file mode 100644 index 0000000..83a97ef --- /dev/null +++ b/ExpressionsLoader.playground/Sources/TranslationsLoader.swift @@ -0,0 +1,103 @@ +import Foundation + +/** +Represents translations loader. +Class is looking for json file named as country code. +It parse such file. +*/ +public final class TranslationsLoader: JSONFileLoader { + + typealias DictWithStrings = Dictionary + typealias DictWithDicts = Dictionary + + enum ElementType { + case NotSupported + case TranslationWithLengthVariations + case TranslationWithLoadedExpressions + case TranslationWithLengthVariationsAndLoadedExpressions + } + + /** + Method loads a file and parses it. + + :params: countryCode A country code. + + :return: translations parsed from the file. + */ + public class func loadTranslations(countryCode: CountryCode) -> [TranslationType] { + var loadedTranslations = [TranslationType]() + let json = self.load(countryCode, fileType: "json", bundle: NSBundle.mainBundle()) + if json == nil { return [TranslationType]() } + + for (translationKey, value) in json! { + if let translationValue = value as? String { + loadedTranslations.append(TranslationSimple(key: translationKey, value: translationValue)) + } else { + let dictionary = value as! JSONDictionary + switch detectElementType(dictionary) { + case .TranslationWithLengthVariations: + let variations = parseLengthVariations(dictionary as! DictWithStrings) + loadedTranslations.append(TranslationLengthVariation(key: translationKey, variations: variations)) + + case .TranslationWithLoadedExpressions: + let expressions = parseExpressions(dictionary as! DictWithStrings) + loadedTranslations.append(ProcessableTranslation(key: translationKey, loadedExpressions: expressions)) + + case .TranslationWithLengthVariationsAndLoadedExpressions: + var expressions = [ProcessableLengthVariationExpression]() + for (expressionIdentifier, lengthVariationsDict) in dictionary as! DictWithDicts { + let variations = parseLengthVariations(lengthVariationsDict) + expressions.append(ProcessableLengthVariationExpression(identifier: expressionIdentifier, variations: variations)) + } + loadedTranslations.append(ProcessableTranslationLengthVariationExpression(key: translationKey, expressions: expressions)) + + case .NotSupported: + // Do nothing + continue + } + } + } + + return loadedTranslations + } + + private class func parseLengthVariations(dict: DictWithStrings) -> [LengthVariation] { + var variations = [LengthVariation]() + for (key, translationValue) in dict { + let numberValue = parseNumberFromLengthVariation(key) + variations.append(LengthVariation(length: numberValue, value: translationValue)) + } + return variations + } + + private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpression] { + var expressions = [ProcessableExpression]() + for (expressionKey, translationValue) in dict { + expressions.append(ProcessableExpression(identifier: expressionKey, pattern: translationValue)) + } + return expressions + } + + private class func detectElementType(element: JSONDictionary) -> ElementType { + + if element is DictWithStrings { + if let key = element.keys.first { + let toIndex = advance(key.startIndex, 1) + let firstCharacter = key.substringToIndex(toIndex) + if firstCharacter == "@" { + return .TranslationWithLengthVariations + } else { + return .TranslationWithLoadedExpressions + } + } + } else if element is DictWithDicts { + return .TranslationWithLengthVariationsAndLoadedExpressions + } + + return .NotSupported + } + + private class func parseNumberFromLengthVariation(string: String) -> Int { + return (Regex.matchInString(string, pattern: "@(\\d+)", capturingGroupIdx: 1)! as NSString).integerValue + } +} diff --git a/ExpressionsLoader.playground/contents.xcplayground b/ExpressionsLoader.playground/contents.xcplayground new file mode 100644 index 0000000..5da2641 --- /dev/null +++ b/ExpressionsLoader.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata b/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c706b0d --- /dev/null +++ b/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate b/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..58a59172a76f73547d8a48b37c1665e9bb3574da GIT binary patch literal 26703 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 b# + + + + HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/ExpressionsLoader.playground/timeline.xctimeline b/ExpressionsLoader.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/ExpressionsLoader.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/example/base b/example/base deleted file mode 100644 index a1eb031..0000000 --- a/example/base +++ /dev/null @@ -1,38 +0,0 @@ -/* -Displayed at welcome screen -*/ -"welcome": "welcome" - -/* -Displays string with number of cars -1 car or X cars -*/ -"cars" { - "one": "1 car" - "more": "%d cars" -} - -/* -Forgot password sentence displayed on login form -*/ -"forgot-password" { - @100: "Forgot Password? Help." - @200: "Forgot Password? Get password Help." - @300: "Forgotten Your Password? Get password Help." -} - -/* -Sentence about cars -*/ -"car-sentence" { - "one" { - @100: "one car" - @200: "just one car" - @300: "you've got just one car" - } - - "more" { - @100: "%d cars" - @300: "you've got %d cars" - } -} diff --git a/example/base.json b/example/base.json new file mode 100644 index 0000000..e0b4524 --- /dev/null +++ b/example/base.json @@ -0,0 +1,28 @@ +{ + "welcome": "welcome", + + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + }, + + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + }, + + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + } + } +} diff --git a/example/expressions b/example/expressions deleted file mode 100644 index 68afebc..0000000 --- a/example/expressions +++ /dev/null @@ -1,9 +0,0 @@ -"base" { - "one": "ie:x=1" - "two": "ie:x=2" - "more": "exp:(^[^1])|(^\\d{2,})" -} - -"pl" { - "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" -} diff --git a/example/expressions.json b/example/expressions.json new file mode 100644 index 0000000..05f0b5d --- /dev/null +++ b/example/expressions.json @@ -0,0 +1,11 @@ +{ + "base": { + "one": "ie:x=1", + "two": "ie:x=2", + "more": "exp:(^[^1])|(^\\d{2,})" + }, + + "pl": { + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" + } +} diff --git a/example/pl b/example/pl deleted file mode 100644 index ad07916..0000000 --- a/example/pl +++ /dev/null @@ -1,38 +0,0 @@ -/* -Displayed at welcome screen -*/ -"welcome": "witaj" - -/* -Displays string with number of cars -1 car or X cars -*/ -"cars" { - "one": "1 samochód" - "more": "%d samochodów (simple)" -} - -/* -Forgot password sentence displayed on login form -*/ -"forgot-password" { - @100: "Zapomniałeś hasła? Pomoc." - @200: "Zapomniałeś hasła? Skorzystaj z Pomocy." - @300: "Zapomniałeś swojego hasła? Skorzystaj z Pomocy." -} - -/* -Sentence about cars -*/ -"car-sentence" { - "one" { - @100: "jeden samochód" - @200: "tylko jeden samochód" - @300: "posiadasz tylko jeden samochód" - } - - "more" { - @100: "%d samochodów (simple)" - @300: "masz %d samochodów (simple)" - } -} \ No newline at end of file diff --git a/example/pl.json b/example/pl.json new file mode 100644 index 0000000..ebea036 --- /dev/null +++ b/example/pl.json @@ -0,0 +1,28 @@ +{ + "welcome": "witaj", + + "cars": { + "one": "1 samochód", + "ie:x=2": "2 samochody", + "more": "%d samochodów" + }, + + "forgot-password": { + "@100": "Zapomniałeś hasła? Pomoc.", + "@200": "Zapomniałeś hasła? Skorzystaj z Pomocy.", + "@300": "Zapomniałeś swojego hasła? Skorzystaj z pomocy." + }, + + "car-sentence": { + "one": { + "@100": "jeden samochód", + "@200": "tylko jeden samochód", + "@300": "posiadasz tylko jeden samochód" + }, + + "more": { + "@100": "%d samochodów", + "@300": "posiadasz %d samochodów" + } + } +} From b2c82e8a7b2d2739cbeeceea6af8eee35bb2ce98 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Tue, 21 Jul 2015 19:49:11 +0200 Subject: [PATCH 03/27] Rename file --- ...ift => ExpressionRepresentationType.swift} | 2 +- .../Sources/ProcessableExpression.swift | 2 +- ...ProcessableLengthVariationExpression.swift | 2 +- .../UserInterfaceState.xcuserstate | Bin 26703 -> 27792 bytes 4 files changed, 3 insertions(+), 3 deletions(-) rename ExpressionsLoader.playground/Sources/{ExpressionType.swift => ExpressionRepresentationType.swift} (74%) diff --git a/ExpressionsLoader.playground/Sources/ExpressionType.swift b/ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift similarity index 74% rename from ExpressionsLoader.playground/Sources/ExpressionType.swift rename to ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift index cb07383..1703518 100644 --- a/ExpressionsLoader.playground/Sources/ExpressionType.swift +++ b/ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift @@ -3,7 +3,7 @@ import Foundation /** Represents expressions in the framework. */ -public protocol ExpressionType { +public protocol ExpressionRepresentationType { /// Identifier of expression. var identifier: String {get} } diff --git a/ExpressionsLoader.playground/Sources/ProcessableExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableExpression.swift index 8f74695..a0c0bac 100644 --- a/ExpressionsLoader.playground/Sources/ProcessableExpression.swift +++ b/ExpressionsLoader.playground/Sources/ProcessableExpression.swift @@ -3,7 +3,7 @@ import Foundation /** Represents loaded expression that will be processed later. */ -public struct ProcessableExpression: ExpressionType, ExpressionPatternType, Processable { +public struct ProcessableExpression: ExpressionRepresentationType, ExpressionPatternType, Processable { /// Identifier of expression. public let identifier: String diff --git a/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift index fb61dfe..77c84f5 100644 --- a/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift +++ b/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift @@ -3,7 +3,7 @@ import Foundation /** Represents processable expression with length variations. */ -public struct ProcessableLengthVariationExpression: ExpressionType, Processable { +public struct ProcessableLengthVariationExpression: ExpressionRepresentationType, Processable { /// Identifier of expression. public var identifier: String diff --git a/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate b/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate index 58a59172a76f73547d8a48b37c1665e9bb3574da..f98a0d1a491086015b2c3be74c87396706570b72 100644 GIT binary patch literal 27792 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 b# Date: Tue, 21 Jul 2015 19:51:57 +0200 Subject: [PATCH 04/27] Add Loader to the project --- Swifternalization.xcodeproj/project.pbxproj | 72 ++++++++++++ Swifternalization/CountryCode.swift | 8 ++ .../ExpressionRepresentationType.swift | 9 ++ Swifternalization/ExpressionsLoader.swift | 27 +++++ .../ExpressionsPatternType.swift | 11 ++ Swifternalization/JSONFileLoader.swift | 45 ++++++++ Swifternalization/LengthVariation.swift | 18 +++ Swifternalization/LengthVariationType.swift | 10 ++ Swifternalization/Processable.swift | 12 ++ Swifternalization/ProcessableExpression.swift | 18 +++ ...ProcessableLengthVariationExpression.swift | 18 +++ .../ProcessableTranslation.swift | 18 +++ ...TranslationLengthVariationExpression.swift | 18 +++ .../TranslationLengthVariation.swift | 18 +++ Swifternalization/TranslationSimple.swift | 18 +++ Swifternalization/TranslationType.swift | 9 ++ Swifternalization/TranslationsLoader.swift | 103 ++++++++++++++++++ 17 files changed, 432 insertions(+) create mode 100644 Swifternalization/CountryCode.swift create mode 100644 Swifternalization/ExpressionRepresentationType.swift create mode 100644 Swifternalization/ExpressionsLoader.swift create mode 100644 Swifternalization/ExpressionsPatternType.swift create mode 100644 Swifternalization/JSONFileLoader.swift create mode 100644 Swifternalization/LengthVariation.swift create mode 100644 Swifternalization/LengthVariationType.swift create mode 100644 Swifternalization/Processable.swift create mode 100644 Swifternalization/ProcessableExpression.swift create mode 100644 Swifternalization/ProcessableLengthVariationExpression.swift create mode 100644 Swifternalization/ProcessableTranslation.swift create mode 100644 Swifternalization/ProcessableTranslationLengthVariationExpression.swift create mode 100644 Swifternalization/TranslationLengthVariation.swift create mode 100644 Swifternalization/TranslationSimple.swift create mode 100644 Swifternalization/TranslationType.swift create mode 100644 Swifternalization/TranslationsLoader.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 748e475..dbadc59 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -56,6 +56,22 @@ 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6D6464841B40146100C46C6D /* KeyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValue.swift */; }; 6D6464851B40146600C46C6D /* KeyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValue.swift */; }; + 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; + 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; + 6DB3CC771B5EBDA600A1220F /* ExpressionsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */; }; + 6DB3CC781B5EBDA600A1220F /* ExpressionsPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */; }; + 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; + 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; + 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; + 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; + 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; + 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; + 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */; }; + 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; + 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; + 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */; }; + 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; + 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */; }; 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */; }; @@ -127,6 +143,22 @@ 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoader.swift; sourceTree = ""; }; 6D6464831B40146100C46C6D /* KeyValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValue.swift; sourceTree = ""; }; + 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; + 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionRepresentationType.swift; sourceTree = ""; }; + 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsLoader.swift; sourceTree = ""; }; + 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsPatternType.swift; sourceTree = ""; }; + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; + 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; + 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationType.swift; sourceTree = ""; }; + 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processable.swift; sourceTree = ""; }; + 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableExpression.swift; sourceTree = ""; }; + 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableLengthVariationExpression.swift; sourceTree = ""; }; + 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslation.swift; sourceTree = ""; }; + 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslationLengthVariationExpression.swift; sourceTree = ""; }; + 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationLengthVariation.swift; sourceTree = ""; }; + 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSimple.swift; sourceTree = ""; }; + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; + 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsConfigurator.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; @@ -197,6 +229,7 @@ 6D50044A1B3EF91600A54B36 /* Swifternalization */ = { isa = PBXGroup; children = ( + 6DB3CC641B5EBD9C00A1220F /* Loader */, 6D6282911B3F04C800E65FCD /* Expression.swift */, 6D6282A21B3F247000E65FCD /* ExpressionMatcher.swift */, 6D6282A41B3F24A800E65FCD /* ExpressionParser.swift */, @@ -292,6 +325,29 @@ name = Resources; sourceTree = ""; }; + 6DB3CC641B5EBD9C00A1220F /* Loader */ = { + isa = PBXGroup; + children = ( + 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, + 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */, + 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, + 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */, + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, + 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, + 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */, + 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */, + 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */, + 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */, + 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */, + 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, + 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */, + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, + 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */, + ); + name = Loader; + sourceTree = ""; + }; 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */ = { isa = PBXGroup; children = ( @@ -448,25 +504,41 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */, + 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */, 6D6282A31B3F247000E65FCD /* ExpressionMatcher.swift in Sources */, 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, + 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */, 6DBB6C901B40768A002F39A3 /* SharedExpression.swift in Sources */, 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, + 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValue.swift in Sources */, + 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, + 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, + 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, 6D62829F1B3F1FA000E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282A51B3F24A800E65FCD /* ExpressionParser.swift in Sources */, + 6DB3CC771B5EBDA600A1220F /* ExpressionsLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, + 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */, + 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */, 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, + 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */, 6DBB6C651B403367002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, + 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */, + 6DB3CC781B5EBDA600A1220F /* ExpressionsPatternType.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, + 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */, 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */, 6D6282B11B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D6282BA1B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, + 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslation.swift in Sources */, + 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */, 6D6282AC1B3F327800E65FCD /* InequalityExpressionMatcher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swifternalization/CountryCode.swift b/Swifternalization/CountryCode.swift new file mode 100644 index 0000000..f847da0 --- /dev/null +++ b/Swifternalization/CountryCode.swift @@ -0,0 +1,8 @@ +import Foundation + +/** +https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + "base" +Represents country codes in framework. +Country codes are used to get correct user device's language. +*/ +typealias CountryCode = String diff --git a/Swifternalization/ExpressionRepresentationType.swift b/Swifternalization/ExpressionRepresentationType.swift new file mode 100644 index 0000000..ed808ca --- /dev/null +++ b/Swifternalization/ExpressionRepresentationType.swift @@ -0,0 +1,9 @@ +import Foundation + +/** +Represents expressions in the framework. +*/ +protocol ExpressionRepresentationType { + /// Identifier of expression. + var identifier: String {get} +} diff --git a/Swifternalization/ExpressionsLoader.swift b/Swifternalization/ExpressionsLoader.swift new file mode 100644 index 0000000..6654368 --- /dev/null +++ b/Swifternalization/ExpressionsLoader.swift @@ -0,0 +1,27 @@ +import Foundation + +/** +Used to load content from `expressions.json` file for specified language. +*/ +final class ExpressionsLoader: JSONFileLoader { + + /** + Loads expressions for specified language. + + :param: countryCode A country code + + :returns: array of loaded expressions. + */ + class func loadExpressions(countryCode: CountryCode) -> [ProcessableExpression] { + var expressions = [ProcessableExpression]() + if let json = self.load("expressions", fileType: "json", bundle: NSBundle.mainBundle()), + let expressionsDict = json[countryCode] as? Dictionary { + for (identifier, pattern) in expressionsDict { + expressions.append(ProcessableExpression(identifier: identifier, pattern: pattern)) + } + } else { + println("expressions.json file structure is incorrect.") + } + return expressions + } +} diff --git a/Swifternalization/ExpressionsPatternType.swift b/Swifternalization/ExpressionsPatternType.swift new file mode 100644 index 0000000..5bcb3b8 --- /dev/null +++ b/Swifternalization/ExpressionsPatternType.swift @@ -0,0 +1,11 @@ +import Foundation + +/** +Represents objects that have expression pattern. +Example an expression that contains just pattern, without things like length +variations. +*/ +protocol ExpressionPatternType { + /// A pattern. + var pattern: String {get} +} \ No newline at end of file diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift new file mode 100644 index 0000000..05c2096 --- /dev/null +++ b/Swifternalization/JSONFileLoader.swift @@ -0,0 +1,45 @@ +import Foundation + +/** +Simple JSON loader. +*/ +class JSONFileLoader { + typealias JSONDictionary = Dictionary + + /** + Load content of file with specified name, type and bundle. + + :param: fileName A name of a file. + :param: fileType A type of a file. + :param: bundle A bundle when file is located. + + :return: JSON or nil. + */ + final class func load(fileName: String, fileType: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + if let fileURL = bundle.URLForResource(fileName, withExtension: fileType) { + return load(fileURL) + } + println("Cannot find file \(fileName).\(fileType).") + return nil + } + + /** + Loads file for specified URL and try to serialize it. + + :params: fileURL url to JSON file. + + :return: Dictionary with content of JSON file or nil. + */ + private class func load(fileURL: NSURL) -> JSONDictionary? { + if let data = NSData(contentsOfURL: fileURL) { + var error: NSError? + if let dictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? JSONDictionary { + return dictionary + } else { + print("Cannot parse JSON. It might be broken.") + } + } + print("Cannot load content of file.") + return nil + } +} diff --git a/Swifternalization/LengthVariation.swift b/Swifternalization/LengthVariation.swift new file mode 100644 index 0000000..3516535 --- /dev/null +++ b/Swifternalization/LengthVariation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Struct that represents length variation. +*/ +struct LengthVariation: LengthVariationType { + /// Length - width of a screen. + let length: Int + + /// localized string. + let value: String + + /// Create length variation object. + init(length: Int, value: String) { + self.length = length + self.value = value + } +} \ No newline at end of file diff --git a/Swifternalization/LengthVariationType.swift b/Swifternalization/LengthVariationType.swift new file mode 100644 index 0000000..ffd3ffc --- /dev/null +++ b/Swifternalization/LengthVariationType.swift @@ -0,0 +1,10 @@ +import Foundation + +/// Represents length variation. +protocol LengthVariationType { + /// Length of a screen. + var length: Int {get} + + /// Localized value. + var value: String {get} +} diff --git a/Swifternalization/Processable.swift b/Swifternalization/Processable.swift new file mode 100644 index 0000000..4deef2a --- /dev/null +++ b/Swifternalization/Processable.swift @@ -0,0 +1,12 @@ +import Foundation + +/** +Specifies objects that need processing and are not final objects that can be +used in the framework. An example of such object might be `ProcessableExpression` +that is not ready to be used because it has to be converted into normal kind of +expression that has its fields correctly filled. + +Another good example is `TranslationType` object that contains expression or few +that need to be processed. +*/ +protocol Processable {} \ No newline at end of file diff --git a/Swifternalization/ProcessableExpression.swift b/Swifternalization/ProcessableExpression.swift new file mode 100644 index 0000000..665ed17 --- /dev/null +++ b/Swifternalization/ProcessableExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents loaded expression that will be processed later. +*/ +struct ProcessableExpression: ExpressionRepresentationType, ExpressionPatternType, Processable { + /// Identifier of expression. + let identifier: String + + /// Pattern of expression. + let pattern: String + + /// Creates expression. + init(identifier: String, pattern: String) { + self.identifier = identifier + self.pattern = pattern + } +} diff --git a/Swifternalization/ProcessableLengthVariationExpression.swift b/Swifternalization/ProcessableLengthVariationExpression.swift new file mode 100644 index 0000000..c916fc1 --- /dev/null +++ b/Swifternalization/ProcessableLengthVariationExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents processable expression with length variations. +*/ +struct ProcessableLengthVariationExpression: ExpressionRepresentationType, Processable { + /// Identifier of expression. + var identifier: String + + /// Array of length variations + var variations: [LengthVariation] + + /// Creates instance of the class. + init(identifier: String, variations: [LengthVariation]) { + self.identifier = identifier + self.variations = variations + } +} diff --git a/Swifternalization/ProcessableTranslation.swift b/Swifternalization/ProcessableTranslation.swift new file mode 100644 index 0000000..24f9400 --- /dev/null +++ b/Swifternalization/ProcessableTranslation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents translation which contains expressions that are not processed yet. +*/ +struct ProcessableTranslation: TranslationType, Processable { + /// Key that identifies translation. + let key: String + + /// Array with loaded expressions. + let loadedExpressions: [ProcessableExpression] + + /// Creates instances of the class. + init(key: String, loadedExpressions: [ProcessableExpression]) { + self.key = key + self.loadedExpressions = loadedExpressions + } +} diff --git a/Swifternalization/ProcessableTranslationLengthVariationExpression.swift b/Swifternalization/ProcessableTranslationLengthVariationExpression.swift new file mode 100644 index 0000000..0b2da88 --- /dev/null +++ b/Swifternalization/ProcessableTranslationLengthVariationExpression.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents translation which contains expressions with length variations. +*/ +struct ProcessableTranslationLengthVariationExpression: TranslationType, Processable { + /// Key that identifies translation. + let key: String + + /// Array with expressions. + let expressions: [ProcessableLengthVariationExpression] + + /// Creates instances of the class. + init(key: String, expressions: [ProcessableLengthVariationExpression]) { + self.key = key + self.expressions = expressions + } +} \ No newline at end of file diff --git a/Swifternalization/TranslationLengthVariation.swift b/Swifternalization/TranslationLengthVariation.swift new file mode 100644 index 0000000..0f4dd65 --- /dev/null +++ b/Swifternalization/TranslationLengthVariation.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Class represents translation which contains key and length variations. +*/ +struct TranslationLengthVariation: TranslationType { + /// Key that identifies this translation. + let key: String + + /// list of variations. + let variations: [LengthVariation] + + /// Creates translation object + init(key: String, variations: [LengthVariation]) { + self.key = key + self.variations = variations + } +} \ No newline at end of file diff --git a/Swifternalization/TranslationSimple.swift b/Swifternalization/TranslationSimple.swift new file mode 100644 index 0000000..36b799f --- /dev/null +++ b/Swifternalization/TranslationSimple.swift @@ -0,0 +1,18 @@ +import Foundation + +/** +Represents simple key-value translation. +*/ +struct TranslationSimple: TranslationType { + /// Key that identifies translation. + let key: String + + /// Localized string. + let value: String + + /// Creates instance of the class. + init(key: String, value: String) { + self.key = key + self.value = value + } +} diff --git a/Swifternalization/TranslationType.swift b/Swifternalization/TranslationType.swift new file mode 100644 index 0000000..f5f4667 --- /dev/null +++ b/Swifternalization/TranslationType.swift @@ -0,0 +1,9 @@ +import Foundation + +/** +Represents translation. +*/ +protocol TranslationType { + /// Key that identifies translation. + var key: String {get} +} diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift new file mode 100644 index 0000000..90ba99c --- /dev/null +++ b/Swifternalization/TranslationsLoader.swift @@ -0,0 +1,103 @@ +import Foundation + +/** +Represents translations loader. +Class is looking for json file named as country code. +It parse such file. +*/ +final class TranslationsLoader: JSONFileLoader { + + typealias DictWithStrings = Dictionary + typealias DictWithDicts = Dictionary + + enum ElementType { + case NotSupported + case TranslationWithLengthVariations + case TranslationWithLoadedExpressions + case TranslationWithLengthVariationsAndLoadedExpressions + } + + /** + Method loads a file and parses it. + + :params: countryCode A country code. + + :return: translations parsed from the file. + */ + class func loadTranslations(countryCode: CountryCode) -> [TranslationType] { + var loadedTranslations = [TranslationType]() + let json = self.load(countryCode, fileType: "json", bundle: NSBundle.mainBundle()) + if json == nil { return [TranslationType]() } + + for (translationKey, value) in json! { + if let translationValue = value as? String { + loadedTranslations.append(TranslationSimple(key: translationKey, value: translationValue)) + } else { + let dictionary = value as! JSONDictionary + switch detectElementType(dictionary) { + case .TranslationWithLengthVariations: + let variations = parseLengthVariations(dictionary as! DictWithStrings) + loadedTranslations.append(TranslationLengthVariation(key: translationKey, variations: variations)) + + case .TranslationWithLoadedExpressions: + let expressions = parseExpressions(dictionary as! DictWithStrings) + loadedTranslations.append(ProcessableTranslation(key: translationKey, loadedExpressions: expressions)) + + case .TranslationWithLengthVariationsAndLoadedExpressions: + var expressions = [ProcessableLengthVariationExpression]() + for (expressionIdentifier, lengthVariationsDict) in dictionary as! DictWithDicts { + let variations = parseLengthVariations(lengthVariationsDict) + expressions.append(ProcessableLengthVariationExpression(identifier: expressionIdentifier, variations: variations)) + } + loadedTranslations.append(ProcessableTranslationLengthVariationExpression(key: translationKey, expressions: expressions)) + + case .NotSupported: + // Do nothing + continue + } + } + } + + return loadedTranslations + } + + private class func parseLengthVariations(dict: DictWithStrings) -> [LengthVariation] { + var variations = [LengthVariation]() + for (key, translationValue) in dict { + let numberValue = parseNumberFromLengthVariation(key) + variations.append(LengthVariation(length: numberValue, value: translationValue)) + } + return variations + } + + private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpression] { + var expressions = [ProcessableExpression]() + for (expressionKey, translationValue) in dict { + expressions.append(ProcessableExpression(identifier: expressionKey, pattern: translationValue)) + } + return expressions + } + + private class func detectElementType(element: JSONDictionary) -> ElementType { + + if element is DictWithStrings { + if let key = element.keys.first { + let toIndex = advance(key.startIndex, 1) + let firstCharacter = key.substringToIndex(toIndex) + if firstCharacter == "@" { + return .TranslationWithLengthVariations + } else { + return .TranslationWithLoadedExpressions + } + } + } else if element is DictWithDicts { + return .TranslationWithLengthVariationsAndLoadedExpressions + } + + return .NotSupported + } + + private class func parseNumberFromLengthVariation(string: String) -> Int { + return (Regex.matchInString(string, pattern: "@(\\d+)", capturingGroupIdx: 1)! as NSString).integerValue + } +} From f1582e266820d54426a96853b97c3d2484345c18 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Tue, 21 Jul 2015 20:12:06 +0200 Subject: [PATCH 05/27] Group files --- Swifternalization.xcodeproj/project.pbxproj | 110 ++++++++++++------ Swifternalization/Expression.swift | 12 -- ...Type.swift => ExpressionPatternType.swift} | 0 Swifternalization/ExpressionType.swift | 21 ++++ .../{KeyValue.swift => KeyValueType.swift} | 4 +- ...=> ProcessableTranslationExpression.swift} | 2 +- ...TranslationLengthVariationExpression.swift | 2 +- Swifternalization/TranslatablePair.swift | 2 +- Swifternalization/TranslationsLoader.swift | 4 +- 9 files changed, 103 insertions(+), 54 deletions(-) rename Swifternalization/{ExpressionsPatternType.swift => ExpressionPatternType.swift} (100%) create mode 100644 Swifternalization/ExpressionType.swift rename Swifternalization/{KeyValue.swift => KeyValueType.swift} (93%) rename Swifternalization/{ProcessableTranslation.swift => ProcessableTranslationExpression.swift} (86%) diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index dbadc59..883abe6 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -54,24 +54,27 @@ 6D62834E1B3F628000E65FCD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834D1B3F628000E65FCD /* Main.storyboard */; }; 6D6283501B3F62B100E65FCD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */; }; 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; - 6D6464841B40146100C46C6D /* KeyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValue.swift */; }; - 6D6464851B40146600C46C6D /* KeyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValue.swift */; }; + 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; + 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; 6DB3CC771B5EBDA600A1220F /* ExpressionsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */; }; - 6DB3CC781B5EBDA600A1220F /* ExpressionsPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */; }; + 6DB3CC781B5EBDA600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; - 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */; }; + 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */; }; 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */; }; 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */; }; + 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; + 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; + 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */; }; @@ -142,23 +145,24 @@ 6D62834D1B3F628000E65FCD /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoader.swift; sourceTree = ""; }; - 6D6464831B40146100C46C6D /* KeyValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValue.swift; sourceTree = ""; }; + 6D6464831B40146100C46C6D /* KeyValueType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueType.swift; sourceTree = ""; }; 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionRepresentationType.swift; sourceTree = ""; }; 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsLoader.swift; sourceTree = ""; }; - 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsPatternType.swift; sourceTree = ""; }; + 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationType.swift; sourceTree = ""; }; 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processable.swift; sourceTree = ""; }; 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableExpression.swift; sourceTree = ""; }; 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableLengthVariationExpression.swift; sourceTree = ""; }; - 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslation.swift; sourceTree = ""; }; + 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslationExpression.swift; sourceTree = ""; }; 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslationLengthVariationExpression.swift; sourceTree = ""; }; 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationLengthVariation.swift; sourceTree = ""; }; 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSimple.swift; sourceTree = ""; }; 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; + 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsConfigurator.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; @@ -229,21 +233,20 @@ 6D50044A1B3EF91600A54B36 /* Swifternalization */ = { isa = PBXGroup; children = ( - 6DB3CC641B5EBD9C00A1220F /* Loader */, + 6DB3CC8E1B5EC27600A1220F /* Enums */, + 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, + 6DB3CC8C1B5EC1E000A1220F /* Processable */, + 6DB3CC8A1B5EC19400A1220F /* Protocols */, + 6DB3CC8B1B5EC1A400A1220F /* Typealiases */, + 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, + 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, + 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */, + 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, 6D6282911B3F04C800E65FCD /* Expression.swift */, - 6D6282A21B3F247000E65FCD /* ExpressionMatcher.swift */, - 6D6282A41B3F24A800E65FCD /* ExpressionParser.swift */, - 6D6282AB1B3F327800E65FCD /* InequalityExpressionMatcher.swift */, - 6D62829E1B3F1FA000E65FCD /* InequalityExpressionParser.swift */, - 6D6282B91B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift */, - 6D6282B01B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift */, - 6D6282B41B3F3C4100E65FCD /* InequalitySign.swift */, - 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */, - 6D6464831B40146100C46C6D /* KeyValue.swift */, 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, - 6D6282C61B3F4F2100E65FCD /* RegexExpressionMatcher.swift */, - 6D6282C41B3F4ED100E65FCD /* RegexExpressionParser.swift */, 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */, 6D5004641B3EF92600A54B36 /* Swifternalization.swift */, 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */, @@ -325,27 +328,61 @@ name = Resources; sourceTree = ""; }; - 6DB3CC641B5EBD9C00A1220F /* Loader */ = { + 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { isa = PBXGroup; children = ( - 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, + 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */, 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */, - 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, - 6DB3CC681B5EBDA600A1220F /* ExpressionsPatternType.swift */, - 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, - 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, + 6D6464831B40146100C46C6D /* KeyValueType.swift */, 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */, + 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */, + ); + name = Protocols; + sourceTree = ""; + }; + 6DB3CC8B1B5EC1A400A1220F /* Typealiases */ = { + isa = PBXGroup; + children = ( + 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, + ); + name = Typealiases; + sourceTree = ""; + }; + 6DB3CC8C1B5EC1E000A1220F /* Processable */ = { + isa = PBXGroup; + children = ( 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */, 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */, - 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslation.swift */, + 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */, 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */, - 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, - 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */, - 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, - 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */, ); - name = Loader; + name = Processable; + sourceTree = ""; + }; + 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */ = { + isa = PBXGroup; + children = ( + 6D6282A21B3F247000E65FCD /* ExpressionMatcher.swift */, + 6D6282A41B3F24A800E65FCD /* ExpressionParser.swift */, + 6D6282AB1B3F327800E65FCD /* InequalityExpressionMatcher.swift */, + 6D62829E1B3F1FA000E65FCD /* InequalityExpressionParser.swift */, + 6D6282B91B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift */, + 6D6282B01B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift */, + 6D6282C61B3F4F2100E65FCD /* RegexExpressionMatcher.swift */, + 6D6282C41B3F4ED100E65FCD /* RegexExpressionParser.swift */, + ); + name = "Matchers and Parsers"; + sourceTree = ""; + }; + 6DB3CC8E1B5EC27600A1220F /* Enums */ = { + isa = PBXGroup; + children = ( + 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */, + 6D6282B41B3F3C4100E65FCD /* InequalitySign.swift */, + 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */, + ); + name = Enums; sourceTree = ""; }; 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */ = { @@ -514,8 +551,9 @@ 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, + 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, - 6D6464841B40146100C46C6D /* KeyValue.swift in Sources */, + 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, @@ -531,13 +569,13 @@ 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */, - 6DB3CC781B5EBDA600A1220F /* ExpressionsPatternType.swift in Sources */, + 6DB3CC781B5EBDA600A1220F /* ExpressionPatternType.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */, 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */, 6D6282B11B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D6282BA1B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, - 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslation.swift in Sources */, + 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift in Sources */, 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */, 6D6282AC1B3F327800E65FCD /* InequalityExpressionMatcher.swift in Sources */, ); @@ -555,10 +593,12 @@ 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, 6D6282A61B3F25B900E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, + 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, - 6D6464851B40146600C46C6D /* KeyValue.swift in Sources */, + 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, + 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C931B40769F002F39A3 /* SharedExpression.swift in Sources */, diff --git a/Swifternalization/Expression.swift b/Swifternalization/Expression.swift index 6b996b0..bdf6ae4 100644 --- a/Swifternalization/Expression.swift +++ b/Swifternalization/Expression.swift @@ -8,18 +8,6 @@ import Foundation -/// Supported expression types -enum ExpressionType: String { - /// works on Int only, e.g. `x<5`, `x=3` - case Inequality = "ie" - - /// works on Int only, e.g. `4 Protocol that defines properties and methods that need to be implemented by objects that keeps key-value pair things. */ -protocol KeyValue { +protocol KeyValueType { /// A key. var key: Key {get set} diff --git a/Swifternalization/ProcessableTranslation.swift b/Swifternalization/ProcessableTranslationExpression.swift similarity index 86% rename from Swifternalization/ProcessableTranslation.swift rename to Swifternalization/ProcessableTranslationExpression.swift index 24f9400..d3b7ab8 100644 --- a/Swifternalization/ProcessableTranslation.swift +++ b/Swifternalization/ProcessableTranslationExpression.swift @@ -3,7 +3,7 @@ import Foundation /** Represents translation which contains expressions that are not processed yet. */ -struct ProcessableTranslation: TranslationType, Processable { +struct ProcessableTranslationExpression: TranslationType, Processable { /// Key that identifies translation. let key: String diff --git a/Swifternalization/ProcessableTranslationLengthVariationExpression.swift b/Swifternalization/ProcessableTranslationLengthVariationExpression.swift index 0b2da88..00bb8d8 100644 --- a/Swifternalization/ProcessableTranslationLengthVariationExpression.swift +++ b/Swifternalization/ProcessableTranslationLengthVariationExpression.swift @@ -3,7 +3,7 @@ import Foundation /** Represents translation which contains expressions with length variations. */ -struct ProcessableTranslationLengthVariationExpression: TranslationType, Processable { +struct ProcessableTranslationExpressionLengthVariationExpression: TranslationType, Processable { /// Key that identifies translation. let key: String diff --git a/Swifternalization/TranslatablePair.swift b/Swifternalization/TranslatablePair.swift index 07593d4..e790c29 100644 --- a/Swifternalization/TranslatablePair.swift +++ b/Swifternalization/TranslatablePair.swift @@ -13,7 +13,7 @@ Represents key-value pair from Localizable.strings files. It contains key, value and expression if exists for the key. It can also validate if text matches expression's requirements. */ -struct TranslatablePair: KeyValue { +struct TranslatablePair: KeyValueType { /// Key from Localizable.strings. var key: Key diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index 90ba99c..de4a241 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -41,7 +41,7 @@ final class TranslationsLoader: JSONFileLoader { case .TranslationWithLoadedExpressions: let expressions = parseExpressions(dictionary as! DictWithStrings) - loadedTranslations.append(ProcessableTranslation(key: translationKey, loadedExpressions: expressions)) + loadedTranslations.append(ProcessableTranslationExpression(key: translationKey, loadedExpressions: expressions)) case .TranslationWithLengthVariationsAndLoadedExpressions: var expressions = [ProcessableLengthVariationExpression]() @@ -49,7 +49,7 @@ final class TranslationsLoader: JSONFileLoader { let variations = parseLengthVariations(lengthVariationsDict) expressions.append(ProcessableLengthVariationExpression(identifier: expressionIdentifier, variations: variations)) } - loadedTranslations.append(ProcessableTranslationLengthVariationExpression(key: translationKey, expressions: expressions)) + loadedTranslations.append(ProcessableTranslationExpressionLengthVariationExpression(key: translationKey, expressions: expressions)) case .NotSupported: // Do nothing From f947174dc3d8b5626a939d946575945510c127ce Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Tue, 21 Jul 2015 21:49:29 +0200 Subject: [PATCH 06/27] Write tests for JSONFileLoader and TranslationsLoader --- Swifternalization.xcodeproj/project.pbxproj | 66 ++++++++++++++++++- Swifternalization/ExpressionsLoader.swift | 4 +- Swifternalization/JSONFileLoader.swift | 6 +- Swifternalization/TranslationsLoader.swift | 2 +- .../ExpressionsLoaderTests.swift | 28 ++++++++ .../JSONFileLoaderTests.swift | 23 +++++++ .../NSBundle+TestExtension.swift | 15 +++++ SwifternalizationTests/base.json | 28 ++++++++ SwifternalizationTests/expressions.json | 11 ++++ SwifternalizationTests/pl.json | 28 ++++++++ 10 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 SwifternalizationTests/ExpressionsLoaderTests.swift create mode 100644 SwifternalizationTests/JSONFileLoaderTests.swift create mode 100644 SwifternalizationTests/NSBundle+TestExtension.swift create mode 100644 SwifternalizationTests/base.json create mode 100644 SwifternalizationTests/expressions.json create mode 100644 SwifternalizationTests/pl.json diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 883abe6..a31b8e3 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -92,6 +92,18 @@ 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */; }; 6DBB6C981B4077FA002F39A3 /* SharedPolishExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */; }; + 6DD3B93A1B5ED35200C79EAC /* base.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD3B9371B5ED35200C79EAC /* base.json */; }; + 6DD3B93B1B5ED35200C79EAC /* expressions.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD3B9381B5ED35200C79EAC /* expressions.json */; }; + 6DD3B93C1B5ED35200C79EAC /* pl.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD3B9391B5ED35200C79EAC /* pl.json */; }; + 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */; }; + 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; + 6DD3B9441B5ED55500C79EAC /* ExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */; }; + 6DD3B9451B5ED58A00C79EAC /* ExpressionsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */; }; + 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; + 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; + 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; + 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; + 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -174,6 +186,12 @@ 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpression.swift; sourceTree = ""; }; 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpressionTests.swift; sourceTree = ""; }; 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpressionTests.swift; sourceTree = ""; }; + 6DD3B9371B5ED35200C79EAC /* base.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = base.json; sourceTree = ""; }; + 6DD3B9381B5ED35200C79EAC /* expressions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = expressions.json; sourceTree = ""; }; + 6DD3B9391B5ED35200C79EAC /* pl.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pl.json; sourceTree = ""; }; + 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoaderTests.swift; sourceTree = ""; }; + 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsLoaderTests.swift; sourceTree = ""; }; + 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBundle+TestExtension.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -206,6 +224,7 @@ isa = PBXGroup; children = ( 6D140F431B56D03D00359143 /* RandomNumbers.swift */, + 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */, ); name = Helpers; sourceTree = ""; @@ -234,6 +253,7 @@ isa = PBXGroup; children = ( 6DB3CC8E1B5EC27600A1220F /* Enums */, + 6DD3B93E1B5ED36F00C79EAC /* Loaders */, 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, 6DB3CC8C1B5EC1E000A1220F /* Processable */, 6DB3CC8A1B5EC19400A1220F /* Protocols */, @@ -241,9 +261,6 @@ 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */, - 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, - 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, - 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, 6D6282911B3F04C800E65FCD /* Expression.swift */, 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, @@ -268,6 +285,7 @@ 6D5004571B3EF91600A54B36 /* SwifternalizationTests */ = { isa = PBXGroup; children = ( + 6DD3B93F1B5ED37A00C79EAC /* Loaders */, 6D140F451B56D04300359143 /* Helpers */, 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */, 6D6282BE1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift */, @@ -285,6 +303,7 @@ 6D5004581B3EF91600A54B36 /* Supporting Files */, 6D5004941B3EFF6D00A54B36 /* Localizable.strings */, 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */, + 6DD3B93D1B5ED35600C79EAC /* Localizable Files */, ); path = SwifternalizationTests; sourceTree = ""; @@ -395,6 +414,35 @@ path = "Shared Expressions"; sourceTree = ""; }; + 6DD3B93D1B5ED35600C79EAC /* Localizable Files */ = { + isa = PBXGroup; + children = ( + 6DD3B9371B5ED35200C79EAC /* base.json */, + 6DD3B9381B5ED35200C79EAC /* expressions.json */, + 6DD3B9391B5ED35200C79EAC /* pl.json */, + ); + name = "Localizable Files"; + sourceTree = ""; + }; + 6DD3B93E1B5ED36F00C79EAC /* Loaders */ = { + isa = PBXGroup; + children = ( + 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, + ); + name = Loaders; + sourceTree = ""; + }; + 6DD3B93F1B5ED37A00C79EAC /* Loaders */ = { + isa = PBXGroup; + children = ( + 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, + 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */, + ); + name = Loaders; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -516,7 +564,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6DD3B93B1B5ED35200C79EAC /* expressions.json in Resources */, + 6DD3B93A1B5ED35200C79EAC /* base.json in Resources */, 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */, + 6DD3B93C1B5ED35200C79EAC /* pl.json in Resources */, 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */, 6D5004921B3EFF6D00A54B36 /* Localizable.strings in Resources */, ); @@ -587,7 +638,9 @@ files = ( 6D6282C91B3F4F6700E65FCD /* RegexExpressionParser.swift in Sources */, 6DBB6C981B4077FA002F39A3 /* SharedPolishExpressionTests.swift in Sources */, + 6DD3B9441B5ED55500C79EAC /* ExpressionsLoaderTests.swift in Sources */, 6D6282C11B3F43C600E65FCD /* InequalityExtendedExpressionMatcherTests.swift in Sources */, + 6DD3B9451B5ED58A00C79EAC /* ExpressionsLoader.swift in Sources */, 6D6282981B3F13C300E65FCD /* ExpressionTests.swift in Sources */, 6DBB6C661B40369A002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, @@ -595,9 +648,13 @@ 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, + 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, + 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, + 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, + 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, @@ -610,14 +667,17 @@ 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */, 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, + 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */, 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */, 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, + 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, 6D6282A91B3F25DC00E65FCD /* InequalityExpressionParserTests.swift in Sources */, 6D6282AA1B3F269900E65FCD /* ExpressionParser.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */, 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, + 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */, diff --git a/Swifternalization/ExpressionsLoader.swift b/Swifternalization/ExpressionsLoader.swift index 6654368..7f83995 100644 --- a/Swifternalization/ExpressionsLoader.swift +++ b/Swifternalization/ExpressionsLoader.swift @@ -12,9 +12,9 @@ final class ExpressionsLoader: JSONFileLoader { :returns: array of loaded expressions. */ - class func loadExpressions(countryCode: CountryCode) -> [ProcessableExpression] { + class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> [ProcessableExpression] { var expressions = [ProcessableExpression]() - if let json = self.load("expressions", fileType: "json", bundle: NSBundle.mainBundle()), + if let json = self.load("expressions", bundle: bundle), let expressionsDict = json[countryCode] as? Dictionary { for (identifier, pattern) in expressionsDict { expressions.append(ProcessableExpression(identifier: identifier, pattern: pattern)) diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index 05c2096..39f9e94 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -15,11 +15,11 @@ class JSONFileLoader { :return: JSON or nil. */ - final class func load(fileName: String, fileType: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { - if let fileURL = bundle.URLForResource(fileName, withExtension: fileType) { + final class func load(fileName: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + if let fileURL = bundle.URLForResource(fileName, withExtension: "json") { return load(fileURL) } - println("Cannot find file \(fileName).\(fileType).") + println("Cannot find file \(fileName).json.") return nil } diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index de4a241..0c47403 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -26,7 +26,7 @@ final class TranslationsLoader: JSONFileLoader { */ class func loadTranslations(countryCode: CountryCode) -> [TranslationType] { var loadedTranslations = [TranslationType]() - let json = self.load(countryCode, fileType: "json", bundle: NSBundle.mainBundle()) + let json = self.load(countryCode, bundle: NSBundle.mainBundle()) if json == nil { return [TranslationType]() } for (translationKey, value) in json! { diff --git a/SwifternalizationTests/ExpressionsLoaderTests.swift b/SwifternalizationTests/ExpressionsLoaderTests.swift new file mode 100644 index 0000000..4673893 --- /dev/null +++ b/SwifternalizationTests/ExpressionsLoaderTests.swift @@ -0,0 +1,28 @@ +// +// ExpressionsLoaderTests.swift +// Swifternalization +// +// Created by Tomasz Szulc on 21/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import UIKit +import XCTest + +class ExpressionsLoaderTests: XCTestCase { + + func testShouldLoadBase() { + let content = ExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) + XCTAssertTrue(content.count > 0, "") + } + + func testShouldLoadPL() { + let content = ExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) + XCTAssertTrue(content.count > 0, "") + } + + func testShouldNotLoadDE() { + let content = ExpressionsLoader.loadExpressions("de", bundle: NSBundle.testBundle()) + XCTAssertFalse(content.count > 0, "") + } +} diff --git a/SwifternalizationTests/JSONFileLoaderTests.swift b/SwifternalizationTests/JSONFileLoaderTests.swift new file mode 100644 index 0000000..0d308ba --- /dev/null +++ b/SwifternalizationTests/JSONFileLoaderTests.swift @@ -0,0 +1,23 @@ +// +// JSONFileLoaderTests.swift +// Swifternalization +// +// Created by Tomasz Szulc on 21/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import UIKit +import XCTest + +class JSONFileLoaderTests: XCTestCase { + + func testJSONShouldBeLoaded() { + let content = JSONFileLoader.load("base", bundle: NSBundle.testBundle()) + XCTAssertNotNil(content!, "") + } + + func testFileShouldNotBeLoaded() { + let content = JSONFileLoader.load("not-existing", bundle: NSBundle.testBundle()) + XCTAssertNil(content, "") + } +} diff --git a/SwifternalizationTests/NSBundle+TestExtension.swift b/SwifternalizationTests/NSBundle+TestExtension.swift new file mode 100644 index 0000000..780dc4e --- /dev/null +++ b/SwifternalizationTests/NSBundle+TestExtension.swift @@ -0,0 +1,15 @@ +// +// NSBundle+TestExtension.swift +// Swifternalization +// +// Created by Tomasz Szulc on 21/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +extension NSBundle { + class func testBundle() -> NSBundle { + return NSBundle(forClass: JSONFileLoaderTests.self) + } +} \ No newline at end of file diff --git a/SwifternalizationTests/base.json b/SwifternalizationTests/base.json new file mode 100644 index 0000000..e0b4524 --- /dev/null +++ b/SwifternalizationTests/base.json @@ -0,0 +1,28 @@ +{ + "welcome": "welcome", + + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + }, + + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + }, + + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + } + } +} diff --git a/SwifternalizationTests/expressions.json b/SwifternalizationTests/expressions.json new file mode 100644 index 0000000..05f0b5d --- /dev/null +++ b/SwifternalizationTests/expressions.json @@ -0,0 +1,11 @@ +{ + "base": { + "one": "ie:x=1", + "two": "ie:x=2", + "more": "exp:(^[^1])|(^\\d{2,})" + }, + + "pl": { + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" + } +} diff --git a/SwifternalizationTests/pl.json b/SwifternalizationTests/pl.json new file mode 100644 index 0000000..ebea036 --- /dev/null +++ b/SwifternalizationTests/pl.json @@ -0,0 +1,28 @@ +{ + "welcome": "witaj", + + "cars": { + "one": "1 samochód", + "ie:x=2": "2 samochody", + "more": "%d samochodów" + }, + + "forgot-password": { + "@100": "Zapomniałeś hasła? Pomoc.", + "@200": "Zapomniałeś hasła? Skorzystaj z Pomocy.", + "@300": "Zapomniałeś swojego hasła? Skorzystaj z pomocy." + }, + + "car-sentence": { + "one": { + "@100": "jeden samochód", + "@200": "tylko jeden samochód", + "@300": "posiadasz tylko jeden samochód" + }, + + "more": { + "@100": "%d samochodów", + "@300": "posiadasz %d samochodów" + } + } +} From eed66f3cc8b56b7fd8b9278f5226a80357173a79 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 26 Jul 2015 15:32:44 +0200 Subject: [PATCH 07/27] Add tests for TranslationsLoader --- Swifternalization.xcodeproj/project.pbxproj | 24 +++++++++++++++- Swifternalization/TranslationsLoader.swift | 4 +-- .../TranslationsLoaderTests.swift | 28 +++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 SwifternalizationTests/TranslationsLoaderTests.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index a31b8e3..b3a4648 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -15,6 +15,16 @@ 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; 6D5004921B3EFF6D00A54B36 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004941B3EFF6D00A54B36 /* Localizable.strings */; }; 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004591B3EF91600A54B36 /* Info.plist */; }; + 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */; }; + 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; + 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */; }; + 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; + 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; + 6D5BA5F51B65181C000D7E49 /* TranslationSimple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */; }; + 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; + 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; + 6D5BA5F81B65182D000D7E49 /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; + 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */; }; 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */; }; 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; @@ -127,6 +137,7 @@ 6D5004641B3EF92600A54B36 /* Swifternalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifternalization.swift; sourceTree = ""; }; 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePair.swift; sourceTree = ""; }; 6D5004931B3EFF6D00A54B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; + 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; 6D6282911B3F04C800E65FCD /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; @@ -285,8 +296,8 @@ 6D5004571B3EF91600A54B36 /* SwifternalizationTests */ = { isa = PBXGroup; children = ( - 6DD3B93F1B5ED37A00C79EAC /* Loaders */, 6D140F451B56D04300359143 /* Helpers */, + 6DD3B93F1B5ED37A00C79EAC /* Loaders */, 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */, 6D6282BE1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift */, 6D6282A81B3F25DC00E65FCD /* InequalityExpressionParserTests.swift */, @@ -439,6 +450,7 @@ children = ( 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */, + 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, ); name = Loaders; sourceTree = ""; @@ -646,6 +658,7 @@ 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, 6D6282A61B3F25B900E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, + 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */, 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, @@ -653,6 +666,8 @@ 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, + 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */, + 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, @@ -660,6 +675,9 @@ 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C931B40769F002F39A3 /* SharedExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, + 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */, + 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, + 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */, 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */, 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */, @@ -672,14 +690,18 @@ 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, + 6D5BA5F51B65181C000D7E49 /* TranslationSimple.swift in Sources */, 6D6282A91B3F25DC00E65FCD /* InequalityExpressionParserTests.swift in Sources */, + 6D5BA5F81B65182D000D7E49 /* ProcessableTranslationLengthVariationExpression.swift in Sources */, 6D6282AA1B3F269900E65FCD /* ExpressionParser.swift in Sources */, + 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */, 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, + 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */, 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index 0c47403..b9675f5 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -24,9 +24,9 @@ final class TranslationsLoader: JSONFileLoader { :return: translations parsed from the file. */ - class func loadTranslations(countryCode: CountryCode) -> [TranslationType] { + class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> [TranslationType] { var loadedTranslations = [TranslationType]() - let json = self.load(countryCode, bundle: NSBundle.mainBundle()) + let json = self.load(countryCode, bundle: bundle) if json == nil { return [TranslationType]() } for (translationKey, value) in json! { diff --git a/SwifternalizationTests/TranslationsLoaderTests.swift b/SwifternalizationTests/TranslationsLoaderTests.swift new file mode 100644 index 0000000..e15dc4c --- /dev/null +++ b/SwifternalizationTests/TranslationsLoaderTests.swift @@ -0,0 +1,28 @@ +// +// TranslationsLoaderTests.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import UIKit +import XCTest + +class TranslationsLoaderTests: XCTestCase { + + func testShouldLoadBase() { + let content = TranslationsLoader.loadTranslations("pl", bundle: NSBundle.testBundle()) + XCTAssertTrue(content.count > 0, "") + } + + func testShouldLoadPL() { + let content = TranslationsLoader.loadTranslations("base", bundle: NSBundle.testBundle()) + XCTAssertTrue(content.count > 0, "") + } + + func testShouldNotLoadDE() { + let content = TranslationsLoader.loadTranslations("de", bundle: NSBundle.testBundle()) + XCTAssertFalse(content.count > 0, "") + } +} From 3208b881158a2f0654a20e95b3fda4688fc422bc Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 26 Jul 2015 17:56:00 +0200 Subject: [PATCH 08/27] Add SharedExpressionsLoader --- Swifternalization.xcodeproj/project.pbxproj | 62 ++++++++--- .../SharedBaseExpression.swift | 8 +- .../SharedPolishExpression.swift | 4 +- .../SharedExpression.swift | 28 ++--- ...der.swift => SharedExpressionLoader.swift} | 8 +- .../SharedExpressionsConfigurator.swift | 4 +- .../SharedExpressionsProcessor.swift | 104 ++++++++++++++++++ Swifternalization/Swifternalization.swift | 2 +- Swifternalization/TranslationsProcessor.swift | 13 +++ .../SharedBaseExpressionTests.swift | 8 +- ...ift => SharedExpressionsLoaderTests.swift} | 8 +- .../SharedExpressionsProcessorTests.swift | 22 ++++ .../SharedPolishExpressionTests.swift | 4 +- SwifternalizationTests/expressions.json | 4 +- 14 files changed, 223 insertions(+), 56 deletions(-) rename Swifternalization/{Shared Expressions => }/SharedExpression.swift (56%) rename Swifternalization/{ExpressionsLoader.swift => SharedExpressionLoader.swift} (73%) create mode 100644 Swifternalization/SharedExpressionsProcessor.swift create mode 100644 Swifternalization/TranslationsProcessor.swift rename SwifternalizationTests/{ExpressionsLoaderTests.swift => SharedExpressionsLoaderTests.swift} (56%) create mode 100644 SwifternalizationTests/SharedExpressionsProcessorTests.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index b3a4648..ddf90e5 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -25,6 +25,12 @@ 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; 6D5BA5F81B65182D000D7E49 /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */; }; + 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; + 6D5BA5FE1B6525F9000D7E49 /* TranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */; }; + 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; + 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; + 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */; }; + 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */; }; 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; @@ -68,7 +74,7 @@ 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; - 6DB3CC771B5EBDA600A1220F /* ExpressionsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */; }; + 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; 6DB3CC781B5EBDA600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; @@ -95,10 +101,8 @@ 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C6B1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift */; }; 6DBB6C871B40718F002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C891B40718F002F39A3 /* Expressions.strings */; }; 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; - 6DBB6C901B40768A002F39A3 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8D1B40768A002F39A3 /* SharedExpression.swift */; }; 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; - 6DBB6C931B40769F002F39A3 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8D1B40768A002F39A3 /* SharedExpression.swift */; }; 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */; }; 6DBB6C981B4077FA002F39A3 /* SharedPolishExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */; }; @@ -107,8 +111,8 @@ 6DD3B93C1B5ED35200C79EAC /* pl.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD3B9391B5ED35200C79EAC /* pl.json */; }; 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */; }; 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; - 6DD3B9441B5ED55500C79EAC /* ExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */; }; - 6DD3B9451B5ED58A00C79EAC /* ExpressionsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */; }; + 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */; }; + 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; @@ -138,6 +142,10 @@ 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePair.swift; sourceTree = ""; }; 6D5004931B3EFF6D00A54B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; + 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessor.swift; sourceTree = ""; }; + 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsProcessor.swift; sourceTree = ""; }; + 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; + 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessorTests.swift; sourceTree = ""; }; 6D6282911B3F04C800E65FCD /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; @@ -171,7 +179,7 @@ 6D6464831B40146100C46C6D /* KeyValueType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueType.swift; sourceTree = ""; }; 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionRepresentationType.swift; sourceTree = ""; }; - 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsLoader.swift; sourceTree = ""; }; + 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionLoader.swift; sourceTree = ""; }; 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; @@ -193,7 +201,6 @@ 6DBB6C881B40718F002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8A1B407190002F39A3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpression.swift; sourceTree = ""; }; - 6DBB6C8D1B40768A002F39A3 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpression.swift; sourceTree = ""; }; 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpression.swift; sourceTree = ""; }; 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpressionTests.swift; sourceTree = ""; }; 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpressionTests.swift; sourceTree = ""; }; @@ -201,7 +208,7 @@ 6DD3B9381B5ED35200C79EAC /* expressions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = expressions.json; sourceTree = ""; }; 6DD3B9391B5ED35200C79EAC /* pl.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pl.json; sourceTree = ""; }; 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoaderTests.swift; sourceTree = ""; }; - 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionsLoaderTests.swift; sourceTree = ""; }; + 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsLoaderTests.swift; sourceTree = ""; }; 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBundle+TestExtension.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -267,6 +274,7 @@ 6DD3B93E1B5ED36F00C79EAC /* Loaders */, 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, 6DB3CC8C1B5EC1E000A1220F /* Processable */, + 6D5BA5FC1B652543000D7E49 /* Processors */, 6DB3CC8A1B5EC19400A1220F /* Protocols */, 6DB3CC8B1B5EC1A400A1220F /* Typealiases */, 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, @@ -298,6 +306,7 @@ children = ( 6D140F451B56D04300359143 /* Helpers */, 6DD3B93F1B5ED37A00C79EAC /* Loaders */, + 6D5BA6021B6537BE000D7E49 /* Processors */, 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */, 6D6282BE1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift */, 6D6282A81B3F25DC00E65FCD /* InequalityExpressionParserTests.swift */, @@ -327,6 +336,23 @@ name = "Supporting Files"; sourceTree = ""; }; + 6D5BA5FC1B652543000D7E49 /* Processors */ = { + isa = PBXGroup; + children = ( + 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, + 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */, + ); + name = Processors; + sourceTree = ""; + }; + 6D5BA6021B6537BE000D7E49 /* Processors */ = { + isa = PBXGroup; + children = ( + 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */, + ); + name = Processors; + sourceTree = ""; + }; 6D6283261B3F613D00E65FCD /* DemoOrdering */ = { isa = PBXGroup; children = ( @@ -418,8 +444,8 @@ 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */ = { isa = PBXGroup; children = ( + 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */, 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */, - 6DBB6C8D1B40768A002F39A3 /* SharedExpression.swift */, 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */, ); path = "Shared Expressions"; @@ -438,7 +464,7 @@ 6DD3B93E1B5ED36F00C79EAC /* Loaders */ = { isa = PBXGroup; children = ( - 6DB3CC671B5EBDA600A1220F /* ExpressionsLoader.swift */, + 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, ); @@ -449,7 +475,7 @@ isa = PBXGroup; children = ( 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, - 6DD3B9431B5ED55500C79EAC /* ExpressionsLoaderTests.swift */, + 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */, 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, ); name = Loaders; @@ -604,25 +630,27 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */, 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */, 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */, 6D6282A31B3F247000E65FCD /* ExpressionMatcher.swift in Sources */, 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */, - 6DBB6C901B40768A002F39A3 /* SharedExpression.swift in Sources */, 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, + 6D5BA5FE1B6525F9000D7E49 /* TranslationsProcessor.swift in Sources */, 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, + 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */, 6D62829F1B3F1FA000E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282A51B3F24A800E65FCD /* ExpressionParser.swift in Sources */, - 6DB3CC771B5EBDA600A1220F /* ExpressionsLoader.swift in Sources */, + 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */, 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */, @@ -650,9 +678,9 @@ files = ( 6D6282C91B3F4F6700E65FCD /* RegexExpressionParser.swift in Sources */, 6DBB6C981B4077FA002F39A3 /* SharedPolishExpressionTests.swift in Sources */, - 6DD3B9441B5ED55500C79EAC /* ExpressionsLoaderTests.swift in Sources */, + 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */, 6D6282C11B3F43C600E65FCD /* InequalityExtendedExpressionMatcherTests.swift in Sources */, - 6DD3B9451B5ED58A00C79EAC /* ExpressionsLoader.swift in Sources */, + 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */, 6D6282981B3F13C300E65FCD /* ExpressionTests.swift in Sources */, 6DBB6C661B40369A002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, @@ -660,12 +688,14 @@ 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */, 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, + 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, + 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */, 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, @@ -673,7 +703,6 @@ 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, - 6DBB6C931B40769F002F39A3 /* SharedExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */, 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, @@ -682,6 +711,7 @@ 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */, 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */, + 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */, 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */, 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, diff --git a/Swifternalization/Shared Expressions/SharedBaseExpression.swift b/Swifternalization/Shared Expressions/SharedBaseExpression.swift index ea243ee..30ee66a 100644 --- a/Swifternalization/Shared Expressions/SharedBaseExpression.swift +++ b/Swifternalization/Shared Expressions/SharedBaseExpression.swift @@ -14,16 +14,16 @@ class SharedBaseExpression: SharedExpressionProtocol { return [ /// Matches value equals 1. - SharedExpression(key: "one", pattern: "ie:x=1"), + SharedExpression(identifier: "one", pattern: "ie:x=1"), /// Matches value greater than 1. - SharedExpression(key: ">one", pattern: "ie:x>1"), + SharedExpression(identifier: ">one", pattern: "ie:x>1"), /// Matches value equals 2. - SharedExpression(key: "two", pattern: "ie:x=2"), + SharedExpression(identifier: "two", pattern: "ie:x=2"), /// Matches value other than 1. - SharedExpression(key: "other", pattern: "exp:(^[^1])|(^\\d{2,})") + SharedExpression(identifier: "other", pattern: "exp:(^[^1])|(^\\d{2,})") ] } } \ No newline at end of file diff --git a/Swifternalization/Shared Expressions/SharedPolishExpression.swift b/Swifternalization/Shared Expressions/SharedPolishExpression.swift index db107cd..7c4d46c 100644 --- a/Swifternalization/Shared Expressions/SharedPolishExpression.swift +++ b/Swifternalization/Shared Expressions/SharedPolishExpression.swift @@ -19,7 +19,7 @@ class SharedPolishExpression: SharedExpressionProtocol { - 22 samochody, 1334 samochody, 53 samochody - 2 minuty, 4 minuty, 23 minuty */ - SharedExpression(key: "few", pattern: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"), + SharedExpression(identifier: "few", pattern: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"), /** 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9) @@ -28,7 +28,7 @@ class SharedPolishExpression: SharedExpressionProtocol { - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów - 5 minut, 18 minut, 117 minut, 1009 minut */ - SharedExpression(key: "many", pattern: "exp:(^[05-9]$)|(.*(?=1).[0-9]$)|(^[0-9]{1}.*[0156789]$)"), + SharedExpression(identifier: "many", pattern: "exp:(^[05-9]$)|(.*(?=1).[0-9]$)|(^[0-9]{1}.*[0156789]$)"), ] } } diff --git a/Swifternalization/Shared Expressions/SharedExpression.swift b/Swifternalization/SharedExpression.swift similarity index 56% rename from Swifternalization/Shared Expressions/SharedExpression.swift rename to Swifternalization/SharedExpression.swift index f106e46..bf2b2f5 100644 --- a/Swifternalization/Shared Expressions/SharedExpression.swift +++ b/Swifternalization/SharedExpression.swift @@ -1,15 +1,16 @@ // -// SharedExpression.swift +// ProcessableSharedExpression.swift // Swifternalization // -// Created by Tomasz Szulc on 28/06/15. +// Created by Tomasz Szulc on 26/07/15. // Copyright (c) 2015 Tomasz Szulc. All rights reserved. // +import Foundation /** -Protocol that is implemented by classes that contains shared expressions. -Shared expressions are built-in expressions that user can easily use when +Protocol that is implemented by classes/structs that contains shared expressions. +Shared expressions are built-in expressions that user can easily use when localizing app. Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html @@ -22,21 +23,16 @@ protocol SharedExpressionProtocol { /** Represents built-in expression and expressions from Expressions.strings file. */ -struct SharedExpression { - /// Key of expression. - let key: Key +struct SharedExpression: ExpressionRepresentationType, ExpressionPatternType { + /// Identifier of expression. + let identifier: String /// Pattern of expression. - let pattern: ExpressionPattern + let pattern: String - /** - Creates shared expression. - - :param: key A key of expression. - :param: pattern A pattern of expression. - */ - init(key: Key, pattern: ExpressionPattern) { - self.key = key + /// Creates expression. + init(identifier: String, pattern: String) { + self.identifier = identifier self.pattern = pattern } } \ No newline at end of file diff --git a/Swifternalization/ExpressionsLoader.swift b/Swifternalization/SharedExpressionLoader.swift similarity index 73% rename from Swifternalization/ExpressionsLoader.swift rename to Swifternalization/SharedExpressionLoader.swift index 7f83995..95c638d 100644 --- a/Swifternalization/ExpressionsLoader.swift +++ b/Swifternalization/SharedExpressionLoader.swift @@ -3,7 +3,7 @@ import Foundation /** Used to load content from `expressions.json` file for specified language. */ -final class ExpressionsLoader: JSONFileLoader { +final class SharedExpressionsLoader: JSONFileLoader { /** Loads expressions for specified language. @@ -12,12 +12,12 @@ final class ExpressionsLoader: JSONFileLoader { :returns: array of loaded expressions. */ - class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> [ProcessableExpression] { - var expressions = [ProcessableExpression]() + class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> [SharedExpression] { + var expressions = [SharedExpression]() if let json = self.load("expressions", bundle: bundle), let expressionsDict = json[countryCode] as? Dictionary { for (identifier, pattern) in expressionsDict { - expressions.append(ProcessableExpression(identifier: identifier, pattern: pattern)) + expressions.append(SharedExpression(identifier: identifier, pattern: pattern)) } } else { println("expressions.json file structure is incorrect.") diff --git a/Swifternalization/SharedExpressionsConfigurator.swift b/Swifternalization/SharedExpressionsConfigurator.swift index 9f55727..b6f888f 100644 --- a/Swifternalization/SharedExpressionsConfigurator.swift +++ b/Swifternalization/SharedExpressionsConfigurator.swift @@ -85,7 +85,7 @@ class SharedExpressionsConfigurator { private class func convert(expressionsDict: KVDict) -> [SharedExpression] { var result = [SharedExpression]() for (key, pattern) in expressionsDict { - result.append(SharedExpression(key: key, pattern: pattern)) + result.append(SharedExpression(identifier: key, pattern: pattern)) } return result } @@ -117,7 +117,7 @@ class SharedExpressionsConfigurator { */ private class func mergeExpressions(var source: [SharedExpression], additional: [SharedExpression]) -> [SharedExpression] { for additionalExp in additional { - if source.filter({$0.key == additionalExp.key}).first == nil { + if source.filter({$0.identifier == additionalExp.identifier}).first == nil { source.append(additionalExp) } } diff --git a/Swifternalization/SharedExpressionsProcessor.swift b/Swifternalization/SharedExpressionsProcessor.swift new file mode 100644 index 0000000..11ad957 --- /dev/null +++ b/Swifternalization/SharedExpressionsProcessor.swift @@ -0,0 +1,104 @@ +// +// ExpressionsProcessor.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Swifternalization contains some built-in country-related shared expressions. +Developer can create its own expressions in expressions.json file for +base and preferred languages. + +The class is responsible for proper loading the built-in shared ones and +those loaded from project's files. It handles overriding of built-in and loads +those from Base and preferred language version. + +It always load base expressions, then looking for language specific. +If in Base are some expressions that overrides built-ins, that's fine. +The class adds developer's custom expressions and adds only those of built-in +that are not contained in the Base. + +The same is for preferred languages but also here what is important is that +at first preferred language file expressions are loaded, then if something +different is defined in Base it will be also loaded and then the built-in +expression that differs. +*/ +class SharedExpressionsProcessor { + + /** + Method takes expression for both Base and preferred language localizations + and also internally loads built-in expressions, combine them and returns + expressions for Base and prefered language. + + :param: preferedLanguage A user device's language. + :param: preferedLanguageExpressions Expressions from expressions.json + :param: baseLanguageExpressions Expressions from base section of + expression.json. + + :returns: array of shared expressions for Base and preferred language. + */ + class func processSharedExpression(preferedLanguage: CountryCode, preferedLanguageExpressions: [SharedExpression], baseLanguageExpressions: [SharedExpression]) -> [SharedExpression] { + /* + Get unique base expressions that are not presented in prefered language + expressions. Those from base will be used in a case when programmer + will ask for string localization and when there is no such expression + in prefered language section defined. + + It means two things: + 1. Programmer make this expression shared through prefered language + and this is good as base expression. + 2. He forgot to define such expression for prefered language. + */ + var uniqueBaseExpressions = baseLanguageExpressions + if preferedLanguageExpressions.count > 0 { + uniqueBaseExpressions = baseLanguageExpressions.filter({ + let pref = $0 + return preferedLanguageExpressions.filter({$0.identifier == pref.identifier}).count == 0 + }) + } + + // Expressions from json files. + var loadedExpressions = uniqueBaseExpressions + preferedLanguageExpressions + + + // Load prefered language nad base built-in expressions. Get unique. + let prefBuiltInExpressions = loadBuiltInExpressions(preferedLanguage) + let baseBuiltInExpressions = SharedBaseExpression.allExpressions() + let uniqueBaseBuiltInExpressions = baseBuiltInExpressions.filter({ + let pref = $0 + return prefBuiltInExpressions.filter({$0.identifier == pref.identifier}).count == 0 + }) + + // Unique built-in expressions made of base + prefered language. + let builtInExpressions = uniqueBaseBuiltInExpressions + prefBuiltInExpressions + + /* + To get it done we must get only unique built-in expressions that are not + in loaded expressions. + */ + let uniqueBuiltInExpressions = builtInExpressions.filter({ + let builtIn = $0 + return loadedExpressions.filter({$0.identifier == builtIn.identifier}).count == 0 + }) + + return loadedExpressions + uniqueBuiltInExpressions + } + + /** + Method loads built-in framework's built-in expressions for specific language. + + :param: language A preferred user's language. + :returns: Shared expressions for specific language. If there is no + expression for passed language empty array is returned. + */ + private class func loadBuiltInExpressions(language: Language) -> [SharedExpression] { + switch language { + case "pl": return SharedPolishExpression.allExpressions() + default: return [] + } + } +} diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 49f5155..3dac14f 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -226,7 +226,7 @@ public class Swifternalization { for (tKey, tValue) in translatableDict { // Check if there is expression in tKey if let expressionPattern = Expression.parseExpressionPattern(tKey), - let sharedExpression = expressions.filter({$0.key == expressionPattern}).first, + let sharedExpression = expressions.filter({$0.identifier == expressionPattern}).first, // Create expression with pattern from Expressions.strings and // it it is correct use it. let updatedExpression = Expression.expressionFromString("{" + sharedExpression.pattern + "}"), diff --git a/Swifternalization/TranslationsProcessor.swift b/Swifternalization/TranslationsProcessor.swift new file mode 100644 index 0000000..9ee69e7 --- /dev/null +++ b/Swifternalization/TranslationsProcessor.swift @@ -0,0 +1,13 @@ +// +// TranslationsProcessor.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +class TranslationsProcessor { + +} \ No newline at end of file diff --git a/SwifternalizationTests/SharedBaseExpressionTests.swift b/SwifternalizationTests/SharedBaseExpressionTests.swift index 50440fd..3ba83ff 100644 --- a/SwifternalizationTests/SharedBaseExpressionTests.swift +++ b/SwifternalizationTests/SharedBaseExpressionTests.swift @@ -13,7 +13,7 @@ import Swifternalization class SharedBaseExpressionTests: XCTestCase { func testOne() { - let sharedExp = SharedBaseExpression.allExpressions().filter({$0.key == "one"}).first! + let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "one"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("1"), "Should match 1") @@ -21,7 +21,7 @@ class SharedBaseExpressionTests: XCTestCase { } func testMoreThanOne() { - let sharedExp = SharedBaseExpression.allExpressions().filter({$0.key == ">one"}).first! + let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == ">one"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("2"), "Should match 2") @@ -30,7 +30,7 @@ class SharedBaseExpressionTests: XCTestCase { } func testTwo() { - let sharedExp = SharedBaseExpression.allExpressions().filter({$0.key == "two"}).first! + let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "two"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("2"), "Should match 2") @@ -38,7 +38,7 @@ class SharedBaseExpressionTests: XCTestCase { } func testOther() { - let sharedExp = SharedBaseExpression.allExpressions().filter({$0.key == "other"}).first! + let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "other"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("0"), "Should match 0") diff --git a/SwifternalizationTests/ExpressionsLoaderTests.swift b/SwifternalizationTests/SharedExpressionsLoaderTests.swift similarity index 56% rename from SwifternalizationTests/ExpressionsLoaderTests.swift rename to SwifternalizationTests/SharedExpressionsLoaderTests.swift index 4673893..80e06cb 100644 --- a/SwifternalizationTests/ExpressionsLoaderTests.swift +++ b/SwifternalizationTests/SharedExpressionsLoaderTests.swift @@ -9,20 +9,20 @@ import UIKit import XCTest -class ExpressionsLoaderTests: XCTestCase { +class SharedExpressionsLoaderTests: XCTestCase { func testShouldLoadBase() { - let content = ExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) + let content = SharedExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) XCTAssertTrue(content.count > 0, "") } func testShouldLoadPL() { - let content = ExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) + let content = SharedExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) XCTAssertTrue(content.count > 0, "") } func testShouldNotLoadDE() { - let content = ExpressionsLoader.loadExpressions("de", bundle: NSBundle.testBundle()) + let content = SharedExpressionsLoader.loadExpressions("de", bundle: NSBundle.testBundle()) XCTAssertFalse(content.count > 0, "") } } diff --git a/SwifternalizationTests/SharedExpressionsProcessorTests.swift b/SwifternalizationTests/SharedExpressionsProcessorTests.swift new file mode 100644 index 0000000..90e96ea --- /dev/null +++ b/SwifternalizationTests/SharedExpressionsProcessorTests.swift @@ -0,0 +1,22 @@ +// +// SharedExpressionsProcessor.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation +import XCTest + +class SharedExpressionsProcessorTests: XCTestCase { + + func testThatAllExpressionsShouldBeLoadedCorreclty() { + let baseExpressions = SharedExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) + let preferedExpressions = SharedExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) + + let sharedExpressions = SharedExpressionsProcessor.processSharedExpression("pl", preferedLanguageExpressions: preferedExpressions, baseLanguageExpressions: baseExpressions) + + XCTAssertEqual(sharedExpressions.count, 8, "") + } +} diff --git a/SwifternalizationTests/SharedPolishExpressionTests.swift b/SwifternalizationTests/SharedPolishExpressionTests.swift index aab30c9..8cbf12f 100644 --- a/SwifternalizationTests/SharedPolishExpressionTests.swift +++ b/SwifternalizationTests/SharedPolishExpressionTests.swift @@ -12,7 +12,7 @@ import XCTest class SharedPolishExpressionTests: XCTestCase { func testFew() { - let sharedExp = SharedPolishExpression.allExpressions().filter({$0.key == "few"}).first! + let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "few"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("2"), "Should match 2") @@ -27,7 +27,7 @@ class SharedPolishExpressionTests: XCTestCase { } func testMany() { - let sharedExp = SharedPolishExpression.allExpressions().filter({$0.key == "many"}).first! + let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "many"}).first! let expression = Expression(pattern: sharedExp.pattern)! XCTAssertTrue(expression.validate("10"), "Should match 10") diff --git a/SwifternalizationTests/expressions.json b/SwifternalizationTests/expressions.json index 05f0b5d..953b595 100644 --- a/SwifternalizationTests/expressions.json +++ b/SwifternalizationTests/expressions.json @@ -6,6 +6,8 @@ }, "pl": { - "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)", + "two": "ie:x=2", + "three": "ie:x=3" } } From 5932213925d14cfa5a7b56d36b972733b770db89 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 26 Jul 2015 23:06:03 +0200 Subject: [PATCH 09/27] Processing translations - WIP --- Swifternalization.xcodeproj/project.pbxproj | 8 +++++ Swifternalization/ProcessableExpression.swift | 8 ++--- .../ProcessableTranslationExpression.swift | 4 +-- Swifternalization/SimpleExpression.swift | 22 ++++++++++++ Swifternalization/TranslationExpression.swift | 23 ++++++++++++ Swifternalization/TranslationsLoader.swift | 6 ++-- Swifternalization/TranslationsProcessor.swift | 35 +++++++++++++++++-- 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 Swifternalization/SimpleExpression.swift create mode 100644 Swifternalization/TranslationExpression.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index ddf90e5..9f25c23 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */; }; 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; + 6D5BA6071B655E06000D7E49 /* TranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */; }; + 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */; }; 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; @@ -146,6 +148,8 @@ 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsProcessor.swift; sourceTree = ""; }; 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessorTests.swift; sourceTree = ""; }; + 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationExpression.swift; sourceTree = ""; }; + 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleExpression.swift; sourceTree = ""; }; 6D6282911B3F04C800E65FCD /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; @@ -341,6 +345,8 @@ children = ( 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */, + 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */, + 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */, ); name = Processors; sourceTree = ""; @@ -643,6 +649,7 @@ 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, + 6D5BA6071B655E06000D7E49 /* TranslationExpression.swift in Sources */, 6D5BA5FE1B6525F9000D7E49 /* TranslationsProcessor.swift in Sources */, 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, @@ -653,6 +660,7 @@ 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */, + 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */, 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */, 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */, diff --git a/Swifternalization/ProcessableExpression.swift b/Swifternalization/ProcessableExpression.swift index 665ed17..ce2ab66 100644 --- a/Swifternalization/ProcessableExpression.swift +++ b/Swifternalization/ProcessableExpression.swift @@ -3,16 +3,16 @@ import Foundation /** Represents loaded expression that will be processed later. */ -struct ProcessableExpression: ExpressionRepresentationType, ExpressionPatternType, Processable { +struct ProcessableExpressionSimple: ExpressionRepresentationType, Processable { /// Identifier of expression. let identifier: String /// Pattern of expression. - let pattern: String + let value: String /// Creates expression. - init(identifier: String, pattern: String) { + init(identifier: String, value: String) { self.identifier = identifier - self.pattern = pattern + self.value = value } } diff --git a/Swifternalization/ProcessableTranslationExpression.swift b/Swifternalization/ProcessableTranslationExpression.swift index d3b7ab8..da6560d 100644 --- a/Swifternalization/ProcessableTranslationExpression.swift +++ b/Swifternalization/ProcessableTranslationExpression.swift @@ -8,10 +8,10 @@ struct ProcessableTranslationExpression: TranslationType, Processable { let key: String /// Array with loaded expressions. - let loadedExpressions: [ProcessableExpression] + let loadedExpressions: [ProcessableExpressionSimple] /// Creates instances of the class. - init(key: String, loadedExpressions: [ProcessableExpression]) { + init(key: String, loadedExpressions: [ProcessableExpressionSimple]) { self.key = key self.loadedExpressions = loadedExpressions } diff --git a/Swifternalization/SimpleExpression.swift b/Swifternalization/SimpleExpression.swift new file mode 100644 index 0000000..c6cd7bf --- /dev/null +++ b/Swifternalization/SimpleExpression.swift @@ -0,0 +1,22 @@ +// +// SimpleExpression.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +class SimpleExpression: ExpressionRepresentationType { + /// Identifier of expression. + let identifier: String + + /// A localized value. + let value: String + + init(identifier: String, value: String) { + self.identifier = identifier + self.value = value + } +} diff --git a/Swifternalization/TranslationExpression.swift b/Swifternalization/TranslationExpression.swift new file mode 100644 index 0000000..d2456b3 --- /dev/null +++ b/Swifternalization/TranslationExpression.swift @@ -0,0 +1,23 @@ +// +// TranslationExpression.swift +// Swifternalization +// +// Created by Tomasz Szulc on 26/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +class TranslationExpression: TranslationType { + /// Key that identifies translation. + let key: String + + /// Array with loaded expressions. + let loadedExpressions: [SimpleExpression] + + /// Creates instances of the class. + init(key: String, loadedExpressions: [SimpleExpression]) { + self.key = key + self.loadedExpressions = loadedExpressions + } +} diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index b9675f5..1ba08e0 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -70,10 +70,10 @@ final class TranslationsLoader: JSONFileLoader { return variations } - private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpression] { - var expressions = [ProcessableExpression]() + private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpressionSimple] { + var expressions = [ProcessableExpressionSimple]() for (expressionKey, translationValue) in dict { - expressions.append(ProcessableExpression(identifier: expressionKey, pattern: translationValue)) + expressions.append(ProcessableExpressionSimple(identifier: expressionKey, value: translationValue)) } return expressions } diff --git a/Swifternalization/TranslationsProcessor.swift b/Swifternalization/TranslationsProcessor.swift index 9ee69e7..af4a579 100644 --- a/Swifternalization/TranslationsProcessor.swift +++ b/Swifternalization/TranslationsProcessor.swift @@ -8,6 +8,37 @@ import Foundation -class TranslationsProcessor { - +class TranslationsProcessor { + class func processTranslations(baseTranslations: [T], preferedLanguageTranslations: [T], sharedExpressions: [SharedExpression]) { + + var uniqueBaseTranslations = baseTranslations + if preferedLanguageTranslations.count > 0 { + uniqueBaseTranslations = baseTranslations.filter({ + let base = $0 + return preferedLanguageTranslations.filter({$0.key == base.key}).count == 0 + }) + } + + var translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations + + var translations: [TranslationType] = translationsReadyToProcess.map({ + if $0 is TranslationSimple { + return $0 + } else if $0 is ProcessableTranslationExpression { + let processableTranslation = $0 as! ProcessableTranslationExpression + + let expressions = processableTranslation.loadedExpressions.map({ (processableExpr: ProcessableExpressionSimple) -> SimpleExpression in + var identifier = processableExpr.identifier + if let sharedExpression = sharedExpressions.filter({$0.identifier == identifier}).first { + identifier = sharedExpression.pattern + } + return SimpleExpression(identifier: identifier, value: processableExpr.value) + }) + + return TranslationExpression(key: processableTranslation.key, loadedExpressions: expressions) + } + + return $0 + }) + } } \ No newline at end of file From 78e15bb0314e117b5783851485da2b59465854b4 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Thu, 30 Jul 2015 20:57:47 +0200 Subject: [PATCH 10/27] Simplify TranslationsLoader and add LoadedTranslation struct with corresponding type --- Swifternalization.xcodeproj/project.pbxproj | 6 ++ Swifternalization/LoadedTranslation.swift | 76 ++++++++++++++ Swifternalization/TranslationsLoader.swift | 108 ++++++-------------- 3 files changed, 111 insertions(+), 79 deletions(-) create mode 100644 Swifternalization/LoadedTranslation.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 9f25c23..0ea86a6 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D140F431B56D03D00359143 /* RandomNumbers.swift */; }; + 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; + 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D50044E1B3EF91600A54B36 /* Swifternalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D50044D1B3EF91600A54B36 /* Swifternalization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6D5004541B3EF91600A54B36 /* Swifternalization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D5004481B3EF91600A54B36 /* Swifternalization.framework */; }; 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */; }; @@ -134,6 +136,7 @@ /* Begin PBXFileReference section */ 6D140F431B56D03D00359143 /* RandomNumbers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomNumbers.swift; sourceTree = ""; }; + 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslation.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50044D1B3EF91600A54B36 /* Swifternalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swifternalization.h; sourceTree = ""; }; @@ -473,6 +476,7 @@ 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, + 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, ); name = Loaders; sourceTree = ""; @@ -654,6 +658,7 @@ 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, + 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */, 6D62829F1B3F1FA000E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282A51B3F24A800E65FCD /* ExpressionParser.swift in Sources */, @@ -708,6 +713,7 @@ 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, + 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */, 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, diff --git a/Swifternalization/LoadedTranslation.swift b/Swifternalization/LoadedTranslation.swift new file mode 100644 index 0000000..10e911f --- /dev/null +++ b/Swifternalization/LoadedTranslation.swift @@ -0,0 +1,76 @@ +// +// LoadedTranslation.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Specifies few types of available translation type. +*/ +enum LoadedTranslationType { + /** + Simple key-value pair. + + "welcome": "welcome" + */ + case Simple + + /** + Pair where value is a dictionary with expressions. + + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + } + */ + case WithExpressions + + /** + Pair where value is a dictionary with length variations. + + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + } + */ + case WithLengthVariations + + /** + Pair where value is dictionary that contains dictionary with expression and + length variations. + + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + } + } + */ + case WithExpressionsAndLengthVariations + + /// Not supported type. + case NotSupported +} + +/** +Struct that represents loaded translation. +*/ +struct LoadedTranslation { + /// A type of translation. + let type: LoadedTranslationType + + /// A content of translation just loaded from a file. + let content: Dictionary +} diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index 1ba08e0..2200a12 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -1,59 +1,29 @@ import Foundation -/** -Represents translations loader. -Class is looking for json file named as country code. -It parse such file. +/** +Class that loads translations from a file. */ final class TranslationsLoader: JSONFileLoader { - typealias DictWithStrings = Dictionary - typealias DictWithDicts = Dictionary - - enum ElementType { - case NotSupported - case TranslationWithLengthVariations - case TranslationWithLoadedExpressions - case TranslationWithLengthVariationsAndLoadedExpressions - } - /** - Method loads a file and parses it. + Loads content from file with name equal to passed country code in a bundle. :params: countryCode A country code. - - :return: translations parsed from the file. + :params: bundle A bundle when file is placed. + :return: `LoadedTranslation` objects from specified file. */ - class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> [TranslationType] { - var loadedTranslations = [TranslationType]() - let json = self.load(countryCode, bundle: bundle) - if json == nil { return [TranslationType]() } - - for (translationKey, value) in json! { - if let translationValue = value as? String { - loadedTranslations.append(TranslationSimple(key: translationKey, value: translationValue)) - } else { - let dictionary = value as! JSONDictionary - switch detectElementType(dictionary) { - case .TranslationWithLengthVariations: - let variations = parseLengthVariations(dictionary as! DictWithStrings) - loadedTranslations.append(TranslationLengthVariation(key: translationKey, variations: variations)) - - case .TranslationWithLoadedExpressions: - let expressions = parseExpressions(dictionary as! DictWithStrings) - loadedTranslations.append(ProcessableTranslationExpression(key: translationKey, loadedExpressions: expressions)) - - case .TranslationWithLengthVariationsAndLoadedExpressions: - var expressions = [ProcessableLengthVariationExpression]() - for (expressionIdentifier, lengthVariationsDict) in dictionary as! DictWithDicts { - let variations = parseLengthVariations(lengthVariationsDict) - expressions.append(ProcessableLengthVariationExpression(identifier: expressionIdentifier, variations: variations)) + class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> [LoadedTranslation] { + var loadedTranslations = [LoadedTranslation]() + if let json = self.load(countryCode, bundle: bundle) { + for (key, value) in json { + if value is String { + loadedTranslations.append(LoadedTranslation(type: .Simple, content: [key: value])) + } else { + let dictionary = value as! JSONDictionary + let type = detectElementType(dictionary) + if type != .NotSupported { + loadedTranslations.append(LoadedTranslation(type: type, content: dictionary)) } - loadedTranslations.append(ProcessableTranslationExpressionLengthVariationExpression(key: translationKey, expressions: expressions)) - - case .NotSupported: - // Do nothing - continue } } } @@ -61,43 +31,23 @@ final class TranslationsLoader: JSONFileLoader { return loadedTranslations } - private class func parseLengthVariations(dict: DictWithStrings) -> [LengthVariation] { - var variations = [LengthVariation]() - for (key, translationValue) in dict { - let numberValue = parseNumberFromLengthVariation(key) - variations.append(LengthVariation(length: numberValue, value: translationValue)) - } - return variations - } - - private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpressionSimple] { - var expressions = [ProcessableExpressionSimple]() - for (expressionKey, translationValue) in dict { - expressions.append(ProcessableExpressionSimple(identifier: expressionKey, value: translationValue)) - } - return expressions - } + /** + Analyzes passed dictionary and checks its content to match it to some translation type. - private class func detectElementType(element: JSONDictionary) -> ElementType { - - if element is DictWithStrings { - if let key = element.keys.first { - let toIndex = advance(key.startIndex, 1) - let firstCharacter = key.substringToIndex(toIndex) - if firstCharacter == "@" { - return .TranslationWithLengthVariations - } else { - return .TranslationWithLoadedExpressions - } - } + :params: element A dictionary that will be analyzed. + :returns: translation type of a dictionary. + */ + private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType { + typealias DictWithStrings = Dictionary + typealias DictWithDicts = Dictionary + + if element is DictWithStrings, let key = element.keys.first { + let toIndex = advance(key.startIndex, 1) + return key.substringToIndex(toIndex) == "@" ? .WithLengthVariations : .WithExpressions } else if element is DictWithDicts { - return .TranslationWithLengthVariationsAndLoadedExpressions + return .WithExpressionsAndLengthVariations } return .NotSupported } - - private class func parseNumberFromLengthVariation(string: String) -> Int { - return (Regex.matchInString(string, pattern: "@(\\d+)", capturingGroupIdx: 1)! as NSString).integerValue - } } From 702e54941a6300959c2018580289b749b893ddb0 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Thu, 30 Jul 2015 22:32:08 +0200 Subject: [PATCH 11/27] Implement loaded translation processor - WIP --- Swifternalization.xcodeproj/project.pbxproj | 126 +++++++----------- Swifternalization/Expression.swift | 14 +- Swifternalization/ExpressionPatternType.swift | 11 -- .../ExpressionRepresentationType.swift | 6 +- Swifternalization/ExpressionType.swift | 4 +- .../InequalityExpressionParser.swift | 4 +- .../InequalityExtendedExpressionParser.swift | 6 +- Swifternalization/InternalPattern.swift | 2 +- Swifternalization/JSONFileLoader.swift | 8 +- Swifternalization/LoadedTranslation.swift | 61 +-------- Swifternalization/LoadedTranslationType.swift | 62 +++++++++ .../LoadedTranslationsProcessor.swift | 70 ++++++++++ Swifternalization/Processable.swift | 2 +- Swifternalization/ProcessableExpression.swift | 18 --- ...ProcessableLengthVariationExpression.swift | 18 --- .../ProcessableTranslationExpression.swift | 18 --- ...TranslationLengthVariationExpression.swift | 18 --- Swifternalization/RegexExpressionParser.swift | 2 +- Swifternalization/SharedExpression.swift | 2 +- .../SharedExpressionLoader.swift | 1 - Swifternalization/SimpleExpression.swift | 24 +++- Swifternalization/SimpleTranslation.swift | 20 +++ Swifternalization/TranslationExpression.swift | 23 ---- .../TranslationLengthVariation.swift | 2 +- Swifternalization/TranslationSimple.swift | 18 --- Swifternalization/TranslationType.swift | 12 +- Swifternalization/TranslationTypeOld.swift | 9 ++ .../TranslationWithExpressions.swift | 20 +++ Swifternalization/TranslationsLoader.swift | 11 +- Swifternalization/TranslationsProcessor.swift | 44 ------ 30 files changed, 290 insertions(+), 346 deletions(-) delete mode 100644 Swifternalization/ExpressionPatternType.swift create mode 100644 Swifternalization/LoadedTranslationType.swift create mode 100644 Swifternalization/LoadedTranslationsProcessor.swift delete mode 100644 Swifternalization/ProcessableExpression.swift delete mode 100644 Swifternalization/ProcessableLengthVariationExpression.swift delete mode 100644 Swifternalization/ProcessableTranslationExpression.swift delete mode 100644 Swifternalization/ProcessableTranslationLengthVariationExpression.swift create mode 100644 Swifternalization/SimpleTranslation.swift delete mode 100644 Swifternalization/TranslationExpression.swift delete mode 100644 Swifternalization/TranslationSimple.swift create mode 100644 Swifternalization/TranslationTypeOld.swift create mode 100644 Swifternalization/TranslationWithExpressions.swift delete mode 100644 Swifternalization/TranslationsProcessor.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 0ea86a6..5ea764d 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -10,6 +10,12 @@ 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D140F431B56D03D00359143 /* RandomNumbers.swift */; }; 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; + 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; + 6D4C4EB51B6AB38400B7839A /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */; }; + 6D4C4EB61B6AB43F00B7839A /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */; }; + 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; + 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; + 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; 6D50044E1B3EF91600A54B36 /* Swifternalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D50044D1B3EF91600A54B36 /* Swifternalization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6D5004541B3EF91600A54B36 /* Swifternalization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D5004481B3EF91600A54B36 /* Swifternalization.framework */; }; 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */; }; @@ -19,21 +25,16 @@ 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004591B3EF91600A54B36 /* Info.plist */; }; 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */; }; 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; - 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */; }; + 6D5BA5F21B6517FE000D7E49 /* TranslationTypeOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */; }; 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; - 6D5BA5F51B65181C000D7E49 /* TranslationSimple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */; }; + 6D5BA5F51B65181C000D7E49 /* SimpleTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */; }; 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; - 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; - 6D5BA5F81B65182D000D7E49 /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; - 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */; }; 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; - 6D5BA5FE1B6525F9000D7E49 /* TranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */; }; 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */; }; 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; - 6D5BA6071B655E06000D7E49 /* TranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */; }; 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */; }; @@ -79,19 +80,14 @@ 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; - 6DB3CC781B5EBDA600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; - 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; - 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */; }; - 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */; }; - 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */; }; 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; - 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */; }; + 6DB3CC821B5EBDA600A1220F /* SimpleTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */; }; 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; - 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */; }; + 6DB3CC841B5EBDA600A1220F /* TranslationTypeOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */; }; 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; @@ -117,8 +113,6 @@ 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */; }; 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; - 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */; }; - 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */; }; 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */; }; @@ -137,6 +131,10 @@ /* Begin PBXFileReference section */ 6D140F431B56D03D00359143 /* RandomNumbers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomNumbers.swift; sourceTree = ""; }; 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslation.swift; sourceTree = ""; }; + 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessor.swift; sourceTree = ""; }; + 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; + 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; + 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationWithExpressions.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50044D1B3EF91600A54B36 /* Swifternalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swifternalization.h; sourceTree = ""; }; @@ -148,10 +146,8 @@ 6D5004931B3EFF6D00A54B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessor.swift; sourceTree = ""; }; - 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsProcessor.swift; sourceTree = ""; }; 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessorTests.swift; sourceTree = ""; }; - 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationExpression.swift; sourceTree = ""; }; 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleExpression.swift; sourceTree = ""; }; 6D6282911B3F04C800E65FCD /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; @@ -187,19 +183,14 @@ 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionRepresentationType.swift; sourceTree = ""; }; 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionLoader.swift; sourceTree = ""; }; - 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationType.swift; sourceTree = ""; }; 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processable.swift; sourceTree = ""; }; - 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableExpression.swift; sourceTree = ""; }; - 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableLengthVariationExpression.swift; sourceTree = ""; }; - 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslationExpression.swift; sourceTree = ""; }; - 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessableTranslationLengthVariationExpression.swift; sourceTree = ""; }; 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationLengthVariation.swift; sourceTree = ""; }; - 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSimple.swift; sourceTree = ""; }; + 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTranslation.swift; sourceTree = ""; }; 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; - 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; + 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationTypeOld.swift; sourceTree = ""; }; 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsConfigurator.swift; sourceTree = ""; }; @@ -254,6 +245,24 @@ name = Helpers; sourceTree = ""; }; + 6D4C4EB11B6AAB5100B7839A /* Next */ = { + isa = PBXGroup; + children = ( + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, + 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */, + 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */, + 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */, + 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, + 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, + 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */, + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, + 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */, + 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */, + ); + name = Next; + sourceTree = ""; + }; 6D50043E1B3EF91600A54B36 = { isa = PBXGroup; children = ( @@ -277,16 +286,13 @@ 6D50044A1B3EF91600A54B36 /* Swifternalization */ = { isa = PBXGroup; children = ( + 6D4C4EB11B6AAB5100B7839A /* Next */, 6DB3CC8E1B5EC27600A1220F /* Enums */, - 6DD3B93E1B5ED36F00C79EAC /* Loaders */, 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, - 6DB3CC8C1B5EC1E000A1220F /* Processable */, - 6D5BA5FC1B652543000D7E49 /* Processors */, 6DB3CC8A1B5EC19400A1220F /* Protocols */, 6DB3CC8B1B5EC1A400A1220F /* Typealiases */, 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, - 6DB3CC721B5EBDA600A1220F /* TranslationSimple.swift */, 6D6282911B3F04C800E65FCD /* Expression.swift */, 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, @@ -343,17 +349,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 6D5BA5FC1B652543000D7E49 /* Processors */ = { - isa = PBXGroup; - children = ( - 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, - 6D5BA5FD1B6525F9000D7E49 /* TranslationsProcessor.swift */, - 6D5BA6061B655E06000D7E49 /* TranslationExpression.swift */, - 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */, - ); - name = Processors; - sourceTree = ""; - }; 6D5BA6021B6537BE000D7E49 /* Processors */ = { isa = PBXGroup; children = ( @@ -396,12 +391,11 @@ 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { isa = PBXGroup; children = ( - 6DB3CC681B5EBDA600A1220F /* ExpressionPatternType.swift */, 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */, 6D6464831B40146100C46C6D /* KeyValueType.swift */, 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */, - 6DB3CC741B5EBDA600A1220F /* TranslationType.swift */, + 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */, ); name = Protocols; sourceTree = ""; @@ -414,17 +408,6 @@ name = Typealiases; sourceTree = ""; }; - 6DB3CC8C1B5EC1E000A1220F /* Processable */ = { - isa = PBXGroup; - children = ( - 6DB3CC6D1B5EBDA600A1220F /* ProcessableExpression.swift */, - 6DB3CC6E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift */, - 6DB3CC6F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift */, - 6DB3CC701B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift */, - ); - name = Processable; - sourceTree = ""; - }; 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */ = { isa = PBXGroup; children = ( @@ -470,17 +453,6 @@ name = "Localizable Files"; sourceTree = ""; }; - 6DD3B93E1B5ED36F00C79EAC /* Loaders */ = { - isa = PBXGroup; - children = ( - 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, - 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, - 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, - 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, - ); - name = Loaders; - sourceTree = ""; - }; 6DD3B93F1B5ED37A00C79EAC /* Loaders */ = { isa = PBXGroup; children = ( @@ -641,45 +613,42 @@ buildActionMask = 2147483647; files = ( 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */, + 6D4C4EB51B6AB38400B7839A /* TranslationType.swift in Sources */, 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */, 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */, 6D6282A31B3F247000E65FCD /* ExpressionMatcher.swift in Sources */, 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, - 6DB3CC841B5EBDA600A1220F /* TranslationType.swift in Sources */, + 6DB3CC841B5EBDA600A1220F /* TranslationTypeOld.swift in Sources */, 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, + 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */, 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, - 6D5BA6071B655E06000D7E49 /* TranslationExpression.swift in Sources */, - 6D5BA5FE1B6525F9000D7E49 /* TranslationsProcessor.swift in Sources */, 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, + 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */, 6D62829F1B3F1FA000E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282A51B3F24A800E65FCD /* ExpressionParser.swift in Sources */, + 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */, 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, - 6DB3CC801B5EBDA600A1220F /* ProcessableTranslationLengthVariationExpression.swift in Sources */, 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */, - 6DB3CC7D1B5EBDA600A1220F /* ProcessableExpression.swift in Sources */, 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, - 6DB3CC7E1B5EBDA600A1220F /* ProcessableLengthVariationExpression.swift in Sources */, 6DBB6C651B403367002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, - 6DB3CC821B5EBDA600A1220F /* TranslationSimple.swift in Sources */, + 6DB3CC821B5EBDA600A1220F /* SimpleTranslation.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */, - 6DB3CC781B5EBDA600A1220F /* ExpressionPatternType.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */, 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */, 6D6282B11B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D6282BA1B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, - 6DB3CC7F1B5EBDA600A1220F /* ProcessableTranslationExpression.swift in Sources */, 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */, 6D6282AC1B3F327800E65FCD /* InequalityExpressionMatcher.swift in Sources */, ); @@ -703,22 +672,22 @@ 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, + 6D4C4EB61B6AB43F00B7839A /* TranslationType.swift in Sources */, 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, - 6D5BA5F91B651831000D7E49 /* ProcessableTranslationExpression.swift in Sources */, - 6D5BA5F21B6517FE000D7E49 /* TranslationType.swift in Sources */, + 6D5BA5F21B6517FE000D7E49 /* TranslationTypeOld.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, + 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */, 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */, 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, - 6D5BA5F71B651825000D7E49 /* ProcessableLengthVariationExpression.swift in Sources */, 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */, 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */, @@ -732,17 +701,14 @@ 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */, 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */, 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, - 6DD3B9461B5ED5B500C79EAC /* ProcessableExpression.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, - 6D5BA5F51B65181C000D7E49 /* TranslationSimple.swift in Sources */, + 6D5BA5F51B65181C000D7E49 /* SimpleTranslation.swift in Sources */, 6D6282A91B3F25DC00E65FCD /* InequalityExpressionParserTests.swift in Sources */, - 6D5BA5F81B65182D000D7E49 /* ProcessableTranslationLengthVariationExpression.swift in Sources */, 6D6282AA1B3F269900E65FCD /* ExpressionParser.swift in Sources */, 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */, 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, - 6DD3B9471B5ED61100C79EAC /* ExpressionPatternType.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */, diff --git a/Swifternalization/Expression.swift b/Swifternalization/Expression.swift index bdf6ae4..3dd58ed 100644 --- a/Swifternalization/Expression.swift +++ b/Swifternalization/Expression.swift @@ -23,7 +23,7 @@ class Expression { let pattern: ExpressionPattern /// Type of expression computed based on `pattern`. - private var type: ExpressionType! + private var type: ExpressionPatternType! /// Matcher created based on `pattern`. private var matcher: ExpressionMatcher! @@ -105,7 +105,7 @@ class Expression { pattern of this `Expression` object. */ private func buildMatcher() { - switch (type as ExpressionType) { + switch (type as ExpressionPatternType) { case .Inequality: matcher = InequalityExpressionParser(pattern).parse() @@ -118,14 +118,14 @@ class Expression { } /** - Method used to get `ExpressionType` of passed `ExpressionPattern`. + Method used to get `ExpressionPatternType` of passed `ExpressionPattern`. :param: pattern expression pattern that will be checked. - :returns: `ExpressionType` if pattern is supported, otherwise nil. + :returns: `ExpressionPatternType` if pattern is supported, otherwise nil. */ - private class func getExpressionType(pattern: ExpressionPattern) -> ExpressionType? { - if let result = Regex.firstMatchInString(pattern, pattern: InternalPattern.ExpressionType.rawValue) { - return ExpressionType(rawValue: result) + private class func getExpressionType(pattern: ExpressionPattern) -> ExpressionPatternType? { + if let result = Regex.firstMatchInString(pattern, pattern: InternalPattern.ExpressionPatternType.rawValue) { + return ExpressionPatternType(rawValue: result) } return nil } diff --git a/Swifternalization/ExpressionPatternType.swift b/Swifternalization/ExpressionPatternType.swift deleted file mode 100644 index 5bcb3b8..0000000 --- a/Swifternalization/ExpressionPatternType.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -/** -Represents objects that have expression pattern. -Example an expression that contains just pattern, without things like length -variations. -*/ -protocol ExpressionPatternType { - /// A pattern. - var pattern: String {get} -} \ No newline at end of file diff --git a/Swifternalization/ExpressionRepresentationType.swift b/Swifternalization/ExpressionRepresentationType.swift index ed808ca..476c725 100644 --- a/Swifternalization/ExpressionRepresentationType.swift +++ b/Swifternalization/ExpressionRepresentationType.swift @@ -3,7 +3,7 @@ import Foundation /** Represents expressions in the framework. */ -protocol ExpressionRepresentationType { - /// Identifier of expression. - var identifier: String {get} +protocol ExpressionType { + /// Pattern of expression. + var pattern: String {get} } diff --git a/Swifternalization/ExpressionType.swift b/Swifternalization/ExpressionType.swift index 5dcd7e4..b0a5665 100644 --- a/Swifternalization/ExpressionType.swift +++ b/Swifternalization/ExpressionType.swift @@ -1,5 +1,5 @@ // -// ExpressionType.swift +// ExpressionPatternType.swift // Swifternalization // // Created by Tomasz Szulc on 21/07/15. @@ -9,7 +9,7 @@ import Foundation /// Supported expression types -enum ExpressionType: String { +enum ExpressionPatternType: String { /// works on Int only, e.g. `x<5`, `x=3` case Inequality = "ie" diff --git a/Swifternalization/InequalityExpressionParser.swift b/Swifternalization/InequalityExpressionParser.swift index ec206c1..5acc69d 100644 --- a/Swifternalization/InequalityExpressionParser.swift +++ b/Swifternalization/InequalityExpressionParser.swift @@ -42,7 +42,7 @@ class InequalityExpressionParser: ExpressionParser { :returns: `InequalitySign` or nil if sign cannot be found. */ private func sign() -> InequalitySign? { - return getSign(ExpressionType.Inequality.rawValue+":x(<=|<|=|>=|>)", failureMessage: "Cannot find any sign", capturingGroupIdx: 1) + return getSign(ExpressionPatternType.Inequality.rawValue+":x(<=|<|=|>=|>)", failureMessage: "Cannot find any sign", capturingGroupIdx: 1) } /** @@ -51,7 +51,7 @@ class InequalityExpressionParser: ExpressionParser { :returns: value or nil if value cannot be found */ private func value() -> Double? { - return getValue(ExpressionType.Inequality.rawValue+":x[^-\\d]{1,2}(-?\\d+[.]{0,1}[\\d]{0,})", failureMessage: "Cannot find any value", capturingGroupIdx: 1) + return getValue(ExpressionPatternType.Inequality.rawValue+":x[^-\\d]{1,2}(-?\\d+[.]{0,1}[\\d]{0,})", failureMessage: "Cannot find any value", capturingGroupIdx: 1) } diff --git a/Swifternalization/InequalityExtendedExpressionParser.swift b/Swifternalization/InequalityExtendedExpressionParser.swift index 3ebce8a..f2b2b90 100644 --- a/Swifternalization/InequalityExtendedExpressionParser.swift +++ b/Swifternalization/InequalityExtendedExpressionParser.swift @@ -44,7 +44,7 @@ class InequalityExtendedExpressionParser: InequalityExpressionParser { :returns: `Int` or nil if value cannot be found. */ private func firstValue() -> Double? { - return getValue(ExpressionType.InequalityExtended.rawValue+":(?<=^iex:)(-?\\d+[.]{0,1}[\\d]{0,})", failureMessage: "Cannot find first value", capturingGroupIdx: 1) + return getValue(ExpressionPatternType.InequalityExtended.rawValue+":(?<=^iex:)(-?\\d+[.]{0,1}[\\d]{0,})", failureMessage: "Cannot find first value", capturingGroupIdx: 1) } @@ -54,7 +54,7 @@ class InequalityExtendedExpressionParser: InequalityExpressionParser { :returns: inequality sign or nil if sign cannot be found. */ private func firstSign() -> InequalitySign? { - return getSign(ExpressionType.InequalityExtended.rawValue+":-?\\d{0,}[.]?\\d{0,}(<=|<|)", failureMessage: "Cannot find first sign", capturingGroupIdx: 1) + return getSign(ExpressionPatternType.InequalityExtended.rawValue+":-?\\d{0,}[.]?\\d{0,}(<=|<|)", failureMessage: "Cannot find first sign", capturingGroupIdx: 1) } /** @@ -63,7 +63,7 @@ class InequalityExtendedExpressionParser: InequalityExpressionParser { :returns: A second sign or nil if sign cannot be found. */ private func secondSign() -> InequalitySign? { - return getSign(ExpressionType.InequalityExtended.rawValue+":[-]?\\d*[.]?\\d*[<=>]{1,2}x(<=|<|)", failureMessage: "Cannot find second sign", capturingGroupIdx: 1) + return getSign(ExpressionPatternType.InequalityExtended.rawValue+":[-]?\\d*[.]?\\d*[<=>]{1,2}x(<=|<|)", failureMessage: "Cannot find second sign", capturingGroupIdx: 1) } /** diff --git a/Swifternalization/InternalPattern.swift b/Swifternalization/InternalPattern.swift index 1b290d0..ed5829f 100644 --- a/Swifternalization/InternalPattern.swift +++ b/Swifternalization/InternalPattern.swift @@ -16,7 +16,7 @@ enum InternalPattern: String { case Expression = "(?<=\\{)(.+)(?=\\})" /// Pattern that matches expression types. - case ExpressionType = "(^.{2,3})(?=:)" + case ExpressionPatternType = "(^.{2,3})(?=:)" /// Pattern that matches key without expression. case KeyWithoutExpression = "^(.*?)(?=\\{)" diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index 39f9e94..b879214 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -7,13 +7,12 @@ class JSONFileLoader { typealias JSONDictionary = Dictionary /** - Load content of file with specified name, type and bundle. + Loads content of a file with specified name, type and bundle. :param: fileName A name of a file. :param: fileType A type of a file. :param: bundle A bundle when file is located. - - :return: JSON or nil. + :returns: JSON or nil. */ final class func load(fileName: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { if let fileURL = bundle.URLForResource(fileName, withExtension: "json") { @@ -27,8 +26,7 @@ class JSONFileLoader { Loads file for specified URL and try to serialize it. :params: fileURL url to JSON file. - - :return: Dictionary with content of JSON file or nil. + :returns: Dictionary with content of JSON file or nil. */ private class func load(fileURL: NSURL) -> JSONDictionary? { if let data = NSData(contentsOfURL: fileURL) { diff --git a/Swifternalization/LoadedTranslation.swift b/Swifternalization/LoadedTranslation.swift index 10e911f..65a71f2 100644 --- a/Swifternalization/LoadedTranslation.swift +++ b/Swifternalization/LoadedTranslation.swift @@ -8,62 +8,6 @@ import Foundation -/** -Specifies few types of available translation type. -*/ -enum LoadedTranslationType { - /** - Simple key-value pair. - - "welcome": "welcome" - */ - case Simple - - /** - Pair where value is a dictionary with expressions. - - "cars": { - "one": "1 car", - "ie:x=2": "2 cars", - "more": "%d cars" - } - */ - case WithExpressions - - /** - Pair where value is a dictionary with length variations. - - "forgot-password": { - "@100": "Forgot Password? Help.", - "@200": "Forgot Password? Get password Help.", - "@300": "Forgotten Your Password? Get password Help." - } - */ - case WithLengthVariations - - /** - Pair where value is dictionary that contains dictionary with expression and - length variations. - - "car-sentence": { - "one": { - "@100": "one car", - "@200": "just one car", - "@300": "you've got just one car" - }, - - "more": { - "@100": "%d cars", - "@300": "you've got %d cars" - } - } - */ - case WithExpressionsAndLengthVariations - - /// Not supported type. - case NotSupported -} - /** Struct that represents loaded translation. */ @@ -73,4 +17,9 @@ struct LoadedTranslation { /// A content of translation just loaded from a file. let content: Dictionary + + /// Key that identifies this translation + var key: String { + return content.keys.first! + } } diff --git a/Swifternalization/LoadedTranslationType.swift b/Swifternalization/LoadedTranslationType.swift new file mode 100644 index 0000000..d0a7875 --- /dev/null +++ b/Swifternalization/LoadedTranslationType.swift @@ -0,0 +1,62 @@ +// +// LoadedTranslationType.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Specifies few types of available translation type. +*/ +enum LoadedTranslationType { + /** + Simple key-value pair. + + "welcome": "welcome" + */ + case Simple + + /** + Pair where value is a dictionary with expressions. + + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + } + */ + case WithExpressions + + /** + Pair where value is a dictionary with length variations. + + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + } + */ + case WithLengthVariations + + /** + Pair where value is dictionary that contains dictionary with expression and + length variations. + + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + } + } + */ + case WithExpressionsAndLengthVariations +} \ No newline at end of file diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift new file mode 100644 index 0000000..73cd499 --- /dev/null +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -0,0 +1,70 @@ +// +// LoadedTranslationsProcessor.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Translation processor that takes loaded translations and process them to make +them regular translation objects that can be used for further work. +*/ +class LoadedTranslationsProcessor { + + /** + Method takes base and prefered language translations and also shared + expressions and mix them together. Meaning, It checks which base loaded + translations are not contained in prefered language translations and + allow them to be used by framework later. It fit to the rule of how system + localization mechanism work, that when there is no prefered language + translation it takes base one if exists and return translation. + + Shared expressions are used to replace shared-expression-keys from loaded + translations and use resolved full shared expressions from file with + expressions as well as built-in ones. Translations are processed in shared + expressions processor. + */ + class func processTrnslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) { + + // Find those base translations that are not contained in prefered + // language translations. + var uniqueBaseTranslations = baseTranslations + if preferedLanguageTranslations.count > 0 { + uniqueBaseTranslations = baseTranslations.filter({ + let base = $0 + return preferedLanguageTranslations.filter({$0.key == base.key}).count == 0 + }) + } + + var translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations + var translations: [TranslationType] = translationsReadyToProcess.map({ + switch $0.type { + case .Simple: + let value = $0.content[$0.key] as! String + return SimpleTranslation(key: $0.key, value: value) + + case .WithExpressions: + var expressions = [SimpleExpression]() + for (key, value) in $0.content as! Dictionary { + let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key + expressions.append(SimpleExpression(pattern: pattern, localizedValue: value)) + } + return TranslationWithExpressions(key: $0.key, expressions: expressions) + + + // TEMPORARY + // + // + case .WithLengthVariations: + return SimpleTranslation(key: $0.key, value: $0.content[$0.key]! as! String) + + case .WithExpressionsAndLengthVariations: + return SimpleTranslation(key: $0.key, value: $0.content[$0.key]! as! String) + + } + }) + } +} diff --git a/Swifternalization/Processable.swift b/Swifternalization/Processable.swift index 4deef2a..7cfaa52 100644 --- a/Swifternalization/Processable.swift +++ b/Swifternalization/Processable.swift @@ -6,7 +6,7 @@ used in the framework. An example of such object might be `ProcessableExpression that is not ready to be used because it has to be converted into normal kind of expression that has its fields correctly filled. -Another good example is `TranslationType` object that contains expression or few +Another good example is `TranslationTypeOld` object that contains expression or few that need to be processed. */ protocol Processable {} \ No newline at end of file diff --git a/Swifternalization/ProcessableExpression.swift b/Swifternalization/ProcessableExpression.swift deleted file mode 100644 index ce2ab66..0000000 --- a/Swifternalization/ProcessableExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents loaded expression that will be processed later. -*/ -struct ProcessableExpressionSimple: ExpressionRepresentationType, Processable { - /// Identifier of expression. - let identifier: String - - /// Pattern of expression. - let value: String - - /// Creates expression. - init(identifier: String, value: String) { - self.identifier = identifier - self.value = value - } -} diff --git a/Swifternalization/ProcessableLengthVariationExpression.swift b/Swifternalization/ProcessableLengthVariationExpression.swift deleted file mode 100644 index c916fc1..0000000 --- a/Swifternalization/ProcessableLengthVariationExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents processable expression with length variations. -*/ -struct ProcessableLengthVariationExpression: ExpressionRepresentationType, Processable { - /// Identifier of expression. - var identifier: String - - /// Array of length variations - var variations: [LengthVariation] - - /// Creates instance of the class. - init(identifier: String, variations: [LengthVariation]) { - self.identifier = identifier - self.variations = variations - } -} diff --git a/Swifternalization/ProcessableTranslationExpression.swift b/Swifternalization/ProcessableTranslationExpression.swift deleted file mode 100644 index da6560d..0000000 --- a/Swifternalization/ProcessableTranslationExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents translation which contains expressions that are not processed yet. -*/ -struct ProcessableTranslationExpression: TranslationType, Processable { - /// Key that identifies translation. - let key: String - - /// Array with loaded expressions. - let loadedExpressions: [ProcessableExpressionSimple] - - /// Creates instances of the class. - init(key: String, loadedExpressions: [ProcessableExpressionSimple]) { - self.key = key - self.loadedExpressions = loadedExpressions - } -} diff --git a/Swifternalization/ProcessableTranslationLengthVariationExpression.swift b/Swifternalization/ProcessableTranslationLengthVariationExpression.swift deleted file mode 100644 index 00bb8d8..0000000 --- a/Swifternalization/ProcessableTranslationLengthVariationExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents translation which contains expressions with length variations. -*/ -struct ProcessableTranslationExpressionLengthVariationExpression: TranslationType, Processable { - /// Key that identifies translation. - let key: String - - /// Array with expressions. - let expressions: [ProcessableLengthVariationExpression] - - /// Creates instances of the class. - init(key: String, expressions: [ProcessableLengthVariationExpression]) { - self.key = key - self.expressions = expressions - } -} \ No newline at end of file diff --git a/Swifternalization/RegexExpressionParser.swift b/Swifternalization/RegexExpressionParser.swift index 365772f..bd00f1e 100644 --- a/Swifternalization/RegexExpressionParser.swift +++ b/Swifternalization/RegexExpressionParser.swift @@ -45,7 +45,7 @@ class RegexExpressionParser: ExpressionParser { in the pattern. */ private func regexPattern() -> RegexPattern? { - if let regex = Regex.firstMatchInString(pattern, pattern: "(?<=^\(ExpressionType.Regex.rawValue):).*") { + if let regex = Regex.firstMatchInString(pattern, pattern: "(?<=^\(ExpressionPatternType.Regex.rawValue):).*") { return regex } else { println("Cannot find any regular expression, pattern: \(pattern)") diff --git a/Swifternalization/SharedExpression.swift b/Swifternalization/SharedExpression.swift index bf2b2f5..c660efc 100644 --- a/Swifternalization/SharedExpression.swift +++ b/Swifternalization/SharedExpression.swift @@ -23,7 +23,7 @@ protocol SharedExpressionProtocol { /** Represents built-in expression and expressions from Expressions.strings file. */ -struct SharedExpression: ExpressionRepresentationType, ExpressionPatternType { +struct SharedExpression: ExpressionType { /// Identifier of expression. let identifier: String diff --git a/Swifternalization/SharedExpressionLoader.swift b/Swifternalization/SharedExpressionLoader.swift index 95c638d..d248a49 100644 --- a/Swifternalization/SharedExpressionLoader.swift +++ b/Swifternalization/SharedExpressionLoader.swift @@ -9,7 +9,6 @@ final class SharedExpressionsLoader: JSONFileLoader { Loads expressions for specified language. :param: countryCode A country code - :returns: array of loaded expressions. */ class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> [SharedExpression] { diff --git a/Swifternalization/SimpleExpression.swift b/Swifternalization/SimpleExpression.swift index c6cd7bf..d9f11a3 100644 --- a/Swifternalization/SimpleExpression.swift +++ b/Swifternalization/SimpleExpression.swift @@ -8,15 +8,25 @@ import Foundation -class SimpleExpression: ExpressionRepresentationType { - /// Identifier of expression. - let identifier: String +class SimpleExpression: ExpressionType { + /// Pattern of expression. + let pattern: String /// A localized value. - let value: String + let localizedValue: String - init(identifier: String, value: String) { - self.identifier = identifier - self.value = value + init(pattern: String, localizedValue: String) { + self.pattern = pattern + self.localizedValue = localizedValue + } + + /** + Validates passed string. + + :param: text A text to be validated. + :returns: True if text matches validation rules, otherwise false. + */ + func validate(text: String) -> Bool { + return true } } diff --git a/Swifternalization/SimpleTranslation.swift b/Swifternalization/SimpleTranslation.swift new file mode 100644 index 0000000..0c50414 --- /dev/null +++ b/Swifternalization/SimpleTranslation.swift @@ -0,0 +1,20 @@ +// +// SimpleTranslation.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Represents simple key-value translation. +*/ +struct SimpleTranslation: TranslationType { + /// Key that identifies a translation. + let key: String + + /// Localized string. + let value: String +} diff --git a/Swifternalization/TranslationExpression.swift b/Swifternalization/TranslationExpression.swift deleted file mode 100644 index d2456b3..0000000 --- a/Swifternalization/TranslationExpression.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// TranslationExpression.swift -// Swifternalization -// -// Created by Tomasz Szulc on 26/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -class TranslationExpression: TranslationType { - /// Key that identifies translation. - let key: String - - /// Array with loaded expressions. - let loadedExpressions: [SimpleExpression] - - /// Creates instances of the class. - init(key: String, loadedExpressions: [SimpleExpression]) { - self.key = key - self.loadedExpressions = loadedExpressions - } -} diff --git a/Swifternalization/TranslationLengthVariation.swift b/Swifternalization/TranslationLengthVariation.swift index 0f4dd65..6537398 100644 --- a/Swifternalization/TranslationLengthVariation.swift +++ b/Swifternalization/TranslationLengthVariation.swift @@ -3,7 +3,7 @@ import Foundation /** Class represents translation which contains key and length variations. */ -struct TranslationLengthVariation: TranslationType { +struct TranslationLengthVariation: TranslationTypeOld { /// Key that identifies this translation. let key: String diff --git a/Swifternalization/TranslationSimple.swift b/Swifternalization/TranslationSimple.swift deleted file mode 100644 index 36b799f..0000000 --- a/Swifternalization/TranslationSimple.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents simple key-value translation. -*/ -struct TranslationSimple: TranslationType { - /// Key that identifies translation. - let key: String - - /// Localized string. - let value: String - - /// Creates instance of the class. - init(key: String, value: String) { - self.key = key - self.value = value - } -} diff --git a/Swifternalization/TranslationType.swift b/Swifternalization/TranslationType.swift index f5f4667..dc9d02a 100644 --- a/Swifternalization/TranslationType.swift +++ b/Swifternalization/TranslationType.swift @@ -1,9 +1,17 @@ +// +// TranslationType.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + import Foundation -/** +/** Represents translation. */ protocol TranslationType { /// Key that identifies translation. var key: String {get} -} +} \ No newline at end of file diff --git a/Swifternalization/TranslationTypeOld.swift b/Swifternalization/TranslationTypeOld.swift new file mode 100644 index 0000000..ff27967 --- /dev/null +++ b/Swifternalization/TranslationTypeOld.swift @@ -0,0 +1,9 @@ +import Foundation + +/** +Represents translation. +*/ +protocol TranslationTypeOld { + /// Key that identifies translation. + var key: String {get} +} diff --git a/Swifternalization/TranslationWithExpressions.swift b/Swifternalization/TranslationWithExpressions.swift new file mode 100644 index 0000000..972a3c5 --- /dev/null +++ b/Swifternalization/TranslationWithExpressions.swift @@ -0,0 +1,20 @@ +// +// TranslationWithExpression.swift +// Swifternalization +// +// Created by Tomasz Szulc on 30/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/** +Represents translation with expressions. +*/ +struct TranslationWithExpressions: TranslationType { + /// Key that identifies a translation. + let key: String + + /// Expressions that are related to a translation. + let expressions: [ExpressionType] +} diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index 2200a12..b5aba34 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -10,7 +10,7 @@ final class TranslationsLoader: JSONFileLoader { :params: countryCode A country code. :params: bundle A bundle when file is placed. - :return: `LoadedTranslation` objects from specified file. + :returns: `LoadedTranslation` objects from specified file. */ class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> [LoadedTranslation] { var loadedTranslations = [LoadedTranslation]() @@ -20,9 +20,10 @@ final class TranslationsLoader: JSONFileLoader { loadedTranslations.append(LoadedTranslation(type: .Simple, content: [key: value])) } else { let dictionary = value as! JSONDictionary - let type = detectElementType(dictionary) - if type != .NotSupported { + if let type = detectElementType(dictionary) { loadedTranslations.append(LoadedTranslation(type: type, content: dictionary)) + } else { + println("Translation type is not supported for: \(dictionary)") } } } @@ -37,7 +38,7 @@ final class TranslationsLoader: JSONFileLoader { :params: element A dictionary that will be analyzed. :returns: translation type of a dictionary. */ - private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType { + private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType? { typealias DictWithStrings = Dictionary typealias DictWithDicts = Dictionary @@ -48,6 +49,6 @@ final class TranslationsLoader: JSONFileLoader { return .WithExpressionsAndLengthVariations } - return .NotSupported + return nil } } diff --git a/Swifternalization/TranslationsProcessor.swift b/Swifternalization/TranslationsProcessor.swift deleted file mode 100644 index af4a579..0000000 --- a/Swifternalization/TranslationsProcessor.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// TranslationsProcessor.swift -// Swifternalization -// -// Created by Tomasz Szulc on 26/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -class TranslationsProcessor { - class func processTranslations(baseTranslations: [T], preferedLanguageTranslations: [T], sharedExpressions: [SharedExpression]) { - - var uniqueBaseTranslations = baseTranslations - if preferedLanguageTranslations.count > 0 { - uniqueBaseTranslations = baseTranslations.filter({ - let base = $0 - return preferedLanguageTranslations.filter({$0.key == base.key}).count == 0 - }) - } - - var translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations - - var translations: [TranslationType] = translationsReadyToProcess.map({ - if $0 is TranslationSimple { - return $0 - } else if $0 is ProcessableTranslationExpression { - let processableTranslation = $0 as! ProcessableTranslationExpression - - let expressions = processableTranslation.loadedExpressions.map({ (processableExpr: ProcessableExpressionSimple) -> SimpleExpression in - var identifier = processableExpr.identifier - if let sharedExpression = sharedExpressions.filter({$0.identifier == identifier}).first { - identifier = sharedExpression.pattern - } - return SimpleExpression(identifier: identifier, value: processableExpr.value) - }) - - return TranslationExpression(key: processableTranslation.key, loadedExpressions: expressions) - } - - return $0 - }) - } -} \ No newline at end of file From 1034f9cebf120ede79a3e8d54d083a5eb056d8fe Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Fri, 31 Jul 2015 00:37:36 +0200 Subject: [PATCH 12/27] Implement processing loaded translations --- Swifternalization.xcodeproj/project.pbxproj | 72 +++++++++---------- Swifternalization/ExpressionPatternType.swift | 21 ++++++ .../ExpressionRepresentationType.swift | 9 --- Swifternalization/ExpressionType.swift | 26 ++----- Swifternalization/LengthVariation.swift | 10 +-- .../LengthVariationExpression.swift | 20 ++++++ ...swift => LengthVariationTranslation.swift} | 12 ++-- Swifternalization/LoadedTranslation.swift | 8 +-- .../LoadedTranslationsProcessor.swift | 59 +++++++++++---- Swifternalization/Processable.swift | 12 ---- Swifternalization/SimpleExpression.swift | 20 ++---- .../TranslationLengthVariation.swift | 18 ----- Swifternalization/TranslationTypeOld.swift | 9 --- Swifternalization/TranslationsLoader.swift | 4 +- 14 files changed, 145 insertions(+), 155 deletions(-) create mode 100644 Swifternalization/ExpressionPatternType.swift delete mode 100644 Swifternalization/ExpressionRepresentationType.swift create mode 100644 Swifternalization/LengthVariationExpression.swift rename Swifternalization/{SimpleTranslation.swift => LengthVariationTranslation.swift} (52%) delete mode 100644 Swifternalization/Processable.swift delete mode 100644 Swifternalization/TranslationLengthVariation.swift delete mode 100644 Swifternalization/TranslationTypeOld.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 5ea764d..887d2db 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -16,6 +16,13 @@ 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; + 6D4C4EBF1B6ACA8700B7839A /* LengthVariationTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */; }; + 6D4C4EC11B6ACF2200B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; + 6D4C4EC21B6ACF2600B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; + 6D4C4EC31B6ACF2900B7839A /* LengthVariationTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */; }; + 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; + 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; + 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; 6D50044E1B3EF91600A54B36 /* Swifternalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D50044D1B3EF91600A54B36 /* Swifternalization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6D5004541B3EF91600A54B36 /* Swifternalization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D5004481B3EF91600A54B36 /* Swifternalization.framework */; }; 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */; }; @@ -25,11 +32,8 @@ 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004591B3EF91600A54B36 /* Info.plist */; }; 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */; }; 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; - 6D5BA5F21B6517FE000D7E49 /* TranslationTypeOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */; }; 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; - 6D5BA5F51B65181C000D7E49 /* SimpleTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */; }; - 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; @@ -78,19 +82,15 @@ 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; - 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; + 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */; }; 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; - 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; - 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */; }; - 6DB3CC821B5EBDA600A1220F /* SimpleTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */; }; 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; - 6DB3CC841B5EBDA600A1220F /* TranslationTypeOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */; }; 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; - 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; - 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */; }; + 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; + 6DB3CC911B5EC29E00A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */; }; @@ -113,8 +113,7 @@ 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */; }; 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; - 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */; }; - 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */; }; + 6DD3B9491B5ED61800C79EAC /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */; }; 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */; }; /* End PBXBuildFile section */ @@ -135,6 +134,8 @@ 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationWithExpressions.swift; sourceTree = ""; }; + 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationTranslation.swift; sourceTree = ""; }; + 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationExpression.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50044D1B3EF91600A54B36 /* Swifternalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swifternalization.h; sourceTree = ""; }; @@ -181,17 +182,13 @@ 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoader.swift; sourceTree = ""; }; 6D6464831B40146100C46C6D /* KeyValueType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueType.swift; sourceTree = ""; }; 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; - 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionRepresentationType.swift; sourceTree = ""; }; + 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionLoader.swift; sourceTree = ""; }; 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationType.swift; sourceTree = ""; }; - 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processable.swift; sourceTree = ""; }; - 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationLengthVariation.swift; sourceTree = ""; }; - 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTranslation.swift; sourceTree = ""; }; 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; - 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationTypeOld.swift; sourceTree = ""; }; - 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; + 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsConfigurator.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; @@ -248,17 +245,22 @@ 6D4C4EB11B6AAB5100B7839A /* Next */ = { isa = PBXGroup; children = ( + 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, + 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */, 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, + 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */, 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */, 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */, 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, - 6DB3CC721B5EBDA600A1220F /* SimpleTranslation.swift */, 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */, 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */, + 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */, + 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */, ); name = Next; sourceTree = ""; @@ -291,8 +293,6 @@ 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, 6DB3CC8A1B5EC19400A1220F /* Protocols */, 6DB3CC8B1B5EC1A400A1220F /* Typealiases */, - 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, - 6DB3CC711B5EBDA600A1220F /* TranslationLengthVariation.swift */, 6D6282911B3F04C800E65FCD /* Expression.swift */, 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, @@ -391,11 +391,7 @@ 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { isa = PBXGroup; children = ( - 6DB3CC661B5EBDA600A1220F /* ExpressionRepresentationType.swift */, 6D6464831B40146100C46C6D /* KeyValueType.swift */, - 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, - 6DB3CC6C1B5EBDA600A1220F /* Processable.swift */, - 6DB3CC741B5EBDA600A1220F /* TranslationTypeOld.swift */, ); name = Protocols; sourceTree = ""; @@ -403,7 +399,6 @@ 6DB3CC8B1B5EC1A400A1220F /* Typealiases */ = { isa = PBXGroup; children = ( - 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, ); name = Typealiases; sourceTree = ""; @@ -426,7 +421,7 @@ 6DB3CC8E1B5EC27600A1220F /* Enums */ = { isa = PBXGroup; children = ( - 6DB3CC8F1B5EC29600A1220F /* ExpressionType.swift */, + 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */, 6D6282B41B3F3C4100E65FCD /* InequalitySign.swift */, 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */, ); @@ -615,20 +610,19 @@ 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */, 6D4C4EB51B6AB38400B7839A /* TranslationType.swift in Sources */, 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */, - 6DB3CC811B5EBDA600A1220F /* TranslationLengthVariation.swift in Sources */, 6D6282A31B3F247000E65FCD /* ExpressionMatcher.swift in Sources */, 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, - 6DB3CC841B5EBDA600A1220F /* TranslationTypeOld.swift in Sources */, 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, + 6D4C4EBF1B6ACA8700B7839A /* LengthVariationTranslation.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */, - 6DB3CC901B5EC29600A1220F /* ExpressionType.swift in Sources */, + 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, - 6DB3CC761B5EBDA600A1220F /* ExpressionRepresentationType.swift in Sources */, - 6DB3CC7C1B5EBDA600A1220F /* Processable.swift in Sources */, + 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */, + 6D4C4EC11B6ACF2200B7839A /* LengthVariationExpression.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, @@ -641,7 +635,6 @@ 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */, 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C651B403367002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, - 6DB3CC821B5EBDA600A1220F /* SimpleTranslation.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, @@ -675,34 +668,36 @@ 6D4C4EB61B6AB43F00B7839A /* TranslationType.swift in Sources */, 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, - 6DD3B9481B5ED61500C79EAC /* Processable.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, - 6D5BA5F21B6517FE000D7E49 /* TranslationTypeOld.swift in Sources */, + 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */, 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */, - 6DB3CC911B5EC29E00A1220F /* ExpressionType.swift in Sources */, + 6DB3CC911B5EC29E00A1220F /* ExpressionPatternType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */, + 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */, 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */, 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */, + 6D4C4EC31B6ACF2900B7839A /* LengthVariationTranslation.swift in Sources */, 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */, 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */, 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */, 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, - 6DD3B9491B5ED61800C79EAC /* ExpressionRepresentationType.swift in Sources */, + 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */, + 6DD3B9491B5ED61800C79EAC /* ExpressionType.swift in Sources */, + 6D4C4EC21B6ACF2600B7839A /* LengthVariationExpression.swift in Sources */, 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */, 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, - 6D5BA5F51B65181C000D7E49 /* SimpleTranslation.swift in Sources */, 6D6282A91B3F25DC00E65FCD /* InequalityExpressionParserTests.swift in Sources */, 6D6282AA1B3F269900E65FCD /* ExpressionParser.swift in Sources */, 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, @@ -711,7 +706,6 @@ 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, - 6D5BA5F61B651821000D7E49 /* TranslationLengthVariation.swift in Sources */, 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swifternalization/ExpressionPatternType.swift b/Swifternalization/ExpressionPatternType.swift new file mode 100644 index 0000000..21ab0af --- /dev/null +++ b/Swifternalization/ExpressionPatternType.swift @@ -0,0 +1,21 @@ +// +// ExpressionPatternType.swift +// Swifternalization +// +// Created by Tomasz Szulc on 21/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +/// Supported expression types +enum ExpressionPatternType: String { + /// works on Int only, e.g. `x<5`, `x=3` + case Inequality = "ie" + + /// works on Int only, e.g. `4 - - /// Key that identifies this translation - var key: String { - return content.keys.first! - } } diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index 73cd499..5d22f5a 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -27,7 +27,7 @@ class LoadedTranslationsProcessor { expressions as well as built-in ones. Translations are processed in shared expressions processor. */ - class func processTrnslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) { + class func processTrnslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [TranslationType] { // Find those base translations that are not contained in prefered // language translations. @@ -39,32 +39,67 @@ class LoadedTranslationsProcessor { }) } - var translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations - var translations: [TranslationType] = translationsReadyToProcess.map({ + let translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations + + // Create array with translations. Array is just a map created from + // loaded translations. There are few types of translations and + // expressions used by the framework. + return translationsReadyToProcess.map({ switch $0.type { case .Simple: + // Simple translation with key and value. let value = $0.content[$0.key] as! String - return SimpleTranslation(key: $0.key, value: value) + return TranslationWithExpressions(key: $0.key, expressions: [SimpleExpression(pattern: $0.key, localizedValue: value)]) case .WithExpressions: - var expressions = [SimpleExpression]() + // Translation that contains expression. + // Every time when new expressions is about to create, + // the shared expressions are filtered to get expression that + // matches key and if there is a key it is replaced with real + // expression pattern. + var expressions = [ExpressionType]() for (key, value) in $0.content as! Dictionary { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key expressions.append(SimpleExpression(pattern: pattern, localizedValue: value)) } return TranslationWithExpressions(key: $0.key, expressions: expressions) - - // TEMPORARY - // - // case .WithLengthVariations: - return SimpleTranslation(key: $0.key, value: $0.content[$0.key]! as! String) + // Translation contains length expressions like @100, @200, etc. + var lengthVariations = [LengthVariation]() + for (key, value) in $0.content as! Dictionary { + lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(key), value: value)) + } + return LengthVariationTranslation(key: $0.key, variations: lengthVariations) case .WithExpressionsAndLengthVariations: - return SimpleTranslation(key: $0.key, value: $0.content[$0.key]! as! String) - + // The most advanced translation type. It contains expressions + // that contain length variations. THe job done here is similar + // to the one in .WithExpressions and .WithLengthVariations + // cases. key is filtered in shared expressions to get one of + // shared expressions and then method builds array of variations. + var expressions = [ExpressionType]() + for (key, value) in $0.content as! Dictionary> { + let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key + + var lengthVariations = [LengthVariation]() + for (lvKey, lvValue) in value as Dictionary { + lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) + } + expressions.append(LengthVariationExpression(pattern: pattern, variations: lengthVariations)) + } + return TranslationWithExpressions(key: $0.key, expressions: expressions) } }) } + + /** + Parses nubmer from length variation key. + + :param: string A string that contains length variation string like @100. + :returns: A number parsed from the string. + */ + private class func parseNumberFromLengthVariation(string: String) -> Int { + return (Regex.matchInString(string, pattern: "@(\\d+)", capturingGroupIdx: 1)! as NSString).integerValue + } } diff --git a/Swifternalization/Processable.swift b/Swifternalization/Processable.swift deleted file mode 100644 index 7cfaa52..0000000 --- a/Swifternalization/Processable.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -/** -Specifies objects that need processing and are not final objects that can be -used in the framework. An example of such object might be `ProcessableExpression` -that is not ready to be used because it has to be converted into normal kind of -expression that has its fields correctly filled. - -Another good example is `TranslationTypeOld` object that contains expression or few -that need to be processed. -*/ -protocol Processable {} \ No newline at end of file diff --git a/Swifternalization/SimpleExpression.swift b/Swifternalization/SimpleExpression.swift index d9f11a3..75bff54 100644 --- a/Swifternalization/SimpleExpression.swift +++ b/Swifternalization/SimpleExpression.swift @@ -8,25 +8,13 @@ import Foundation -class SimpleExpression: ExpressionType { +/** +Represents simple epxressions that has only pattern and localized value. +*/ +struct SimpleExpression: ExpressionType { /// Pattern of expression. let pattern: String /// A localized value. let localizedValue: String - - init(pattern: String, localizedValue: String) { - self.pattern = pattern - self.localizedValue = localizedValue - } - - /** - Validates passed string. - - :param: text A text to be validated. - :returns: True if text matches validation rules, otherwise false. - */ - func validate(text: String) -> Bool { - return true - } } diff --git a/Swifternalization/TranslationLengthVariation.swift b/Swifternalization/TranslationLengthVariation.swift deleted file mode 100644 index 6537398..0000000 --- a/Swifternalization/TranslationLengthVariation.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Class represents translation which contains key and length variations. -*/ -struct TranslationLengthVariation: TranslationTypeOld { - /// Key that identifies this translation. - let key: String - - /// list of variations. - let variations: [LengthVariation] - - /// Creates translation object - init(key: String, variations: [LengthVariation]) { - self.key = key - self.variations = variations - } -} \ No newline at end of file diff --git a/Swifternalization/TranslationTypeOld.swift b/Swifternalization/TranslationTypeOld.swift deleted file mode 100644 index ff27967..0000000 --- a/Swifternalization/TranslationTypeOld.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -/** -Represents translation. -*/ -protocol TranslationTypeOld { - /// Key that identifies translation. - var key: String {get} -} diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index b5aba34..f7f2e58 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -17,11 +17,11 @@ final class TranslationsLoader: JSONFileLoader { if let json = self.load(countryCode, bundle: bundle) { for (key, value) in json { if value is String { - loadedTranslations.append(LoadedTranslation(type: .Simple, content: [key: value])) + loadedTranslations.append(LoadedTranslation(type: .Simple, key: key, content: [key: value])) } else { let dictionary = value as! JSONDictionary if let type = detectElementType(dictionary) { - loadedTranslations.append(LoadedTranslation(type: type, content: dictionary)) + loadedTranslations.append(LoadedTranslation(type: type, key: key, content: dictionary)) } else { println("Translation type is not supported for: \(dictionary)") } From 08f29522b475c0f29ed9fbb074356d0b9da08ba7 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Fri, 31 Jul 2015 00:44:56 +0200 Subject: [PATCH 13/27] Remove LengthVariationTranslation --- Swifternalization.xcodeproj/project.pbxproj | 6 ------ .../LengthVariationTranslation.swift | 20 ------------------- .../LoadedTranslationsProcessor.swift | 2 +- 3 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 Swifternalization/LengthVariationTranslation.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 887d2db..0049185 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -16,10 +16,8 @@ 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; - 6D4C4EBF1B6ACA8700B7839A /* LengthVariationTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */; }; 6D4C4EC11B6ACF2200B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; 6D4C4EC21B6ACF2600B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; - 6D4C4EC31B6ACF2900B7839A /* LengthVariationTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */; }; 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; @@ -134,7 +132,6 @@ 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationWithExpressions.swift; sourceTree = ""; }; - 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationTranslation.swift; sourceTree = ""; }; 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationExpression.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -259,7 +256,6 @@ 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */, 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */, - 6D4C4EBE1B6ACA8700B7839A /* LengthVariationTranslation.swift */, 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */, ); name = Next; @@ -614,7 +610,6 @@ 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, - 6D4C4EBF1B6ACA8700B7839A /* LengthVariationTranslation.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */, @@ -686,7 +681,6 @@ 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */, 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */, - 6D4C4EC31B6ACF2900B7839A /* LengthVariationTranslation.swift in Sources */, 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */, 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */, 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, diff --git a/Swifternalization/LengthVariationTranslation.swift b/Swifternalization/LengthVariationTranslation.swift deleted file mode 100644 index 4f22688..0000000 --- a/Swifternalization/LengthVariationTranslation.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// LengthVariationTranslation.swift -// Swifternalization -// -// Created by Tomasz Szulc on 30/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Represents length variation translation. -*/ -struct LengthVariationTranslation: TranslationType { - /// Key that identifies a translation. - let key: String - - /// Length variations. - let variations: [LengthVariation] -} diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index 5d22f5a..fb2b555 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -70,7 +70,7 @@ class LoadedTranslationsProcessor { for (key, value) in $0.content as! Dictionary { lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(key), value: value)) } - return LengthVariationTranslation(key: $0.key, variations: lengthVariations) + return TranslationWithExpressions(key: $0.key, expressions: [LengthVariationExpression(pattern: $0.key, variations: lengthVariations)]) case .WithExpressionsAndLengthVariations: // The most advanced translation type. It contains expressions From cd2aca90e0290db44e02368ecf7291be307f7ef4 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Fri, 31 Jul 2015 15:17:36 +0200 Subject: [PATCH 14/27] Add tests for LoadedTranslationsProcessor --- Swifternalization.xcodeproj/project.pbxproj | 50 ++++--- Swifternalization/JSONFileLoader.swift | 36 ++++- Swifternalization/LengthVariationType.swift | 4 +- .../LoadedTranslationsProcessor.swift | 26 ++-- .../SharedExpressionLoader.swift | 13 +- Swifternalization/TranslationsLoader.swift | 48 ++++--- SwifternalizationTests/ExpressionJSONs.swift | 28 ++++ .../JSONFileLoaderTests.swift | 36 ++++- .../LoadedTranslationsProcessorTests.swift | 125 ++++++++++++++++++ .../SharedExpressionsLoaderTests.swift | 16 +-- .../SharedExpressionsProcessorTests.swift | 13 +- SwifternalizationTests/TranslationJSONs.swift | 56 ++++++++ .../TranslationsLoaderTests.swift | 16 +-- 13 files changed, 356 insertions(+), 111 deletions(-) create mode 100644 SwifternalizationTests/ExpressionJSONs.swift create mode 100644 SwifternalizationTests/LoadedTranslationsProcessorTests.swift create mode 100644 SwifternalizationTests/TranslationJSONs.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 0049185..4271158 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -76,9 +76,12 @@ 6D62834B1B3F622A00E65FCD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D6283491B3F622A00E65FCD /* Localizable.strings */; }; 6D62834E1B3F628000E65FCD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834D1B3F628000E65FCD /* Main.storyboard */; }; 6D6283501B3F62B100E65FCD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */; }; + 6D62F9FC1B6B808400596A7C /* ExpressionJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */; }; + 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */; }; 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; + 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */; }; 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */; }; 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; @@ -176,8 +179,11 @@ 6D62834C1B3F623200E65FCD /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 6D62834D1B3F628000E65FCD /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; + 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionJSONs.swift; sourceTree = ""; }; + 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationJSONs.swift; sourceTree = ""; }; 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoader.swift; sourceTree = ""; }; 6D6464831B40146100C46C6D /* KeyValueType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueType.swift; sourceTree = ""; }; + 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessorTests.swift; sourceTree = ""; }; 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionLoader.swift; sourceTree = ""; }; @@ -233,8 +239,10 @@ 6D140F451B56D04300359143 /* Helpers */ = { isa = PBXGroup; children = ( - 6D140F431B56D03D00359143 /* RandomNumbers.swift */, + 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */, 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */, + 6D140F431B56D03D00359143 /* RandomNumbers.swift */, + 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */, ); name = Helpers; sourceTree = ""; @@ -288,7 +296,6 @@ 6DB3CC8E1B5EC27600A1220F /* Enums */, 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, 6DB3CC8A1B5EC19400A1220F /* Protocols */, - 6DB3CC8B1B5EC1A400A1220F /* Typealiases */, 6D6282911B3F04C800E65FCD /* Expression.swift */, 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, @@ -313,9 +320,8 @@ 6D5004571B3EF91600A54B36 /* SwifternalizationTests */ = { isa = PBXGroup; children = ( + 6D75E6D21B6B6CBC00B370DC /* Next */, 6D140F451B56D04300359143 /* Helpers */, - 6DD3B93F1B5ED37A00C79EAC /* Loaders */, - 6D5BA6021B6537BE000D7E49 /* Processors */, 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */, 6D6282BE1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift */, 6D6282A81B3F25DC00E65FCD /* InequalityExpressionParserTests.swift */, @@ -345,14 +351,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 6D5BA6021B6537BE000D7E49 /* Processors */ = { - isa = PBXGroup; - children = ( - 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */, - ); - name = Processors; - sourceTree = ""; - }; 6D6283261B3F613D00E65FCD /* DemoOrdering */ = { isa = PBXGroup; children = ( @@ -384,19 +382,24 @@ name = Resources; sourceTree = ""; }; - 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { + 6D75E6D21B6B6CBC00B370DC /* Next */ = { isa = PBXGroup; children = ( - 6D6464831B40146100C46C6D /* KeyValueType.swift */, + 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, + 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */, + 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */, + 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */, + 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, ); - name = Protocols; + name = Next; sourceTree = ""; }; - 6DB3CC8B1B5EC1A400A1220F /* Typealiases */ = { + 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { isa = PBXGroup; children = ( + 6D6464831B40146100C46C6D /* KeyValueType.swift */, ); - name = Typealiases; + name = Protocols; sourceTree = ""; }; 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */ = { @@ -444,16 +447,6 @@ name = "Localizable Files"; sourceTree = ""; }; - 6DD3B93F1B5ED37A00C79EAC /* Loaders */ = { - isa = PBXGroup; - children = ( - 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, - 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */, - 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, - ); - name = Loaders; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -653,6 +646,7 @@ 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */, 6D6282981B3F13C300E65FCD /* ExpressionTests.swift in Sources */, 6DBB6C661B40369A002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, + 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */, 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, 6D6282A61B3F25B900E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, @@ -668,6 +662,7 @@ 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, + 6D62F9FC1B6B808400596A7C /* ExpressionJSONs.swift in Sources */, 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */, 6DD3B9411B5ED38B00C79EAC /* JSONFileLoaderTests.swift in Sources */, 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */, @@ -697,6 +692,7 @@ 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */, + 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */, 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index b879214..c310847 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -1,10 +1,36 @@ import Foundation +/** +Represents json content. +*/ +typealias JSONDictionary = Dictionary + /** Simple JSON loader. */ -class JSONFileLoader { - typealias JSONDictionary = Dictionary +final class JSONFileLoader { + + /** + Loads translations dict for specified language. + + :param: countryCode A country code. + :param: bundle A bundle when file is located. + :returns: Returns json of file or nil if cannot load a file. + */ + class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + return self.load(countryCode, bundle: bundle) + } + + /** + Loads expressions dict for specified language. + + :param: countryCode A country code. + :param: bundle A bundle when file is located. + :returns: dictionary with expressions or nil. + */ + class func loadExpressions(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> Dictionary? { + return self.load("expressions", bundle: bundle)?[countryCode] as? Dictionary + } /** Loads content of a file with specified name, type and bundle. @@ -14,7 +40,7 @@ class JSONFileLoader { :param: bundle A bundle when file is located. :returns: JSON or nil. */ - final class func load(fileName: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + private class func load(fileName: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { if let fileURL = bundle.URLForResource(fileName, withExtension: "json") { return load(fileURL) } @@ -33,9 +59,9 @@ class JSONFileLoader { var error: NSError? if let dictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? JSONDictionary { return dictionary - } else { - print("Cannot parse JSON. It might be broken.") } + print("Cannot parse JSON. It might be broken.") + return nil } print("Cannot load content of file.") return nil diff --git a/Swifternalization/LengthVariationType.swift b/Swifternalization/LengthVariationType.swift index ffd3ffc..14bc186 100644 --- a/Swifternalization/LengthVariationType.swift +++ b/Swifternalization/LengthVariationType.swift @@ -1,6 +1,8 @@ import Foundation -/// Represents length variation. +/** +Represents length variation. +*/ protocol LengthVariationType { /// Length of a screen. var length: Int {get} diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index fb2b555..3d632f1 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -27,7 +27,7 @@ class LoadedTranslationsProcessor { expressions as well as built-in ones. Translations are processed in shared expressions processor. */ - class func processTrnslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [TranslationType] { + class func processTranslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [TranslationType] { // Find those base translations that are not contained in prefered // language translations. @@ -74,19 +74,23 @@ class LoadedTranslationsProcessor { case .WithExpressionsAndLengthVariations: // The most advanced translation type. It contains expressions - // that contain length variations. THe job done here is similar - // to the one in .WithExpressions and .WithLengthVariations - // cases. key is filtered in shared expressions to get one of - // shared expressions and then method builds array of variations. + // that contain length variations or just simple expressions. + // THe job done here is similar to the one in .WithExpressions + // and .WithLengthVariations cases. key is filtered in shared + // expressions to get one of shared expressions and then method + // builds array of variations. var expressions = [ExpressionType]() - for (key, value) in $0.content as! Dictionary> { + for (key, value) in $0.content { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key - - var lengthVariations = [LengthVariation]() - for (lvKey, lvValue) in value as Dictionary { - lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) + if value is Dictionary { + var lengthVariations = [LengthVariation]() + for (lvKey, lvValue) in value as! Dictionary { + lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) + } + expressions.append(LengthVariationExpression(pattern: pattern, variations: lengthVariations)) + } else if value is String { + expressions.append(SimpleExpression(pattern:pattern, localizedValue: value as! String)) } - expressions.append(LengthVariationExpression(pattern: pattern, variations: lengthVariations)) } return TranslationWithExpressions(key: $0.key, expressions: expressions) } diff --git a/Swifternalization/SharedExpressionLoader.swift b/Swifternalization/SharedExpressionLoader.swift index d248a49..4ff9216 100644 --- a/Swifternalization/SharedExpressionLoader.swift +++ b/Swifternalization/SharedExpressionLoader.swift @@ -3,7 +3,7 @@ import Foundation /** Used to load content from `expressions.json` file for specified language. */ -final class SharedExpressionsLoader: JSONFileLoader { +final class SharedExpressionsLoader { /** Loads expressions for specified language. @@ -11,15 +11,10 @@ final class SharedExpressionsLoader: JSONFileLoader { :param: countryCode A country code :returns: array of loaded expressions. */ - class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> [SharedExpression] { + class func loadExpressions(json: Dictionary) -> [SharedExpression] { var expressions = [SharedExpression]() - if let json = self.load("expressions", bundle: bundle), - let expressionsDict = json[countryCode] as? Dictionary { - for (identifier, pattern) in expressionsDict { - expressions.append(SharedExpression(identifier: identifier, pattern: pattern)) - } - } else { - println("expressions.json file structure is incorrect.") + for (identifier, pattern) in json { + expressions.append(SharedExpression(identifier: identifier, pattern: pattern)) } return expressions } diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index f7f2e58..a68fd34 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -1,30 +1,28 @@ import Foundation /** -Class that loads translations from a file. +Class that gets dictionary of translations and turn it into `LoadedTranslation`s. */ -final class TranslationsLoader: JSONFileLoader { +final class TranslationsLoader { /** - Loads content from file with name equal to passed country code in a bundle. + Converts dictionary into array of `LoadedTranslation`s. - :params: countryCode A country code. - :params: bundle A bundle when file is placed. - :returns: `LoadedTranslation` objects from specified file. + :params: dictionary A dictionary with content of file which contains + translations. + :returns: Array of `LoadedTranslation` objects from specified file. */ - class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> [LoadedTranslation] { + class func loadTranslations(json: Dictionary) -> [LoadedTranslation] { var loadedTranslations = [LoadedTranslation]() - if let json = self.load(countryCode, bundle: bundle) { - for (key, value) in json { - if value is String { - loadedTranslations.append(LoadedTranslation(type: .Simple, key: key, content: [key: value])) + for (key, value) in json { + if value is String { + loadedTranslations.append(LoadedTranslation(type: .Simple, key: key, content: [key: value])) + } else { + let dictionary = value as! JSONDictionary + if let type = detectElementType(dictionary) { + loadedTranslations.append(LoadedTranslation(type: type, key: key, content: dictionary)) } else { - let dictionary = value as! JSONDictionary - if let type = detectElementType(dictionary) { - loadedTranslations.append(LoadedTranslation(type: type, key: key, content: dictionary)) - } else { - println("Translation type is not supported for: \(dictionary)") - } + println("Translation type is not supported for: \(dictionary)") } } } @@ -42,10 +40,22 @@ final class TranslationsLoader: JSONFileLoader { typealias DictWithStrings = Dictionary typealias DictWithDicts = Dictionary - if element is DictWithStrings, let key = element.keys.first { + var dicts = 0 + var strings = 0 + + for (key, value) in element { + if value is String { + strings++ + } else if value is Dictionary { + dicts++ + } + } + + if strings > 0 && dicts == 0 { + let key = element.keys.first! let toIndex = advance(key.startIndex, 1) return key.substringToIndex(toIndex) == "@" ? .WithLengthVariations : .WithExpressions - } else if element is DictWithDicts { + } else if strings >= 0 && dicts > 0 { return .WithExpressionsAndLengthVariations } diff --git a/SwifternalizationTests/ExpressionJSONs.swift b/SwifternalizationTests/ExpressionJSONs.swift new file mode 100644 index 0000000..926945c --- /dev/null +++ b/SwifternalizationTests/ExpressionJSONs.swift @@ -0,0 +1,28 @@ +// +// ExpressionJSONs.swift +// Swifternalization +// +// Created by Tomasz Szulc on 31/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +class ExpressionJSONs { + + class func base() -> Dictionary { + return [ + "key1": "val1", + "key2": "val2", + "key3": "val3" + ] + } + + class func en() -> Dictionary { + return [ + "key3": "val3", + "key4": "val4", + "key5": "val5" + ] + } +} \ No newline at end of file diff --git a/SwifternalizationTests/JSONFileLoaderTests.swift b/SwifternalizationTests/JSONFileLoaderTests.swift index 0d308ba..fbd61b9 100644 --- a/SwifternalizationTests/JSONFileLoaderTests.swift +++ b/SwifternalizationTests/JSONFileLoaderTests.swift @@ -10,14 +10,38 @@ import UIKit import XCTest class JSONFileLoaderTests: XCTestCase { + + // Expressions + func loadExpressions(cc: CountryCode) -> Dictionary? { + return JSONFileLoader.loadExpressions(cc, bundle: NSBundle.testBundle()) + } + + func testShouldLoadBaseExpressions() { + XCTAssertNotNil(loadExpressions("base"), "") + } + + func testShouldLoadPLExpressions() { + XCTAssertNotNil(loadExpressions("pl"), "") + } + + func testShouldNotLoadDEExpressions() { + XCTAssertNil(loadExpressions("de"), "") + } + + // Translations + func loadTranslations(cc: CountryCode) -> JSONDictionary? { + return JSONFileLoader.loadTranslations(cc, bundle: NSBundle.testBundle()) + } - func testJSONShouldBeLoaded() { - let content = JSONFileLoader.load("base", bundle: NSBundle.testBundle()) - XCTAssertNotNil(content!, "") + func testShouldLoadBaseTranslations() { + XCTAssertNotNil(loadTranslations("base"), "") + } + + func testShouldLoadPLTranslations() { + XCTAssertNotNil(loadTranslations("pl"), "") } - func testFileShouldNotBeLoaded() { - let content = JSONFileLoader.load("not-existing", bundle: NSBundle.testBundle()) - XCTAssertNil(content, "") + func testShouldNotLoadDETranslations() { + XCTAssertNil(loadTranslations("de"), "") } } diff --git a/SwifternalizationTests/LoadedTranslationsProcessorTests.swift b/SwifternalizationTests/LoadedTranslationsProcessorTests.swift new file mode 100644 index 0000000..84e4fe9 --- /dev/null +++ b/SwifternalizationTests/LoadedTranslationsProcessorTests.swift @@ -0,0 +1,125 @@ +// +// LoadedTranslationsProcessorTests.swift +// Swifternalization +// +// Created by Tomasz Szulc on 31/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import UIKit +import XCTest + +class LoadedTranslationsProcessorTests: XCTestCase { + + private func _baseExpressions() -> Dictionary { + return [ + "e1": "ie:x=1", + "e2": "ie:x=2" + ] + } + + private func _enExpressions() -> Dictionary { + return [ + "e1": "ie:x=1", + "e2": "ie:x=2", + "e3": "ie:x=3", + "e4": "ie:x=4" + ] + } + + private func _baseTranslations() -> Dictionary { + return [ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4" + ] + } + + private func _enTranslations() -> Dictionary { + return [ + "k1": "v1", + "k2": [ + "@100": "v2-1", + "@200": "v2-2" + ], + "k3": [ + "e1": "v3-1", + "e2": "v3-2" + ], + "k4": [ + "e3": [ + "@100": "v4-1", + "@200": "v4-2" + ], + + "e4": [ + "@100": "v4-3", + "@300": "v4-4" + ], + + "e2": "v4-5" + ] + ] + } + + func testShouldProcessTranslations() { + let baseExpressions = SharedExpressionsLoader.loadExpressions(_baseExpressions()) + let enExpressions = SharedExpressionsLoader.loadExpressions(_enExpressions()) + let expressions = SharedExpressionsProcessor.processSharedExpression("en", preferedLanguageExpressions: enExpressions, baseLanguageExpressions: baseExpressions) + + let baseTranslations = TranslationsLoader.loadTranslations(_baseTranslations()) + let enTranslations = TranslationsLoader.loadTranslations(_enTranslations()) + var translations = LoadedTranslationsProcessor.processTranslations(baseTranslations, preferedLanguageTranslations: enTranslations, sharedExpressions: expressions) + + translations.sort({$0.key < $1.key}) + + XCTAssertEqual(translations.count, 4, "") + + /** + Check if there is k1 translation + */ + XCTAssertEqual(translations[0].key, "k1", "") + + /** + Check content of k2 translation + */ + let k2Translation = translations[1] as! TranslationWithExpressions + XCTAssertEqual(k2Translation.key, "k2", "") + XCTAssertEqual(k2Translation.expressions.count, 1, "") + let k2Expression = k2Translation.expressions[0] as! LengthVariationExpression + XCTAssertEqual(k2Expression.variations.count, 2, "") + + /** + Check content of k3 translation + */ + let k3Translation = translations[2] as! TranslationWithExpressions + XCTAssertEqual(k3Translation.key, "k3", "") + XCTAssertEqual(k3Translation.expressions.count, 2, "") + + // Get patterns of expressions + var k3ExpressionPatterns: [String] = k3Translation.expressions.map({ $0.pattern }) + k3ExpressionPatterns.sort({$0 < $1}) + + var k3ExpressionsToMatch: [String] = [_enExpressions()["e1"]!, _enExpressions()["e2"]!] + k3ExpressionsToMatch.sort({$0 < $1}) + + XCTAssertEqual(k3ExpressionPatterns, k3ExpressionsToMatch, "") + + /** + Check content of k4 translation + */ + let k4Translation = translations[3] as! TranslationWithExpressions + XCTAssertEqual(k4Translation.key, "k4", "") + XCTAssertEqual(k4Translation.expressions.count, 3, "") + + // Get patterns of expressions + var k4ExpressionPatterns: [String] = k4Translation.expressions.map({ $0.pattern }) + k4ExpressionPatterns.sort({$0 < $1}) + + var k4ExpressionsToMatch: [String] = [_enExpressions()["e2"]!, _enExpressions()["e3"]!, _enExpressions()["e4"]!] + k4ExpressionsToMatch.sort({$0 < $1}) + + XCTAssertEqual(k4ExpressionPatterns, k4ExpressionsToMatch, "") + } +} diff --git a/SwifternalizationTests/SharedExpressionsLoaderTests.swift b/SwifternalizationTests/SharedExpressionsLoaderTests.swift index 80e06cb..91ddaea 100644 --- a/SwifternalizationTests/SharedExpressionsLoaderTests.swift +++ b/SwifternalizationTests/SharedExpressionsLoaderTests.swift @@ -11,18 +11,8 @@ import XCTest class SharedExpressionsLoaderTests: XCTestCase { - func testShouldLoadBase() { - let content = SharedExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) - XCTAssertTrue(content.count > 0, "") - } - - func testShouldLoadPL() { - let content = SharedExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) - XCTAssertTrue(content.count > 0, "") - } - - func testShouldNotLoadDE() { - let content = SharedExpressionsLoader.loadExpressions("de", bundle: NSBundle.testBundle()) - XCTAssertFalse(content.count > 0, "") + func testShouldLoadExpressions() { + let expressions = SharedExpressionsLoader.loadExpressions(ExpressionJSONs.base()) + XCTAssertEqual(expressions.count, 3, "") } } diff --git a/SwifternalizationTests/SharedExpressionsProcessorTests.swift b/SwifternalizationTests/SharedExpressionsProcessorTests.swift index 90e96ea..4793c09 100644 --- a/SwifternalizationTests/SharedExpressionsProcessorTests.swift +++ b/SwifternalizationTests/SharedExpressionsProcessorTests.swift @@ -10,13 +10,12 @@ import Foundation import XCTest class SharedExpressionsProcessorTests: XCTestCase { - - func testThatAllExpressionsShouldBeLoadedCorreclty() { - let baseExpressions = SharedExpressionsLoader.loadExpressions("base", bundle: NSBundle.testBundle()) - let preferedExpressions = SharedExpressionsLoader.loadExpressions("pl", bundle: NSBundle.testBundle()) - - let sharedExpressions = SharedExpressionsProcessor.processSharedExpression("pl", preferedLanguageExpressions: preferedExpressions, baseLanguageExpressions: baseExpressions) + + func testShouldProcessExpressions() { + let base = SharedExpressionsLoader.loadExpressions(ExpressionJSONs.base()) + let en = SharedExpressionsLoader.loadExpressions(ExpressionJSONs.en()) - XCTAssertEqual(sharedExpressions.count, 8, "") + let shared = SharedExpressionsProcessor.processSharedExpression("en", preferedLanguageExpressions: base, baseLanguageExpressions: en) + XCTAssertEqual(shared.count, 9, "") } } diff --git a/SwifternalizationTests/TranslationJSONs.swift b/SwifternalizationTests/TranslationJSONs.swift new file mode 100644 index 0000000..3ce4744 --- /dev/null +++ b/SwifternalizationTests/TranslationJSONs.swift @@ -0,0 +1,56 @@ +// +// TranslationJSONs.swift +// Swifternalization +// +// Created by Tomasz Szulc on 31/07/15. +// Copyright (c) 2015 Tomasz Szulc. All rights reserved. +// + +import Foundation + +class TranslationJSONs { + + class func base() -> Dictionary { + return [ + "key-1": "value-1", + "key-2": [ + "@100": "value-2-1", + "@200": "value-2-2", + "@300": "value-2-3" + ], + "key-3": [ + "key-3-1": [ + "@100": "value-3-1-1", + "@200": "value-3-1-2" + ], + + "key-3-2": [ + "@100": "value-3-2-1", + "@200": "value-3-2-2" + ] + ] + ] + } + + class func en() -> Dictionary { + return [ + "key-1": "en-value-1", + "key-2": [ + "@100": "en-value-2-1", + "@200": "en-value-2-2", + "@300": "en-value-2-3" + ], + "key-4": [ + "key-4-1": [ + "@100": "value-4-1-1", + "@200": "value-$-1-2" + ], + + "key-4-2": [ + "@100": "value-4-2-1", + "@200": "value-4-2-2" + ] + ] + ] + } +} \ No newline at end of file diff --git a/SwifternalizationTests/TranslationsLoaderTests.swift b/SwifternalizationTests/TranslationsLoaderTests.swift index e15dc4c..b1114f3 100644 --- a/SwifternalizationTests/TranslationsLoaderTests.swift +++ b/SwifternalizationTests/TranslationsLoaderTests.swift @@ -11,18 +11,8 @@ import XCTest class TranslationsLoaderTests: XCTestCase { - func testShouldLoadBase() { - let content = TranslationsLoader.loadTranslations("pl", bundle: NSBundle.testBundle()) - XCTAssertTrue(content.count > 0, "") - } - - func testShouldLoadPL() { - let content = TranslationsLoader.loadTranslations("base", bundle: NSBundle.testBundle()) - XCTAssertTrue(content.count > 0, "") - } - - func testShouldNotLoadDE() { - let content = TranslationsLoader.loadTranslations("de", bundle: NSBundle.testBundle()) - XCTAssertFalse(content.count > 0, "") + func testShouldLoadTranslations() { + let translations = TranslationsLoader.loadTranslations(TranslationJSONs.base()) + XCTAssertEqual(translations.count, 3, "") } } From e00c3db72efce28ce229fccebabbfd92f61efc7a Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Fri, 31 Jul 2015 15:49:40 +0200 Subject: [PATCH 15/27] Remove LengthVariationExpression and extend SimpleExpression --- Swifternalization.xcodeproj/project.pbxproj | 6 ------ .../LengthVariationExpression.swift | 20 ------------------- .../LoadedTranslationsProcessor.swift | 4 ++-- Swifternalization/SimpleExpression.swift | 15 ++++++++++++-- .../TranslationWithExpressions.swift | 6 ++++++ .../LoadedTranslationsProcessorTests.swift | 4 ++-- 6 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 Swifternalization/LengthVariationExpression.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 4271158..fcdcd18 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -16,8 +16,6 @@ 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; - 6D4C4EC11B6ACF2200B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; - 6D4C4EC21B6ACF2600B7839A /* LengthVariationExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */; }; 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; @@ -135,7 +133,6 @@ 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationWithExpressions.swift; sourceTree = ""; }; - 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationExpression.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50044D1B3EF91600A54B36 /* Swifternalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swifternalization.h; sourceTree = ""; }; @@ -264,7 +261,6 @@ 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */, 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */, - 6D4C4EC01B6ACF2200B7839A /* LengthVariationExpression.swift */, ); name = Next; sourceTree = ""; @@ -610,7 +606,6 @@ 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */, - 6D4C4EC11B6ACF2200B7839A /* LengthVariationExpression.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, @@ -683,7 +678,6 @@ 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */, 6DD3B9491B5ED61800C79EAC /* ExpressionType.swift in Sources */, - 6D4C4EC21B6ACF2600B7839A /* LengthVariationExpression.swift in Sources */, 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */, 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, diff --git a/Swifternalization/LengthVariationExpression.swift b/Swifternalization/LengthVariationExpression.swift deleted file mode 100644 index 7bf0ef5..0000000 --- a/Swifternalization/LengthVariationExpression.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// LengthVariationExpression.swift -// Swifternalization -// -// Created by Tomasz Szulc on 30/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Represents expression that contains length variations. -*/ -struct LengthVariationExpression: ExpressionType { - /// Pattern of expression. - let pattern: String - - /// Array with length variations. - let variations: [LengthVariation] -} diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index 3d632f1..6ba8b9e 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -70,7 +70,7 @@ class LoadedTranslationsProcessor { for (key, value) in $0.content as! Dictionary { lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(key), value: value)) } - return TranslationWithExpressions(key: $0.key, expressions: [LengthVariationExpression(pattern: $0.key, variations: lengthVariations)]) + return TranslationWithExpressions(key: $0.key, expressions: [SimpleExpression(pattern: $0.key, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)]) case .WithExpressionsAndLengthVariations: // The most advanced translation type. It contains expressions @@ -87,7 +87,7 @@ class LoadedTranslationsProcessor { for (lvKey, lvValue) in value as! Dictionary { lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) } - expressions.append(LengthVariationExpression(pattern: pattern, variations: lengthVariations)) + expressions.append(SimpleExpression(pattern: pattern, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)) } else if value is String { expressions.append(SimpleExpression(pattern:pattern, localizedValue: value as! String)) } diff --git a/Swifternalization/SimpleExpression.swift b/Swifternalization/SimpleExpression.swift index 75bff54..dada1b8 100644 --- a/Swifternalization/SimpleExpression.swift +++ b/Swifternalization/SimpleExpression.swift @@ -9,12 +9,23 @@ import Foundation /** -Represents simple epxressions that has only pattern and localized value. +Represents simple epxressions. */ struct SimpleExpression: ExpressionType { /// Pattern of expression. let pattern: String - /// A localized value. + /// A localized value. If `lengthVariations` array is empty or you want to + /// get full localized value use this property. let localizedValue: String + + /// Array of length variations + let lengthVariations: [LengthVariation] + + // Returns expression object + init(pattern: String, localizedValue: String, lengthVariations: [LengthVariation] = [LengthVariation]()) { + self.pattern = pattern + self.localizedValue = localizedValue + self.lengthVariations = lengthVariations + } } diff --git a/Swifternalization/TranslationWithExpressions.swift b/Swifternalization/TranslationWithExpressions.swift index 972a3c5..6fd0959 100644 --- a/Swifternalization/TranslationWithExpressions.swift +++ b/Swifternalization/TranslationWithExpressions.swift @@ -17,4 +17,10 @@ struct TranslationWithExpressions: TranslationType { /// Expressions that are related to a translation. let expressions: [ExpressionType] + + func validate(text: String, length: Int?) { + for expression in expressions { + // Do something + } + } } diff --git a/SwifternalizationTests/LoadedTranslationsProcessorTests.swift b/SwifternalizationTests/LoadedTranslationsProcessorTests.swift index 84e4fe9..e1d1177 100644 --- a/SwifternalizationTests/LoadedTranslationsProcessorTests.swift +++ b/SwifternalizationTests/LoadedTranslationsProcessorTests.swift @@ -87,8 +87,8 @@ class LoadedTranslationsProcessorTests: XCTestCase { let k2Translation = translations[1] as! TranslationWithExpressions XCTAssertEqual(k2Translation.key, "k2", "") XCTAssertEqual(k2Translation.expressions.count, 1, "") - let k2Expression = k2Translation.expressions[0] as! LengthVariationExpression - XCTAssertEqual(k2Expression.variations.count, 2, "") + let k2Expression = k2Translation.expressions[0] as! SimpleExpression + XCTAssertEqual(k2Expression.lengthVariations.count, 2, "") /** Check content of k3 translation From 9b24d42399ec2e3a02c3be46d95de3b290dfb668 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Fri, 31 Jul 2015 23:31:32 +0200 Subject: [PATCH 16/27] Implementing new Swifternalization class - WIP --- Swifternalization.xcodeproj/project.pbxproj | 162 +++--------- Swifternalization/Expression.swift | 128 +++------- Swifternalization/ExpressionType.swift | 9 - Swifternalization/KeyValueType.swift | 33 --- Swifternalization/LengthVariation.swift | 2 +- Swifternalization/LengthVariationType.swift | 12 - .../LoadedTranslationsProcessor.swift | 20 +- .../LocalizableFilesLoader.swift | 112 -------- Swifternalization/SharedExpression.swift | 2 +- .../SharedExpressionsConfigurator.swift | 140 ---------- .../SharedExpressionsProcessor.swift | 2 +- Swifternalization/SimpleExpression.swift | 31 --- Swifternalization/Swifternalization.swift | 241 +++--------------- Swifternalization/TranslatablePair.swift | 69 ----- ...ithExpressions.swift => Translation.swift} | 11 +- Swifternalization/TranslationType.swift | 17 -- SwifternalizationTests/ExpressionTests.swift | 53 ---- .../LoadedTranslationsProcessorTests.swift | 8 +- .../LocalizableFilesLoaderTests.swift | 36 --- .../SharedBaseExpressionTests.swift | 8 +- .../SharedPolishExpressionTests.swift | 4 +- .../SwifternalizationTests.swift | 30 +-- .../TranslatablePairTests.swift | 16 +- 23 files changed, 162 insertions(+), 984 deletions(-) delete mode 100644 Swifternalization/ExpressionType.swift delete mode 100644 Swifternalization/KeyValueType.swift delete mode 100644 Swifternalization/LengthVariationType.swift delete mode 100644 Swifternalization/LocalizableFilesLoader.swift delete mode 100644 Swifternalization/SharedExpressionsConfigurator.swift delete mode 100644 Swifternalization/SimpleExpression.swift delete mode 100644 Swifternalization/TranslatablePair.swift rename Swifternalization/{TranslationWithExpressions.swift => Translation.swift} (64%) delete mode 100644 Swifternalization/TranslationType.swift delete mode 100644 SwifternalizationTests/ExpressionTests.swift delete mode 100644 SwifternalizationTests/LocalizableFilesLoaderTests.swift diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index fcdcd18..ede3edb 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -11,36 +11,28 @@ 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; - 6D4C4EB51B6AB38400B7839A /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */; }; - 6D4C4EB61B6AB43F00B7839A /* TranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */; }; 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; 6D4C4EB91B6AB6EF00B7839A /* LoadedTranslationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */; }; - 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; - 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */; }; + 6D4C4EBD1B6ABE0700B7839A /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* Translation.swift */; }; + 6D4C4EC41B6ACF2C00B7839A /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EBC1B6ABE0700B7839A /* Translation.swift */; }; 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; - 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; + 6D4C4EC61B6AD01D00B7839A /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* Expression.swift */; }; 6D50044E1B3EF91600A54B36 /* Swifternalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D50044D1B3EF91600A54B36 /* Swifternalization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6D5004541B3EF91600A54B36 /* Swifternalization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D5004481B3EF91600A54B36 /* Swifternalization.framework */; }; 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */; }; 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; - 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; 6D5004921B3EFF6D00A54B36 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004941B3EFF6D00A54B36 /* Localizable.strings */; }; 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004591B3EF91600A54B36 /* Info.plist */; }; 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */; }; 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; - 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */; }; 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; - 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */; }; - 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; + 6D5BA6091B655F1D000D7E49 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* Expression.swift */; }; 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */; }; - 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */; }; - 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282911B3F04C800E65FCD /* Expression.swift */; }; - 6D6282981B3F13C300E65FCD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */; }; 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282991B3F17CA00E65FCD /* Regex.swift */; }; 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282991B3F17CA00E65FCD /* Regex.swift */; }; 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62829C1B3F19CC00E65FCD /* RegexTests.swift */; }; @@ -76,28 +68,19 @@ 6D6283501B3F62B100E65FCD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */; }; 6D62F9FC1B6B808400596A7C /* ExpressionJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */; }; 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */; }; - 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; - 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; - 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464831B40146100C46C6D /* KeyValueType.swift */; }; 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */; }; 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; - 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */; }; 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; - 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */; }; 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */; }; 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; 6DB3CC911B5EC29E00A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; - 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */; }; 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */; }; - 6DBB6C651B403367002F39A3 /* SharedExpressionsConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */; }; - 6DBB6C661B40369A002F39A3 /* SharedExpressionsConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */; }; 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; - 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C6B1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift */; }; 6DBB6C871B40718F002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C891B40718F002F39A3 /* Expressions.strings */; }; 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; @@ -112,7 +95,6 @@ 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */; }; 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */; }; 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */; }; - 6DD3B9491B5ED61800C79EAC /* ExpressionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */; }; 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD3B94A1B5ED65A00C79EAC /* NSBundle+TestExtension.swift */; }; /* End PBXBuildFile section */ @@ -130,9 +112,8 @@ 6D140F431B56D03D00359143 /* RandomNumbers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomNumbers.swift; sourceTree = ""; }; 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslation.swift; sourceTree = ""; }; 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessor.swift; sourceTree = ""; }; - 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationType.swift; sourceTree = ""; }; 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; - 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationWithExpressions.swift; sourceTree = ""; }; + 6D4C4EBC1B6ABE0700B7839A /* Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Translation.swift; sourceTree = ""; }; 6D5004481B3EF91600A54B36 /* Swifternalization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifternalization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6D50044C1B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50044D1B3EF91600A54B36 /* Swifternalization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swifternalization.h; sourceTree = ""; }; @@ -140,16 +121,13 @@ 6D5004591B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwifternalizationTests.swift; sourceTree = ""; }; 6D5004641B3EF92600A54B36 /* Swifternalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifternalization.swift; sourceTree = ""; }; - 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePair.swift; sourceTree = ""; }; 6D5004931B3EFF6D00A54B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessor.swift; sourceTree = ""; }; 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessorTests.swift; sourceTree = ""; }; - 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleExpression.swift; sourceTree = ""; }; - 6D6282911B3F04C800E65FCD /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; + 6D5BA6081B655F1D000D7E49 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; - 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; 6D6282991B3F17CA00E65FCD /* Regex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = ""; }; 6D62829C1B3F19CC00E65FCD /* RegexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexTests.swift; sourceTree = ""; }; 6D62829E1B3F1FA000E65FCD /* InequalityExpressionParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InequalityExpressionParser.swift; sourceTree = ""; }; @@ -178,21 +156,15 @@ 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionJSONs.swift; sourceTree = ""; }; 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationJSONs.swift; sourceTree = ""; }; - 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoader.swift; sourceTree = ""; }; - 6D6464831B40146100C46C6D /* KeyValueType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueType.swift; sourceTree = ""; }; 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessorTests.swift; sourceTree = ""; }; 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCode.swift; sourceTree = ""; }; - 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionType.swift; sourceTree = ""; }; 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionLoader.swift; sourceTree = ""; }; 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = ""; }; 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; - 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariationType.swift; sourceTree = ""; }; 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; - 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsConfigurator.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; - 6DBB6C6B1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableFilesLoaderTests.swift; sourceTree = ""; }; 6DBB6C881B40718F002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8A1B407190002F39A3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpression.swift; sourceTree = ""; }; @@ -244,27 +216,6 @@ name = Helpers; sourceTree = ""; }; - 6D4C4EB11B6AAB5100B7839A /* Next */ = { - isa = PBXGroup; - children = ( - 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, - 6DB3CC661B5EBDA600A1220F /* ExpressionType.swift */, - 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, - 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, - 6DB3CC6B1B5EBDA600A1220F /* LengthVariationType.swift */, - 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, - 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */, - 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */, - 6D5BA6081B655F1D000D7E49 /* SimpleExpression.swift */, - 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, - 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, - 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, - 6D4C4EB41B6AB38400B7839A /* TranslationType.swift */, - 6D4C4EBC1B6ABE0700B7839A /* TranslationWithExpressions.swift */, - ); - name = Next; - sourceTree = ""; - }; 6D50043E1B3EF91600A54B36 = { isa = PBXGroup; children = ( @@ -288,19 +239,16 @@ 6D50044A1B3EF91600A54B36 /* Swifternalization */ = { isa = PBXGroup; children = ( - 6D4C4EB11B6AAB5100B7839A /* Next */, - 6DB3CC8E1B5EC27600A1220F /* Enums */, - 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, - 6DB3CC8A1B5EC19400A1220F /* Protocols */, - 6D6282911B3F04C800E65FCD /* Expression.swift */, - 6D6464811B40106C00C46C6D /* LocalizableFilesLoader.swift */, + 6DB3CC651B5EBDA600A1220F /* CountryCode.swift */, + 6D5BA6081B655F1D000D7E49 /* Expression.swift */, + 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */, 6D6282991B3F17CA00E65FCD /* Regex.swift */, - 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */, 6D5004641B3EF92600A54B36 /* Swifternalization.swift */, - 6DBB6C641B403367002F39A3 /* SharedExpressionsConfigurator.swift */, - 6D5004651B3EF92600A54B36 /* TranslatablePair.swift */, + 6D4C4EBC1B6ABE0700B7839A /* Translation.swift */, + 6DFC3A071B6C1FDF000296D1 /* Loaders and Processors */, + 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */, + 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */, 6D50044B1B3EF91600A54B36 /* Supporting Files */, - 6D50044D1B3EF91600A54B36 /* Swifternalization.h */, ); path = Swifternalization; sourceTree = ""; @@ -308,6 +256,7 @@ 6D50044B1B3EF91600A54B36 /* Supporting Files */ = { isa = PBXGroup; children = ( + 6D50044D1B3EF91600A54B36 /* Swifternalization.h */, 6D50044C1B3EF91600A54B36 /* Info.plist */, ); name = "Supporting Files"; @@ -316,21 +265,23 @@ 6D5004571B3EF91600A54B36 /* SwifternalizationTests */ = { isa = PBXGroup; children = ( - 6D75E6D21B6B6CBC00B370DC /* Next */, - 6D140F451B56D04300359143 /* Helpers */, - 6D6282971B3F13C300E65FCD /* ExpressionTests.swift */, 6D6282BE1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift */, 6D6282A81B3F25DC00E65FCD /* InequalityExpressionParserTests.swift */, 6D6282C01B3F43C600E65FCD /* InequalityExtendedExpressionMatcherTests.swift */, 6D6282C21B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift */, - 6DBB6C6B1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift */, + 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, + 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */, 6D62829C1B3F19CC00E65FCD /* RegexTests.swift */, 6D6282CA1B3F508C00E65FCD /* RegexExpressionParserTests.swift */, 6D6282CC1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift */, 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */, + 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */, + 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */, 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */, 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */, + 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */, + 6D140F451B56D04300359143 /* Helpers */, 6D5004581B3EF91600A54B36 /* Supporting Files */, 6D5004941B3EFF6D00A54B36 /* Localizable.strings */, 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */, @@ -378,51 +329,24 @@ name = Resources; sourceTree = ""; }; - 6D75E6D21B6B6CBC00B370DC /* Next */ = { - isa = PBXGroup; - children = ( - 6DD3B9401B5ED38B00C79EAC /* JSONFileLoaderTests.swift */, - 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */, - 6DD3B9431B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift */, - 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */, - 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, - ); - name = Next; - sourceTree = ""; - }; - 6DB3CC8A1B5EC19400A1220F /* Protocols */ = { - isa = PBXGroup; - children = ( - 6D6464831B40146100C46C6D /* KeyValueType.swift */, - ); - name = Protocols; - sourceTree = ""; - }; 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */ = { isa = PBXGroup; children = ( 6D6282A21B3F247000E65FCD /* ExpressionMatcher.swift */, 6D6282A41B3F24A800E65FCD /* ExpressionParser.swift */, + 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */, 6D6282AB1B3F327800E65FCD /* InequalityExpressionMatcher.swift */, 6D62829E1B3F1FA000E65FCD /* InequalityExpressionParser.swift */, 6D6282B91B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift */, 6D6282B01B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift */, + 6D6282B41B3F3C4100E65FCD /* InequalitySign.swift */, + 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */, 6D6282C61B3F4F2100E65FCD /* RegexExpressionMatcher.swift */, 6D6282C41B3F4ED100E65FCD /* RegexExpressionParser.swift */, ); name = "Matchers and Parsers"; sourceTree = ""; }; - 6DB3CC8E1B5EC27600A1220F /* Enums */ = { - isa = PBXGroup; - children = ( - 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */, - 6D6282B41B3F3C4100E65FCD /* InequalitySign.swift */, - 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */, - ); - name = Enums; - sourceTree = ""; - }; 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */ = { isa = PBXGroup; children = ( @@ -443,6 +367,20 @@ name = "Localizable Files"; sourceTree = ""; }; + 6DFC3A071B6C1FDF000296D1 /* Loaders and Processors */ = { + isa = PBXGroup; + children = ( + 6DB3CC691B5EBDA600A1220F /* JSONFileLoader.swift */, + 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */, + 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */, + 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */, + 6DB3CC671B5EBDA600A1220F /* SharedExpressionLoader.swift */, + 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */, + 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */, + ); + name = "Loaders and Processors"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -593,19 +531,14 @@ buildActionMask = 2147483647; files = ( 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */, - 6D4C4EB51B6AB38400B7839A /* TranslationType.swift in Sources */, - 6DB3CC7B1B5EBDA600A1220F /* LengthVariationType.swift in Sources */, 6D6282A31B3F247000E65FCD /* ExpressionMatcher.swift in Sources */, 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, - 6D5004671B3EF92600A54B36 /* TranslatablePair.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, - 6D4C4EBD1B6ABE0700B7839A /* TranslationWithExpressions.swift in Sources */, + 6D4C4EBD1B6ABE0700B7839A /* Translation.swift in Sources */, 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, - 6D6464841B40146100C46C6D /* KeyValueType.swift in Sources */, - 6DB3CC761B5EBDA600A1220F /* ExpressionType.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, @@ -615,14 +548,11 @@ 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */, 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, - 6D5BA6091B655F1D000D7E49 /* SimpleExpression.swift in Sources */, + 6D5BA6091B655F1D000D7E49 /* Expression.swift in Sources */, 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, - 6DBB6C651B403367002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, - 6D6282921B3F04C800E65FCD /* Expression.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */, - 6D6464821B40106C00C46C6D /* LocalizableFilesLoader.swift in Sources */, 6D6282B11B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D6282BA1B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */, @@ -639,8 +569,6 @@ 6DD3B9441B5ED55500C79EAC /* SharedExpressionsLoaderTests.swift in Sources */, 6D6282C11B3F43C600E65FCD /* InequalityExtendedExpressionMatcherTests.swift in Sources */, 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */, - 6D6282981B3F13C300E65FCD /* ExpressionTests.swift in Sources */, - 6DBB6C661B40369A002F39A3 /* SharedExpressionsConfigurator.swift in Sources */, 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */, 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, 6D6282A61B3F25B900E65FCD /* InequalityExpressionParser.swift in Sources */, @@ -649,9 +577,7 @@ 6DB3CC861B5EBF4800A1220F /* CountryCode.swift in Sources */, 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */, 6D6282A71B3F25BE00E65FCD /* ExpressionMatcher.swift in Sources */, - 6D4C4EB61B6AB43F00B7839A /* TranslationType.swift in Sources */, 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, - 6D6464851B40146600C46C6D /* KeyValueType.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, @@ -666,18 +592,14 @@ 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, - 6D5BA5F41B65180F000D7E49 /* LengthVariationType.swift in Sources */, - 6D4C4EC41B6ACF2C00B7839A /* TranslationWithExpressions.swift in Sources */, - 6D6282951B3F05DE00E65FCD /* TranslatablePair.swift in Sources */, + 6D4C4EC41B6ACF2C00B7839A /* Translation.swift in Sources */, 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, - 6D6282961B3F063A00E65FCD /* Expression.swift in Sources */, 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */, 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */, 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */, 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, - 6D4C4EC61B6AD01D00B7839A /* SimpleExpression.swift in Sources */, - 6DD3B9491B5ED61800C79EAC /* ExpressionType.swift in Sources */, + 6D4C4EC61B6AD01D00B7839A /* Expression.swift in Sources */, 6D62829D1B3F19CC00E65FCD /* RegexTests.swift in Sources */, 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */, 6D6282C31B3F45A600E65FCD /* InequalityExtendedExpressionParserTests.swift in Sources */, @@ -685,9 +607,7 @@ 6D6282AA1B3F269900E65FCD /* ExpressionParser.swift in Sources */, 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, - 6DBB6C511B401B7C002F39A3 /* LocalizableFilesLoader.swift in Sources */, 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */, - 6DBB6C6C1B40431D002F39A3 /* LocalizableFilesLoaderTests.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */, diff --git a/Swifternalization/Expression.swift b/Swifternalization/Expression.swift index 3dd58ed..a82af1d 100644 --- a/Swifternalization/Expression.swift +++ b/Swifternalization/Expression.swift @@ -2,128 +2,74 @@ // Expression.swift // Swifternalization // -// Created by Tomasz Szulc on 27/06/15. +// Created by Tomasz Szulc on 26/07/15. // Copyright (c) 2015 Tomasz Szulc. All rights reserved. // import Foundation /// String that contains expression pattern, e.g. `ie:x<5`, `exp:^1$`. -internal typealias ExpressionPattern = String +typealias ExpressionPattern = String /** -Class represents single expression that is added in curly -brackets inside key in Localizable.strings file or as a value in -Expressions.strings file. - -It is able to validate passed string using internal expression matcher. +Represents simple epxressions. */ -class Expression { - /// Pattern of expression passed during initialization. - let pattern: ExpressionPattern - - /// Type of expression computed based on `pattern`. - private var type: ExpressionPatternType! - - /// Matcher created based on `pattern`. - private var matcher: ExpressionMatcher! - - /** - Return `ExpressionPattern` object if passed `str` parameter contains - some pattern. The pattern may not be one of the supported one. - If the syntax of the passed string is correct it is treated as pattern. +struct Expression { + /// Pattern of expression. + let pattern: String - :param: str string which may or may not contain a pattern. - :returns: `ExpressionPattern` object or nil when pattern is not found. - */ - class func parseExpressionPattern(str: String) -> ExpressionPattern? { - if let pattern = Regex.firstMatchInString(str, pattern: InternalPattern.Expression.rawValue) { - return pattern - } else { -// println("Cannot get expression pattern from string: \(str).") - return nil - } - } + /// A localized value. If `lengthVariations` array is empty or you want to + /// get full localized value use this property. + let localizedValue: String - /** - Tries to parse passed `str` parameter and created `Expression` from it. - If passed string contains expression that is supported and inside logic - compute that this string is some supported pattern an `Expression` object - will be returned. - - :param: str string that may or may not contain expression pattern - :returns: `Expression` object or nil if pattern is not supported. - */ - class func expressionFromString(str: String) -> Expression? { - if let pattern = parseExpressionPattern(str) { - return Expression(pattern: pattern) - } - return nil - } + /// Array of length variations + let lengthVariations: [LengthVariation] - /** - Initializer that takes expression pattern. It may fail when expression - type of passed expression is not supported. - - :param: pattern pattern of expression - :returns: `Expression` object or nil when pattern is not supported. - */ - init?(pattern: ExpressionPattern) { - // pattern is assigned even if init fails because of Swift/compiler bug. + /// Expression matcher that is used in validation + private var expressionMatcher: ExpressionMatcher? = nil + + // Returns expression object + init(pattern: String, localizedValue: String, lengthVariations: [LengthVariation] = [LengthVariation]()) { self.pattern = pattern - - if let type = Expression.getExpressionType(pattern) { - self.type = type - - // build correct expression matcher - buildMatcher() - if matcher == nil { - println("Cannot create expression because expression pattern cannot be parsed: \(pattern)") - return nil + self.localizedValue = localizedValue + self.lengthVariations = lengthVariations + + // Create expression matcher + if let type = getExpressionType(pattern) { + switch (type as ExpressionPatternType) { + case .Inequality: + expressionMatcher = InequalityExpressionParser(pattern).parse() + + case .InequalityExtended: + expressionMatcher = InequalityExtendedExpressionParser(pattern).parse() + + case .Regex: + expressionMatcher = RegexExpressionParser(pattern).parse() } - } else { - println("Cannot create expression with pattern: \(pattern).") - return nil } } /** Method that validates passed string. - + :param: value value that should be matched :returns: `true` if value match expression, otherwise `false`. */ func validate(value: String) -> Bool { - return matcher.validate(value) - } - - - // MARK: Private methods - - /** - Method used to create `ExpressionMatcher` instance that match expression - pattern of this `Expression` object. - */ - private func buildMatcher() { - switch (type as ExpressionPatternType) { - case .Inequality: - matcher = InequalityExpressionParser(pattern).parse() - - case .InequalityExtended: - matcher = InequalityExtendedExpressionParser(pattern).parse() - - case .Regex: - matcher = RegexExpressionParser(pattern).parse() + if let matcher = expressionMatcher { + return matcher.validate(value) + } else { + return pattern == value } } /** - Method used to get `ExpressionPatternType` of passed `ExpressionPattern`. + Method used to get `ExpressionPatternType` of passed `ExpressionPattern`. :param: pattern expression pattern that will be checked. :returns: `ExpressionPatternType` if pattern is supported, otherwise nil. */ - private class func getExpressionType(pattern: ExpressionPattern) -> ExpressionPatternType? { + private func getExpressionType(pattern: ExpressionPattern) -> ExpressionPatternType? { if let result = Regex.firstMatchInString(pattern, pattern: InternalPattern.ExpressionPatternType.rawValue) { return ExpressionPatternType(rawValue: result) } diff --git a/Swifternalization/ExpressionType.swift b/Swifternalization/ExpressionType.swift deleted file mode 100644 index 476c725..0000000 --- a/Swifternalization/ExpressionType.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -/** -Represents expressions in the framework. -*/ -protocol ExpressionType { - /// Pattern of expression. - var pattern: String {get} -} diff --git a/Swifternalization/KeyValueType.swift b/Swifternalization/KeyValueType.swift deleted file mode 100644 index 8e1de47..0000000 --- a/Swifternalization/KeyValueType.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// KeyValueType.swift -// Swifternalization -// -// Created by Tomasz Szulc on 28/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/// Represents key from .strings file -internal typealias Key = String - -/// Representes value from .strings file -internal typealias Value = String - -/// Represent key-value pair of Key and Value from .strings file. -internal typealias KVDict = Dictionary - -/** -Protocol that defines properties and methods that need to be implemented by -objects that keeps key-value pair things. -*/ -protocol KeyValueType { - /// A key. - var key: Key {get set} - - /// A value. - var value: Value {get set} - - /// Initializer. - init(key: Key, value: Value) -} \ No newline at end of file diff --git a/Swifternalization/LengthVariation.swift b/Swifternalization/LengthVariation.swift index 98f0c58..09dd4b3 100644 --- a/Swifternalization/LengthVariation.swift +++ b/Swifternalization/LengthVariation.swift @@ -3,7 +3,7 @@ import Foundation /** Length variation representation. */ -struct LengthVariation: LengthVariationType { +struct LengthVariation { /// Length - width of a screen. let length: Int diff --git a/Swifternalization/LengthVariationType.swift b/Swifternalization/LengthVariationType.swift deleted file mode 100644 index 14bc186..0000000 --- a/Swifternalization/LengthVariationType.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -/** -Represents length variation. -*/ -protocol LengthVariationType { - /// Length of a screen. - var length: Int {get} - - /// Localized value. - var value: String {get} -} diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index 6ba8b9e..8111d2c 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -27,7 +27,7 @@ class LoadedTranslationsProcessor { expressions as well as built-in ones. Translations are processed in shared expressions processor. */ - class func processTranslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [TranslationType] { + class func processTranslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [Translation] { // Find those base translations that are not contained in prefered // language translations. @@ -49,7 +49,7 @@ class LoadedTranslationsProcessor { case .Simple: // Simple translation with key and value. let value = $0.content[$0.key] as! String - return TranslationWithExpressions(key: $0.key, expressions: [SimpleExpression(pattern: $0.key, localizedValue: value)]) + return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, localizedValue: value)]) case .WithExpressions: // Translation that contains expression. @@ -57,12 +57,12 @@ class LoadedTranslationsProcessor { // the shared expressions are filtered to get expression that // matches key and if there is a key it is replaced with real // expression pattern. - var expressions = [ExpressionType]() + var expressions = [Expression]() for (key, value) in $0.content as! Dictionary { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key - expressions.append(SimpleExpression(pattern: pattern, localizedValue: value)) + expressions.append(Expression(pattern: pattern, localizedValue: value)) } - return TranslationWithExpressions(key: $0.key, expressions: expressions) + return Translation(key: $0.key, expressions: expressions) case .WithLengthVariations: // Translation contains length expressions like @100, @200, etc. @@ -70,7 +70,7 @@ class LoadedTranslationsProcessor { for (key, value) in $0.content as! Dictionary { lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(key), value: value)) } - return TranslationWithExpressions(key: $0.key, expressions: [SimpleExpression(pattern: $0.key, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)]) + return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)]) case .WithExpressionsAndLengthVariations: // The most advanced translation type. It contains expressions @@ -79,7 +79,7 @@ class LoadedTranslationsProcessor { // and .WithLengthVariations cases. key is filtered in shared // expressions to get one of shared expressions and then method // builds array of variations. - var expressions = [ExpressionType]() + var expressions = [Expression]() for (key, value) in $0.content { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key if value is Dictionary { @@ -87,12 +87,12 @@ class LoadedTranslationsProcessor { for (lvKey, lvValue) in value as! Dictionary { lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) } - expressions.append(SimpleExpression(pattern: pattern, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)) + expressions.append(Expression(pattern: pattern, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)) } else if value is String { - expressions.append(SimpleExpression(pattern:pattern, localizedValue: value as! String)) + expressions.append(Expression(pattern:pattern, localizedValue: value as! String)) } } - return TranslationWithExpressions(key: $0.key, expressions: expressions) + return Translation(key: $0.key, expressions: expressions) } }) } diff --git a/Swifternalization/LocalizableFilesLoader.swift b/Swifternalization/LocalizableFilesLoader.swift deleted file mode 100644 index f51fc9e..0000000 --- a/Swifternalization/LocalizableFilesLoader.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// LocalizableFilesLoader.swift -// Swifternalization -// -// Created by Tomasz Szulc on 28/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - - -/** -Type that contains two dictionaries. - -base - for Base localization - -pref - for preferred language localization - -It contains `KVDict` instanes so every one has key and value. -*/ -internal typealias BasePrefDicts = (base: KVDict, pref: KVDict) - -/// Enum that represents file types used by `LocalizableFilesLoader`. -enum StringsFileType: String { - /// Localizable.strings file. - case Localizable = "Localizable" - /// Expressions.strings file. - case Expressions = "Expressions" -} - -/** -Class responsible for loading content from specified file type. -*/ -class LocalizableFilesLoader { - - /// Defines Base language - which is equal "Base". - private let BaseLanguage: Language = "Base" - - /// Bundle where files are located - private let bundle: NSBundle - - /** - Creates `LocalizableFilesLoader` instance. - - :param: bundle A bundle where .strings files are located. - */ - init(_ bundle: NSBundle) { - self.bundle = bundle - } - - /** - Loads content from files of specified `type` and `language`. - Converts them to `BasePrefDicts` instance. - - :param: type A type of files that will be loaded. - :param: language A preferred language - it will be used to find proper - file for specified file type. - - :returns: Returns content of files converted into key-value pairs. - */ - func loadContentFromFilesOfType(type: StringsFileType, language: Language) -> BasePrefDicts { - var basePairs = KVDict() - var preferredPairs = KVDict() - - let getURL = URLforFile(type) - - // Get file url for preferred language .strings file and load if exist. - if let localizableStringsFileURL = getURL(language) { - preferredPairs = parse(localizableStringsFileURL) - } else { - println("\(type.rawValue).strings file not found for \"\(language)\" language") - } - - // Get file url for Base .strings file and load if exist. - if let baseStringsFileURL = getURL(BaseLanguage) { - basePairs = parse(baseStringsFileURL) - } else { - println("\(type.rawValue).strings file not found for Base localization") - } - - return (basePairs, preferredPairs) - } - - // MARK: Private - - /** - Returns URL for file with specified type. - - :param: type A type of file. - :param: language A language of file. (localization). - :returns: `NSURL` to file or nil when file cannot be found. - */ - private func URLforFile(type: StringsFileType)(_ language: Language) -> NSURL? { - return bundle.URLForResource(type.rawValue, withExtension: "strings", subdirectory: language + ".lproj") - } - - /** - Parses files and return key-value pairs. - - :param: fileURL URL to file. - :returns: key-value pairs from .strings files. - */ - private func parse(fileURL: NSURL) -> KVDict { - if let dictionary = NSDictionary(contentsOfURL: fileURL) as? KVDict { - return dictionary - } else { - println("Cannot load Dictionary from content of URL: \(fileURL)") - } - - return KVDict() - } -} diff --git a/Swifternalization/SharedExpression.swift b/Swifternalization/SharedExpression.swift index c660efc..821bf36 100644 --- a/Swifternalization/SharedExpression.swift +++ b/Swifternalization/SharedExpression.swift @@ -23,7 +23,7 @@ protocol SharedExpressionProtocol { /** Represents built-in expression and expressions from Expressions.strings file. */ -struct SharedExpression: ExpressionType { +struct SharedExpression { /// Identifier of expression. let identifier: String diff --git a/Swifternalization/SharedExpressionsConfigurator.swift b/Swifternalization/SharedExpressionsConfigurator.swift deleted file mode 100644 index b6f888f..0000000 --- a/Swifternalization/SharedExpressionsConfigurator.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// SharedExpressionsConfigurator.swift -// Swifternalization -// -// Created by Tomasz Szulc on 28/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Swifternalization contains some built-in country-related shared expressions. -Developer can create its own expressions in Expressions.strings file for -base and preferred language version of the file. - -The class is responsible for proper loading the built-in shared ones and -those loaded from project's files. It handles overriding of built-in and loads -those from Base and preferred language version. - -It always load base expressions, then looking for language specific. -If in Base file are some expressions that overrides built-in, that's fine -the class adds or developer's custom expressions and add only those of built-in -that are not contained in the Base. - -The same is for preferred languages but also here what is important is that -at first preferred language file expressions are loaded, then if something -different is defined in Base it will be also loaded and then the built-in -expression that differs. -*/ -class SharedExpressionsConfigurator { - - /** - Method takes expression for both Base and preferred language localizations - and also internally loads built-in expressions, combine them and returns - tuple with expressions for Base and another array for preferred language - localization. - - :param: dicts A `BasePrefDicts` tuple that contains two dicts of - shared expressions for Base and preferred language localization. - :param: language A preferred user's language. - :returns: Tuple with arrays of shared expressions for Base and preferred - language localizations. - */ - class func configureExpressions(dicts: BasePrefDicts, language: Language) -> (base: [SharedExpression], pref: [SharedExpression]) { - // Convert loaded expressions dicts to SharedExpression arrays - let loadedBaseExpressions = convert(dicts.base) - let loadedPrefExpressions = convert(dicts.pref) - - - // Load country specific expressions from Expressions.strings - var prefBuiltIn = loadBuiltInExpressions(language) - - // Get base built-in expressions - let baseBuiltIn = SharedBaseExpression.allExpressions() - - // Add unique expressions from Base to language specific (preferred) - mergeExpressions(&prefBuiltIn, additional: baseBuiltIn) - - - // Add built-in expressions to Base expressions from Expressions.strings - let resultBaseExpressions = mergeExpressions(loadedBaseExpressions, additional: baseBuiltIn) - - // Add built-in preferred language expressions to those from - // Expressions.strings - var resultPrefExpressions = mergeExpressions(loadedPrefExpressions, additional: prefBuiltIn) - - // Add those from result of base expressions to result of preferred - // language expressions - mergeExpressions(&resultPrefExpressions, additional: resultBaseExpressions) - - - return (resultBaseExpressions, resultPrefExpressions) - } - - - // MARK: Private - - /** - Converts dictionary with expressions to array of `SharedExpression` objects. - - :param: expressionsDict Dictionary with key-value pair of expression - from Expressions.strings. - :returns: Array of `SharedExpression` objects. - */ - private class func convert(expressionsDict: KVDict) -> [SharedExpression] { - var result = [SharedExpression]() - for (key, pattern) in expressionsDict { - result.append(SharedExpression(identifier: key, pattern: pattern)) - } - return result - } - - /** - Method loads built-in framework's built-in expressions for specific language. - - :param: language A preferred user's language. - :returns: Shared expressions for specific language. If there is no - expression for passed language empty array is returned. - */ - private class func loadBuiltInExpressions(language: Language) -> [SharedExpression] { - switch language { - case "pl": return SharedPolishExpression.allExpressions() - default: return [] - } - } - - /** - Method that merges expressions. It takes two arrays, one is `source` and one - is `additional`. If the `source` does not contain some expression from - `additional` array this expression will be added to the `source`. - - :param: source A source array with expressions. - :param: additional Array with additional expressions that may or may not - be added to `source` array. - :returns: Array with expressions that contains all elements from `source` - and elements from `additional` that were not in `source`. - */ - private class func mergeExpressions(var source: [SharedExpression], additional: [SharedExpression]) -> [SharedExpression] { - for additionalExp in additional { - if source.filter({$0.identifier == additionalExp.identifier}).first == nil { - source.append(additionalExp) - } - } - return source - } - - /** - This is just helper method. It does the same like - `mergeExpressions(source:additional:)` but this one takes reference to - `source` array instead of pasing it by value. - - :param: source reference to source array. - :param: additional Array of additional shared expressions. - - :returns: merged array of shared expressions. - */ - private class func mergeExpressions(inout source: [SharedExpression], additional: [SharedExpression]) -> Void { - source = mergeExpressions(source, additional: additional) - } -} \ No newline at end of file diff --git a/Swifternalization/SharedExpressionsProcessor.swift b/Swifternalization/SharedExpressionsProcessor.swift index 11ad957..696fae8 100644 --- a/Swifternalization/SharedExpressionsProcessor.swift +++ b/Swifternalization/SharedExpressionsProcessor.swift @@ -95,7 +95,7 @@ class SharedExpressionsProcessor { :returns: Shared expressions for specific language. If there is no expression for passed language empty array is returned. */ - private class func loadBuiltInExpressions(language: Language) -> [SharedExpression] { + private class func loadBuiltInExpressions(language: CountryCode) -> [SharedExpression] { switch language { case "pl": return SharedPolishExpression.allExpressions() default: return [] diff --git a/Swifternalization/SimpleExpression.swift b/Swifternalization/SimpleExpression.swift deleted file mode 100644 index dada1b8..0000000 --- a/Swifternalization/SimpleExpression.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SimpleExpression.swift -// Swifternalization -// -// Created by Tomasz Szulc on 26/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Represents simple epxressions. -*/ -struct SimpleExpression: ExpressionType { - /// Pattern of expression. - let pattern: String - - /// A localized value. If `lengthVariations` array is empty or you want to - /// get full localized value use this property. - let localizedValue: String - - /// Array of length variations - let lengthVariations: [LengthVariation] - - // Returns expression object - init(pattern: String, localizedValue: String, lengthVariations: [LengthVariation] = [LengthVariation]()) { - self.pattern = pattern - self.localizedValue = localizedValue - self.lengthVariations = lengthVariations - } -} diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 3dac14f..31b552b 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -11,236 +11,61 @@ import Foundation /// Handy typealias that can be used instead of long `Swifternalization` public typealias I18n = Swifternalization -/** -Defines language selected on the user's device e.g. en, pl, ru. -Language can be also Base. It used used for finding right Localizable.strings -and Expression.strings and to load built-in shared expressions. -*/ -internal typealias Language = String - -/** -This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files. - -It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file. - -Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value. - -It is able to work with genstrings command-line tool like NSLocalizedString() -macro does. - -It looks for content in the NSBundle you can provide and try to find: - -- Localizable.strings (Base), -- Localizable.strings of preferred language, e.g. Localizable.strings (en) -- Expressions.strings (Base), -- Expressions.strings of preferred language, e.g. Expressions.strings (en) -*/ public class Swifternalization { + static let sharedInstance = Swifternalization() - /// Struct to keep shared instance of `Swifternalization` - private struct Static { - /// Instance of `Swifternalization` that might be nil if not set. - static var instance: Swifternalization? = nil - } - - /// Bundle when Localizable and Expressions .strings files are located. - private let bundle: NSBundle - - /// key-value from base Localizable.strings file - private var basePairs = [TranslatablePair]() - - /// key-value airs from preferred language Localizable.strings file - private var preferredPairs = [TranslatablePair]() - - // MARK: Public methods - /** - Swifternalization takes NSBundle when Localizable.strings file is located. - This method return instance of the class but you don't need it because - shared instance is set automatically. - - It get Localizable.strings file version based on the first language from - the prefferedLocalizations property of NSBundle. If Localizable.strings for - preferred language isn't exist then Base is used instead. + private var translations = [Translation]() - :param: bundle bundle when .strings files are located. - */ - public init(bundle: NSBundle) { - self.bundle = bundle - - /// Set it as shared instance - Swifternalization.setSharedInstance(self) - - /// load all the content - load() - } - - /** - Returns localized string for simple key that does not contain any expression. - - I18n.localizedString("car") - I18n.localizedString("car", defaultValue: "Audi") - I18n.localizedString("car", defaultValue: "Audi", comment: "Comment") - I18n.localizedString("car", comment: "Comment") - - :param: key key placed in Localizable.strings file. - :param: defaultValue default value that will be returned when there is no - translation for passed key. Default is nil. - :param: comment comment used by genstrings tool to generate description of - a key. Default is nil. + // MARK: Public Methods - :returns: Returns translation for passed key if found. If not found and - defaultValue is not nil it return defaultValue, otherwise - returns key. - */ - public class func localizedString(key: String, defaultValue: String? = nil, comment: String? = nil) -> String { - if sharedInstance() == nil { return (defaultValue != nil) ? defaultValue! : key } - - for TranslatablePair in sharedInstance().preferredPairs.filter({$0.key == key}) { - return TranslatablePair.value - } - - for TranslatablePair in sharedInstance().basePairs.filter({$0.key == key}) { - return TranslatablePair.value - } - - return (defaultValue != nil) ? defaultValue! : key + public class func configure(bundle: NSBundle = NSBundle.mainBundle()) { + sharedInstance.load(bundle: bundle) } - /** - Returns localized string for key which contains expression. - - I18n.localizedExpressionString("cars", value: "10") - I18n.localizedExpressionString("cars", value: "10", defaultValue: "Few cars") - I18n.localizedExpressionString("cars", value: "10", defaultValue: "10", comment: "This is a comment") - I18n.localizedExpressionString("cars", value: "10", comment: "This is a comment") - - :param: key key placed in Localizable.strings file. - :param: value value used when validating expressions. - :param: defaultValue default value that will be returned when there is no - translation for passed key. Default is nil. - :param: comment comment used by genstrings tool to generate description - of a key. Default is nil. - - :returns: Returns translation for passed key if found. If not found and - defaultValue is not nil it return defaultValue, otherwise returns key. - */ - public class func localizedExpressionString(key: String, value: String, defaultValue: String? = nil, comment: String? = nil) -> String { - if sharedInstance() == nil { return (defaultValue != nil) ? defaultValue! : key } - - let filter = {(pair: TranslatablePair) -> Bool in - return pair.hasExpression == true && pair.keyWithoutExpression == key - } - - // Filter preferred pairs - for pair in sharedInstance().preferredPairs.filter(filter) { - if pair.validate(value) { - return pair.value - } - } - - // Filter base pairs - for pair in sharedInstance().basePairs.filter(filter) { - if pair.validate(value) { - return pair.value + public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { + let filteredTranslations = sharedInstance.translations.filter({$0.key == key}) + if let translation = filteredTranslations.first { + if let matchingExpression = translation.validate(stringValue ?? key, length: fittingWidth) { + if fittingWidth == nil { + return matchingExpression.localizedValue + } else if matchingExpression.lengthVariations.count == 0 { + return matchingExpression.localizedValue + } else if matchingExpression.lengthVariations.count > 0 { + /// GET PROPER VARIANT + } } } return (defaultValue != nil) ? defaultValue! : key } - - /** - This method is just extension to method - `localizedExpressionString(_:value:defaultValue:comment:)` that takes - `String` as a `value` parameter. - */ - public class func localizedExpressionString(key: String, value: Int, defaultValue: String? = nil, comment: String? = nil) -> String { - return self.localizedExpressionString(key, value: "\(value)", defaultValue: defaultValue, comment: comment) - } - - // MARK: Private methods - /// Method that set shared instance of Swifternalization - private class func setSharedInstance(instance: Swifternalization) { - Static.instance = instance + public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { + return localizedString(key, stringValue: "\(intValue)", fittingWidth: fittingWidth, defaultValue: defaultValue, comment: comment) } - /// Method that returns shared instance of Swifternalization - /// :returns: `Swifternalization` shared instance - private class func sharedInstance() -> Swifternalization! { - return Static.instance + public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { + return localizedString(key, stringValue: key, fittingWidth: fittingWidth, defaultValue: defaultValue, comment: comment) } - /** - Method responsible for loading content into Swifternaliztion library. - It takes preferred language of user's device, and tries to find - Localizable.strings (Preferred Language) and Localizable.strings (Base) - to get keys and values of words to translate. - - It also tries to find and load Expressions.strings (Preferred Language) - and Expressions.strings (Base) files when shared expressions might be - and tries to combine them together with built-in shared expression, - and at the end enumeate through key-value pairs for translation and - changes keys that have only expression shortcuts to full expressions. - */ - private func load() { - let language = getPreferredLanguage() - let loader = LocalizableFilesLoader(bundle) - - // Get expressions pairs from Expressions.strings files - let expressionPairsDict = loader.loadContentFromFilesOfType(.Expressions, language: language) + // MARK: Private Methods + + private func load(bundle: NSBundle = NSBundle.mainBundle()) { + let base = "base" + let language = getPreferredLanguage(bundle) - // Get shared expressions for Base and preferred language - // including Framwork's shared expressions - let sharedExpressions = SharedExpressionsConfigurator.configureExpressions(expressionPairsDict, language: language) + let baseExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(base, bundle: bundle) ?? [:]) + let languageExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(language, bundle: bundle) ?? [:]) + let expressions = SharedExpressionsProcessor.processSharedExpression(language, preferedLanguageExpressions: languageExpressions, baseLanguageExpressions: baseExpressions) + let baseTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(base, bundle: bundle) ?? [:]) + let languageTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(language, bundle: bundle) ?? [:]) - // Get key-value translatable pairs from Localizable.strings files - let translatablePairsDicts = loader.loadContentFromFilesOfType(.Localizable, language: language) - - // Create TranslatablePair objects - basePairs = createTranslablePairs(translatablePairsDicts.base, expressions: sharedExpressions.base) - preferredPairs = createTranslablePairs(translatablePairsDicts.pref, expressions: sharedExpressions.pref) + translations = LoadedTranslationsProcessor.processTranslations(baseTranslations, preferedLanguageTranslations: languageTranslations, sharedExpressions: expressions) } /// Gets preferred language of user's device - private func getPreferredLanguage() -> Language { + private func getPreferredLanguage(bundle: NSBundle) -> CountryCode { // Get preferred language, the one which is set on user's device - return bundle.preferredLocalizations.first as! Language - } - - /** - Enumerate through translatable pairs dict and check if there are some shared - expression identifiers that needs to be replaced with full expressions. - - Next create translatable pairs with susch updated expressions to make it - ready to be used by framework. - */ - private func createTranslablePairs(translatableDict: KVDict, expressions: [SharedExpression]) -> [TranslatablePair] { - var pairs = [TranslatablePair]() - - for (tKey, tValue) in translatableDict { - // Check if there is expression in tKey - if let expressionPattern = Expression.parseExpressionPattern(tKey), - let sharedExpression = expressions.filter({$0.identifier == expressionPattern}).first, - // Create expression with pattern from Expressions.strings and - // it it is correct use it. - let updatedExpression = Expression.expressionFromString("{" + sharedExpression.pattern + "}"), - // Add translable pair with this new updated expression - let keyWithoutExpression = Regex.firstMatchInString(tKey, pattern: InternalPattern.KeyWithoutExpression.rawValue) { - - pairs.append(TranslatablePair(key: keyWithoutExpression + "{" + updatedExpression.pattern + "}", value: tValue)) - continue - } - - pairs.append(TranslatablePair(key: tKey, value: tValue)) - } - - return pairs + return bundle.preferredLocalizations.first as! CountryCode } } - diff --git a/Swifternalization/TranslatablePair.swift b/Swifternalization/TranslatablePair.swift deleted file mode 100644 index e790c29..0000000 --- a/Swifternalization/TranslatablePair.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// Pairs.swift -// Swifternalization -// -// Created by Tomasz Szulc on 27/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Represents key-value pair from Localizable.strings files. -It contains key, value and expression if exists for the key. -It can also validate if text matches expression's requirements. -*/ -struct TranslatablePair: KeyValueType { - /// Key from Localizable.strings. - var key: Key - - /// Value from Localizable.strings. - var value: Value - - /// `Expression` which is parsed from `key`. - var expression: Expression? = nil - - /// Tells if pair has `expression` or not. - var hasExpression: Bool { return expression != nil } - - /** - It returns key without expression pattern. - If pair has `expression` set to nil it will return `key`. - If `expression` exist the `key` will be parsed and returned without - expression pattern. - */ - var keyWithoutExpression: String { - if hasExpression == false { return key } - return Regex.firstMatchInString(key, pattern: InternalPattern.KeyWithoutExpression.rawValue)! - } - - /** - Creates `TranslatablePair`. It automatically tries to parse - expression from key - if there is any. - - :param: key A key from Localizable.strings - :param: value A value from Localizable.strings - */ - init(key: Key, value: Value) { - self.key = key - self.value = value - parseExpression() - } - - /// Method parses expression from the `key` property. - mutating func parseExpression() { - self.expression = Expression.expressionFromString(key) - } - - /** - Validates string and check if matches `expression`'s requirements. - If pair has no expression it return false. - - :param: value A value that will be matched. - :returns: `true` if value matches `expression`, otherwise `false`. - */ - func validate(value: String) -> Bool { - if hasExpression == false { return false } - return expression!.validate(value) - } -} diff --git a/Swifternalization/TranslationWithExpressions.swift b/Swifternalization/Translation.swift similarity index 64% rename from Swifternalization/TranslationWithExpressions.swift rename to Swifternalization/Translation.swift index 6fd0959..7420e6a 100644 --- a/Swifternalization/TranslationWithExpressions.swift +++ b/Swifternalization/Translation.swift @@ -11,16 +11,19 @@ import Foundation /** Represents translation with expressions. */ -struct TranslationWithExpressions: TranslationType { +struct Translation { /// Key that identifies a translation. let key: String /// Expressions that are related to a translation. - let expressions: [ExpressionType] + let expressions: [Expression] - func validate(text: String, length: Int?) { + func validate(text: String, length: Int?) -> Expression? { for expression in expressions { - // Do something + if expression.validate(text) { + return expression + } } + return nil } } diff --git a/Swifternalization/TranslationType.swift b/Swifternalization/TranslationType.swift deleted file mode 100644 index dc9d02a..0000000 --- a/Swifternalization/TranslationType.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// TranslationType.swift -// Swifternalization -// -// Created by Tomasz Szulc on 30/07/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Represents translation. -*/ -protocol TranslationType { - /// Key that identifies translation. - var key: String {get} -} \ No newline at end of file diff --git a/SwifternalizationTests/ExpressionTests.swift b/SwifternalizationTests/ExpressionTests.swift deleted file mode 100644 index 5bc26a3..0000000 --- a/SwifternalizationTests/ExpressionTests.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// ExpressionTests.swift -// Swifternalization -// -// Created by Tomasz Szulc on 27/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import UIKit -import XCTest -import Swifternalization - -class ExpressionTests: XCTestCase { - // MARK: - IE - func testIESupported1() { - XCTAssertTrue(Expression.parseExpressionPattern("{ie:x>2}") != nil, "") - } - - func testIESupported2() { - XCTAssertNotNil(Expression.parseExpressionPattern("{ie:x>0.2}"), "") - } - - func testIESupported3() { - XCTAssertTrue(Expression.expressionFromString("abc{ie:x=2}") != nil, "") - } - - // MARK: - IEX - func testIEXSupported1() { - XCTAssertNotNil(Expression.parseExpressionPattern("{iex:0.1 0, "Keys should be greater than 0") - XCTAssertTrue(result.pref.count == 0, "There should be no preferred language dictionary") - } - - func testLoading2() { - let result = loader.loadContentFromFilesOfType(.Expressions, language: language) - XCTAssertTrue(result.base.count > 0, "Keys should be greater than 0") - XCTAssertTrue(result.pref.count == 0, "There should be no preferred language dictionary") - } -} diff --git a/SwifternalizationTests/SharedBaseExpressionTests.swift b/SwifternalizationTests/SharedBaseExpressionTests.swift index 3ba83ff..31d1888 100644 --- a/SwifternalizationTests/SharedBaseExpressionTests.swift +++ b/SwifternalizationTests/SharedBaseExpressionTests.swift @@ -14,7 +14,7 @@ class SharedBaseExpressionTests: XCTestCase { func testOne() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "one"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("1"), "Should match 1") XCTAssertFalse(expression.validate("2"), "Should not match 2") @@ -22,7 +22,7 @@ class SharedBaseExpressionTests: XCTestCase { func testMoreThanOne() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == ">one"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertTrue(expression.validate("3"), "Should match 3") @@ -31,7 +31,7 @@ class SharedBaseExpressionTests: XCTestCase { func testTwo() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "two"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertFalse(expression.validate("1"), "Should not match 1") @@ -39,7 +39,7 @@ class SharedBaseExpressionTests: XCTestCase { func testOther() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "other"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("0"), "Should match 0") XCTAssertTrue(expression.validate("2"), "Should match 2") diff --git a/SwifternalizationTests/SharedPolishExpressionTests.swift b/SwifternalizationTests/SharedPolishExpressionTests.swift index 8cbf12f..d2ed5fb 100644 --- a/SwifternalizationTests/SharedPolishExpressionTests.swift +++ b/SwifternalizationTests/SharedPolishExpressionTests.swift @@ -13,7 +13,7 @@ class SharedPolishExpressionTests: XCTestCase { func testFew() { let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "few"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertTrue(expression.validate("24"), "Should match 24") @@ -28,7 +28,7 @@ class SharedPolishExpressionTests: XCTestCase { func testMany() { let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "many"}).first! - let expression = Expression(pattern: sharedExp.pattern)! + let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") XCTAssertTrue(expression.validate("10"), "Should match 10") XCTAssertTrue(expression.validate("18"), "Should match 18") diff --git a/SwifternalizationTests/SwifternalizationTests.swift b/SwifternalizationTests/SwifternalizationTests.swift index c44e5ce..e466c9b 100644 --- a/SwifternalizationTests/SwifternalizationTests.swift +++ b/SwifternalizationTests/SwifternalizationTests.swift @@ -14,11 +14,7 @@ class SwifternalizationTests: XCTestCase { override func setUp() { super.setUp() - Swifternalization(bundle: NSBundle(forClass: self.dynamicType)) - } - - override func tearDown() { - super.tearDown() + Swifternalization.configure(bundle: NSBundle.testBundle()) } func testShouldReturnKeyWhenNotTranslated() { @@ -36,24 +32,24 @@ class SwifternalizationTests: XCTestCase { // Inequality func testInequality1() { - XCTAssertEqual(Swifternalization.localizedExpressionString("cars", value: "1"), "one car", "") + XCTAssertEqual(Swifternalization.localizedString("cars", intValue: 1), "one car", "") } func testInequality2() { - XCTAssertEqual(Swifternalization.localizedExpressionString("cars", value: "2"), "%d cars", "") + XCTAssertEqual(Swifternalization.localizedString("cars", intValue: 2), "%d cars", "") } func testInequality3() { - XCTAssertEqual(Swifternalization.localizedExpressionString("cars", value: "-3"), "minus %d cars", "") + XCTAssertEqual(Swifternalization.localizedString("cars", intValue: -3), "minus %d cars", "") } // Shared Expression func testSharedExpression1() { - XCTAssertEqual(Swifternalization.localizedExpressionString("things", value: 10), "10 things", "") + XCTAssertEqual(Swifternalization.localizedString("things", intValue: 10), "10 things", "") } func testSharedExpression2() { - XCTAssertEqual(Swifternalization.localizedExpressionString("things", value: 26), ">20 things", "") + XCTAssertEqual(Swifternalization.localizedString("things", intValue: 26), ">20 things", "") } @@ -61,33 +57,33 @@ class SwifternalizationTests: XCTestCase { // Inequality func testPLInequality1() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-cars", value: "1"), "jeden samochód", "") + XCTAssertEqual(Swifternalization.localizedString("pl-cars", intValue: 1), "jeden samochód", "") } // Inequality Extended func testPLInequalityExtended2() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-cars", value: "2"), "%d samochody", "") + XCTAssertEqual(Swifternalization.localizedString("pl-cars", intValue: 2), "%d samochody", "") } func testPLInequalityExtended3() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-cars", value: "-3"), "-2 - -4 samochody", "") + XCTAssertEqual(Swifternalization.localizedString("pl-cars", intValue: -3), "-2 - -4 samochody", "") } // Regex func testPLRegex1() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-police-cars", value: "1"), "1 samochód policyjny", "") + XCTAssertEqual(Swifternalization.localizedString("pl-police-cars", intValue: 1), "1 samochód policyjny", "") } func testPLRegex2() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-police-cars", value: "2"), "%d samochody policyjne", "") + XCTAssertEqual(Swifternalization.localizedString("pl-police-cars", intValue: 2), "%d samochody policyjne", "") } func testPLRegex3() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-police-cars", value: "5"), "%d samochodów policyjnych", "") + XCTAssertEqual(Swifternalization.localizedString("pl-police-cars", intValue: 5), "%d samochodów policyjnych", "") } func testPLRegex4() { - XCTAssertEqual(Swifternalization.localizedExpressionString("pl-police-cars", value: 13), "%d samochodów policyjnych", "") + XCTAssertEqual(Swifternalization.localizedString("pl-police-cars", intValue: 13), "%d samochodów policyjnych", "") } } diff --git a/SwifternalizationTests/TranslatablePairTests.swift b/SwifternalizationTests/TranslatablePairTests.swift index e637761..84c73a2 100644 --- a/SwifternalizationTests/TranslatablePairTests.swift +++ b/SwifternalizationTests/TranslatablePairTests.swift @@ -11,12 +11,12 @@ import XCTest import Swifternalization class TranslatablePairTests: XCTestCase { - - func testShouldNotHaveExpression() { - XCTAssertFalse(TranslatablePair(key: "abc", value: "def").hasExpression, "Shouldn't have expression") - } - - func testShouldHaveExpression() { - XCTAssertTrue(TranslatablePair(key: "abc{ie:x=2}", value: "def").hasExpression, "Should have expression") - } +// +// func testShouldNotHaveExpression() { +// XCTAssertFalse(TranslatablePair(key: "abc", value: "def").hasExpression, "Shouldn't have expression") +// } +// +// func testShouldHaveExpression() { +// XCTAssertTrue(TranslatablePair(key: "abc{ie:x=2}", value: "def").hasExpression, "Should have expression") +// } } From 22943d30a999840f9eea6a7eaacf6952f88fe684 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 00:00:24 +0200 Subject: [PATCH 17/27] Fix test environment to run old tests --- Swifternalization.xcodeproj/project.pbxproj | 26 +----------- Swifternalization/JSONFileLoader.swift | 8 ++-- Swifternalization/LengthVariation.swift | 4 +- .../LoadedTranslationsProcessor.swift | 4 +- Swifternalization/Swifternalization.swift | 21 +++++++--- .../Base.lproj/Expressions.strings | 12 ------ .../Base.lproj/Localizable.strings | 21 ---------- .../JSONFileLoaderTests.swift | 16 +++---- SwifternalizationTests/base.json | 42 +++++++++---------- SwifternalizationTests/expressions.json | 6 +-- 10 files changed, 57 insertions(+), 103 deletions(-) delete mode 100644 SwifternalizationTests/Base.lproj/Expressions.strings delete mode 100644 SwifternalizationTests/Base.lproj/Localizable.strings diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index ede3edb..da6aca3 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 6D5004541B3EF91600A54B36 /* Swifternalization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D5004481B3EF91600A54B36 /* Swifternalization.framework */; }; 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */; }; 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; - 6D5004921B3EFF6D00A54B36 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004941B3EFF6D00A54B36 /* Localizable.strings */; }; 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D5004591B3EF91600A54B36 /* Info.plist */; }; 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */; }; 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; @@ -78,7 +77,6 @@ 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; 6DB3CC911B5EC29E00A1220F /* ExpressionPatternType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */; }; 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; - 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */; }; 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; 6DBB6C871B40718F002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C891B40718F002F39A3 /* Expressions.strings */; }; @@ -121,7 +119,6 @@ 6D5004591B3EF91600A54B36 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D50045A1B3EF91600A54B36 /* SwifternalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwifternalizationTests.swift; sourceTree = ""; }; 6D5004641B3EF92600A54B36 /* Swifternalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifternalization.swift; sourceTree = ""; }; - 6D5004931B3EFF6D00A54B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessor.swift; sourceTree = ""; }; 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; @@ -163,7 +160,6 @@ 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LengthVariation.swift; sourceTree = ""; }; 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; - 6DBB6C5A1B4026B8002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; 6DBB6C881B40718F002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8A1B407190002F39A3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Expressions.strings; sourceTree = ""; }; @@ -283,8 +279,6 @@ 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */, 6D140F451B56D04300359143 /* Helpers */, 6D5004581B3EF91600A54B36 /* Supporting Files */, - 6D5004941B3EFF6D00A54B36 /* Localizable.strings */, - 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */, 6DD3B93D1B5ED35600C79EAC /* Localizable Files */, ); path = SwifternalizationTests; @@ -360,8 +354,8 @@ 6DD3B93D1B5ED35600C79EAC /* Localizable Files */ = { isa = PBXGroup; children = ( - 6DD3B9371B5ED35200C79EAC /* base.json */, 6DD3B9381B5ED35200C79EAC /* expressions.json */, + 6DD3B9371B5ED35200C79EAC /* base.json */, 6DD3B9391B5ED35200C79EAC /* pl.json */, ); name = "Localizable Files"; @@ -506,8 +500,6 @@ 6DD3B93A1B5ED35200C79EAC /* base.json in Resources */, 6D5004961B3EFFC100A54B36 /* Info.plist in Resources */, 6DD3B93C1B5ED35200C79EAC /* pl.json in Resources */, - 6DBB6C591B4026B8002F39A3 /* Expressions.strings in Resources */, - 6D5004921B3EFF6D00A54B36 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -634,14 +626,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 6D5004941B3EFF6D00A54B36 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - 6D5004931B3EFF6D00A54B36 /* Base */, - ); - name = Localizable.strings; - sourceTree = ""; - }; 6D6283491B3F622A00E65FCD /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -651,14 +635,6 @@ name = Localizable.strings; sourceTree = ""; }; - 6DBB6C5B1B4026B8002F39A3 /* Expressions.strings */ = { - isa = PBXVariantGroup; - children = ( - 6DBB6C5A1B4026B8002F39A3 /* Base */, - ); - name = Expressions.strings; - sourceTree = ""; - }; 6DBB6C891B40718F002F39A3 /* Expressions.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index c310847..bdfac40 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -17,8 +17,8 @@ final class JSONFileLoader { :param: bundle A bundle when file is located. :returns: Returns json of file or nil if cannot load a file. */ - class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { - return self.load(countryCode, bundle: bundle) + class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary { + return self.load(countryCode, bundle: bundle) ?? [:] } /** @@ -28,8 +28,8 @@ final class JSONFileLoader { :param: bundle A bundle when file is located. :returns: dictionary with expressions or nil. */ - class func loadExpressions(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> Dictionary? { - return self.load("expressions", bundle: bundle)?[countryCode] as? Dictionary + class func loadExpressions(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> Dictionary { + return self.load("expressions", bundle: bundle)?[countryCode] as? Dictionary ?? [:] } /** diff --git a/Swifternalization/LengthVariation.swift b/Swifternalization/LengthVariation.swift index 09dd4b3..9fa797e 100644 --- a/Swifternalization/LengthVariation.swift +++ b/Swifternalization/LengthVariation.swift @@ -4,8 +4,8 @@ import Foundation Length variation representation. */ struct LengthVariation { - /// Length - width of a screen. - let length: Int + /// width of a screen. + let width: Int /// localized string. let value: String diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index 8111d2c..bf423cc 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -68,7 +68,7 @@ class LoadedTranslationsProcessor { // Translation contains length expressions like @100, @200, etc. var lengthVariations = [LengthVariation]() for (key, value) in $0.content as! Dictionary { - lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(key), value: value)) + lengthVariations.append(LengthVariation(width: self.parseNumberFromLengthVariation(key), value: value)) } return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)]) @@ -85,7 +85,7 @@ class LoadedTranslationsProcessor { if value is Dictionary { var lengthVariations = [LengthVariation]() for (lvKey, lvValue) in value as! Dictionary { - lengthVariations.append(LengthVariation(length: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) + lengthVariations.append(LengthVariation(width: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) } expressions.append(Expression(pattern: pattern, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)) } else if value is String { diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 31b552b..53886a2 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -31,7 +31,18 @@ public class Swifternalization { } else if matchingExpression.lengthVariations.count == 0 { return matchingExpression.localizedValue } else if matchingExpression.lengthVariations.count > 0 { - /// GET PROPER VARIANT + let sortedVariations = matchingExpression.lengthVariations.sorted({$0.width < $1.width}) + + var selectedValue = matchingExpression.localizedValue + for variation in sortedVariations { + if variation.width <= fittingWidth { + selectedValue = variation.value + } else { + break + } + } + + return selectedValue } } } @@ -53,12 +64,12 @@ public class Swifternalization { let base = "base" let language = getPreferredLanguage(bundle) - let baseExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(base, bundle: bundle) ?? [:]) - let languageExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(language, bundle: bundle) ?? [:]) + let baseExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(base, bundle: bundle)) + let languageExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(language, bundle: bundle)) let expressions = SharedExpressionsProcessor.processSharedExpression(language, preferedLanguageExpressions: languageExpressions, baseLanguageExpressions: baseExpressions) - let baseTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(base, bundle: bundle) ?? [:]) - let languageTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(language, bundle: bundle) ?? [:]) + let baseTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(base, bundle: bundle)) + let languageTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(language, bundle: bundle)) translations = LoadedTranslationsProcessor.processTranslations(baseTranslations, preferedLanguageTranslations: languageTranslations, sharedExpressions: expressions) } diff --git a/SwifternalizationTests/Base.lproj/Expressions.strings b/SwifternalizationTests/Base.lproj/Expressions.strings deleted file mode 100644 index 1d5c4ed..0000000 --- a/SwifternalizationTests/Base.lproj/Expressions.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* - Expressions.strings - Swifternalization - - Created by Tomasz Szulc on 28/06/15. - Copyright (c) 2015 Tomasz Szulc. All rights reserved. -*/ - -"ten" = "ie:x=10"; -">20" = "ie:x>20"; - -"custom-pl-few" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"; \ No newline at end of file diff --git a/SwifternalizationTests/Base.lproj/Localizable.strings b/SwifternalizationTests/Base.lproj/Localizable.strings deleted file mode 100644 index acb109d..0000000 --- a/SwifternalizationTests/Base.lproj/Localizable.strings +++ /dev/null @@ -1,21 +0,0 @@ -"welcome-key" = "welcome"; - -"only-in-base" = "this is from base"; - -"cars{one}" = "one car"; -"cars{ie:x>=2}" = "%d cars"; -"cars{ie:x<=-2}" = "minus %d cars"; - -// Polish language -"pl-cars{one}" = "jeden samochód"; -"pl-cars{iex:2<=x<=4}" = "%d samochody"; -"pl-cars{iex:-4<=x<=-2}" = "-2 - -4 samochody"; - -// Polish tricky language support -"pl-police-cars{ie:x=1}" = "1 samochód policyjny"; -"pl-police-cars{exp:(((?!1).[2-4]{1})$)|(^[2-4]$)}" = "%d samochody policyjne"; -"pl-police-cars{custom-pl-few}" = "%d samochodów policyjnych"; - -// Shared expression test - custom -"things{ten}" = "10 things"; -"things{>20}" = ">20 things"; \ No newline at end of file diff --git a/SwifternalizationTests/JSONFileLoaderTests.swift b/SwifternalizationTests/JSONFileLoaderTests.swift index fbd61b9..91fa82e 100644 --- a/SwifternalizationTests/JSONFileLoaderTests.swift +++ b/SwifternalizationTests/JSONFileLoaderTests.swift @@ -12,36 +12,36 @@ import XCTest class JSONFileLoaderTests: XCTestCase { // Expressions - func loadExpressions(cc: CountryCode) -> Dictionary? { + func loadExpressions(cc: CountryCode) -> Dictionary { return JSONFileLoader.loadExpressions(cc, bundle: NSBundle.testBundle()) } func testShouldLoadBaseExpressions() { - XCTAssertNotNil(loadExpressions("base"), "") + XCTAssertFalse(loadExpressions("base").isEmpty, "") } func testShouldLoadPLExpressions() { - XCTAssertNotNil(loadExpressions("pl"), "") + XCTAssertFalse(loadExpressions("pl").isEmpty, "") } func testShouldNotLoadDEExpressions() { - XCTAssertNil(loadExpressions("de"), "") + XCTAssertTrue(loadExpressions("de").isEmpty, "") } // Translations - func loadTranslations(cc: CountryCode) -> JSONDictionary? { + func loadTranslations(cc: CountryCode) -> JSONDictionary { return JSONFileLoader.loadTranslations(cc, bundle: NSBundle.testBundle()) } func testShouldLoadBaseTranslations() { - XCTAssertNotNil(loadTranslations("base"), "") + XCTAssertFalse(loadTranslations("base").isEmpty, "") } func testShouldLoadPLTranslations() { - XCTAssertNotNil(loadTranslations("pl"), "") + XCTAssertFalse(loadTranslations("pl").isEmpty, "") } func testShouldNotLoadDETranslations() { - XCTAssertNil(loadTranslations("de"), "") + XCTAssertTrue(loadTranslations("de").isEmpty, "") } } diff --git a/SwifternalizationTests/base.json b/SwifternalizationTests/base.json index e0b4524..542262c 100644 --- a/SwifternalizationTests/base.json +++ b/SwifternalizationTests/base.json @@ -1,28 +1,28 @@ { - "welcome": "welcome", + "welcome-key": "welcome", + + "only-in-base": "this is from base", "cars": { - "one": "1 car", - "ie:x=2": "2 cars", - "more": "%d cars" + "one": "one car", + "ie:x>=2": "%d cars", + "ie:x<=-2": "minus %d cars" }, - - "forgot-password": { - "@100": "Forgot Password? Help.", - "@200": "Forgot Password? Get password Help.", - "@300": "Forgotten Your Password? Get password Help." + + "pl-cars": { + "one": "jeden samochód", + "iex:2<=x<=4": "%d samochody", + "iex:-4<=x<=-2": "-2 - -4 samochody" }, - - "car-sentence": { - "one": { - "@100": "one car", - "@200": "just one car", - "@300": "you've got just one car" - }, - - "more": { - "@100": "%d cars", - "@300": "you've got %d cars" - } + + "pl-police-cars": { + "ie:x=1": "1 samochód policyjny", + "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)": "%d samochody policyjne", + "custom-pl-few": "%d samochodów policyjnych" + }, + + "things": { + "ten": "10 things", + ">20": ">20 things" } } diff --git a/SwifternalizationTests/expressions.json b/SwifternalizationTests/expressions.json index 953b595..6b20b7b 100644 --- a/SwifternalizationTests/expressions.json +++ b/SwifternalizationTests/expressions.json @@ -1,8 +1,8 @@ { "base": { - "one": "ie:x=1", - "two": "ie:x=2", - "more": "exp:(^[^1])|(^\\d{2,})" + "ten": "ie:x=10", + ">20": "ie:x>20", + "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])" }, "pl": { From 6c3b81be1c2f919707d089f8daa86af07dda336a Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 00:02:23 +0200 Subject: [PATCH 18/27] Remove demo target --- DemoOrdering/AppDelegate.swift | 46 ----- DemoOrdering/Base.lproj/Expressions.strings | 10 -- DemoOrdering/Base.lproj/Localizable.strings | 12 -- .../AppIcon.appiconset/Contents.json | 38 ----- DemoOrdering/Info.plist | 40 ----- DemoOrdering/LaunchScreen.xib | 41 ----- DemoOrdering/Main.storyboard | 25 --- DemoOrdering/ViewController.swift | 34 ---- DemoOrdering/pl.lproj/Expressions.strings | 7 - DemoOrdering/pl.lproj/Localizable.strings | 13 -- Swifternalization.xcodeproj/project.pbxproj | 158 +----------------- 11 files changed, 1 insertion(+), 423 deletions(-) delete mode 100644 DemoOrdering/AppDelegate.swift delete mode 100644 DemoOrdering/Base.lproj/Expressions.strings delete mode 100644 DemoOrdering/Base.lproj/Localizable.strings delete mode 100644 DemoOrdering/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 DemoOrdering/Info.plist delete mode 100644 DemoOrdering/LaunchScreen.xib delete mode 100644 DemoOrdering/Main.storyboard delete mode 100644 DemoOrdering/ViewController.swift delete mode 100644 DemoOrdering/pl.lproj/Expressions.strings delete mode 100644 DemoOrdering/pl.lproj/Localizable.strings diff --git a/DemoOrdering/AppDelegate.swift b/DemoOrdering/AppDelegate.swift deleted file mode 100644 index 55747fc..0000000 --- a/DemoOrdering/AppDelegate.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// AppDelegate.swift -// DemoOrdering -// -// Created by Tomasz Szulc on 28/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - -} - diff --git a/DemoOrdering/Base.lproj/Expressions.strings b/DemoOrdering/Base.lproj/Expressions.strings deleted file mode 100644 index bcc70c8..0000000 --- a/DemoOrdering/Base.lproj/Expressions.strings +++ /dev/null @@ -1,10 +0,0 @@ -/* - Expressions.strings - Swifternalization - - Created by Tomasz Szulc on 28/06/15. - Copyright (c) 2015 Tomasz Szulc. All rights reserved. -*/ - - -"ten" = "ie:x=10"; \ No newline at end of file diff --git a/DemoOrdering/Base.lproj/Localizable.strings b/DemoOrdering/Base.lproj/Localizable.strings deleted file mode 100644 index 78266dd..0000000 --- a/DemoOrdering/Base.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* - Localizable.strings - Swifternalization - - Created by Tomasz Szulc on 28/06/15. - Copyright (c) 2015 Tomasz Szulc. All rights reserved. -*/ - -"hello-base-test" = "Welcome!"; - -"cars{one}" = "%d car"; -"cars{other}" = "%d cars"; \ No newline at end of file diff --git a/DemoOrdering/Images.xcassets/AppIcon.appiconset/Contents.json b/DemoOrdering/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 118c98f..0000000 --- a/DemoOrdering/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DemoOrdering/Info.plist b/DemoOrdering/Info.plist deleted file mode 100644 index acbb120..0000000 --- a/DemoOrdering/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.tomaszszulc.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/DemoOrdering/LaunchScreen.xib b/DemoOrdering/LaunchScreen.xib deleted file mode 100644 index 42fbfe8..0000000 --- a/DemoOrdering/LaunchScreen.xib +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DemoOrdering/Main.storyboard b/DemoOrdering/Main.storyboard deleted file mode 100644 index 52ea29e..0000000 --- a/DemoOrdering/Main.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DemoOrdering/ViewController.swift b/DemoOrdering/ViewController.swift deleted file mode 100644 index fbc08c7..0000000 --- a/DemoOrdering/ViewController.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// ViewController.swift -// DemoOrdering -// -// Created by Tomasz Szulc on 28/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import UIKit -import Swifternalization - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - order() - } - - func order() { - Swifternalization(bundle: NSBundle.mainBundle()) - - /// shared expression - println(Swifternalization.localizedExpressionString("tomato", value: 10)); - - /// simple key - println(Swifternalization.localizedString("hello-base-test")) - - for num in 0...1000 { - let format = Swifternalization.localizedExpressionString("cars", value: "\(num)") - let localizedString = String(format: format, num) - println(localizedString) - } - } -} diff --git a/DemoOrdering/pl.lproj/Expressions.strings b/DemoOrdering/pl.lproj/Expressions.strings deleted file mode 100644 index 5068cee..0000000 --- a/DemoOrdering/pl.lproj/Expressions.strings +++ /dev/null @@ -1,7 +0,0 @@ -/* - Expressions.strings - Swifternalization - - Created by Tomasz Szulc on 28/06/15. - Copyright (c) 2015 Tomasz Szulc. All rights reserved. -*/ diff --git a/DemoOrdering/pl.lproj/Localizable.strings b/DemoOrdering/pl.lproj/Localizable.strings deleted file mode 100644 index 0353efb..0000000 --- a/DemoOrdering/pl.lproj/Localizable.strings +++ /dev/null @@ -1,13 +0,0 @@ -/* - Localizable.strings - Swifternalization - - Created by Tomasz Szulc on 28/06/15. - Copyright (c) 2015 Tomasz Szulc. All rights reserved. -*/ - -"cars{one}" = "%d samochód"; -"cars{few}" = "%d samochody"; -"cars{many}" = "%d samochodów"; - -"tomato{ten}" = "10 tomatos"; \ No newline at end of file diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index da6aca3..7c35ba9 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -59,12 +59,6 @@ 6D6282C91B3F4F6700E65FCD /* RegexExpressionParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282C41B3F4ED100E65FCD /* RegexExpressionParser.swift */; }; 6D6282CB1B3F508C00E65FCD /* RegexExpressionParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282CA1B3F508C00E65FCD /* RegexExpressionParserTests.swift */; }; 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6282CC1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift */; }; - 6D62832A1B3F613D00E65FCD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6283291B3F613D00E65FCD /* AppDelegate.swift */; }; - 6D62832C1B3F613D00E65FCD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62832B1B3F613D00E65FCD /* ViewController.swift */; }; - 6D6283311B3F613D00E65FCD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D6283301B3F613D00E65FCD /* Images.xcassets */; }; - 6D62834B1B3F622A00E65FCD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D6283491B3F622A00E65FCD /* Localizable.strings */; }; - 6D62834E1B3F628000E65FCD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834D1B3F628000E65FCD /* Main.storyboard */; }; - 6D6283501B3F62B100E65FCD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */; }; 6D62F9FC1B6B808400596A7C /* ExpressionJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */; }; 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */; }; 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */; }; @@ -79,7 +73,6 @@ 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; - 6DBB6C871B40718F002F39A3 /* Expressions.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6DBB6C891B40718F002F39A3 /* Expressions.strings */; }; 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; @@ -142,15 +135,6 @@ 6D6282C61B3F4F2100E65FCD /* RegexExpressionMatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexExpressionMatcher.swift; sourceTree = ""; }; 6D6282CA1B3F508C00E65FCD /* RegexExpressionParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexExpressionParserTests.swift; sourceTree = ""; }; 6D6282CC1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexExpressionMatcherTests.swift; sourceTree = ""; }; - 6D6283251B3F613D00E65FCD /* DemoOrdering.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoOrdering.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 6D6283281B3F613D00E65FCD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6D6283291B3F613D00E65FCD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 6D62832B1B3F613D00E65FCD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 6D6283301B3F613D00E65FCD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 6D62834A1B3F622A00E65FCD /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; - 6D62834C1B3F623200E65FCD /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 6D62834D1B3F628000E65FCD /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; - 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 6D62F9FB1B6B808400596A7C /* ExpressionJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionJSONs.swift; sourceTree = ""; }; 6D62F9FD1B6B824300596A7C /* TranslationJSONs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationJSONs.swift; sourceTree = ""; }; 6D75E6D31B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessorTests.swift; sourceTree = ""; }; @@ -161,8 +145,6 @@ 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; - 6DBB6C881B40718F002F39A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Expressions.strings; sourceTree = ""; }; - 6DBB6C8A1B407190002F39A3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Expressions.strings; sourceTree = ""; }; 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpression.swift; sourceTree = ""; }; 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpression.swift; sourceTree = ""; }; 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpressionTests.swift; sourceTree = ""; }; @@ -191,13 +173,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6283221B3F613D00E65FCD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -217,7 +192,6 @@ children = ( 6D50044A1B3EF91600A54B36 /* Swifternalization */, 6D5004571B3EF91600A54B36 /* SwifternalizationTests */, - 6D6283261B3F613D00E65FCD /* DemoOrdering */, 6D5004491B3EF91600A54B36 /* Products */, ); sourceTree = ""; @@ -227,7 +201,6 @@ children = ( 6D5004481B3EF91600A54B36 /* Swifternalization.framework */, 6D5004531B3EF91600A54B36 /* SwifternalizationTests.xctest */, - 6D6283251B3F613D00E65FCD /* DemoOrdering.app */, ); name = Products; sourceTree = ""; @@ -278,8 +251,8 @@ 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */, 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */, 6D140F451B56D04300359143 /* Helpers */, - 6D5004581B3EF91600A54B36 /* Supporting Files */, 6DD3B93D1B5ED35600C79EAC /* Localizable Files */, + 6D5004581B3EF91600A54B36 /* Supporting Files */, ); path = SwifternalizationTests; sourceTree = ""; @@ -292,37 +265,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 6D6283261B3F613D00E65FCD /* DemoOrdering */ = { - isa = PBXGroup; - children = ( - 6D6283491B3F622A00E65FCD /* Localizable.strings */, - 6DBB6C891B40718F002F39A3 /* Expressions.strings */, - 6D6283291B3F613D00E65FCD /* AppDelegate.swift */, - 6D62832B1B3F613D00E65FCD /* ViewController.swift */, - 6D6283471B3F615000E65FCD /* Resources */, - 6D6283271B3F613D00E65FCD /* Supporting Files */, - ); - path = DemoOrdering; - sourceTree = ""; - }; - 6D6283271B3F613D00E65FCD /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 6D6283281B3F613D00E65FCD /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 6D6283471B3F615000E65FCD /* Resources */ = { - isa = PBXGroup; - children = ( - 6D62834D1B3F628000E65FCD /* Main.storyboard */, - 6D62834F1B3F62B100E65FCD /* LaunchScreen.xib */, - 6D6283301B3F613D00E65FCD /* Images.xcassets */, - ); - name = Resources; - sourceTree = ""; - }; 6DB3CC8D1B5EC1FF00A1220F /* Matchers and Parsers */ = { isa = PBXGroup; children = ( @@ -425,23 +367,6 @@ productReference = 6D5004531B3EF91600A54B36 /* SwifternalizationTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 6D6283241B3F613D00E65FCD /* DemoOrdering */ = { - isa = PBXNativeTarget; - buildConfigurationList = 6D6283411B3F613D00E65FCD /* Build configuration list for PBXNativeTarget "DemoOrdering" */; - buildPhases = ( - 6D6283211B3F613D00E65FCD /* Sources */, - 6D6283221B3F613D00E65FCD /* Frameworks */, - 6D6283231B3F613D00E65FCD /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = DemoOrdering; - productName = DemoOrdering; - productReference = 6D6283251B3F613D00E65FCD /* DemoOrdering.app */; - productType = "com.apple.product-type.application"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -458,9 +383,6 @@ 6D5004521B3EF91600A54B36 = { CreatedOnToolsVersion = 6.3.2; }; - 6D6283241B3F613D00E65FCD = { - CreatedOnToolsVersion = 6.3.2; - }; }; }; buildConfigurationList = 6D5004421B3EF91600A54B36 /* Build configuration list for PBXProject "Swifternalization" */; @@ -479,7 +401,6 @@ targets = ( 6D5004471B3EF91600A54B36 /* Swifternalization */, 6D5004521B3EF91600A54B36 /* SwifternalizationTests */, - 6D6283241B3F613D00E65FCD /* DemoOrdering */, ); }; /* End PBXProject section */ @@ -503,18 +424,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6283231B3F613D00E65FCD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D6283501B3F62B100E65FCD /* LaunchScreen.xib in Resources */, - 6D62834B1B3F622A00E65FCD /* Localizable.strings in Resources */, - 6DBB6C871B40718F002F39A3 /* Expressions.strings in Resources */, - 6D6283311B3F613D00E65FCD /* Images.xcassets in Resources */, - 6D62834E1B3F628000E65FCD /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -606,15 +515,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6283211B3F613D00E65FCD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D62832C1B3F613D00E65FCD /* ViewController.swift in Sources */, - 6D62832A1B3F613D00E65FCD /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -625,27 +525,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 6D6283491B3F622A00E65FCD /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - 6D62834A1B3F622A00E65FCD /* Base */, - 6D62834C1B3F623200E65FCD /* pl */, - ); - name = Localizable.strings; - sourceTree = ""; - }; - 6DBB6C891B40718F002F39A3 /* Expressions.strings */ = { - isa = PBXVariantGroup; - children = ( - 6DBB6C881B40718F002F39A3 /* Base */, - 6DBB6C8A1B407190002F39A3 /* pl */, - ); - name = Expressions.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 6D50045C1B3EF91600A54B36 /* Debug */ = { isa = XCBuildConfiguration; @@ -805,32 +684,6 @@ }; name = Release; }; - 6D6283421B3F613D00E65FCD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = DemoOrdering/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 6D6283431B3F613D00E65FCD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = DemoOrdering/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -861,15 +714,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 6D6283411B3F613D00E65FCD /* Build configuration list for PBXNativeTarget "DemoOrdering" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 6D6283421B3F613D00E65FCD /* Debug */, - 6D6283431B3F613D00E65FCD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 6D50043F1B3EF91600A54B36 /* Project object */; From e84a64903861633e828671d56b065904213599a9 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 14:36:22 +0200 Subject: [PATCH 19/27] Document the code --- Swifternalization/Expression.swift | 66 +++++--- Swifternalization/ExpressionMatcher.swift | 4 +- Swifternalization/LengthVariation.swift | 11 +- .../LoadedTranslationsProcessor.swift | 10 +- Swifternalization/Regex.swift | 5 - Swifternalization/Swifternalization.swift | 146 +++++++++++++++--- Swifternalization/Translation.swift | 33 +++- .../SharedBaseExpressionTests.swift | 8 +- .../SharedPolishExpressionTests.swift | 4 +- 9 files changed, 215 insertions(+), 72 deletions(-) diff --git a/Swifternalization/Expression.swift b/Swifternalization/Expression.swift index a82af1d..66dc65c 100644 --- a/Swifternalization/Expression.swift +++ b/Swifternalization/Expression.swift @@ -8,44 +8,62 @@ import Foundation -/// String that contains expression pattern, e.g. `ie:x<5`, `exp:^1$`. +/** +String that contains expression pattern, e.g. `ie:x<5`, `exp:^1$`. +*/ typealias ExpressionPattern = String /** -Represents simple epxressions. +This class contains pattern of expression and localized value as well as +length variations if any are associated. During instance initialization pattern +is analyzed and correct expression matcher is created. If no matcher matches +the expression pattern then when validating there is only check if passed value +is the same like pattern (equality). If there is matcher then its internal logic +validates passed value. */ struct Expression { - /// Pattern of expression. - let pattern: String + /** + Pattern of an expression. + */ + let pattern: ExpressionPattern - /// A localized value. If `lengthVariations` array is empty or you want to - /// get full localized value use this property. - let localizedValue: String + /** + A localized value. If length vartiations array is empty or you want to + get full localized value use this property. + */ + let value: String - /// Array of length variations + /** + Array of length variations. + */ let lengthVariations: [LengthVariation] - /// Expression matcher that is used in validation + /** + Expression matcher that is used in validation. + */ private var expressionMatcher: ExpressionMatcher? = nil - // Returns expression object - init(pattern: String, localizedValue: String, lengthVariations: [LengthVariation] = [LengthVariation]()) { + /** + Returns expression object. + */ + init(pattern: String, value: String, lengthVariations: [LengthVariation] = [LengthVariation]()) { self.pattern = pattern - self.localizedValue = localizedValue + self.value = value self.lengthVariations = lengthVariations - // Create expression matcher + /* + Create expression matcher if pattern matches some expression type. + If not matching any expression type then the pattern equality test + will be perfomed when during validation. + */ if let type = getExpressionType(pattern) { - switch (type as ExpressionPatternType) { - case .Inequality: - expressionMatcher = InequalityExpressionParser(pattern).parse() - - case .InequalityExtended: - expressionMatcher = InequalityExtendedExpressionParser(pattern).parse() - - case .Regex: - expressionMatcher = RegexExpressionParser(pattern).parse() - } + expressionMatcher = { + switch (type as ExpressionPatternType) { + case .Inequality: return InequalityExpressionParser(pattern).parse() + case .InequalityExtended: return InequalityExtendedExpressionParser(pattern).parse() + case .Regex: return RegexExpressionParser(pattern).parse() + } + }() } } @@ -64,7 +82,7 @@ struct Expression { } /** - Method used to get `ExpressionPatternType` of passed `ExpressionPattern`. + Method used to get `ExpressionPatternType` of passed expression pattern. :param: pattern expression pattern that will be checked. :returns: `ExpressionPatternType` if pattern is supported, otherwise nil. diff --git a/Swifternalization/ExpressionMatcher.swift b/Swifternalization/ExpressionMatcher.swift index d4e9e4e..eab29bb 100644 --- a/Swifternalization/ExpressionMatcher.swift +++ b/Swifternalization/ExpressionMatcher.swift @@ -7,8 +7,8 @@ // /** - Protocol that is the base protocol to conform for expression matchers like - `InequalityExpressionMatcher` or `RegexExpressionMatcher`. +Protocol that is the base protocol to conform for expression matchers like +`InequalityExpressionMatcher` or `RegexExpressionMatcher`. */ protocol ExpressionMatcher { /** diff --git a/Swifternalization/LengthVariation.swift b/Swifternalization/LengthVariation.swift index 9fa797e..b154d02 100644 --- a/Swifternalization/LengthVariation.swift +++ b/Swifternalization/LengthVariation.swift @@ -1,12 +1,17 @@ import Foundation /** -Length variation representation. +Length variation representation. It contains a width property which specifies +up to which width of a screen the text in `value` property should be presented. */ struct LengthVariation { - /// width of a screen. + /** + Max width of a screen on which the `value` should be presented. + */ let width: Int - /// localized string. + /** + String with localized content in some language. + */ let value: String } diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index bf423cc..f1e78ed 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -49,7 +49,7 @@ class LoadedTranslationsProcessor { case .Simple: // Simple translation with key and value. let value = $0.content[$0.key] as! String - return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, localizedValue: value)]) + return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, value: value)]) case .WithExpressions: // Translation that contains expression. @@ -60,7 +60,7 @@ class LoadedTranslationsProcessor { var expressions = [Expression]() for (key, value) in $0.content as! Dictionary { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key - expressions.append(Expression(pattern: pattern, localizedValue: value)) + expressions.append(Expression(pattern: pattern, value: value)) } return Translation(key: $0.key, expressions: expressions) @@ -70,7 +70,7 @@ class LoadedTranslationsProcessor { for (key, value) in $0.content as! Dictionary { lengthVariations.append(LengthVariation(width: self.parseNumberFromLengthVariation(key), value: value)) } - return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)]) + return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, value: lengthVariations.last!.value, lengthVariations: lengthVariations)]) case .WithExpressionsAndLengthVariations: // The most advanced translation type. It contains expressions @@ -87,9 +87,9 @@ class LoadedTranslationsProcessor { for (lvKey, lvValue) in value as! Dictionary { lengthVariations.append(LengthVariation(width: self.parseNumberFromLengthVariation(lvKey), value: lvValue)) } - expressions.append(Expression(pattern: pattern, localizedValue: lengthVariations.last!.value, lengthVariations: lengthVariations)) + expressions.append(Expression(pattern: pattern, value: lengthVariations.last!.value, lengthVariations: lengthVariations)) } else if value is String { - expressions.append(Expression(pattern:pattern, localizedValue: value as! String)) + expressions.append(Expression(pattern:pattern, value: value as! String)) } } return Translation(key: $0.key, expressions: expressions) diff --git a/Swifternalization/Regex.swift b/Swifternalization/Regex.swift index aa27f58..a9d0def 100644 --- a/Swifternalization/Regex.swift +++ b/Swifternalization/Regex.swift @@ -18,7 +18,6 @@ class Regex { :param: str A string that will be matched. :param: pattern A regex pattern. - :returns: `String` that matches pattern or nil. */ class func matchInString(str: String, pattern: String, capturingGroupIdx: Int?) -> String? { @@ -44,7 +43,6 @@ class Regex { :param: str A string that will be matched. :param: pattern A regexp pattern. - :returns: `String` that matches pattern or nil. */ class func firstMatchInString(str: String, pattern: String) -> String? { @@ -59,7 +57,6 @@ class Regex { :param: str A string that will be matched. :param: pattern A regexp pattern. - :returns: Array of `Strings`s. If nothing found empty array is returned. */ class func matchesInString(str: String, pattern: String) -> [String] { @@ -77,7 +74,6 @@ class Regex { Returns new `NSRegularExpression` object. :param: pattern A regexp pattern. - :returns: `NSRegularExpression` object or nil if it cannot be created. */ private class func regexp(pattern: String) -> NSRegularExpression? { @@ -94,7 +90,6 @@ class Regex { :param: str A string that is source of substraction. :param: range A range that tells which part of `str` will be substracted. - :returns: A string contained in `range`. */ private class func substring(str: String, range: NSRange) -> String { diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 53886a2..99c097c 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -8,73 +8,169 @@ import Foundation -/// Handy typealias that can be used instead of long `Swifternalization` +/// Handy typealias that can be used instead of longer `Swifternalization` public typealias I18n = Swifternalization +/** +This is the main class of Swifternalization library. It exposes methods +that can be used to get localized strings. + +It uses expressions.json and base.json, en.json, pl.json and so on to work. +expressions.json file contains shared expressions that are used by other +files with translations. + +Internal classes of the Swifternalization contains logic that is responsible +for detecting which value should be used for the given key and value. + +Before you can get any localized value you have to configure the Swifternalization +first. Call `configure:` method first and then you can use other methods. +*/ public class Swifternalization { - static let sharedInstance = Swifternalization() + /** + Shared instance of Swifternalization used internally. + */ + private static let sharedInstance = Swifternalization() + /** + Array of translations that contain expressions and localized values. + */ private var translations = [Translation]() // MARK: Public Methods + /** + Call the method to configure Swifternalization. + + :param: bundle A bundle when expressions.json and other files are located. + */ public class func configure(bundle: NSBundle = NSBundle.mainBundle()) { sharedInstance.load(bundle: bundle) } + /** + Get localized value for a key. + + :param: key A key. + :param: fittingWidth A max width that value should fit to. If there is no + value specified the full-length localized string is returned. If a + passed fitting width is greater than highest available then a value for + highest available width is returned. + :param: defaultValue A default value that is returned when there is no + localized value for passed `key`. + :param: comment A comment about the key and localized value. Just for + developer use for describing key-value pair. + :returns: localized string if found, otherwise `defaultValue` is returned if + specified or `key` if `defaultValue` is not specified. + */ + public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { + return localizedString(key, stringValue: key, fittingWidth: fittingWidth, defaultValue: defaultValue, comment: comment) + } + + + /** + Get localized value for a key and string value. + + :param: key A key. + :param: stringValue A value that is matched by expressions. + :param: fittingWidth A max width that value should fit to. If there is no + value specified the full-length localized string is returned. If a + passed fitting width is greater than highest available then a value for + highest available width is returned. + :param: defaultValue A default value that is returned when there is no + localized value for passed `key`. + :param: comment A comment about the key and localized value. Just for + developer use for describing key-value pair. + :returns: localized string if found, otherwise `defaultValue` is returned if + specified or `key` if `defaultValue` is not specified. + */ public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { + /** + Filter translations and get only these that match passed `key`. + In ideal case when all is correctly filled by a developer it should be + at most only one key so we're getting just first found key. + */ let filteredTranslations = sharedInstance.translations.filter({$0.key == key}) if let translation = filteredTranslations.first { - if let matchingExpression = translation.validate(stringValue ?? key, length: fittingWidth) { - if fittingWidth == nil { - return matchingExpression.localizedValue - } else if matchingExpression.lengthVariations.count == 0 { - return matchingExpression.localizedValue - } else if matchingExpression.lengthVariations.count > 0 { - let sortedVariations = matchingExpression.lengthVariations.sorted({$0.width < $1.width}) - - var selectedValue = matchingExpression.localizedValue - for variation in sortedVariations { - if variation.width <= fittingWidth { - selectedValue = variation.value - } else { - break - } - } - - return selectedValue - } + /** + If there is translation for the `key` it should validate passed + `stringValue`and `fittingWidth`. Translation returns localized + string (that matches `fittingWidth` if specified or nil. + If `fittingWidth` is not specified then full length localized string + is returned if translation matches the validated `stringValue`. + */ + if let localizedValue = translation.validate(stringValue, length: fittingWidth) { + return localizedValue } } + /** + If there is not translation that validated `stringValue` successfully + then return `defaultValue` if not nil or the `key`. + */ return (defaultValue != nil) ? defaultValue! : key } + /** + Get localized value for a key and string value. + + :param: key A key. + :param: intValue A value that is matched by expressions. + :param: fittingWidth A max width that value should fit to. If there is no + value specified the full-length localized string is returned. If a + passed fitting width is greater than highest available then a value for + highest available width is returned. + :param: defaultValue A default value that is returned when there is no + localized value for passed `key`. + :param: comment A comment about the key and localized value. Just for + developer use for describing key-value pair. + :returns: localized string if found, otherwise `defaultValue` is returned if + specified or `key` if `defaultValue` is not specified. + */ public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { return localizedString(key, stringValue: "\(intValue)", fittingWidth: fittingWidth, defaultValue: defaultValue, comment: comment) } - public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String { - return localizedString(key, stringValue: key, fittingWidth: fittingWidth, defaultValue: defaultValue, comment: comment) - } // MARK: Private Methods + /** + Loads expressions and translations from expression.json and translation + json files. + + :param: bundle A bundle when files are located. + */ private func load(bundle: NSBundle = NSBundle.mainBundle()) { + // Set base and prefered languages. let base = "base" let language = getPreferredLanguage(bundle) + /* + Load base and prefered language expressions from expressions.json, + convert them into SharedExpression objects and process them and return + array only of unique expressions. `SharedExpressionsProcessor` do its + own things inside like check if expression is unique or overriding base + expressions by prefered language ones if there is such expression. + */ let baseExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(base, bundle: bundle)) let languageExpressions = SharedExpressionsLoader.loadExpressions(JSONFileLoader.loadExpressions(language, bundle: bundle)) let expressions = SharedExpressionsProcessor.processSharedExpression(language, preferedLanguageExpressions: languageExpressions, baseLanguageExpressions: baseExpressions) + /* + Load base and prefered language translations from proper language files + specified by `language` constant. Convert them into arrays of + `LoadedTranslation`s and then process and convert into `Translation` + objects using `LoadedTranslationsProcessor`. + */ let baseTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(base, bundle: bundle)) let languageTranslations = TranslationsLoader.loadTranslations(JSONFileLoader.loadTranslations(language, bundle: bundle)) + // Store processed translations in `translations` variable for future use. translations = LoadedTranslationsProcessor.processTranslations(baseTranslations, preferedLanguageTranslations: languageTranslations, sharedExpressions: expressions) } - /// Gets preferred language of user's device + /** + Gets preferred language of user's device + */ private func getPreferredLanguage(bundle: NSBundle) -> CountryCode { // Get preferred language, the one which is set on user's device return bundle.preferredLocalizations.first as! CountryCode diff --git a/Swifternalization/Translation.swift b/Swifternalization/Translation.swift index 7420e6a..89840f0 100644 --- a/Swifternalization/Translation.swift +++ b/Swifternalization/Translation.swift @@ -18,10 +18,39 @@ struct Translation { /// Expressions that are related to a translation. let expressions: [Expression] - func validate(text: String, length: Int?) -> Expression? { + /** + Validates passed `text` and uses `fittingWidth` for getting proper + localized string. + + :param: text A text that is matched. + :param: fittingWidth A max width of a screen that text should match. + :returns: A localized string if any expression validates the `text`, + otherwise nil. + */ + func validate(text: String, fittingWidth: Int?) -> String? { + // Find first expression that validates the `text`. for expression in expressions { if expression.validate(text) { - return expression + /* + Get the localized value of expression if it match the `text`. + Check if there is `fittingValue` defined as method argument + and if there are some variations in the expression get proper + variant for passed length. + */ + var localizedValue = expression.value + if fittingWidth != nil && expression.lengthVariations.count > 0 { + /* + Sort variations in ascending order. + If variation width is shorter or equal `fittingWidth` + take associated value. + */ + for variation in expression.lengthVariations.sorted({$0.width < $1.width}) { + if variation.width <= fittingWidth! { + localizedValue = variation.value + } + } + } + return localizedValue } } return nil diff --git a/SwifternalizationTests/SharedBaseExpressionTests.swift b/SwifternalizationTests/SharedBaseExpressionTests.swift index 31d1888..ab11f61 100644 --- a/SwifternalizationTests/SharedBaseExpressionTests.swift +++ b/SwifternalizationTests/SharedBaseExpressionTests.swift @@ -14,7 +14,7 @@ class SharedBaseExpressionTests: XCTestCase { func testOne() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "one"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("1"), "Should match 1") XCTAssertFalse(expression.validate("2"), "Should not match 2") @@ -22,7 +22,7 @@ class SharedBaseExpressionTests: XCTestCase { func testMoreThanOne() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == ">one"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertTrue(expression.validate("3"), "Should match 3") @@ -31,7 +31,7 @@ class SharedBaseExpressionTests: XCTestCase { func testTwo() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "two"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertFalse(expression.validate("1"), "Should not match 1") @@ -39,7 +39,7 @@ class SharedBaseExpressionTests: XCTestCase { func testOther() { let sharedExp = SharedBaseExpression.allExpressions().filter({$0.identifier == "other"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("0"), "Should match 0") XCTAssertTrue(expression.validate("2"), "Should match 2") diff --git a/SwifternalizationTests/SharedPolishExpressionTests.swift b/SwifternalizationTests/SharedPolishExpressionTests.swift index d2ed5fb..7cd8bcf 100644 --- a/SwifternalizationTests/SharedPolishExpressionTests.swift +++ b/SwifternalizationTests/SharedPolishExpressionTests.swift @@ -13,7 +13,7 @@ class SharedPolishExpressionTests: XCTestCase { func testFew() { let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "few"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("2"), "Should match 2") XCTAssertTrue(expression.validate("24"), "Should match 24") @@ -28,7 +28,7 @@ class SharedPolishExpressionTests: XCTestCase { func testMany() { let sharedExp = SharedPolishExpression.allExpressions().filter({$0.identifier == "many"}).first! - let expression = Expression(pattern: sharedExp.pattern, localizedValue: "") + let expression = Expression(pattern: sharedExp.pattern, value: "") XCTAssertTrue(expression.validate("10"), "Should match 10") XCTAssertTrue(expression.validate("18"), "Should match 18") From 9c49bd2b49c918f7fa01b6eb87729dfd189bccf5 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 14:53:49 +0200 Subject: [PATCH 20/27] Cleanup code and add comments --- Swifternalization/JSONFileLoader.swift | 16 +++---- Swifternalization/LoadedTranslation.swift | 13 +++-- Swifternalization/LoadedTranslationType.swift | 48 ++++++++++--------- .../LoadedTranslationsProcessor.swift | 47 ++++++++++-------- Swifternalization/Regex.swift | 2 +- Swifternalization/Swifternalization.swift | 4 +- Swifternalization/TranslationsLoader.swift | 11 +++-- 7 files changed, 83 insertions(+), 58 deletions(-) diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index bdfac40..cd24d65 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -6,7 +6,7 @@ Represents json content. typealias JSONDictionary = Dictionary /** -Simple JSON loader. +Simple JSON file loader. */ final class JSONFileLoader { @@ -15,9 +15,9 @@ final class JSONFileLoader { :param: countryCode A country code. :param: bundle A bundle when file is located. - :returns: Returns json of file or nil if cannot load a file. + :returns: Returns json of file or empty dictionary if cannot load a file. */ - class func loadTranslations(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary { + class func loadTranslations(countryCode: CountryCode, bundle: NSBundle) -> JSONDictionary { return self.load(countryCode, bundle: bundle) ?? [:] } @@ -26,9 +26,9 @@ final class JSONFileLoader { :param: countryCode A country code. :param: bundle A bundle when file is located. - :returns: dictionary with expressions or nil. + :returns: dictionary with expressions or empty dictionary if cannot load a file. */ - class func loadExpressions(countryCode: CountryCode, bundle: NSBundle = NSBundle.mainBundle()) -> Dictionary { + class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> Dictionary { return self.load("expressions", bundle: bundle)?[countryCode] as? Dictionary ?? [:] } @@ -38,9 +38,9 @@ final class JSONFileLoader { :param: fileName A name of a file. :param: fileType A type of a file. :param: bundle A bundle when file is located. - :returns: JSON or nil. + :returns: JSON or nil if file cannot be loaded. */ - private class func load(fileName: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { + private class func load(fileName: String, bundle: NSBundle) -> JSONDictionary? { if let fileURL = bundle.URLForResource(fileName, withExtension: "json") { return load(fileURL) } @@ -52,7 +52,7 @@ final class JSONFileLoader { Loads file for specified URL and try to serialize it. :params: fileURL url to JSON file. - :returns: Dictionary with content of JSON file or nil. + :returns: Dictionary with content of JSON file or nil if file cannot be loaded. */ private class func load(fileURL: NSURL) -> JSONDictionary? { if let data = NSData(contentsOfURL: fileURL) { diff --git a/Swifternalization/LoadedTranslation.swift b/Swifternalization/LoadedTranslation.swift index ac5d681..0853f4e 100644 --- a/Swifternalization/LoadedTranslation.swift +++ b/Swifternalization/LoadedTranslation.swift @@ -12,12 +12,19 @@ import Foundation Struct that represents loaded translation. */ struct LoadedTranslation { - /// A type of translation. + /** + A type of translation. It is used later to convert object into translation + correclty. + */ let type: LoadedTranslationType - /// Key that identifies this translation + /** + Key that identifies this translation. + */ var key: String - /// A content of translation just loaded from a file. + /** + A content of translation just loaded from a file user in future processing. + */ let content: Dictionary } diff --git a/Swifternalization/LoadedTranslationType.swift b/Swifternalization/LoadedTranslationType.swift index d0a7875..7e8a50e 100644 --- a/Swifternalization/LoadedTranslationType.swift +++ b/Swifternalization/LoadedTranslationType.swift @@ -9,35 +9,37 @@ import Foundation /** -Specifies few types of available translation type. +Specifies available translation types used by translation processor to have +knowledge about specifics of loaded translations. It helps later to decide +what is needed to do with `LoadedTranslation`'s `content` property. */ enum LoadedTranslationType { /** Simple key-value pair. - "welcome": "welcome" + "welcome": "welcome" */ case Simple /** Pair where value is a dictionary with expressions. - "cars": { - "one": "1 car", - "ie:x=2": "2 cars", - "more": "%d cars" - } + "cars": { + "one": "1 car", + "ie:x=2": "2 cars", + "more": "%d cars" + } */ case WithExpressions /** Pair where value is a dictionary with length variations. - "forgot-password": { - "@100": "Forgot Password? Help.", - "@200": "Forgot Password? Get password Help.", - "@300": "Forgotten Your Password? Get password Help." - } + "forgot-password": { + "@100": "Forgot Password? Help.", + "@200": "Forgot Password? Get password Help.", + "@300": "Forgotten Your Password? Get password Help." + } */ case WithLengthVariations @@ -45,18 +47,20 @@ enum LoadedTranslationType { Pair where value is dictionary that contains dictionary with expression and length variations. - "car-sentence": { - "one": { - "@100": "one car", - "@200": "just one car", - "@300": "you've got just one car" - }, + "car-sentence": { + "one": { + "@100": "one car", + "@200": "just one car", + "@300": "you've got just one car" + }, + + "more": { + "@100": "%d cars", + "@300": "you've got %d cars" + }, - "more": { - "@100": "%d cars", - "@300": "you've got %d cars" + "two": "two cars" } - } */ case WithExpressionsAndLengthVariations } \ No newline at end of file diff --git a/Swifternalization/LoadedTranslationsProcessor.swift b/Swifternalization/LoadedTranslationsProcessor.swift index f1e78ed..5315563 100644 --- a/Swifternalization/LoadedTranslationsProcessor.swift +++ b/Swifternalization/LoadedTranslationsProcessor.swift @@ -9,7 +9,7 @@ import Foundation /** -Translation processor that takes loaded translations and process them to make +Translation processor which takes loaded translations and process them to make them regular translation objects that can be used for further work. */ class LoadedTranslationsProcessor { @@ -26,11 +26,16 @@ class LoadedTranslationsProcessor { translations and use resolved full shared expressions from file with expressions as well as built-in ones. Translations are processed in shared expressions processor. + + :param: baseTranslations An array of base translations. + :param: prerferedLanguageTranslations An array of prefered language translations. + :param: sharedExpressions An array of shared expressions. */ class func processTranslations(baseTranslations: [LoadedTranslation], preferedLanguageTranslations: [LoadedTranslation], sharedExpressions: [SharedExpression]) -> [Translation] { - - // Find those base translations that are not contained in prefered - // language translations. + /* + Find those base translations that are not contained in prefered language + translations. + */ var uniqueBaseTranslations = baseTranslations if preferedLanguageTranslations.count > 0 { uniqueBaseTranslations = baseTranslations.filter({ @@ -40,10 +45,11 @@ class LoadedTranslationsProcessor { } let translationsReadyToProcess = preferedLanguageTranslations + uniqueBaseTranslations - - // Create array with translations. Array is just a map created from - // loaded translations. There are few types of translations and - // expressions used by the framework. + /* + Create array with translations. Array is just a map created from loaded + translations. There are few types of translations and expressions used + by the framework. + */ return translationsReadyToProcess.map({ switch $0.type { case .Simple: @@ -52,11 +58,12 @@ class LoadedTranslationsProcessor { return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, value: value)]) case .WithExpressions: - // Translation that contains expression. - // Every time when new expressions is about to create, - // the shared expressions are filtered to get expression that - // matches key and if there is a key it is replaced with real - // expression pattern. + /* + Translation that contains expression. + Every time when new expressions is about to create, the shared + expressions are filtered to get expression that matches key and + if there is a key it is replaced with real expression pattern. + */ var expressions = [Expression]() for (key, value) in $0.content as! Dictionary { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key @@ -73,12 +80,14 @@ class LoadedTranslationsProcessor { return Translation(key: $0.key, expressions: [Expression(pattern: $0.key, value: lengthVariations.last!.value, lengthVariations: lengthVariations)]) case .WithExpressionsAndLengthVariations: - // The most advanced translation type. It contains expressions - // that contain length variations or just simple expressions. - // THe job done here is similar to the one in .WithExpressions - // and .WithLengthVariations cases. key is filtered in shared - // expressions to get one of shared expressions and then method - // builds array of variations. + /* + The most advanced translation type. It contains expressions + that contain length variations or just simple expressions. + THe job done here is similar to the one in .WithExpressions + and .WithLengthVariations cases. key is filtered in shared + expressions to get one of shared expressions and then method + builds array of variations. + */ var expressions = [Expression]() for (key, value) in $0.content { let pattern = sharedExpressions.filter({$0.identifier == key}).first?.pattern ?? key diff --git a/Swifternalization/Regex.swift b/Swifternalization/Regex.swift index a9d0def..9c12be7 100644 --- a/Swifternalization/Regex.swift +++ b/Swifternalization/Regex.swift @@ -11,7 +11,7 @@ import Foundation /** Class uses NSRegularExpression internally and simplifies its usability. */ -class Regex { +final class Regex { /** Return match in a string. Optionally with index of capturing group diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 99c097c..7f20307 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -25,7 +25,7 @@ for detecting which value should be used for the given key and value. Before you can get any localized value you have to configure the Swifternalization first. Call `configure:` method first and then you can use other methods. */ -public class Swifternalization { +final public class Swifternalization { /** Shared instance of Swifternalization used internally. */ @@ -98,7 +98,7 @@ public class Swifternalization { If `fittingWidth` is not specified then full length localized string is returned if translation matches the validated `stringValue`. */ - if let localizedValue = translation.validate(stringValue, length: fittingWidth) { + if let localizedValue = translation.validate(stringValue, fittingWidth: fittingWidth) { return localizedValue } } diff --git a/Swifternalization/TranslationsLoader.swift b/Swifternalization/TranslationsLoader.swift index a68fd34..8af250c 100644 --- a/Swifternalization/TranslationsLoader.swift +++ b/Swifternalization/TranslationsLoader.swift @@ -37,12 +37,11 @@ final class TranslationsLoader { :returns: translation type of a dictionary. */ private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType? { - typealias DictWithStrings = Dictionary - typealias DictWithDicts = Dictionary - + // Method counts dicts and strings occurences and return proper type. var dicts = 0 var strings = 0 + // Count every string or dict occurence. for (key, value) in element { if value is String { strings++ @@ -51,6 +50,11 @@ final class TranslationsLoader { } } + /* + If there is only string then check if the keys are length variations or + contain some expressions. IF there is some dict and some strings (or no + strings) then mark it as expressions with length variations. + */ if strings > 0 && dicts == 0 { let key = element.keys.first! let toIndex = advance(key.startIndex, 1) @@ -59,6 +63,7 @@ final class TranslationsLoader { return .WithExpressionsAndLengthVariations } + // Not supported type should be nil. return nil } } From 58a58741c69e33b6edc5d3710434ad53f06ac0be Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 15:10:53 +0200 Subject: [PATCH 21/27] Create operator for getting unique values of two arrays --- .../SharedExpressionsProcessor.swift | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/Swifternalization/SharedExpressionsProcessor.swift b/Swifternalization/SharedExpressionsProcessor.swift index 696fae8..c132939 100644 --- a/Swifternalization/SharedExpressionsProcessor.swift +++ b/Swifternalization/SharedExpressionsProcessor.swift @@ -38,7 +38,6 @@ class SharedExpressionsProcessor { :param: preferedLanguageExpressions Expressions from expressions.json :param: baseLanguageExpressions Expressions from base section of expression.json. - :returns: array of shared expressions for Base and preferred language. */ class func processSharedExpression(preferedLanguage: CountryCode, preferedLanguageExpressions: [SharedExpression], baseLanguageExpressions: [SharedExpression]) -> [SharedExpression] { @@ -53,25 +52,15 @@ class SharedExpressionsProcessor { and this is good as base expression. 2. He forgot to define such expression for prefered language. */ - var uniqueBaseExpressions = baseLanguageExpressions - if preferedLanguageExpressions.count > 0 { - uniqueBaseExpressions = baseLanguageExpressions.filter({ - let pref = $0 - return preferedLanguageExpressions.filter({$0.identifier == pref.identifier}).count == 0 - }) - } + let uniqueBaseExpressions = baseLanguageExpressions [SharedExpression] { + /* + "Get Unique" operator. It helps in getting unique shared expressions from two arrays. + Content of `lhs` array will be checked in terms on uniqueness. The operator does + check is there is any shared expression in `lhs` that is presented in `rhs`. + If element from `lhs` is not in `rhs` then this element is correct and is returned + in new array which is a result of this operation. + */ + var result = lhs + if rhs.count > 0 { + result = lhs.filter({ + let tmp = $0 + return rhs.filter({$0.identifier == tmp.identifier}).count == 0 + }) + } + return result +} From fa3f881fd4c9f85a22ca8dab3f372ae45662ee9d Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 15:19:10 +0200 Subject: [PATCH 22/27] Update comments --- Swifternalization/ExpressionParser.swift | 4 ++- Swifternalization/ExpressionPatternType.swift | 16 ++++++++--- .../InequalityExpressionMatcher.swift | 8 ++++-- .../InequalityExpressionParser.swift | 6 ++-- .../InequalityExtendedExpressionMatcher.swift | 10 +++++-- Swifternalization/InequalitySign.swift | 28 +++++++++++++++---- Swifternalization/InternalPattern.swift | 12 ++++++-- .../RegexExpressionMatcher.swift | 8 ++++-- Swifternalization/RegexExpressionParser.swift | 4 ++- .../SharedBaseExpression.swift | 24 ++++++++++++---- .../SharedPolishExpression.swift | 8 ++++-- Swifternalization/SharedExpression.swift | 16 ++++++++--- 12 files changed, 108 insertions(+), 36 deletions(-) diff --git a/Swifternalization/ExpressionParser.swift b/Swifternalization/ExpressionParser.swift index 83e9e97..ddc5463 100644 --- a/Swifternalization/ExpressionParser.swift +++ b/Swifternalization/ExpressionParser.swift @@ -13,7 +13,9 @@ or `RegexExpressionParser`. Defines methods and properties that are needed to work as parser. */ protocol ExpressionParser { - /// pattern of expression + /** + Pattern of expression. + */ var pattern: ExpressionPattern {get} /** diff --git a/Swifternalization/ExpressionPatternType.swift b/Swifternalization/ExpressionPatternType.swift index 21ab0af..e83ddeb 100644 --- a/Swifternalization/ExpressionPatternType.swift +++ b/Swifternalization/ExpressionPatternType.swift @@ -8,14 +8,22 @@ import Foundation -/// Supported expression types +/** +Supported expression types. +*/ enum ExpressionPatternType: String { - /// works on Int only, e.g. `x<5`, `x=3` + /** + Works with Int/Float, e.g. `x<5`, `x=3`, `x<4.5`. + */ case Inequality = "ie" - /// works on Int only, e.g. `4 InequalitySign { switch self { case .LessThan: return .GreaterThan diff --git a/Swifternalization/InternalPattern.swift b/Swifternalization/InternalPattern.swift index ed5829f..3b81d03 100644 --- a/Swifternalization/InternalPattern.swift +++ b/Swifternalization/InternalPattern.swift @@ -12,12 +12,18 @@ import Foundation Represents internal patterns used by the framework to avoid copy-pastes. */ enum InternalPattern: String { - /// Pattern that matches expressions. + /** + Pattern that matches expressions. + */ case Expression = "(?<=\\{)(.+)(?=\\})" - /// Pattern that matches expression types. + /** + Pattern that matches expression types. + */ case ExpressionPatternType = "(^.{2,3})(?=:)" - /// Pattern that matches key without expression. + /** + Pattern that matches key without expression. + */ case KeyWithoutExpression = "^(.*?)(?=\\{)" } \ No newline at end of file diff --git a/Swifternalization/RegexExpressionMatcher.swift b/Swifternalization/RegexExpressionMatcher.swift index afc2cdb..6a3b0a9 100644 --- a/Swifternalization/RegexExpressionMatcher.swift +++ b/Swifternalization/RegexExpressionMatcher.swift @@ -8,7 +8,9 @@ import Foundation -/// Type that represents pattern with regular expression +/** +Type that represents pattern with regular expression. +*/ internal typealias RegexPattern = String /** @@ -16,7 +18,9 @@ Matcher is responsible for matching expressions that contains regular expressions. */ struct RegexExpressionMatcher: ExpressionMatcher { - /// Expression pattern with regular expression inside. + /** + Expression pattern with regular expression inside. + */ let pattern: RegexPattern /** diff --git a/Swifternalization/RegexExpressionParser.swift b/Swifternalization/RegexExpressionParser.swift index bd00f1e..2a004d7 100644 --- a/Swifternalization/RegexExpressionParser.swift +++ b/Swifternalization/RegexExpressionParser.swift @@ -12,7 +12,9 @@ import Foundation Parser that parses expressions that contains regular expressions. */ class RegexExpressionParser: ExpressionParser { - /// Expression pattern - regular expression. + /** + Expression pattern - regular expression. + */ let pattern: ExpressionPattern /** diff --git a/Swifternalization/Shared Expressions/SharedBaseExpression.swift b/Swifternalization/Shared Expressions/SharedBaseExpression.swift index 30ee66a..706fcf0 100644 --- a/Swifternalization/Shared Expressions/SharedBaseExpression.swift +++ b/Swifternalization/Shared Expressions/SharedBaseExpression.swift @@ -6,23 +6,35 @@ // Copyright (c) 2015 Tomasz Szulc. All rights reserved. // -/// Contains base expressions that matches every country. +/** +Contains base expressions that matches every country. +*/ class SharedBaseExpression: SharedExpressionProtocol { - /// Return expressions that matches every country. + /** + Return expressions that matches every country. + */ static func allExpressions() -> [SharedExpression] { return [ - /// Matches value equals 1. + /** + Matches value equals 1. + */ SharedExpression(identifier: "one", pattern: "ie:x=1"), - /// Matches value greater than 1. + /** + Matches value greater than 1. + */ SharedExpression(identifier: ">one", pattern: "ie:x>1"), - /// Matches value equals 2. + /** + Matches value equals 2. + */ SharedExpression(identifier: "two", pattern: "ie:x=2"), - /// Matches value other than 1. + /** + Matches value other than 1. + */ SharedExpression(identifier: "other", pattern: "exp:(^[^1])|(^\\d{2,})") ] } diff --git a/Swifternalization/Shared Expressions/SharedPolishExpression.swift b/Swifternalization/Shared Expressions/SharedPolishExpression.swift index 7c4d46c..b9a605d 100644 --- a/Swifternalization/Shared Expressions/SharedPolishExpression.swift +++ b/Swifternalization/Shared Expressions/SharedPolishExpression.swift @@ -6,10 +6,14 @@ // Copyright (c) 2015 Tomasz Szulc. All rights reserved. // -/// Contains Polish expressions. +/** +Contains Polish expressions. +*/ class SharedPolishExpression: SharedExpressionProtocol { - /// Return expressions that are valid in Poland. + /** + Return expressions that are valid in Poland. + */ static func allExpressions() -> [SharedExpression] { return [ /** diff --git a/Swifternalization/SharedExpression.swift b/Swifternalization/SharedExpression.swift index 821bf36..2dcb542 100644 --- a/Swifternalization/SharedExpression.swift +++ b/Swifternalization/SharedExpression.swift @@ -16,7 +16,9 @@ localizing app. Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */ protocol SharedExpressionProtocol { - /// Method returns all expressions for class that conform this protocol + /** + Method returns all expressions for class that conform this protocol + */ static func allExpressions() -> [SharedExpression] } @@ -24,13 +26,19 @@ protocol SharedExpressionProtocol { Represents built-in expression and expressions from Expressions.strings file. */ struct SharedExpression { - /// Identifier of expression. + /** + Identifier of expression. + */ let identifier: String - /// Pattern of expression. + /** + Pattern of expression. + */ let pattern: String - /// Creates expression. + /** + Creates expression. + */ init(identifier: String, pattern: String) { self.identifier = identifier self.pattern = pattern From 966b58dba7f3d19988b6c8bdc8fb1068f9dd18c5 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sat, 1 Aug 2015 17:55:58 +0200 Subject: [PATCH 23/27] Cleanup in project structure --- ExpressionsLoader.playground/Contents.swift | 11 -- .../Resources/base.json | 28 ----- .../Resources/expressions.json | 11 -- .../Sources/CountryCode.swift | 8 -- .../ExpressionRepresentationType.swift | 9 -- .../Sources/ExpressionsLoader.swift | 27 ----- .../Sources/ExpressionsPatternType.swift | 11 -- .../Sources/JSONFileLoader.swift | 45 -------- .../Sources/LengthVariation.swift | 18 --- .../Sources/LengthVariationType.swift | 10 -- .../Sources/Processable.swift | 12 -- .../Sources/ProcessableExpression.swift | 18 --- ...ProcessableLengthVariationExpression.swift | 18 --- .../Sources/ProcessableTranslation.swift | 18 --- ...TranslationLengthVariationExpression.swift | 18 --- .../Sources/Regex.swift | 106 ------------------ .../Sources/TranslationLengthVariation.swift | 18 --- .../Sources/TranslationSimple.swift | 18 --- .../Sources/TranslationType.swift | 9 -- .../Sources/TranslationsLoader.swift | 103 ----------------- .../contents.xcplayground | 4 - .../contents.xcworkspacedata | 7 -- .../UserInterfaceState.xcuserstate | Bin 27792 -> 0 bytes .../WorkspaceSettings.xcsettings | 10 -- .../timeline.xctimeline | 6 - Swifternalization.xcodeproj/project.pbxproj | 36 +++--- .../SharedBaseExpression.swift | 0 .../SharedPolishExpression.swift | 0 28 files changed, 18 insertions(+), 561 deletions(-) delete mode 100644 ExpressionsLoader.playground/Contents.swift delete mode 100644 ExpressionsLoader.playground/Resources/base.json delete mode 100644 ExpressionsLoader.playground/Resources/expressions.json delete mode 100644 ExpressionsLoader.playground/Sources/CountryCode.swift delete mode 100644 ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift delete mode 100644 ExpressionsLoader.playground/Sources/ExpressionsLoader.swift delete mode 100644 ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift delete mode 100644 ExpressionsLoader.playground/Sources/JSONFileLoader.swift delete mode 100644 ExpressionsLoader.playground/Sources/LengthVariation.swift delete mode 100644 ExpressionsLoader.playground/Sources/LengthVariationType.swift delete mode 100644 ExpressionsLoader.playground/Sources/Processable.swift delete mode 100644 ExpressionsLoader.playground/Sources/ProcessableExpression.swift delete mode 100644 ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift delete mode 100644 ExpressionsLoader.playground/Sources/ProcessableTranslation.swift delete mode 100644 ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift delete mode 100644 ExpressionsLoader.playground/Sources/Regex.swift delete mode 100644 ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift delete mode 100644 ExpressionsLoader.playground/Sources/TranslationSimple.swift delete mode 100644 ExpressionsLoader.playground/Sources/TranslationType.swift delete mode 100644 ExpressionsLoader.playground/Sources/TranslationsLoader.swift delete mode 100644 ExpressionsLoader.playground/contents.xcplayground delete mode 100644 ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata delete mode 100644 ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/WorkspaceSettings.xcsettings delete mode 100644 ExpressionsLoader.playground/timeline.xctimeline rename Swifternalization/{Shared Expressions => }/SharedBaseExpression.swift (100%) rename Swifternalization/{Shared Expressions => }/SharedPolishExpression.swift (100%) diff --git a/ExpressionsLoader.playground/Contents.swift b/ExpressionsLoader.playground/Contents.swift deleted file mode 100644 index 642a8c5..0000000 --- a/ExpressionsLoader.playground/Contents.swift +++ /dev/null @@ -1,11 +0,0 @@ -//: Playground - noun: a place where people can play - -import UIKit - -let expsBase = ExpressionsLoader.loadExpressions("base") -let expsPL = ExpressionsLoader.loadExpressions("pl") -let translations = TranslationsLoader.loadTranslations("base") - -for translation in translations { - println(translation.key) -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Resources/base.json b/ExpressionsLoader.playground/Resources/base.json deleted file mode 100644 index e0b4524..0000000 --- a/ExpressionsLoader.playground/Resources/base.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "welcome": "welcome", - - "cars": { - "one": "1 car", - "ie:x=2": "2 cars", - "more": "%d cars" - }, - - "forgot-password": { - "@100": "Forgot Password? Help.", - "@200": "Forgot Password? Get password Help.", - "@300": "Forgotten Your Password? Get password Help." - }, - - "car-sentence": { - "one": { - "@100": "one car", - "@200": "just one car", - "@300": "you've got just one car" - }, - - "more": { - "@100": "%d cars", - "@300": "you've got %d cars" - } - } -} diff --git a/ExpressionsLoader.playground/Resources/expressions.json b/ExpressionsLoader.playground/Resources/expressions.json deleted file mode 100644 index 05f0b5d..0000000 --- a/ExpressionsLoader.playground/Resources/expressions.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "base": { - "one": "ie:x=1", - "two": "ie:x=2", - "more": "exp:(^[^1])|(^\\d{2,})" - }, - - "pl": { - "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" - } -} diff --git a/ExpressionsLoader.playground/Sources/CountryCode.swift b/ExpressionsLoader.playground/Sources/CountryCode.swift deleted file mode 100644 index 05a482c..0000000 --- a/ExpressionsLoader.playground/Sources/CountryCode.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -/** -https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + "base" -Represents country codes in framework. -Country codes are used to get correct user device's language. -*/ -public typealias CountryCode = String diff --git a/ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift b/ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift deleted file mode 100644 index 1703518..0000000 --- a/ExpressionsLoader.playground/Sources/ExpressionRepresentationType.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -/** -Represents expressions in the framework. -*/ -public protocol ExpressionRepresentationType { - /// Identifier of expression. - var identifier: String {get} -} diff --git a/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift b/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift deleted file mode 100644 index da50f07..0000000 --- a/ExpressionsLoader.playground/Sources/ExpressionsLoader.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -/** -Used to load content from `expressions.json` file for specified language. -*/ -public final class ExpressionsLoader: JSONFileLoader { - - /** - Loads expressions for specified language. - - :param: countryCode A country code - - :returns: array of loaded expressions. - */ - public class func loadExpressions(countryCode: CountryCode) -> [ProcessableExpression] { - var expressions = [ProcessableExpression]() - if let json = self.load("expressions", fileType: "json", bundle: NSBundle.mainBundle()), - let expressionsDict = json[countryCode] as? Dictionary { - for (identifier, pattern) in expressionsDict { - expressions.append(ProcessableExpression(identifier: identifier, pattern: pattern)) - } - } else { - println("expressions.json file structure is incorrect.") - } - return expressions - } -} diff --git a/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift b/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift deleted file mode 100644 index 7babb8d..0000000 --- a/ExpressionsLoader.playground/Sources/ExpressionsPatternType.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -/** -Represents objects that have expression pattern. -Example an expression that contains just pattern, without things like length -variations. -*/ -public protocol ExpressionPatternType { - /// A pattern. - var pattern: String {get} -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/JSONFileLoader.swift b/ExpressionsLoader.playground/Sources/JSONFileLoader.swift deleted file mode 100644 index e5e4884..0000000 --- a/ExpressionsLoader.playground/Sources/JSONFileLoader.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation - -/** -Simple JSON loader. -*/ -public class JSONFileLoader { - public typealias JSONDictionary = Dictionary - - /** - Load content of file with specified name, type and bundle. - - :param: fileName A name of a file. - :param: fileType A type of a file. - :param: bundle A bundle when file is located. - - :return: JSON or nil. - */ - public final class func load(fileName: String, fileType: String, bundle: NSBundle = NSBundle.mainBundle()) -> JSONDictionary? { - if let fileURL = bundle.URLForResource(fileName, withExtension: fileType) { - return load(fileURL) - } - println("Cannot find file \(fileName).\(fileType).") - return nil - } - - /** - Loads file for specified URL and try to serialize it. - - :params: fileURL url to JSON file. - - :return: Dictionary with content of JSON file or nil. - */ - private class func load(fileURL: NSURL) -> JSONDictionary? { - if let data = NSData(contentsOfURL: fileURL) { - var error: NSError? - if let dictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? JSONDictionary { - return dictionary - } else { - print("Cannot parse JSON. It might be broken.") - } - } - print("Cannot load content of file.") - return nil - } -} diff --git a/ExpressionsLoader.playground/Sources/LengthVariation.swift b/ExpressionsLoader.playground/Sources/LengthVariation.swift deleted file mode 100644 index beadc32..0000000 --- a/ExpressionsLoader.playground/Sources/LengthVariation.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Struct that represents length variation. -*/ -public struct LengthVariation: LengthVariationType { - /// Length - width of a screen. - public let length: Int - - /// localized string. - public let value: String - - /// Create length variation object. - public init(length: Int, value: String) { - self.length = length - self.value = value - } -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/LengthVariationType.swift b/ExpressionsLoader.playground/Sources/LengthVariationType.swift deleted file mode 100644 index 0959ff2..0000000 --- a/ExpressionsLoader.playground/Sources/LengthVariationType.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -/// Represents length variation. -public protocol LengthVariationType { - /// Length of a screen. - var length: Int {get} - - /// Localized value. - var value: String {get} -} diff --git a/ExpressionsLoader.playground/Sources/Processable.swift b/ExpressionsLoader.playground/Sources/Processable.swift deleted file mode 100644 index 4deef2a..0000000 --- a/ExpressionsLoader.playground/Sources/Processable.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -/** -Specifies objects that need processing and are not final objects that can be -used in the framework. An example of such object might be `ProcessableExpression` -that is not ready to be used because it has to be converted into normal kind of -expression that has its fields correctly filled. - -Another good example is `TranslationType` object that contains expression or few -that need to be processed. -*/ -protocol Processable {} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/ProcessableExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableExpression.swift deleted file mode 100644 index a0c0bac..0000000 --- a/ExpressionsLoader.playground/Sources/ProcessableExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents loaded expression that will be processed later. -*/ -public struct ProcessableExpression: ExpressionRepresentationType, ExpressionPatternType, Processable { - /// Identifier of expression. - public let identifier: String - - /// Pattern of expression. - public let pattern: String - - /// Creates expression. - public init(identifier: String, pattern: String) { - self.identifier = identifier - self.pattern = pattern - } -} diff --git a/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift deleted file mode 100644 index 77c84f5..0000000 --- a/ExpressionsLoader.playground/Sources/ProcessableLengthVariationExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents processable expression with length variations. -*/ -public struct ProcessableLengthVariationExpression: ExpressionRepresentationType, Processable { - /// Identifier of expression. - public var identifier: String - - /// Array of length variations - var variations: [LengthVariation] - - /// Creates instance of the class. - public init(identifier: String, variations: [LengthVariation]) { - self.identifier = identifier - self.variations = variations - } -} diff --git a/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift b/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift deleted file mode 100644 index 6c9725d..0000000 --- a/ExpressionsLoader.playground/Sources/ProcessableTranslation.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents translation which contains expressions that are not processed yet. -*/ -public struct ProcessableTranslation: TranslationType, Processable { - /// Key that identifies translation. - public let key: String - - /// Array with loaded expressions. - let loadedExpressions: [ProcessableExpression] - - /// Creates instances of the class. - public init(key: String, loadedExpressions: [ProcessableExpression]) { - self.key = key - self.loadedExpressions = loadedExpressions - } -} diff --git a/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift b/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift deleted file mode 100644 index 092aa1f..0000000 --- a/ExpressionsLoader.playground/Sources/ProcessableTranslationLengthVariationExpression.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents translation which contains expressions with length variations. -*/ -public struct ProcessableTranslationLengthVariationExpression: TranslationType, Processable { - /// Key that identifies translation. - public let key: String - - /// Array with expressions. - let expressions: [ProcessableLengthVariationExpression] - - /// Creates instances of the class. - public init(key: String, expressions: [ProcessableLengthVariationExpression]) { - self.key = key - self.expressions = expressions - } -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/Regex.swift b/ExpressionsLoader.playground/Sources/Regex.swift deleted file mode 100644 index 91717ca..0000000 --- a/ExpressionsLoader.playground/Sources/Regex.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// Regex.swift -// Swifternalization -// -// Created by Tomasz Szulc on 27/06/15. -// Copyright (c) 2015 Tomasz Szulc. All rights reserved. -// - -import Foundation - -/** -Class uses NSRegularExpression internally and simplifies its usability. -*/ -public class Regex { - - /** - Return match in a string. Optionally with index of capturing group - - :param: str A string that will be matched. - :param: pattern A regex pattern. - - :returns: `String` that matches pattern or nil. - */ - public class func matchInString(str: String, pattern: String, capturingGroupIdx: Int?) -> String? { - var resultString: String? - - let range = NSMakeRange(0, count(str)) - regexp(pattern)?.enumerateMatchesInString(str, options: nil, range: range, usingBlock: { result, flags, stop in - if let result = result { - if let capturingGroupIdx = capturingGroupIdx where result.numberOfRanges > capturingGroupIdx { - resultString = self.substring(str, range: result.rangeAtIndex(capturingGroupIdx)) - } else { - resultString = self.substring(str, range: result.range) - } - } - }) - - return resultString - } - - - /** - Return first match in a string. - - :param: str A string that will be matched. - :param: pattern A regexp pattern. - - :returns: `String` that matches pattern or nil. - */ - class func firstMatchInString(str: String, pattern: String) -> String? { - if let result = regexp(pattern)?.firstMatchInString(str, options: .ReportCompletion, range: NSMakeRange(0, count(str))) { - return substring(str, range: result.range) - } - return nil - } - - /** - Return all matches in a string. - - :param: str A string that will be matched. - :param: pattern A regexp pattern. - - :returns: Array of `Strings`s. If nothing found empty array is returned. - */ - class func matchesInString(str: String, pattern: String) -> [String] { - var matches = [String]() - if let results = regexp(pattern)?.matchesInString(str, options: .ReportCompletion, range: NSMakeRange(0, count(str))) as? [NSTextCheckingResult] { - for result in results { - matches.append(substring(str, range: result.range)) - } - } - - return matches - } - - /** - Returns new `NSRegularExpression` object. - - :param: pattern A regexp pattern. - - :returns: `NSRegularExpression` object or nil if it cannot be created. - */ - private class func regexp(pattern: String) -> NSRegularExpression? { - var error: NSError? = nil - var regexp = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error) - if error != nil { - println(error!) - } - return regexp - } - - /** - Method that substring string with passed range. - - :param: str A string that is source of substraction. - :param: range A range that tells which part of `str` will be substracted. - - :returns: A string contained in `range`. - */ - private class func substring(str: String, range: NSRange) -> String { - let startRange = advance(str.startIndex, range.location) - let endRange = advance(startRange, range.length) - - return str.substringWithRange(Range(start: startRange, end: endRange)) - } -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift b/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift deleted file mode 100644 index 95688c1..0000000 --- a/ExpressionsLoader.playground/Sources/TranslationLengthVariation.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Class represents translation which contains key and length variations. -*/ -public struct TranslationLengthVariation: TranslationType { - /// Key that identifies this translation. - public let key: String - - /// list of variations. - let variations: [LengthVariation] - - /// Creates translation object - public init(key: String, variations: [LengthVariation]) { - self.key = key - self.variations = variations - } -} \ No newline at end of file diff --git a/ExpressionsLoader.playground/Sources/TranslationSimple.swift b/ExpressionsLoader.playground/Sources/TranslationSimple.swift deleted file mode 100644 index 83305cd..0000000 --- a/ExpressionsLoader.playground/Sources/TranslationSimple.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -/** -Represents simple key-value translation. -*/ -public struct TranslationSimple: TranslationType { - /// Key that identifies translation. - public let key: String - - /// Localized string. - public let value: String - - /// Creates instance of the class. - public init(key: String, value: String) { - self.key = key - self.value = value - } -} diff --git a/ExpressionsLoader.playground/Sources/TranslationType.swift b/ExpressionsLoader.playground/Sources/TranslationType.swift deleted file mode 100644 index 653d598..0000000 --- a/ExpressionsLoader.playground/Sources/TranslationType.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -/** -Represents translation. -*/ -public protocol TranslationType { - /// Key that identifies translation. - var key: String {get} -} diff --git a/ExpressionsLoader.playground/Sources/TranslationsLoader.swift b/ExpressionsLoader.playground/Sources/TranslationsLoader.swift deleted file mode 100644 index 83a97ef..0000000 --- a/ExpressionsLoader.playground/Sources/TranslationsLoader.swift +++ /dev/null @@ -1,103 +0,0 @@ -import Foundation - -/** -Represents translations loader. -Class is looking for json file named as country code. -It parse such file. -*/ -public final class TranslationsLoader: JSONFileLoader { - - typealias DictWithStrings = Dictionary - typealias DictWithDicts = Dictionary - - enum ElementType { - case NotSupported - case TranslationWithLengthVariations - case TranslationWithLoadedExpressions - case TranslationWithLengthVariationsAndLoadedExpressions - } - - /** - Method loads a file and parses it. - - :params: countryCode A country code. - - :return: translations parsed from the file. - */ - public class func loadTranslations(countryCode: CountryCode) -> [TranslationType] { - var loadedTranslations = [TranslationType]() - let json = self.load(countryCode, fileType: "json", bundle: NSBundle.mainBundle()) - if json == nil { return [TranslationType]() } - - for (translationKey, value) in json! { - if let translationValue = value as? String { - loadedTranslations.append(TranslationSimple(key: translationKey, value: translationValue)) - } else { - let dictionary = value as! JSONDictionary - switch detectElementType(dictionary) { - case .TranslationWithLengthVariations: - let variations = parseLengthVariations(dictionary as! DictWithStrings) - loadedTranslations.append(TranslationLengthVariation(key: translationKey, variations: variations)) - - case .TranslationWithLoadedExpressions: - let expressions = parseExpressions(dictionary as! DictWithStrings) - loadedTranslations.append(ProcessableTranslation(key: translationKey, loadedExpressions: expressions)) - - case .TranslationWithLengthVariationsAndLoadedExpressions: - var expressions = [ProcessableLengthVariationExpression]() - for (expressionIdentifier, lengthVariationsDict) in dictionary as! DictWithDicts { - let variations = parseLengthVariations(lengthVariationsDict) - expressions.append(ProcessableLengthVariationExpression(identifier: expressionIdentifier, variations: variations)) - } - loadedTranslations.append(ProcessableTranslationLengthVariationExpression(key: translationKey, expressions: expressions)) - - case .NotSupported: - // Do nothing - continue - } - } - } - - return loadedTranslations - } - - private class func parseLengthVariations(dict: DictWithStrings) -> [LengthVariation] { - var variations = [LengthVariation]() - for (key, translationValue) in dict { - let numberValue = parseNumberFromLengthVariation(key) - variations.append(LengthVariation(length: numberValue, value: translationValue)) - } - return variations - } - - private class func parseExpressions(dict: DictWithStrings) -> [ProcessableExpression] { - var expressions = [ProcessableExpression]() - for (expressionKey, translationValue) in dict { - expressions.append(ProcessableExpression(identifier: expressionKey, pattern: translationValue)) - } - return expressions - } - - private class func detectElementType(element: JSONDictionary) -> ElementType { - - if element is DictWithStrings { - if let key = element.keys.first { - let toIndex = advance(key.startIndex, 1) - let firstCharacter = key.substringToIndex(toIndex) - if firstCharacter == "@" { - return .TranslationWithLengthVariations - } else { - return .TranslationWithLoadedExpressions - } - } - } else if element is DictWithDicts { - return .TranslationWithLengthVariationsAndLoadedExpressions - } - - return .NotSupported - } - - private class func parseNumberFromLengthVariation(string: String) -> Int { - return (Regex.matchInString(string, pattern: "@(\\d+)", capturingGroupIdx: 1)! as NSString).integerValue - } -} diff --git a/ExpressionsLoader.playground/contents.xcplayground b/ExpressionsLoader.playground/contents.xcplayground deleted file mode 100644 index 5da2641..0000000 --- a/ExpressionsLoader.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata b/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index c706b0d..0000000 --- a/ExpressionsLoader.playground/playground.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate b/ExpressionsLoader.playground/playground.xcworkspace/xcuserdata/tomkowz.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index f98a0d1a491086015b2c3be74c87396706570b72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27792 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 - - - - HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges - - SnapshotAutomaticallyBeforeSignificantChanges - - - diff --git a/ExpressionsLoader.playground/timeline.xctimeline b/ExpressionsLoader.playground/timeline.xctimeline deleted file mode 100644 index bf468af..0000000 --- a/ExpressionsLoader.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Swifternalization.xcodeproj/project.pbxproj b/Swifternalization.xcodeproj/project.pbxproj index 7c35ba9..7bcf1ed 100644 --- a/Swifternalization.xcodeproj/project.pbxproj +++ b/Swifternalization.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D140F431B56D03D00359143 /* RandomNumbers.swift */; }; + 6D19ABDC1B6D23730063FE1E /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D19ABDA1B6D23730063FE1E /* SharedBaseExpression.swift */; }; + 6D19ABDD1B6D23730063FE1E /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D19ABDA1B6D23730063FE1E /* SharedBaseExpression.swift */; }; + 6D19ABDE1B6D23730063FE1E /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D19ABDB1B6D23730063FE1E /* SharedPolishExpression.swift */; }; + 6D19ABDF1B6D23730063FE1E /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D19ABDB1B6D23730063FE1E /* SharedPolishExpression.swift */; }; + 6D477E701B6D258F001F1904 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D477E6F1B6D258F001F1904 /* SharedExpression.swift */; }; + 6D477E711B6D258F001F1904 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D477E6F1B6D258F001F1904 /* SharedExpression.swift */; }; 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */; }; 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */; }; @@ -26,8 +32,6 @@ 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */; }; 6D5BA5F31B651809000D7E49 /* LengthVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB3CC6A1B5EBDA600A1220F /* LengthVariation.swift */; }; 6D5BA5FB1B65253B000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; - 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; - 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */; }; 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */; }; 6D5BA6051B653935000D7E49 /* SharedExpressionsProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */; }; 6D5BA6091B655F1D000D7E49 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BA6081B655F1D000D7E49 /* Expression.swift */; }; @@ -73,10 +77,6 @@ 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5004641B3EF92600A54B36 /* Swifternalization.swift */; }; 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */; }; - 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; - 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; - 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */; }; - 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */; }; 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */; }; 6DBB6C981B4077FA002F39A3 /* SharedPolishExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */; }; 6DD3B93A1B5ED35200C79EAC /* base.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD3B9371B5ED35200C79EAC /* base.json */; }; @@ -101,6 +101,9 @@ /* Begin PBXFileReference section */ 6D140F431B56D03D00359143 /* RandomNumbers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomNumbers.swift; sourceTree = ""; }; + 6D19ABDA1B6D23730063FE1E /* SharedBaseExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedBaseExpression.swift; path = Swifternalization/SharedBaseExpression.swift; sourceTree = SOURCE_ROOT; }; + 6D19ABDB1B6D23730063FE1E /* SharedPolishExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedPolishExpression.swift; path = Swifternalization/SharedPolishExpression.swift; sourceTree = SOURCE_ROOT; }; + 6D477E6F1B6D258F001F1904 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = Swifternalization/SharedExpression.swift; sourceTree = SOURCE_ROOT; }; 6D4C4EAE1B6AA48F00B7839A /* LoadedTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslation.swift; sourceTree = ""; }; 6D4C4EB21B6AAE3200B7839A /* LoadedTranslationsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationsProcessor.swift; sourceTree = ""; }; 6D4C4EB71B6AB6DE00B7839A /* LoadedTranslationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedTranslationType.swift; sourceTree = ""; }; @@ -114,7 +117,6 @@ 6D5004641B3EF92600A54B36 /* Swifternalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifternalization.swift; sourceTree = ""; }; 6D5BA5EF1B651796000D7E49 /* TranslationsLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoaderTests.swift; sourceTree = ""; }; 6D5BA5FA1B65253B000D7E49 /* SharedExpressionsProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessor.swift; sourceTree = ""; }; - 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedExpression.swift; path = ../SharedExpression.swift; sourceTree = ""; }; 6D5BA6031B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedExpressionsProcessorTests.swift; sourceTree = ""; }; 6D5BA6081B655F1D000D7E49 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 6D6282931B3F052B00E65FCD /* TranslatablePairTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslatablePairTests.swift; sourceTree = ""; }; @@ -145,8 +147,6 @@ 6DB3CC731B5EBDA600A1220F /* TranslationsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationsLoader.swift; sourceTree = ""; }; 6DB3CC8F1B5EC29600A1220F /* ExpressionPatternType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionPatternType.swift; sourceTree = ""; }; 6DBB6C681B4040F0002F39A3 /* InternalPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPattern.swift; sourceTree = ""; }; - 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpression.swift; sourceTree = ""; }; - 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpression.swift; sourceTree = ""; }; 6DBB6C951B4076E8002F39A3 /* SharedBaseExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedBaseExpressionTests.swift; sourceTree = ""; }; 6DBB6C971B4077FA002F39A3 /* SharedPolishExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedPolishExpressionTests.swift; sourceTree = ""; }; 6DD3B9371B5ED35200C79EAC /* base.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = base.json; sourceTree = ""; }; @@ -286,9 +286,9 @@ 6DBB6C8B1B40767B002F39A3 /* Shared Expressions */ = { isa = PBXGroup; children = ( - 6D5BA5FF1B6526F0000D7E49 /* SharedExpression.swift */, - 6DBB6C8C1B40768A002F39A3 /* SharedBaseExpression.swift */, - 6DBB6C8E1B40768A002F39A3 /* SharedPolishExpression.swift */, + 6D19ABDA1B6D23730063FE1E /* SharedBaseExpression.swift */, + 6D477E6F1B6D258F001F1904 /* SharedExpression.swift */, + 6D19ABDB1B6D23730063FE1E /* SharedPolishExpression.swift */, ); path = "Shared Expressions"; sourceTree = ""; @@ -436,26 +436,26 @@ 6D5004661B3EF92600A54B36 /* Swifternalization.swift in Sources */, 6D62829A1B3F17CA00E65FCD /* Regex.swift in Sources */, 6D6282B51B3F3C4100E65FCD /* InequalitySign.swift in Sources */, - 6DBB6C8F1B40768A002F39A3 /* SharedBaseExpression.swift in Sources */, 6D4C4EBD1B6ABE0700B7839A /* Translation.swift in Sources */, 6DB3CC901B5EC29600A1220F /* ExpressionPatternType.swift in Sources */, 6DB3CC7A1B5EBDA600A1220F /* LengthVariation.swift in Sources */, 6DB3CC751B5EBDA600A1220F /* CountryCode.swift in Sources */, 6D4C4EB31B6AAE3200B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6D4C4EAF1B6AA48F00B7839A /* LoadedTranslation.swift in Sources */, - 6D5BA6001B6526F0000D7E49 /* SharedExpression.swift in Sources */, 6D62829F1B3F1FA000E65FCD /* InequalityExpressionParser.swift in Sources */, + 6D19ABDC1B6D23730063FE1E /* SharedBaseExpression.swift in Sources */, 6D6282A51B3F24A800E65FCD /* ExpressionParser.swift in Sources */, 6D4C4EB81B6AB6DE00B7839A /* LoadedTranslationType.swift in Sources */, 6DB3CC771B5EBDA600A1220F /* SharedExpressionLoader.swift in Sources */, 6DBB6C691B4040F0002F39A3 /* InternalPattern.swift in Sources */, + 6D19ABDE1B6D23730063FE1E /* SharedPolishExpression.swift in Sources */, 6D5BA6091B655F1D000D7E49 /* Expression.swift in Sources */, - 6DBB6C911B40768A002F39A3 /* SharedPolishExpression.swift in Sources */, 6D6282C51B3F4ED100E65FCD /* RegexExpressionParser.swift in Sources */, 6D6282C71B3F4F2100E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DB3CC791B5EBDA600A1220F /* JSONFileLoader.swift in Sources */, 6D6282B11B3F3C1E00E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, 6D6282BA1B3F40AF00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, + 6D477E701B6D258F001F1904 /* SharedExpression.swift in Sources */, 6DB3CC831B5EBDA600A1220F /* TranslationsLoader.swift in Sources */, 6D6282AC1B3F327800E65FCD /* InequalityExpressionMatcher.swift in Sources */, ); @@ -471,6 +471,7 @@ 6D6282C11B3F43C600E65FCD /* InequalityExtendedExpressionMatcherTests.swift in Sources */, 6DD3B9451B5ED58A00C79EAC /* SharedExpressionLoader.swift in Sources */, 6D75E6D41B6B6CFE00B370DC /* LoadedTranslationsProcessorTests.swift in Sources */, + 6D19ABDF1B6D23730063FE1E /* SharedPolishExpression.swift in Sources */, 6D50045B1B3EF91600A54B36 /* SwifternalizationTests.swift in Sources */, 6D6282A61B3F25B900E65FCD /* InequalityExpressionParser.swift in Sources */, 6D6282B81B3F3E2200E65FCD /* InequalitySign.swift in Sources */, @@ -481,7 +482,6 @@ 6DD3B94B1B5ED65A00C79EAC /* NSBundle+TestExtension.swift in Sources */, 6D6282C81B3F4F6400E65FCD /* RegexExpressionMatcher.swift in Sources */, 6DD3B9421B5ED3F000C79EAC /* JSONFileLoader.swift in Sources */, - 6D5BA6011B65271A000D7E49 /* SharedExpression.swift in Sources */, 6D4C4EC51B6AD01300B7839A /* LoadedTranslationsProcessor.swift in Sources */, 6DBB6C6A1B40412D002F39A3 /* InternalPattern.swift in Sources */, 6D62F9FC1B6B808400596A7C /* ExpressionJSONs.swift in Sources */, @@ -490,14 +490,13 @@ 6D4C4EB01B6AA54800B7839A /* LoadedTranslation.swift in Sources */, 6DB3CC911B5EC29E00A1220F /* ExpressionPatternType.swift in Sources */, 6D6282941B3F052B00E65FCD /* TranslatablePairTests.swift in Sources */, - 6DBB6C941B40769F002F39A3 /* SharedPolishExpression.swift in Sources */, 6DBB6C521B401B8A002F39A3 /* Swifternalization.swift in Sources */, 6D5BA5F01B651796000D7E49 /* TranslationsLoaderTests.swift in Sources */, 6D4C4EC41B6ACF2C00B7839A /* Translation.swift in Sources */, 6D6282BF1B3F42CA00E65FCD /* InequalityExpressionMatcherTests.swift in Sources */, - 6DBB6C921B40769F002F39A3 /* SharedBaseExpression.swift in Sources */, 6D5BA6041B6537D5000D7E49 /* SharedExpressionsProcessorTests.swift in Sources */, 6D6282B31B3F3C2800E65FCD /* InequalityExtendedExpressionParser.swift in Sources */, + 6D19ABDD1B6D23730063FE1E /* SharedBaseExpression.swift in Sources */, 6D140F441B56D03D00359143 /* RandomNumbers.swift in Sources */, 6D6282AD1B3F327C00E65FCD /* InequalityExpressionMatcher.swift in Sources */, 6D4C4EC61B6AD01D00B7839A /* Expression.swift in Sources */, @@ -509,6 +508,7 @@ 6D5BA5F11B6517A6000D7E49 /* TranslationsLoader.swift in Sources */, 6D6282BB1B3F41DB00E65FCD /* InequalityExtendedExpressionMatcher.swift in Sources */, 6D62F9FE1B6B824300596A7C /* TranslationJSONs.swift in Sources */, + 6D477E711B6D258F001F1904 /* SharedExpression.swift in Sources */, 6DBB6C961B4076E8002F39A3 /* SharedBaseExpressionTests.swift in Sources */, 6D62829B1B3F17FE00E65FCD /* Regex.swift in Sources */, 6D6282CD1B3F52AD00E65FCD /* RegexExpressionMatcherTests.swift in Sources */, diff --git a/Swifternalization/Shared Expressions/SharedBaseExpression.swift b/Swifternalization/SharedBaseExpression.swift similarity index 100% rename from Swifternalization/Shared Expressions/SharedBaseExpression.swift rename to Swifternalization/SharedBaseExpression.swift diff --git a/Swifternalization/Shared Expressions/SharedPolishExpression.swift b/Swifternalization/SharedPolishExpression.swift similarity index 100% rename from Swifternalization/Shared Expressions/SharedPolishExpression.swift rename to Swifternalization/SharedPolishExpression.swift From e6367cbd911f3fd7b524e06703defd075288a6b6 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 2 Aug 2015 11:14:48 +0200 Subject: [PATCH 24/27] Update README.md and update docs --- README.md | 587 ++++++++++------- Swifternalization/CountryCode.swift | 2 +- Swifternalization/Expression.swift | 2 +- Swifternalization/JSONFileLoader.swift | 2 +- .../SharedExpressionsProcessor.swift | 14 +- Swifternalization/Swifternalization.swift | 23 +- gendocs.sh => bin/gendocs.sh | 0 docs/framework/Classes.html | 231 ++++--- docs/framework/Classes/Expression.html | 562 ---------------- .../Classes/InequalityExpressionParser.html | 61 +- .../InequalityExtendedExpressionParser.html | 57 +- .../JSONFileLoader.html} | 251 +++----- .../Classes/LoadedTranslationsProcessor.html | 319 +++++++++ docs/framework/Classes/Regex.html | 57 +- .../Classes/RegexExpressionParser.html | 57 +- .../Classes/SharedBaseExpression.html | 57 +- .../SharedExpressionsConfigurator.html | 511 --------------- .../Classes/SharedExpressionsLoader.html | 235 +++++++ .../Classes/SharedExpressionsProcessor.html | 332 ++++++++++ .../Classes/SharedPolishExpression.html | 57 +- docs/framework/Classes/Swifternalization.html | 516 +++++++-------- .../Classes/Swifternalization/Static.html | 199 ------ .../framework/Classes/TranslationsLoader.html | 247 +++++++ docs/framework/Enums.html | 83 ++- ...onType.html => ExpressionPatternType.html} | 99 +-- docs/framework/Enums/InequalitySign.html | 109 ++-- docs/framework/Enums/InternalPattern.html | 77 ++- .../Enums/LoadedTranslationType.html | 327 ++++++++++ docs/framework/Functions.html | 213 ++++++ docs/framework/Protocols.html | 94 ++- .../Protocols/ExpressionMatcher.html | 57 +- .../framework/Protocols/ExpressionParser.html | 59 +- .../Protocols/SharedExpressionProtocol.html | 61 +- docs/framework/Structs.html | 175 ++++- .../Expression.html} | 199 +++--- .../Structs/InequalityExpressionMatcher.html | 59 +- .../InequalityExtendedExpressionMatcher.html | 63 +- .../LengthVariation.html} | 90 +-- .../LoadedTranslation.html} | 101 +-- .../Structs/RegexExpressionMatcher.html | 57 +- docs/framework/Structs/SharedExpression.html | 112 ++-- docs/framework/Structs/Translation.html | 304 +++++++++ docs/framework/Typealiases.html | 182 ++---- .../Contents/Resources/Documents/Classes.html | 231 ++++--- .../Documents/Classes/Expression.html | 562 ---------------- .../Classes/InequalityExpressionParser.html | 61 +- .../InequalityExtendedExpressionParser.html | 57 +- .../Documents/Classes/JSONFileLoader.html} | 251 +++----- .../Classes/LoadedTranslationsProcessor.html | 319 +++++++++ .../Resources/Documents/Classes/Regex.html | 57 +- .../Classes/RegexExpressionParser.html | 57 +- .../Classes/SharedBaseExpression.html | 57 +- .../SharedExpressionsConfigurator.html | 511 --------------- .../Classes/SharedExpressionsLoader.html | 235 +++++++ .../Classes/SharedExpressionsProcessor.html | 332 ++++++++++ .../Classes/SharedPolishExpression.html | 57 +- .../Documents/Classes/Swifternalization.html | 516 +++++++-------- .../Classes/Swifternalization/Static.html | 199 ------ .../Documents/Classes/TranslationsLoader.html | 247 +++++++ .../Contents/Resources/Documents/Enums.html | 83 ++- ...onType.html => ExpressionPatternType.html} | 99 +-- .../Documents/Enums/InequalitySign.html | 109 ++-- .../Documents/Enums/InternalPattern.html | 77 ++- .../Enums/LoadedTranslationType.html | 327 ++++++++++ .../Resources/Documents/Functions.html | 213 ++++++ .../Resources/Documents/Protocols.html | 94 ++- .../Protocols/ExpressionMatcher.html | 57 +- .../Documents/Protocols/ExpressionParser.html | 59 +- .../Protocols/SharedExpressionProtocol.html | 61 +- .../Contents/Resources/Documents/Structs.html | 175 ++++- .../Documents/Structs/Expression.html} | 199 +++--- .../Structs/InequalityExpressionMatcher.html | 59 +- .../InequalityExtendedExpressionMatcher.html | 63 +- .../LengthVariation.html} | 90 +-- .../Documents/Structs/LoadedTranslation.html} | 101 +-- .../Structs/RegexExpressionMatcher.html | 57 +- .../Documents/Structs/SharedExpression.html | 112 ++-- .../Documents/Structs/Translation.html | 304 +++++++++ .../Resources/Documents/Typealiases.html | 182 ++---- .../Contents/Resources/Documents/index.html | 608 ++++++++++++------ .../Contents/Resources/docSet.dsidx | Bin 53248 -> 53248 bytes docs/framework/docsets/Swifternalization.tgz | Bin 77162 -> 81217 bytes docs/framework/index.html | 608 ++++++++++++------ docs/public/Classes.html | 31 +- docs/public/Classes/Swifternalization.html | 247 ++++--- docs/public/Typealiases.html | 4 +- .../Contents/Resources/Documents/Classes.html | 31 +- .../Documents/Classes/Swifternalization.html | 247 ++++--- .../Resources/Documents/Typealiases.html | 4 +- .../Contents/Resources/Documents/index.html | 553 ++++++++++------ .../Contents/Resources/docSet.dsidx | Bin 12288 -> 12288 bytes .../docsets/Swifternalization Public API.tgz | Bin 49841 -> 52704 bytes docs/public/index.html | 553 ++++++++++------ example/base.json | 28 - example/expressions.json | 11 - example/pl.json | 28 - 96 files changed, 9377 insertions(+), 6746 deletions(-) rename gendocs.sh => bin/gendocs.sh (100%) delete mode 100644 docs/framework/Classes/Expression.html rename docs/framework/{docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/LocalizableFilesLoader.html => Classes/JSONFileLoader.html} (58%) create mode 100644 docs/framework/Classes/LoadedTranslationsProcessor.html delete mode 100644 docs/framework/Classes/SharedExpressionsConfigurator.html create mode 100644 docs/framework/Classes/SharedExpressionsLoader.html create mode 100644 docs/framework/Classes/SharedExpressionsProcessor.html delete mode 100644 docs/framework/Classes/Swifternalization/Static.html create mode 100644 docs/framework/Classes/TranslationsLoader.html rename docs/framework/Enums/{ExpressionType.html => ExpressionPatternType.html} (78%) create mode 100644 docs/framework/Enums/LoadedTranslationType.html create mode 100644 docs/framework/Functions.html rename docs/framework/{docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/TranslatablePair.html => Structs/Expression.html} (67%) rename docs/framework/{Enums/StringsFileType.html => Structs/LengthVariation.html} (73%) rename docs/framework/{docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/KeyValue.html => Structs/LoadedTranslation.html} (72%) create mode 100644 docs/framework/Structs/Translation.html delete mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Expression.html rename docs/framework/{Classes/LocalizableFilesLoader.html => docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/JSONFileLoader.html} (58%) create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/LoadedTranslationsProcessor.html delete mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsConfigurator.html create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsLoader.html create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsProcessor.html delete mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization/Static.html create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/TranslationsLoader.html rename docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/{ExpressionType.html => ExpressionPatternType.html} (78%) create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/LoadedTranslationType.html create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Functions.html rename docs/framework/{Structs/TranslatablePair.html => docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Expression.html} (67%) rename docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/{Enums/StringsFileType.html => Structs/LengthVariation.html} (73%) rename docs/framework/{Protocols/KeyValue.html => docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LoadedTranslation.html} (72%) create mode 100644 docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Translation.html delete mode 100644 example/base.json delete mode 100644 example/expressions.json delete mode 100644 example/pl.json diff --git a/README.md b/README.md index c7431fe..eb8f1bd 100644 --- a/README.md +++ b/README.md @@ -2,146 +2,166 @@ ![CocoaPods Status](https://img.shields.io/cocoapods/v/Swifternalization.svg) -Swifternalization is library that helps in localizing apps. It is written in Swift. +# Swifternalization +Swift library that helps in localizing apps in a different, better, simpler, more powerful way than system localization does. It uses json files instead of strings files. # Features -- [x] Pluralization support - Avoids using .stringdicts -- [x] Expressions - inequality and regular expressions in Localizable.strings -- [x] Shared expressions -- [x] Built-in expressions -- [x] Works similarly to NSLocalizedString() macro -- [x] Uses Localizable.strings file as NSLocalizedString() macro does +- [x] Pluralization support - Without using *stringdict* files +- [x] Length variations support - Supported since iOS 8.0 (instead of iOS 9.0 like system does) and avoids using *stringsdict* files +- [x] Expressions - inequality and regular expressions +- [x] Shared Expressions +- [x] Built-in Expressions +- [x] Works similarly to NSLocalizedString() +- [x] Uses JSON files to minimize boilerplate code - [x] Comprehensive Unit Test Coverage - [x] Full documentation -# Swifternalization -Swifternalization helps in localizing apps in a smarter way. It has been created because of necessary to solve Polish language internalization problems but it is universal and works with every language. +# Table of Contents +- [Introduction](#introdcution) +- [Practical Usage Example](#practical-usage-example) +- [Features](#features) + - [Pluralization](#pluralization) + - [Length variation](#length-variation) +- [Expressions](#expressions) + - [Inequality Expressions](#inequality-expressions) + - [Inequality Extended Expressions](#inequality-extended-expressions) + - [Regex Expressions](#regex-expressions) + - [Shared Expressions](#shared-expressions) + - [Built-in Expressions](#built-in-expressions) +- [Getting Started](#getting-started) + - [Documentation](#documentation) + - [Installation](#installation) + - [Configuration](#configuration) + - [Creating file with shared expressions](#creating-file-with-shared-expressions) + - [Creating file with localization per country](#creating-file-with-localization-per-country) + - [Getting localized string](#getting-localized-string) +- [Contribution](#contribution) +- [Swift 2](#swift-2) +- [Things To Do](#things-to-do) +- [License](#license) -## Installation -With CocoaPods: +## Introduction +Swifternalization helps in localizing apps in a smarter way. It has been created because of necessity to solve Polish language internalization problems but it is universal and works with every language very well. - pod 'Swifternalization', '~> 1.1' +It uses JSON files and expressions that avoid writing code to handle some cases that you have to write when not using this framework. It makes localizing process simpler. -Without CocoaPods: -If you want to integrate it with your project just import files from *Swifternalization/Swifternalization* directory. +## Practical Usage Example +Description of practical usage example will use things that are covered later in the document so keep reading it to the end and then read about details/features presented here. -## Documentation -Swifternalization documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is great for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private. +### Problem +Let's assume the app supports English and Polish languages. Naturally app contains two *Localizable.strings* files. One is for *Base* localization which contains *English* translation and one is *Polish* language. -You can find Public API and Full documentation with docset here in [docs](https://github.com/tomkowz/Swifternalization/tree/master/docs) directory. +App displays label with information which says when object from the backend has been updated for the last time, e.g. "2 minutes ago", "3 hours ago", "1 minute ago", etc. -It is also hosted on [my blog](http://szulctomasz.com): -- [Public API documentation](http://szulctomasz.com/docs/swifternalization/public/) -- [Full API documentation](http://szulctomasz.com/docs/swifternalization/framework/) - -Docsets: -- [Public API docset](http://szulctomasz.com/docs/swifternalization/public/docsets/Swifternalization%20Public%20API.docset.zip) -- [Full API docset](http://szulctomasz.com/docs/swifternalization/framework/docsets/Swifternalization.docset.zip) +### Analysis +The label displays number and a hour/minute/second word in singular or plural forms with "ago" suffix. Different languages handles pluralization/cardinal numbering in slight different ways. Here we need to support English and Polish languages. -## Real Example +In English there are just two cases to cover per hour/minute/second word: -Let's take a look on practical usage of Swifternalization. App supports both English and Polish languages. Naturally app contains two *Localizable.strings* files - one is Base for English (or English for English) and one is Polish... for Polish, obviously :) +- 1 - "one second ago" +- 0, 2, 3... "%d seconds ago" +- Same with minutes and hours. +In Polish it is more tricky because the cardinal numbers are more complicated: -App displays label with information that says when objects from the backend has been updated for the last time, e.g. "2 minutes ago". +- 1 - "jedną sekundę temu" +- 0, (5 - 21) - "%d sekund temu" +- (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "%d sekundy temu" +- Same logic for minutes and hours. -This shouldn't be problem in English: +Following chapters will present solution without and with Swifternalization framework. Each solution describes Base (English) and Polish localizations. -- 0, 2... second ago -- 1 second ago -- ... +Here is a table with [Language Plural Rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way. -The same with minutes and hours. This is easy. Localization file for English will looks like this one: +### Solution without Swifternalization Localizable.strings (Base) -------------------------- - - "one-second" = "1 second ago"; - "many-seconds" = "%d seconds ago"; - - "one-minute" = "1 minute ago"; - "many-minutes" = "%d minutes ago"; + "one-second" = "1 second ago"; + "many-seconds" = "%d seconds ago"; + + "one-minute" = "1 minute ago"; + "many-minutes" = "%d minutes ago"; "one-hour" = "1 hour ago"; "many-hours" = "%d hours ago"; - - -Let's try with Polish language. As mentioned - this is tricky. - Localizable.strings (Polish) - ---------------------------- - - "one-second" = "1 sekundę temu"; - "few-seconds" = "%d sekundy temu"; - "many-seconds" = "%d sekund temu"; - - "one-minute" = "1 minutę temu"; - "few-minutes" = "%d minuty temu"; - "many-minutes" = "%d minut temu"; - - "one-hours" = "1 hodzinę temu"; - "few-hours" = "%d godziny temu"; - "many-hours" = "%d godzin temu"; - - - -Okay... there is 9 cases for now. But this is not the only thing to deal with. It depends on the number of seconds/minutes/hours to select proper one. Without some logic additional logic to find out which case should be used this is impossible to use proper one. - - - 0, (5 - 21) - "few-seconds" - - 1 - "one-second" - - (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "many-seconds" + Localizable.strings (Polish) + ------------------------- + "one-second" = "1 sekundę temu" + "few-seconds" = "%d sekundy temu" + "many-seconds" = "%d sekund temu"" + + "one-minute" = "1 minutę temu" + "few-minutes" = "%d minuty temu " + "many-minutes" = "%d minut temu" + + "one-hours" = "1 godzinę temu" + "few-hours" = "%d godziny temu" + "many-hours" = "%d godzin temu"; -The same logic for minutes and hours. - +There are 6 cases in English and 9 cases in Polish. Notice that without additional logic we're not able to detect which version of a string for hour/minute/second the app should display. The logic differs among different languages. We would have to add some lines of code that handle the logic for all the languages we're using in the app. What if there are more than 2 languages? Don't even think about it - this might be not so easy. -Here is nice table with [Language Plural Rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way. +*The logic is already implemented in Swifternalization framework and it fits to every language.* +### Solution with Swifternalization -With Swifternalization this can be solved e.g. in this way: - - - Localizable.strings (Base) - -------------------------- - "time-seconds{one}" = "%d second ago"; - "time-seconds{other}" = "%d seconds ago"; - - "time-minutes{one}" = "%d minute ago"; - "time-minutes{other}" = "%d minutes ago"; +This is how localizable files will look: - "time-hours{one}" = "%d hour ago"; - "time-hours{other}" = "%d hours ago"; - - + base.json + --------- + "time-seconds": { + "one": "%d second ago" + "other": "%d seconds ago" + }, + + "time-minutes": { + "one": "%d minute ago" + "other": "%d minutes ago" + }, + + "time-hours": { + "one": "%d hours ago" + "other": "%d hours ago" + } - Localizable.strings (Polish) - ---------------------------- - "time-seconds{one}" = "%d sekundę temu"; - "time-seconds{few}" = "%d sekundy temu"; - "time-seconds{many}" = "%d sekund temu"; + pl.json + ------- + "time-seconds": { + "one": "1 sekundę temu", + "few": "%d sekundy temu", + "many": "%d sekund temu" + }, - "time-minutes{one}" = "%d minutę temu"; - "time-minutes{few}" = "%d minuty temu"; - "time-minutes{many}" = "%d minut temu"; + "time-minutes": { + "one": "1 minutę temu", + "few": "%d minuty temu", + "many": "%d minut temu" + }, - "time-hours{one}" = "%d godzinę temu"; - "time-hours{few}" = "%d godziny temu"; - "time-hours{many}" = "%d godzin temu"; - -So the logic is in Swifternalization and you don't need write additional handling code for these cases. + "time-hours": { + "one": "1 godzinę temu", + "few": "%d godziny temu", + "many": "%d godzin temu" + } +- "one", "few", "many", "other" - those are shared expressions already built into Swifternalization - covered below. +- You can add own expressions to handle specific cases - covered below. -And the call will look like this: +As mentioned the logic is implemented into framework so if you want to get one of a localized string you have to make a simple call. - Swifternalization.localizedExpressionString("time-seconds", value: 10) + Swifternalization.localizedString("time-seconds", intValue: 10) or with `I18n` *typealias* (*I-18-letters-n, Internalization*): - I18n.localizedExpressionString("time-seconds", value: 10) + I18n.localizedString("time-seconds", intValue: 10) +The *key* and *intValue* parameters are validated by loaded expressions and proper version of a string is returned - covered below. -There is easy way to add you own expression to handle your specific case with Swifternalization. +## Features -Swifternalization also drops need for having *.stringdicts* files like this one: +### Pluralization +Swifternalization drops necessity of using *stringdicts* files like following one to support pluralization in localized strings. Instead of that you can simply define expressions that cover such cases. @@ -163,55 +183,55 @@ Swifternalization also drops need for having *.stringdicts* files like this one: + +No more *stringsdict* files! +### Length Variations +iOS 9 provides new way to select proper localized string variation depending on a screen width. It uses *stringsdict* file with *NSStringVariableWidthRuleType* key. -## Getting Started - -Configuration is simple. The one thing that Swifternalization needs to works is `NSBundle` where `Localizable.strings` are placed. - -Recommended is to configure it as fast as you can to be sure that before you want to get some localized key it will be able to return you something. - - Swifternalization(bundle: NSBundle.mainBundle()) - -This call will create instance (you can get handle to it but you don't need it) and automatically set it as shared instance and you can easily work with it. - -In *Localizable.strings* the syntax should looks like this: - - "key" = "value"; - "key{expression}" = "value"; - -### How to get localized string - -Swifternalization allows developer to work with its class methods. There are few to use: +Swifternalization drops necessity of using such file and it is not necessary to use this new key to use the feature. - localizedString(key: String, defaultValue: String? = nil) -> String +**With Swifternalization this length variations feature is available since iOS 8.0 because the framework has its own implementation of length variations.** -Allows to get value for simple key. Works similar to `NSLocalizedString`. `key` is the key placed in `Localizable.strings` and `defaultValue` is the value that will be returned when there is no translation found for passed key. If `defaultValue` is nil then key will be return in such case. +To use length variations feature your translation file should has entries like this: -The next one is for getting localized string with keys that contain some expressions: + base.json + --------- + "forgot-password": { + "@200": "Forgot Password? Help.", + "@300": "Forgot Your Password? Use Help.", + "@400": "Do not remember Your Password?" Use Help."" + } - localizedExpressionString(key: String, value: String, defaultValue: String? = nil) -> String - -Similarly to the one above `key` is the key in `Localizable.strings`, `defaultValue` is also the same and methods behaves the same. There is additional parameter called `value`. The value is used for expression matchers to validate expressions and return proper localized value. We'll cover it soon. - -As the method takes some `String` as a `value` and you probably will deal with `Int` there is alternative method to call: - - localizedExpressionString(key: String, value: Int, defaultValue: String? = nil) -> String +The number after `@` sign is max width of a screen or bounds that a string fits to. E.g. the second string will be returned if passed fitting width as a paramter is be greater than 200 and less or equal 300. +To get the second localized string the call looks like below: + I18n.localizedString("forgot-password", fittingWidth: 300) // 201 - 300 + +You can mix expressions with length variations. Following example shows it: + + base.json + --------- + "car": { + "ie:x=1": { + @100: "One car", + @200: "You've got one car" + }, + + "more": "You've got few cars" + } ## Expressions +There are few *expression types*. Every expression type has their own *parser* and *matcher* but they work internally so you don't need to worry about them. -As mentioned there are few *expression types*. Every expression type has their own *parser* and *matcher*. - -There are 3 types: +There are 3 types of expressions: -- *inequality* - this type of expression handles simple inequalities like: *x<3*, *x>10*, *x=5*, *x<=3*, and so on. -- *inequality extended* - this is extended version of *inequality* with syntax like this: *210*, *x=5*, *x<=3*, and so on. Work with integer and float numbers. +- *inequality extended* - extended version of *inequality* with syntax like this: *21}" = "%d cars"; + "cars": { + "ie:x=1": "1 car", + "ie:x=0": "no cars", + "ie:x>1": "%d cars" + } -### Inequality Extended - -This is a bit extended version of *inequality* expression. It is composed of 2 values, one value "marker" and two inequality signs. +### Inequality Extended Expressions +This is extended version of *inequality* expression. It is composed of 2 values, one value "marker" and two inequality signs. - *iex:* - prefix of *inequality extended* expression - *x* - place for number that will be matched. Works with Ints and floating point numbers. @@ -236,131 +257,243 @@ This is a bit extended version of *inequality* expression. It is composed of 2 v Expample: - "tomatos{iex:2 -PS. There is built in solution for Polish language so you can use it with doing just this: +PS. There is built-in solution for Polish language so you can use it with doing just this: - "police-cars{one}" = "1 samochód policyjny"; - "police-cars{few}" = "%d samochody policyjne"; - "police-cars{many}" = "%d samochodów policyjnych"; - + "police-cars": { + "one": "1 samochód policyjny", + "few": "%d samochody policyjne", + "many": "%d samochodów policyjnych" + } -This feature is called *"Shared Expression"* and is covered below. +This is called *"Shared Built-In Expression"* and is covered below. +### Shared Expressions - -## Shared Expressions +Shared expressions are expressions available among all the localization files. They are declared in *expressions.json* file divided by language and you can use them in localization files. The functionality allows developer to observance of DRY principle and to avoid mistakes that exist because of reapeating the code in many places. -It is possible to create shared expression in your project and use it with no configuration with Swifternalization. +Normally you declare expression like this: -### Getting Started of Shared Expressions + ... + "ie:x>1": "Localized String" + ... -1. Create *Expressions.strings* file in the same bundle when *Localizable.strings* file is. -2. Add shortcuts for your expressions and add your expressions ;) +If you want to use the same expression in multiple files there is no necessity to repeat the expression elsewhere. This is even problematic when you decide to improve/change expression to handle another cases you forget about - you would have to change expression in multiple places. Because of that there are Shared Expression. These feature allows you to create expression just in one place and use identifier of it in multiple places where you normally should put this expression. -Example: +What you need to do is to create *expressions.json* file with following structure: - Localizable.strings (Base) - ------------------- - "cars{custom-1}" = "%d car"; - "cars{custom-2}" = "%d cars"; + { + "base": { + "one": "ie:x>1" + }, + "pl": { + // ... other than "one" because "one" is available here too. + } + } - Localizable.strings (Polish) - ---------------------------- - "cars{custom-1}" = "%d samochód"; - "cars{custom-2}" = "%d samochody"; - "cars{custom-3}" = "%d samochodów"; - - - Expressions.strings (Base) - -------------------------- - "custom-1" = "ie:x=1"; - "custom-2" = "exp:(^[^1])|(^\\d{2,})"; - - - Expressions.strings (Polish) - --------------------------- - "custom-1" = "ie:x=1"; - "custom-2" = "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"; - "custom-3" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"; - +Now in *pl.json*, *en.json* and so on you have to use it as below: -Swifternalization load these *Expressions.strings* files and analyze them, and replace shortcuts for expressions with full expressions. + ... + "one": "Localized String" + ... -There is some duplication in Base and Polish version of expressions - *custom-1*. Instead of repeating this in entire language you want to cover you can keep it just in *Base* version of *Expressions.strings* file. Expressions that are find in *Base* and are not in preferred language file will be added to preferred language too to observance of DRY principle. +Before you decide to create your own expression take a look if there is no built-in one with the same name or whether there is such expression but named differently. Maybe you don't need to do this at all and just use it. -Swifternalization also handles the case of overriding built-in expressions. It gives you just few expressions for now like: `one`, `>one`, `two`, `other` as base expressions and `few` and `many` for Polish. If any of your *Expressions.strings* version of file will override it Swifternalization will use your version. +### Built-in expressions -## Demo +Built-in expressions as name suggest are shared expressions built into framework and available to use with zero configuration. They are separated by country and not all country have its own built-in expressions. For now there are e.g. Base built-in expressions and Polish built-in expressions. Base expressions are available in every country and there are very generic to match all countries pluralization/cardinal numbering logic. -There is demo project included in the repo. Just switch to proper target and run. It enumerated cars from 1 to 1000 and print them out to the console. Base (English) and Polish languages are supported. You can find there example of using simple primitive no-expression translation and also with experssions. +List of supported built-in shared expressions: -## Contribution and change or feature requests + Base (English fits to this completely) + - one - detects if number is equal 1 + - >one - detects if number is greater than 1 + - two - detects if number is equal 2 + - other - detects if number is not 1, so 0, 2, 3 and so on. + + Polish + - few - matches (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24) + - many - matches 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9) + +As you can see polish has no "one", ">one", etc. because it inherits from Base by default. + +## Getting Started +This chapter shows you how to start using Swifternalization and how to intergrate it with your code. -Swifternalization is open sources so everyone may contribute if want to. If you want to develop it just fork the repo, do you work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it. - - -## Built-in expressions - -As mentioned in previous chapter Swifternalization has some built-in expressions and is ready to extend. If you want to add expressions specific for your country you can do it by creating class which conforms to `SharedExpressionProtocol`. Methods from protocol returns all expressions for your country. There is already `SharedBaseExpression` with some basic expressions and `SharedPolishExpression` with polish expressions for helping ordering numbers. - -Example of the file ready for pull request should looks like this: - - class SharedPolishExpression: SharedExpressionProtocol { - static func allExpressions() -> [SharedExpression] { - return [ - /** - (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24) - - e.g. - - 22 samochody, 1334 samochody, 53 samochody - - 2 minuty, 4 minuty, 23 minuty - */ - SharedExpression(k: "few", e: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"), - - /** - 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9) - - e.g. - - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów - - 5 minut, 18 minut, 117 minut, 1009 minut - */ - SharedExpression(k: "many", e: "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"), - ] +### Documentation +Documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private. + +You can find Public API and Full documentation with docset here in [docs](https://github.com/tomkowz/Swifternalization/tree/master/docs) directory. + +It is also hosted on [my blog](http://szulctomasz.com): +- [Public API documentation](http://szulctomasz.com/docs/swifternalization/public/) +- [Full API documentation](http://szulctomasz.com/docs/swifternalization/framework/) + +Docsets: +- [Public API docset](http://szulctomasz.com/docs/swifternalization/public/docsets/Swifternalization%20Public%20API.docset.zip) +- [Full API docset](http://szulctomasz.com/docs/swifternalization/framework/docsets/Swifternalization.docset.zip) + +### Instalation +It works with iOS 8.0 and newer. + +With CocoaPods: + + pod 'Swifternalization', '~> 1.2' + +If you are not using CocoaPods just import files from *Swifternalization/Swifternalization* directory to your project. + +Swifternalization also supports Carthage. + +### Configuration +Before you get a first localized string you have to configure Swifternalization by passing to it the bundle where localized json files are placed. + + I18n.configure() // for NSBundle.mainBundle() - Mostly you want to call it this way + I18n.configure(bundle) // if files are in another bundle + +### Creating file with Shared Expressions + +Shared Expressions must be placed in *expressions.json*. Syntax of a file looks like below: + + { + "base": { + "ten": "ie:x=10", + ">20": "ie:x>20", + "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])" + }, + + "pl": { + "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)", + "two": "ie:x=2", + "three": "ie:x=3" } } +In pseudo-language: -Also this is required to cover all shared expressions for a country with unit tests. You can find examples in the repo for e.g. Polish expressions. + { + "language-1": { + "shared-expression-key-1": "expression-1", + "shared-expression-key-2": "expression-2" + }, + + "language-2": { + "shared-expression-key-1": "expression-1" + } + } + +Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in *base* variant because if expression is in *base* it is also available in every other language too. So, "ten" is available in "pl", but "three" is not available in "base". -## Swift 2 -Swifternalization supports Swift 2 and works on Xcode 7 beta 3. Please check *swift2* branch for that. +### Creating Localizable Files +Localizable file contains translations for specific language. The files might look like below: -## Things to do in future release: + { + "welcome-key": "welcome", + + "cars": { + "one": "one car", + "ie:x>=2": "%d cars", + "ie:x<=-2": "minus %d cars" + } + } + +Name of a file should be the same like country code. e.g. for English it is *en.json*, for Polish it is *pl.json*, for base localization it is *base.json*, etc. + +There are few things that you can place in such files. More complex file will look like below: + + { + "welcome": "welcome", + + "cars": { + "one": { + "@100": "one car", + "@200": "You have one car", + "@400": "You have got one car" + }, + + "other": "%d cars" + } + } + +In pseudo-language: + + { + "key": "value", + + "key": { + "expression-1": { + "length-variation-1": "value-1", + "length-variation-2": "value-2", + "length-variation-3": "value-3" + }, + + "expression-2": "value" + } + } + + +### Getting localized string + +Swifternalization allows you to work with its one class method which exposes all the methods you need to localize an app. + +These methods have many optional paramters and you can omit them if you want. There are few common parameters: + +- `key` - A key of localized string. +- `fittingWidth` - A width of a screen or place where you want to put a localized string. It is integer. +- `defaultValue` - A value that will be returned if there is no localized string for a key passed to the method. If this is not specified then `key` is returned. +- `comment` - A comment used just by developer to know a context of translation. + +First method called `localizedString(_:fittingWidth:defaultValue:comment:)` allows you to get value for simple key without expression. + +Examples: + + I18n.localizedString("welcome") + I18n.localizedString("welcome", fittingWidth: 200) + I18n.localizedString("welcome", defaultValue: "Welcome", comment: "Displayed on app start") + +Next method `localizedString(_:stringValue:fittingWidth:defaultValue:comment:)` allows you to get a localized string for string value that match an expression. Actually the string value will contain number inside in most cases or some other string that you would like to match. + + I18n.localizedString("cars", stringValue: "5") + // Other cases similar to above example + +The last method `localizedString(_:intValue:fittingWidth:defaultValue:comment:)` allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings. + + I18n.localizedString("cars", intValue: 5) + +## Contribution and change or feature requests +Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it. + +There is no guide for contributors but if you added new functionality you must write unit tests for it. + +## Swift 2 +Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out *swift2* branch. + +## Things to do in future releases: - Add more built-in expressions for another countries. +- Add support for float numbers in built in expressions that uses regular expressions. ## LICENSE - Swifternalization is released under the MIT license. diff --git a/Swifternalization/CountryCode.swift b/Swifternalization/CountryCode.swift index f847da0..55af5f2 100644 --- a/Swifternalization/CountryCode.swift +++ b/Swifternalization/CountryCode.swift @@ -5,4 +5,4 @@ https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + "base" Represents country codes in framework. Country codes are used to get correct user device's language. */ -typealias CountryCode = String +internal typealias CountryCode = String diff --git a/Swifternalization/Expression.swift b/Swifternalization/Expression.swift index 66dc65c..f78a02a 100644 --- a/Swifternalization/Expression.swift +++ b/Swifternalization/Expression.swift @@ -11,7 +11,7 @@ import Foundation /** String that contains expression pattern, e.g. `ie:x<5`, `exp:^1$`. */ -typealias ExpressionPattern = String +internal typealias ExpressionPattern = String /** This class contains pattern of expression and localized value as well as diff --git a/Swifternalization/JSONFileLoader.swift b/Swifternalization/JSONFileLoader.swift index cd24d65..47db816 100644 --- a/Swifternalization/JSONFileLoader.swift +++ b/Swifternalization/JSONFileLoader.swift @@ -3,7 +3,7 @@ import Foundation /** Represents json content. */ -typealias JSONDictionary = Dictionary +internal typealias JSONDictionary = Dictionary /** Simple JSON file loader. diff --git a/Swifternalization/SharedExpressionsProcessor.swift b/Swifternalization/SharedExpressionsProcessor.swift index c132939..b498f13 100644 --- a/Swifternalization/SharedExpressionsProcessor.swift +++ b/Swifternalization/SharedExpressionsProcessor.swift @@ -88,14 +88,14 @@ class SharedExpressionsProcessor { } infix operator [SharedExpression] { - /* - "Get Unique" operator. It helps in getting unique shared expressions from two arrays. - Content of `lhs` array will be checked in terms on uniqueness. The operator does - check is there is any shared expression in `lhs` that is presented in `rhs`. - If element from `lhs` is not in `rhs` then this element is correct and is returned - in new array which is a result of this operation. - */ var result = lhs if rhs.count > 0 { result = lhs.filter({ diff --git a/Swifternalization/Swifternalization.swift b/Swifternalization/Swifternalization.swift index 7f20307..14b1a76 100644 --- a/Swifternalization/Swifternalization.swift +++ b/Swifternalization/Swifternalization.swift @@ -15,15 +15,14 @@ public typealias I18n = Swifternalization This is the main class of Swifternalization library. It exposes methods that can be used to get localized strings. -It uses expressions.json and base.json, en.json, pl.json and so on to work. -expressions.json file contains shared expressions that are used by other -files with translations. +The framework uses json files and work with them. There are two types of files. +First is "expressions.json" that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have "base.json", +"en.json", "pl.json" which are accordingly used for Base, English and Polish +localizations. -Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value. - -Before you can get any localized value you have to configure the Swifternalization -first. Call `configure:` method first and then you can use other methods. +Before calling any method that return localized string call `configure:`. */ final public class Swifternalization { /** @@ -50,7 +49,7 @@ final public class Swifternalization { /** Get localized value for a key. - :param: key A key. + :param: key A key to which localized string is assigned. :param: fittingWidth A max width that value should fit to. If there is no value specified the full-length localized string is returned. If a passed fitting width is greater than highest available then a value for @@ -70,7 +69,7 @@ final public class Swifternalization { /** Get localized value for a key and string value. - :param: key A key. + :param: key A key to which localized string is assigned. :param: stringValue A value that is matched by expressions. :param: fittingWidth A max width that value should fit to. If there is no value specified the full-length localized string is returned. If a @@ -113,7 +112,7 @@ final public class Swifternalization { /** Get localized value for a key and string value. - :param: key A key. + :param: key A key to which localized string is assigned. :param: intValue A value that is matched by expressions. :param: fittingWidth A max width that value should fit to. If there is no value specified the full-length localized string is returned. If a @@ -169,7 +168,7 @@ final public class Swifternalization { } /** - Gets preferred language of user's device + Get preferred language of user's device. */ private func getPreferredLanguage(bundle: NSBundle) -> CountryCode { // Get preferred language, the one which is set on user's device diff --git a/gendocs.sh b/bin/gendocs.sh similarity index 100% rename from gendocs.sh rename to bin/gendocs.sh diff --git a/docs/framework/Classes.html b/docs/framework/Classes.html index 8db924d..673c50a 100644 --- a/docs/framework/Classes.html +++ b/docs/framework/Classes.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -159,9 +170,9 @@

Classes

  • @@ -169,19 +180,15 @@

    Classes

    -

    Class represents single expression that is added in curly -brackets inside key in Localizable.strings file or as a value in -Expressions.strings file.

    - -

    It is able to validate passed string using internal expression matcher.

    +

    Parses inequality expression patterns. e.g. ie:x=5.

    - See more + See more

    Declaration

    Swift

    -
    class Expression
    +
    class InequalityExpressionParser: ExpressionParser
    @@ -195,9 +202,9 @@

    Declaration

  • @@ -205,15 +212,15 @@

    Declaration

    -

    Parses inequality expression patterns. e.g. ie:x=5.

    +

    Parses inequality extended expressions. iex:5<x<10.

    - See more + See more

    Declaration

    Swift

    -
    class InequalityExpressionParser: ExpressionParser
    +
    class InequalityExtendedExpressionParser: InequalityExpressionParser
    @@ -227,9 +234,9 @@

    Declaration

  • @@ -237,15 +244,15 @@

    Declaration

    -

    Parses inequality extended expressions. iex:5<x<10.

    +

    Simple JSON file loader.

    - See more + See more

    Declaration

    Swift

    -
    class InequalityExtendedExpressionParser: InequalityExpressionParser
    +
    final class JSONFileLoader
    @@ -259,9 +266,9 @@

    Declaration

  • @@ -269,15 +276,16 @@

    Declaration

    -

    Class responsible for loading content from specified file type.

    +

    Translation processor which takes loaded translations and process them to make +them regular translation objects that can be used for further work.

    - See more + See more

    Declaration

    Swift

    -
    class LocalizableFilesLoader
    +
    class LoadedTranslationsProcessor
    @@ -309,7 +317,7 @@

    Declaration

    Declaration

    Swift

    -
    class Regex
    +
    final class Regex
    @@ -387,9 +395,41 @@

    Declaration

  • +
    +
    +
    +
    +
    +

    Used to load content from expressions.json file for specified language.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    final class SharedExpressionsLoader
    + +
    +
    +
    +
    +
  • + +
    +
    +
      +
    • +
      @@ -398,30 +438,30 @@

      Declaration

      Swifternalization contains some built-in country-related shared expressions. -Developer can create its own expressions in Expressions.strings file for -base and preferred language version of the file.

      +Developer can create its own expressions in expressions.json file for +base and preferred languages.

      -

      The class is responsible for proper loading the built-in shared ones and -those loaded from project’s files. It handles overriding of built-in and loads +

      The class is responsible for proper loading the built-in shared ones and +those loaded from project’s files. It handles overriding of built-in and loads those from Base and preferred language version.

      It always load base expressions, then looking for language specific. -If in Base file are some expressions that overrides built-in, that’s fine -the class adds or developer’s custom expressions and add only those of built-in +If in Base are some expressions that overrides built-ins, that’s fine. +The class adds developer’s custom expressions and adds only those of built-in that are not contained in the Base.

      -

      The same is for preferred languages but also here what is important is that -at first preferred language file expressions are loaded, then if something -different is defined in Base it will be also loaded and then the built-in +

      The same is for preferred languages but also here what is important is that +at first preferred language file expressions are loaded, then if something +different is defined in Base it will be also loaded and then the built-in expression that differs.

      - See more + See more

      Declaration

      Swift

      -
      class SharedExpressionsConfigurator
      +
      class SharedExpressionsProcessor
      @@ -478,35 +518,56 @@

      Declaration

      This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

      - -

      It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

      +that can be used to get localized strings.

      -

      Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

      +

      The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

      -

      It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

      +

      Before calling any method that return localized string call configure:.

      -

      It looks for content in the NSBundle you can provide and try to find:

      + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      final public class Swifternalization
      -
        -
      • Localizable.strings (Base),
      • -
      • Localizable.strings of preferred language, e.g. Localizable.strings (en)
      • -
      • Expressions.strings (Base),
      • -
      • Expressions.strings of preferred language, e.g. Expressions.strings (en)
      • -
      +
      +
      +
  • +
    +
  • + +
    +
    + @@ -170,7 +181,7 @@

    InequalityExpressionParser

    -

    Pattern of expression.

    +

    A pattern of expression.

    @@ -308,7 +319,7 @@

    Return Value

    -

    Get value - Int.

    +

    Get value - Double.

    @@ -473,7 +484,7 @@

    Return Value

    diff --git a/docs/framework/Classes/InequalityExtendedExpressionParser.html b/docs/framework/Classes/InequalityExtendedExpressionParser.html index fbe1c5f..b370281 100644 --- a/docs/framework/Classes/InequalityExtendedExpressionParser.html +++ b/docs/framework/Classes/InequalityExtendedExpressionParser.html @@ -30,9 +30,6 @@
  • + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -333,7 +344,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/LocalizableFilesLoader.html b/docs/framework/Classes/JSONFileLoader.html similarity index 58% rename from docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/LocalizableFilesLoader.html rename to docs/framework/Classes/JSONFileLoader.html index 01cbb70..d494697 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/LocalizableFilesLoader.html +++ b/docs/framework/Classes/JSONFileLoader.html @@ -1,7 +1,7 @@ - LocalizableFilesLoader Class Reference + JSONFileLoader Class Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,8 +161,8 @@
    -

    LocalizableFilesLoader

    -

    Class responsible for loading content from specified file type.

    +

    JSONFileLoader

    +

    Simple JSON file loader.

    @@ -160,63 +171,9 @@

    LocalizableFilesLoader

  • - - - BaseLanguage - -
    -
    -
    -
    -
    -
    -

    Defines Base language - which is equal Base.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let BaseLanguage: Language = "Base"
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - bundle - -
    -
    -
    -
    -
    -
    -

    Bundle where files are located

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let bundle: NSBundle
    - -
    -
    -
    -
    -
  • -
  • -
    @@ -224,14 +181,14 @@

    Declaration

    -

    Creates LocalizableFilesLoader instance.

    +

    Loads translations dict for specified language.

    Declaration

    Swift

    -
    init(_ bundle: NSBundle)
    +
    class func loadTranslations(countryCode: CountryCode, bundle: NSBundle) -> JSONDictionary
    @@ -239,6 +196,19 @@

    Declaration

    Parameters

    + + + + @@ -255,15 +225,20 @@

    Parameters

    + + countryCode + + +
    +

    A country code.

    + +
    +
    @@ -247,7 +217,7 @@

    Parameters

    -

    A bundle where .strings files are located.

    +

    A bundle when file is located.

    +
    +

    Return Value

    +

    Returns json of file or empty dictionary if cannot load a file.

    + +
  • @@ -271,15 +246,14 @@

    Parameters

    -

    Loads content from files of specified type and language. -Converts them to BasePrefDicts instance.

    +

    Loads expressions dict for specified language.

    Declaration

    Swift

    -
    func loadContentFromFilesOfType(type: StringsFileType, language: Language) -> BasePrefDicts
    +
    class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> Dictionary<String, String>
    @@ -290,12 +264,12 @@

    Parameters

    - type + countryCode
    -

    A type of files that will be loaded.

    +

    A country code.

    @@ -303,13 +277,12 @@

    Parameters

    - language + bundle
    -

    A preferred language - it will be used to find proper -file for specified file type.

    +

    A bundle when file is located.

    @@ -319,29 +292,18 @@

    Parameters

    Return Value

    -

    Returns content of files converted into key-value pairs.

    +

    dictionary with expressions or empty dictionary if cannot load a file.

  • - - -
    - - @@ -474,7 +485,7 @@

    Return Value

    diff --git a/docs/framework/Classes/RegexExpressionParser.html b/docs/framework/Classes/RegexExpressionParser.html index 95c3009..20410d0 100644 --- a/docs/framework/Classes/RegexExpressionParser.html +++ b/docs/framework/Classes/RegexExpressionParser.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -302,7 +313,7 @@

    Return Value

    diff --git a/docs/framework/Classes/SharedBaseExpression.html b/docs/framework/Classes/SharedBaseExpression.html index e2ad24a..60cec49 100644 --- a/docs/framework/Classes/SharedBaseExpression.html +++ b/docs/framework/Classes/SharedBaseExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -189,7 +200,7 @@

    Declaration

    diff --git a/docs/framework/Classes/SharedExpressionsConfigurator.html b/docs/framework/Classes/SharedExpressionsConfigurator.html deleted file mode 100644 index 32f23ed..0000000 --- a/docs/framework/Classes/SharedExpressionsConfigurator.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - SharedExpressionsConfigurator Class Reference - - - - - - - - - -
    -
    -

    Swifternalization Docs (100% documented)

    -

    View on GitHub

    -
    -
    -
    - -
    -
    - -
    -
    -
    -

    SharedExpressionsConfigurator

    -

    Swifternalization contains some built-in country-related shared expressions. -Developer can create its own expressions in Expressions.strings file for -base and preferred language version of the file.

    - -

    The class is responsible for proper loading the built-in shared ones and -those loaded from project’s files. It handles overriding of built-in and loads -those from Base and preferred language version.

    - -

    It always load base expressions, then looking for language specific. -If in Base file are some expressions that overrides built-in, that’s fine -the class adds or developer’s custom expressions and add only those of built-in -that are not contained in the Base.

    - -

    The same is for preferred languages but also here what is important is that -at first preferred language file expressions are loaded, then if something -different is defined in Base it will be also loaded and then the built-in -expression that differs.

    - -
    -
    -
    -
      -
    • - -
      -
      -
      -
      -
      -

      Method takes expression for both Base and preferred language localizations -and also internally loads built-in expressions, combine them and returns -tuple with expressions for Base and another array for preferred language -localization.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      class func configureExpressions(dicts: BasePrefDicts, language: Language) -> (base: [SharedExpression], pref: [SharedExpression])
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - dicts - - -
      -

      A BasePrefDicts tuple that contains two dicts of -shared expressions for Base and preferred language localization.

      - -
      -
      - - language - - -
      -

      A preferred user’s language.

      - -
      -
      -
      -
      -

      Return Value

      -

      Tuple with arrays of shared expressions for Base and preferred -language localizations.

      - -
      -
      -
      -
    • -
    -
    -
    - -
      -
    • -
      - - - - convert(_:) - -
      -
      -
      -
      -
      -
      -

      Converts dictionary with expressions to array of SharedExpression objects.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func convert(expressionsDict: KVDict) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - expressionsDict - - -
      -

      Dictionary with key-value pair of expression -from Expressions.strings.

      - -
      -
      -
      -
      -

      Return Value

      -

      Array of SharedExpression objects.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Method loads built-in framework’s built-in expressions for specific language.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func loadBuiltInExpressions(language: Language) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - language - - -
      -

      A preferred user’s language.

      - -
      -
      -
      -
      -

      Return Value

      -

      Shared expressions for specific language. If there is no -expression for passed language empty array is returned.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Method that merges expressions. It takes two arrays, one is source and one -is additional. If the source does not contain some expression from -additional array this expression will be added to the source.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func mergeExpressions(var source: [SharedExpression], additional: [SharedExpression]) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - source - - -
      -

      A source array with expressions.

      - -
      -
      - - additional - - -
      -

      Array with additional expressions that may or may not -be added to source array.

      - -
      -
      -
      -
      -

      Return Value

      -

      Array with expressions that contains all elements from source -and elements from additional that were not in source.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      This is just helper method. It does the same like -mergeExpressions(source:additional:) but this one takes reference to -source array instead of pasing it by value.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func mergeExpressions(inout source: [SharedExpression], additional: [SharedExpression]) -> Void
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - source - - -
      -

      reference to source array.

      - -
      -
      - - additional - - -
      -

      Array of additional shared expressions.

      - -
      -
      -
      -
      -

      Return Value

      -

      merged array of shared expressions.

      - -
      -
      -
      -
    • -
    -
    -
    -
    - -
    -
    - -
    - diff --git a/docs/framework/Classes/SharedExpressionsLoader.html b/docs/framework/Classes/SharedExpressionsLoader.html new file mode 100644 index 0000000..1dd2dba --- /dev/null +++ b/docs/framework/Classes/SharedExpressionsLoader.html @@ -0,0 +1,235 @@ + + + + SharedExpressionsLoader Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    SharedExpressionsLoader

    +

    Used to load content from expressions.json file for specified language.

    + +
    +
    +
    +
      +
    • +
      + + + + loadExpressions(_:) + +
      +
      +
      +
      +
      +
      +

      Loads expressions for specified language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func loadExpressions(json: Dictionary<String, String>) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + countryCode + + +
      +

      A country code

      + +
      +
      +
      +
      +

      Return Value

      +

      array of loaded expressions.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/Classes/SharedExpressionsProcessor.html b/docs/framework/Classes/SharedExpressionsProcessor.html new file mode 100644 index 0000000..a352fdc --- /dev/null +++ b/docs/framework/Classes/SharedExpressionsProcessor.html @@ -0,0 +1,332 @@ + + + + SharedExpressionsProcessor Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    SharedExpressionsProcessor

    +

    Swifternalization contains some built-in country-related shared expressions. +Developer can create its own expressions in expressions.json file for +base and preferred languages.

    + +

    The class is responsible for proper loading the built-in shared ones and +those loaded from project’s files. It handles overriding of built-in and loads +those from Base and preferred language version.

    + +

    It always load base expressions, then looking for language specific. +If in Base are some expressions that overrides built-ins, that’s fine. +The class adds developer’s custom expressions and adds only those of built-in +that are not contained in the Base.

    + +

    The same is for preferred languages but also here what is important is that +at first preferred language file expressions are loaded, then if something +different is defined in Base it will be also loaded and then the built-in +expression that differs.

    + +
    +
    +
    +
      +
    • + +
      +
      +
      +
      +
      +

      Method takes expression for both Base and preferred language localizations +and also internally loads built-in expressions, combine them and returns +expressions for Base and prefered language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func processSharedExpression(preferedLanguage: CountryCode, preferedLanguageExpressions: [SharedExpression], baseLanguageExpressions: [SharedExpression]) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + + + + + +
      + + preferedLanguage + + +
      +

      A user device’s language.

      + +
      +
      + + preferedLanguageExpressions + + +
      +

      Expressions from expressions.json

      + +
      +
      + + baseLanguageExpressions + + +
      +

      Expressions from base section of +expression.json.

      + +
      +
      +
      +
      +

      Return Value

      +

      array of shared expressions for Base and preferred language.

      + +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Method loads built-in framework’s built-in expressions for specific language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      private class func loadBuiltInExpressions(language: CountryCode) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + language + + +
      +

      A preferred user’s language.

      + +
      +
      +
      +
      +

      Return Value

      +

      Shared expressions for specific language. If there is no

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/Classes/SharedPolishExpression.html b/docs/framework/Classes/SharedPolishExpression.html index 23bf90d..0eb1601 100644 --- a/docs/framework/Classes/SharedPolishExpression.html +++ b/docs/framework/Classes/SharedPolishExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -189,7 +200,7 @@

    Declaration

    diff --git a/docs/framework/Classes/Swifternalization.html b/docs/framework/Classes/Swifternalization.html index 484fba2..2be03ee 100644 --- a/docs/framework/Classes/Swifternalization.html +++ b/docs/framework/Classes/Swifternalization.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -152,27 +163,16 @@

    Swifternalization

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    @@ -181,64 +181,9 @@

    Swifternalization

  • - - - Static - -
    -
    -
    -
    -
    -
    -

    Struct to keep shared instance of Swifternalization

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private struct Static
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - bundle - -
    -
    -
    -
    -
    -
    -

    Bundle when Localizable and Expressions .strings files are located.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let bundle: NSBundle
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - basePairs + + + sharedInstance
    @@ -246,14 +191,14 @@

    Declaration

    -

    key-value from base Localizable.strings file

    +

    Shared instance of Swifternalization used internally.

    Declaration

    Swift

    -
    private var basePairs = [TranslatablePair]()
    +
    private static let sharedInstance = Swifternalization()
    @@ -263,9 +208,9 @@

    Declaration

  • @@ -273,14 +218,14 @@

    Declaration

    -

    key-value airs from preferred language Localizable.strings file

    +

    Array of translations that contain expressions and localized values.

    Declaration

    Swift

    -
    private var preferredPairs = [TranslatablePair]()
    +
    private var translations = [Translation]()
    @@ -291,19 +236,19 @@

    Declaration

    • @@ -311,20 +256,14 @@

      Public methods

      -

      Swifternalization takes NSBundle when Localizable.strings file is located. -This method return instance of the class but you don’t need it because -shared instance is set automatically.

      - -

      It get Localizable.strings file version based on the first language from -the prefferedLocalizations property of NSBundle. If Localizable.strings for -preferred language isn’t exist then Base is used instead.

      +

      Call the method to configure Swifternalization.

      Declaration

      Swift

      -
      public init(bundle: NSBundle)
      +
      public class func configure(bundle: NSBundle = NSBundle.mainBundle())
      @@ -340,7 +279,7 @@

      Parameters

      -

      bundle when .strings files are located.

      +

      A bundle when expressions.json and other files are located.

      @@ -354,9 +293,9 @@

      Parameters

    • @@ -364,20 +303,14 @@

      Parameters

      -

      Returns localized string for simple key that does not contain any expression.

      - -
      I18n.localizedString("car")
      -I18n.localizedString("car", defaultValue: "Audi")
      -I18n.localizedString("car", defaultValue: "Audi", comment: "Comment")
      -I18n.localizedString("car", comment: "Comment")
      -
      +

      Get localized value for a key.

      Declaration

      Swift

      -
      public class func localizedString(key: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -393,7 +326,23 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -406,8 +355,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -420,8 +369,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description of -a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -431,9 +380,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise -returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    • @@ -442,9 +390,9 @@

      Return Value

    • @@ -452,20 +400,14 @@

      Return Value

      -

      Returns localized string for key which contains expression.

      - -
      I18n.localizedExpressionString("cars", value: "10")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "Few cars")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "10", comment: "This is a comment")
      -I18n.localizedExpressionString("cars", value: "10", comment: "This is a comment")
      -
      +

      Get localized value for a key and string value.

      Declaration

      Swift

      -
      public class func localizedExpressionString(key: String, value: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -481,7 +423,7 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      @@ -489,12 +431,28 @@

      Parameters

      - value + stringValue
      -

      value used when validating expressions.

      +

      A value that is matched by expressions.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -507,8 +465,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -521,8 +479,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description -of a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -532,8 +490,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

  • @@ -542,9 +500,9 @@

    Return Value

  • @@ -552,19 +510,100 @@

    Return Value

    -

    This method is just extension to method -localizedExpressionString(_:value:defaultValue:comment:) that takes -String as a value parameter.

    +

    Get localized value for a key and string value.

    Declaration

    Swift

    -
    public class func localizedExpressionString(key: String, value: Int, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + key + + +
    +

    A key to which localized string is assigned.

    + +
    +
    + + intValue + + +
    +

    A value that is matched by expressions.

    + +
    +
    + + fittingWidth + + +
    +

    A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

    + +
    +
    + + defaultValue + + +
    +

    A default value that is returned when there is no +localized value for passed key.

    + +
    +
    + + comment + + +
    +

    A comment about the key and localized value. Just for +developer use for describing key-value pair.

    + +
    +
    +
    +
    +

    Return Value

    +

    localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    + +
  • @@ -572,73 +611,19 @@

    Declaration

    • -
      -
      -
      -
      -
      -

      Method that set shared instance of Swifternalization

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func setSharedInstance(instance: Swifternalization)
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - sharedInstance() - -
      -
      -
      -
      -
      -
      -

      Method that returns shared instance of Swifternalization

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func sharedInstance() -> Swifternalization!
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - load() + + + load(bundle:)
      @@ -646,52 +631,37 @@

      Declaration

      -

      Method responsible for loading content into Swifternaliztion library. -It takes preferred language of user’s device, and tries to find -Localizable.strings (Preferred Language) and Localizable.strings (Base) -to get keys and values of words to translate.

      - -

      It also tries to find and load Expressions.strings (Preferred Language) -and Expressions.strings (Base) files when shared expressions might be -and tries to combine them together with built-in shared expression, -and at the end enumeate through key-value pairs for translation and -changes keys that have only expression shortcuts to full expressions.

      +

      Loads expressions and translations from expression.json and translation +json files.

      Declaration

      Swift

      -
      private func load()
      +
      private func load(bundle: NSBundle = NSBundle.mainBundle())
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Gets preferred language of user’s device

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private func getPreferredLanguage() -> Language
      +
      +

      Parameters

      + + + + + + + +
      + + bundle + + +
      +

      A bundle when files are located.

      -
      + +
      @@ -699,9 +669,9 @@

      Declaration

    • @@ -709,18 +679,14 @@

      Declaration

      -

      Enumerate through translatable pairs dict and check if there are some shared -expression identifiers that needs to be replaced with full expressions.

      - -

      Next create translatable pairs with susch updated expressions to make it -ready to be used by framework.

      +

      Get preferred language of user’s device.

      Declaration

      Swift

      -
      private func createTranslablePairs(translatableDict: KVDict, expressions: [SharedExpression]) -> [TranslatablePair]
      +
      private func getPreferredLanguage(bundle: NSBundle) -> CountryCode
      @@ -732,7 +698,7 @@

      Declaration

    diff --git a/docs/framework/Classes/Swifternalization/Static.html b/docs/framework/Classes/Swifternalization/Static.html deleted file mode 100644 index 98701fb..0000000 --- a/docs/framework/Classes/Swifternalization/Static.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - Static Struct Reference - - - - - - - - - -
    -
    -

    Swifternalization Docs (100% documented)

    -

    View on GitHub

    -
    -
    -
    - -
    -
    - -
    -
    -
    -

    Static

    -

    Struct to keep shared instance of Swifternalization

    - -
    -
    -
    -
      -
    • -
      - - - - instance - -
      -
      -
      -
      -
      -
      -

      Instance of Swifternalization that might be nil if not set.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      static var instance: Swifternalization? = nil
      - -
      -
      -
      -
      -
    • -
    -
    -
    -
    - -
    -
    - - - diff --git a/docs/framework/Classes/TranslationsLoader.html b/docs/framework/Classes/TranslationsLoader.html new file mode 100644 index 0000000..389ec54 --- /dev/null +++ b/docs/framework/Classes/TranslationsLoader.html @@ -0,0 +1,247 @@ + + + + TranslationsLoader Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    TranslationsLoader

    +

    Class that gets dictionary of translations and turn it into LoadedTranslations.

    + +
    +
    +
    +
      +
    • +
      + + + + loadTranslations(_:) + +
      +
      +
      +
      +
      +
      +

      Converts dictionary into array of LoadedTranslations.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func loadTranslations(json: Dictionary<String, AnyObject>) -> [LoadedTranslation]
      + +
      +
      +
      +

      Return Value

      +

      Array of LoadedTranslation objects from specified file.

      + +
      +
      +
      +
    • +
    • +
      + + + + detectElementType(_:) + +
      +
      +
      +
      +
      +
      +

      Analyzes passed dictionary and checks its content to match it to some translation type.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType?
      + +
      +
      +
      +

      Return Value

      +

      translation type of a dictionary.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/Enums.html b/docs/framework/Enums.html index 461de92..a8b8023 100644 --- a/docs/framework/Enums.html +++ b/docs/framework/Enums.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -159,9 +170,9 @@

    Enums

  • @@ -169,15 +180,15 @@

    Enums

    -

    Supported expression types

    +

    Supported expression types.

    - See more + See more

    Declaration

    Swift

    -
    enum ExpressionType: String
    +
    enum ExpressionPatternType: String
    @@ -255,9 +266,9 @@

    Declaration

  • @@ -265,15 +276,17 @@

    Declaration

    -

    Enum that represents file types used by LocalizableFilesLoader.

    +

    Specifies available translation types used by translation processor to have +knowledge about specifics of loaded translations. It helps later to decide +what is needed to do with LoadedTranslation’s content property.

    - See more + See more

    Declaration

    Swift

    -
    enum StringsFileType: String
    +
    enum LoadedTranslationType
    @@ -285,7 +298,7 @@

    Declaration

  • diff --git a/docs/framework/Enums/ExpressionType.html b/docs/framework/Enums/ExpressionPatternType.html similarity index 78% rename from docs/framework/Enums/ExpressionType.html rename to docs/framework/Enums/ExpressionPatternType.html index 8a0cb44..c05f06b 100644 --- a/docs/framework/Enums/ExpressionType.html +++ b/docs/framework/Enums/ExpressionPatternType.html @@ -1,7 +1,7 @@ - ExpressionType Enum Reference + ExpressionPatternType Enum Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
  • @@ -150,8 +161,8 @@
    -

    ExpressionType

    -

    Supported expression types

    +

    ExpressionPatternType

    +

    Supported expression types.

    @@ -160,9 +171,9 @@

    ExpressionType

  • @@ -170,14 +181,14 @@

    ExpressionType

    -

    works on Int only, e.g. 4<x<10, 1<=x<18

    +

    Works with Int/Float, e.g. x<5, x=3, x<4.5.

    Declaration

    Swift

    -
    case InequalityExtended = "iex"
    +
    case Inequality = "ie"
    @@ -187,9 +198,9 @@

    Declaration

  • @@ -197,14 +208,14 @@

    Declaration

    -

    regular expression, e.g. [02-9]+

    +

    Works on Int/Float only, e.g. 4<x<10, 1<=x<18, 1.3<=x<15.4.

    Declaration

    Swift

    -
    case Regex = "exp"
    +
    case InequalityExtended = "iex"
    @@ -214,9 +225,9 @@

    Declaration

  • - - - Inequality + + + Regex
    @@ -224,14 +235,14 @@

    Declaration

    -

    works on Int only, e.g. x<5, x=3

    +

    Regular expression, e.g. [02-9]+.

    Declaration

    Swift

    -
    case Inequality = "ie"
    +
    case Regex = "exp"
    @@ -243,7 +254,7 @@

    Declaration

  • diff --git a/docs/framework/Enums/InequalitySign.html b/docs/framework/Enums/InequalitySign.html index 671b6e5..64525a3 100644 --- a/docs/framework/Enums/InequalitySign.html +++ b/docs/framework/Enums/InequalitySign.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -170,7 +181,7 @@

    InequalitySign

    -

    Inverts enum

    +

    Inverts enum.

    @@ -187,9 +198,9 @@

    Declaration

  • - - - LessThan + + + Equal
    @@ -197,14 +208,14 @@

    Declaration

    -

    Less than a value

    +

    Equal a value.

    Declaration

    Swift

    -
    case LessThan = "<"
    +
    case Equal = "="
    @@ -214,9 +225,9 @@

    Declaration

  • @@ -224,14 +235,14 @@

    Declaration

    -

    Greater than a value

    +

    Less than or equal a value.

    Declaration

    Swift

    -
    case GreaterThan = ">"
    +
    case LessThanOrEqual = "<="
    @@ -241,9 +252,9 @@

    Declaration

  • @@ -251,14 +262,14 @@

    Declaration

    -

    Less than or equal a value

    +

    Greater than or equal a value.

    Declaration

    Swift

    -
    case LessThanOrEqual = "<="
    +
    case GreaterThanOrEqual = ">="
    @@ -268,9 +279,9 @@

    Declaration

  • - - - Equal + + + GreaterThan
    @@ -278,14 +289,14 @@

    Declaration

    -

    Equal a value

    +

    Greater than a value.

    Declaration

    Swift

    -
    case Equal = "="
    +
    case GreaterThan = ">"
    @@ -295,9 +306,9 @@

    Declaration

  • @@ -305,14 +316,14 @@

    Declaration

    -

    Greater than or equal a value

    +

    Less than a value.

    Declaration

    Swift

    -
    case GreaterThanOrEqual = ">="
    +
    case LessThan = "<"
    @@ -324,7 +335,7 @@

    Declaration

  • diff --git a/docs/framework/Enums/InternalPattern.html b/docs/framework/Enums/InternalPattern.html index dbceacc..5632cba 100644 --- a/docs/framework/Enums/InternalPattern.html +++ b/docs/framework/Enums/InternalPattern.html @@ -30,9 +30,6 @@
  • + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases
  • @@ -160,9 +171,9 @@

    InternalPattern

  • @@ -170,14 +181,14 @@

    InternalPattern

    -

    Pattern that matches expressions.

    +

    Pattern that matches expression types.

    Declaration

    Swift

    -
    case Expression = "(?<=\\{)(.+)(?=\\})"
    +
    case ExpressionPatternType = "(^.{2,3})(?=:)"
    @@ -187,9 +198,9 @@

    Declaration

  • @@ -197,14 +208,14 @@

    Declaration

    -

    Pattern that matches expression types.

    +

    Pattern that matches expressions.

    Declaration

    Swift

    -
    case ExpressionType = "(^.{2,3})(?=:)"
    +
    case Expression = "(?<=\\{)(.+)(?=\\})"
    @@ -243,7 +254,7 @@

    Declaration

  • diff --git a/docs/framework/Enums/LoadedTranslationType.html b/docs/framework/Enums/LoadedTranslationType.html new file mode 100644 index 0000000..e8e5cd3 --- /dev/null +++ b/docs/framework/Enums/LoadedTranslationType.html @@ -0,0 +1,327 @@ + + + + LoadedTranslationType Enum Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    LoadedTranslationType

    +

    Specifies available translation types used by translation processor to have +knowledge about specifics of loaded translations. It helps later to decide +what is needed to do with LoadedTranslation’s content property.

    + +
    +
    +
    +
      +
    • + +
      +
      +
      +
      +
      +

      Pair where value is dictionary that contains dictionary with expression and +length variations.

      + +
      "car-sentence": {
      +    "one": {
      +        "@100": "one car",
      +        "@200": "just one car",
      +        "@300": "you've got just one car"
      +    },
      +
      +    "more": {
      +        "@100": "%d cars",
      +        "@300": "you've got %d cars"
      +    },
      +
      +    "two": "two cars"
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithExpressionsAndLengthVariations
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + WithLengthVariations + +
      +
      +
      +
      +
      +
      +

      Pair where value is a dictionary with length variations.

      + +
      "forgot-password": {
      +    "@100": "Forgot Password? Help.",
      +    "@200": "Forgot Password? Get password Help.",
      +    "@300": "Forgotten Your Password? Get password Help."
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithLengthVariations
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + WithExpressions + +
      +
      +
      +
      +
      +
      +

      Pair where value is a dictionary with expressions.

      + +
      "cars": {
      +    "one": "1 car",
      +    "ie:x=2": "2 cars",
      +    "more": "%d cars"
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithExpressions
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + Simple + +
      +
      +
      +
      +
      +
      +

      Simple key-value pair.

      + +
      "welcome": "welcome"
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case Simple
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
    + diff --git a/docs/framework/Functions.html b/docs/framework/Functions.html new file mode 100644 index 0000000..f190723 --- /dev/null +++ b/docs/framework/Functions.html @@ -0,0 +1,213 @@ + + + + Functions Reference + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    Functions

    +

    The following functions are available globally.

    + +
    +
    +
    +
      +
    • +
      + + + + <!(_:_:) + +
      +
      +
      +
      +
      +
      +

      Get Unique operator. It helps in getting unique shared expressions from two arrays. +Content of lhs array will be checked in terms on uniqueness. The operator does +check is there is any shared expression in lhs that is presented in rhs. +If element from lhs is not in rhs then this element is correct and is returned +in new array which is a result of this operation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      func <! (lhs: [SharedExpression], rhs: [SharedExpression]) -> [SharedExpression]
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
  • + diff --git a/docs/framework/Protocols.html b/docs/framework/Protocols.html index b93a562..38f4e93 100644 --- a/docs/framework/Protocols.html +++ b/docs/framework/Protocols.html @@ -29,9 +29,6 @@
  • + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases
  • @@ -222,39 +233,6 @@

    Declaration

    -
    -
      -
    • -
      - - - - KeyValue - -
      -
      -
      -
      -
      -
      -

      Protocol that defines properties and methods that need to be implemented by -objects that keeps key-value pair things.

      - - See more -
      -
      -

      Declaration

      -
      -

      Swift

      -
      protocol KeyValue
      - -
      -
      -
      -
      -
    • -
    -
    • @@ -270,8 +248,8 @@

      Declaration

      -

      Protocol that is implemented by classes that contains shared expressions. -Shared expressions are built-in expressions that user can easily use when +

      Protocol that is implemented by classes/structs that contains shared expressions. +Shared expressions are built-in expressions that user can easily use when localizing app.

      Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html

      @@ -294,7 +272,7 @@

      Declaration

    diff --git a/docs/framework/Protocols/ExpressionMatcher.html b/docs/framework/Protocols/ExpressionMatcher.html index 25d5b2a..17c3557 100644 --- a/docs/framework/Protocols/ExpressionMatcher.html +++ b/docs/framework/Protocols/ExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -215,7 +226,7 @@

    Return Value

    diff --git a/docs/framework/Protocols/ExpressionParser.html b/docs/framework/Protocols/ExpressionParser.html index 5970ee7..55ccfe2 100644 --- a/docs/framework/Protocols/ExpressionParser.html +++ b/docs/framework/Protocols/ExpressionParser.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -173,7 +184,7 @@

    ExpressionParser

    -

    pattern of expression

    +

    Pattern of expression.

    @@ -273,7 +284,7 @@

    Parameters

    diff --git a/docs/framework/Protocols/SharedExpressionProtocol.html b/docs/framework/Protocols/SharedExpressionProtocol.html index 17bfa2b..98954d3 100644 --- a/docs/framework/Protocols/SharedExpressionProtocol.html +++ b/docs/framework/Protocols/SharedExpressionProtocol.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -151,8 +162,8 @@

    SharedExpressionProtocol

    -

    Protocol that is implemented by classes that contains shared expressions. -Shared expressions are built-in expressions that user can easily use when +

    Protocol that is implemented by classes/structs that contains shared expressions. +Shared expressions are built-in expressions that user can easily use when localizing app.

    Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html

    @@ -193,7 +204,7 @@

    Declaration

    diff --git a/docs/framework/Structs.html b/docs/framework/Structs.html index ddbb47f..27ccb86 100644 --- a/docs/framework/Structs.html +++ b/docs/framework/Structs.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -154,6 +165,43 @@

    Structs

    +
    +
      +
    • +
      + + + + Expression + +
      +
      +
      +
      +
      +
      +

      This class contains pattern of expression and localized value as well as +length variations if any are associated. During instance initialization pattern +is analyzed and correct expression matcher is created. If no matcher matches +the expression pattern then when validating there is only check if passed value +is the same like pattern (equality). If there is matcher then its internal logic +validates passed value.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct Expression
      + +
      +
      +
      +
      +
    • +
    +
    • @@ -201,7 +249,7 @@

      Declaration

      -

      Validates inequality extended expressions

      +

      Validates inequality extended expressions.

      See more
      @@ -218,6 +266,71 @@

      Declaration

    +
    +
      +
    • +
      + + + + LengthVariation + +
      +
      +
      +
      +
      +
      +

      Length variation representation. It contains a width property which specifies +up to which width of a screen the text in value property should be presented.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct LengthVariation
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
      +
    • +
      + + + + LoadedTranslation + +
      +
      +
      +
      +
      +
      +

      Struct that represents loaded translation.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct LoadedTranslation
      + +
      +
      +
      +
      +
    • +
    +
    • @@ -288,9 +401,9 @@

      Declaration

    • @@ -298,17 +411,15 @@

      Declaration

      -

      Represents key-value pair from Localizable.strings files. -It contains key, value and expression if exists for the key. -It can also validate if text matches expression’s requirements.

      +

      Represents translation with expressions.

      - See more + See more

      Declaration

      Swift

      -
      struct TranslatablePair: KeyValue
      +
      struct Translation
      @@ -320,7 +431,7 @@

      Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/TranslatablePair.html b/docs/framework/Structs/Expression.html similarity index 67% rename from docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/TranslatablePair.html rename to docs/framework/Structs/Expression.html index 9f66b76..4aeb32f 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/TranslatablePair.html +++ b/docs/framework/Structs/Expression.html @@ -1,7 +1,7 @@ - TranslatablePair Struct Reference + Expression Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,10 +161,13 @@
    -

    TranslatablePair

    -

    Represents key-value pair from Localizable.strings files. -It contains key, value and expression if exists for the key. -It can also validate if text matches expression’s requirements.

    +

    Expression

    +

    This class contains pattern of expression and localized value as well as +length variations if any are associated. During instance initialization pattern +is analyzed and correct expression matcher is created. If no matcher matches +the expression pattern then when validating there is only check if passed value +is the same like pattern (equality). If there is matcher then its internal logic +validates passed value.

    @@ -162,9 +176,9 @@

    TranslatablePair

  • - - - key + + + pattern
    @@ -172,14 +186,14 @@

    TranslatablePair

    -

    Key from Localizable.strings.

    +

    Pattern of an expression.

    Declaration

    Swift

    -
    var key: Key
    +
    let pattern: ExpressionPattern
    @@ -189,9 +203,9 @@

    Declaration

  • - + - value + value
    @@ -199,14 +213,15 @@

    Declaration

    -

    Value from Localizable.strings.

    +

    A localized value. If length vartiations array is empty or you want to +get full localized value use this property.

    Declaration

    Swift

    -
    var value: Value
    +
    let value: String
    @@ -216,9 +231,9 @@

    Declaration

  • @@ -226,14 +241,14 @@

    Declaration

    -

    Expression which is parsed from key.

    +

    Array of length variations.

    Declaration

    Swift

    -
    var expression: Expression? = nil
    +
    let lengthVariations: [LengthVariation]
    @@ -243,9 +258,9 @@

    Declaration

  • @@ -253,14 +268,14 @@

    Declaration

    -

    Tells if pair has expression or not.

    +

    Expression matcher that is used in validation.

    Declaration

    Swift

    -
    var hasExpression: Bool { return expression != nil }
    +
    private var expressionMatcher: ExpressionMatcher? = nil
    @@ -270,9 +285,9 @@

    Declaration

  • @@ -280,17 +295,14 @@

    Declaration

    -

    It returns key without expression pattern. -If pair has expression set to nil it will return key. -If expression exist the key will be parsed and returned without -expression pattern.

    +

    Returns expression object.

    Declaration

    Swift

    -
    var keyWithoutExpression: String
    +
    init(pattern: String, value: String, lengthVariations: [LengthVariation] = [LengthVariation]())
    @@ -300,9 +312,9 @@

    Declaration

  • @@ -310,15 +322,14 @@

    Declaration

    -

    Creates TranslatablePair. It automatically tries to parse -expression from key - if there is any.

    +

    Method that validates passed string.

    Declaration

    Swift

    -
    init(key: Key, value: Value)
    +
    func validate(value: String) -> Bool
    @@ -326,19 +337,6 @@

    Declaration

    Parameters

    - - - - @@ -355,32 +353,10 @@

    Parameters

    - - key - - -
    -

    A key from Localizable.strings

    - -
    -
    @@ -347,7 +345,7 @@

    Parameters

    -

    A value from Localizable.strings

    +

    value that should be matched

    -
  • -
    -
  • -
  • -
    - - - - parseExpression() - -
    -
    -
    -
    -
    -
    -

    Method parses expression from the key property.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    mutating func parseExpression()
    +
    +

    Return Value

    +

    true if value match expression, otherwise false.

    -
    @@ -388,9 +364,9 @@

    Declaration

  • @@ -398,15 +374,14 @@

    Declaration

    -

    Validates string and check if matches expression’s requirements. -If pair has no expression it return false.

    +

    Method used to get ExpressionPatternType of passed expression pattern.

    Declaration

    Swift

    -
    func validate(value: String) -> Bool
    +
    private func getExpressionType(pattern: ExpressionPattern) -> ExpressionPatternType?
    @@ -417,12 +392,12 @@

    Parameters

    - value + pattern
    -

    A value that will be matched.

    +

    expression pattern that will be checked.

    @@ -432,7 +407,7 @@

    Parameters

    Return Value

    -

    true if value matches expression, otherwise false.

    +

    ExpressionPatternType if pattern is supported, otherwise nil.

  • @@ -443,7 +418,7 @@

    Return Value

  • diff --git a/docs/framework/Structs/InequalityExpressionMatcher.html b/docs/framework/Structs/InequalityExpressionMatcher.html index baf8763..d592fa2 100644 --- a/docs/framework/Structs/InequalityExpressionMatcher.html +++ b/docs/framework/Structs/InequalityExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -197,7 +208,7 @@

    Declaration

    -

    Value that will be used during validation to compare to passed one.

    +

    A value that will be used during validation to compare to passed one.

    @@ -334,7 +345,7 @@

    Return Value

    diff --git a/docs/framework/Structs/InequalityExtendedExpressionMatcher.html b/docs/framework/Structs/InequalityExtendedExpressionMatcher.html index 63c7c68..4c1d6c0 100644 --- a/docs/framework/Structs/InequalityExtendedExpressionMatcher.html +++ b/docs/framework/Structs/InequalityExtendedExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -151,7 +162,7 @@

    InequalityExtendedExpressionMatcher

    -

    Validates inequality extended expressions

    +

    Validates inequality extended expressions.

    @@ -170,7 +181,7 @@

    InequalityExtendedExpressionMatcher

    -

    Matcher that validates left side of expressions

    +

    Matcher that validates left side of expressions.

    @@ -197,7 +208,7 @@

    Declaration

    -

    Matcher that validates right side of expressions

    +

    Matcher that validates right side of expressions.

    @@ -328,7 +339,7 @@

    Return Value

    diff --git a/docs/framework/Enums/StringsFileType.html b/docs/framework/Structs/LengthVariation.html similarity index 73% rename from docs/framework/Enums/StringsFileType.html rename to docs/framework/Structs/LengthVariation.html index 6ad3929..4abb0db 100644 --- a/docs/framework/Enums/StringsFileType.html +++ b/docs/framework/Structs/LengthVariation.html @@ -1,7 +1,7 @@ - StringsFileType Enum Reference + LengthVariation Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,8 +161,9 @@
    -

    StringsFileType

    -

    Enum that represents file types used by LocalizableFilesLoader.

    +

    LengthVariation

    +

    Length variation representation. It contains a width property which specifies +up to which width of a screen the text in value property should be presented.

    @@ -160,9 +172,9 @@

    StringsFileType

  • - - - Expressions + + + width
    @@ -170,14 +182,14 @@

    StringsFileType

    -

    Expressions.strings file.

    +

    Max width of a screen on which the value should be presented.

    Declaration

    Swift

    -
    case Expressions = "Expressions"
    +
    let width: Int
    @@ -187,9 +199,9 @@

    Declaration

  • - - - Localizable + + + value
    @@ -197,14 +209,14 @@

    Declaration

    -

    Localizable.strings file.

    +

    String with localized content in some language.

    Declaration

    Swift

    -
    case Localizable = "Localizable"
    +
    let value: String
    @@ -216,7 +228,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/KeyValue.html b/docs/framework/Structs/LoadedTranslation.html similarity index 72% rename from docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/KeyValue.html rename to docs/framework/Structs/LoadedTranslation.html index a44df05..96b77a5 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/KeyValue.html +++ b/docs/framework/Structs/LoadedTranslation.html @@ -1,7 +1,7 @@ - KeyValue Protocol Reference + LoadedTranslation Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,9 +161,8 @@
    -

    KeyValue

    -

    Protocol that defines properties and methods that need to be implemented by -objects that keeps key-value pair things.

    +

    LoadedTranslation

    +

    Struct that represents loaded translation.

    @@ -161,9 +171,9 @@

    KeyValue

  • - - - key + + + type
    @@ -171,14 +181,15 @@

    KeyValue

    -

    A key.

    +

    A type of translation. It is used later to convert object into translation +correclty.

    Declaration

    Swift

    -
    var key: Key {get set}
    +
    let type: LoadedTranslationType
    @@ -188,9 +199,9 @@

    Declaration

  • - - - value + + + key
    @@ -198,14 +209,14 @@

    Declaration

    -

    A value.

    +

    Key that identifies this translation.

    Declaration

    Swift

    -
    var value: Value {get set}
    +
    var key: String
    @@ -215,9 +226,9 @@

    Declaration

  • @@ -225,14 +236,14 @@

    Declaration

    -

    Initializer.

    +

    A content of translation just loaded from a file user in future processing.

    Declaration

    Swift

    -
    init(key: Key, value: Value)
    +
    let content: Dictionary<String, AnyObject>
    @@ -244,7 +255,7 @@

    Declaration

  • diff --git a/docs/framework/Structs/RegexExpressionMatcher.html b/docs/framework/Structs/RegexExpressionMatcher.html index aa7eb3b..8fb07f0 100644 --- a/docs/framework/Structs/RegexExpressionMatcher.html +++ b/docs/framework/Structs/RegexExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -289,7 +300,7 @@

    Return Value

    diff --git a/docs/framework/Structs/SharedExpression.html b/docs/framework/Structs/SharedExpression.html index 787f52d..a9034f8 100644 --- a/docs/framework/Structs/SharedExpression.html +++ b/docs/framework/Structs/SharedExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -160,9 +171,9 @@

    SharedExpression

  • - - - key + + + identifier
    @@ -170,14 +181,14 @@

    SharedExpression

    -

    Key of expression.

    +

    Identifier of expression.

    Declaration

    Swift

    -
    let key: Key
    +
    let identifier: String
    @@ -204,7 +215,7 @@

    Declaration

    Declaration

    Swift

    -
    let pattern: ExpressionPattern
    +
    let pattern: String
    @@ -214,9 +225,9 @@

    Declaration

  • @@ -224,50 +235,17 @@

    Declaration

    -

    Creates shared expression.

    +

    Creates expression.

    Declaration

    Swift

    -
    init(key: Key, pattern: ExpressionPattern)
    +
    init(identifier: String, pattern: String)
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    A key of expression.

    - -
    -
    - - pattern - - -
    -

    A pattern of expression.

    - -
    -
    -
  • @@ -276,7 +254,7 @@

    Parameters

    diff --git a/docs/framework/Structs/Translation.html b/docs/framework/Structs/Translation.html new file mode 100644 index 0000000..348fc89 --- /dev/null +++ b/docs/framework/Structs/Translation.html @@ -0,0 +1,304 @@ + + + + Translation Struct Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    Translation

    +

    Represents translation with expressions.

    + +
    +
    +
    +
      +
    • +
      + + + + key + +
      +
      +
      +
      +
      +
      +

      Key that identifies a translation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      let key: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + expressions + +
      +
      +
      +
      +
      +
      +

      Expressions that are related to a translation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      let expressions: [Expression]
      + +
      +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Validates passed text and uses fittingWidth for getting proper +localized string.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      func validate(text: String, fittingWidth: Int?) -> String?
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + +
      + + text + + +
      +

      A text that is matched.

      + +
      +
      + + fittingWidth + + +
      +

      A max width of a screen that text should match.

      + +
      +
      +
      +
      +

      Return Value

      +

      A localized string if any expression validates the text, +otherwise nil.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
    + diff --git a/docs/framework/Typealiases.html b/docs/framework/Typealiases.html index e955408..11ea5fc 100644 --- a/docs/framework/Typealiases.html +++ b/docs/framework/Typealiases.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -159,9 +170,9 @@

    Typealiases

  • @@ -169,14 +180,16 @@

    Typealiases

    -

    String that contains expression pattern, e.g. ie:x<5, exp:^1$.

    +

    https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + base +Represents country codes in framework. +Country codes are used to get correct user device’s language.

    Declaration

    Swift

    -
    internal typealias ExpressionPattern = String
    +
    internal typealias CountryCode = String
    @@ -190,63 +203,9 @@

    Declaration

  • - - - KVDict - -
    -
    -
    -
    -
    -
    -

    Represent key-value pair of Key and Value from .strings file.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    internal typealias KVDict = Dictionary<Key, Value>
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Key - -
    -
    -
    -
    -
    -
    -

    Represents key from .strings file

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    internal typealias Key = String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Value + + + ExpressionPattern
    @@ -254,14 +213,14 @@

    Declaration

    -

    Representes value from .strings file

    +

    String that contains expression pattern, e.g. ie:x<5, exp:^1$.

    Declaration

    Swift

    -
    internal typealias Value = String
    +
    internal typealias ExpressionPattern = String
    @@ -275,9 +234,9 @@

    Declaration

  • @@ -285,20 +244,14 @@

    Declaration

    -

    Type that contains two dictionaries.

    - -

    base - for Base localization

    - -

    pref - for preferred language localization

    - -

    It contains KVDict instanes so every one has key and value.

    +

    Represents json content.

    Declaration

    Swift

    -
    internal typealias BasePrefDicts = (base: KVDict, pref: KVDict)
    +
    internal typealias JSONDictionary = Dictionary<String, AnyObject>
    @@ -322,7 +275,7 @@

    Declaration

    -

    Type that represents pattern with regular expression

    +

    Type that represents pattern with regular expression.

    @@ -340,35 +293,6 @@

    Declaration

      -
    • -
      - - - - Language - -
      -
      -
      -
      -
      -
      -

      Defines language selected on the user’s device e.g. en, pl, ru. -Language can be also Base. It used used for finding right Localizable.strings -and Expression.strings and to load built-in shared expressions.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      internal typealias Language = String
      - -
      -
      -
      -
      -
    • @@ -382,7 +306,7 @@

      Declaration

      -

      Handy typealias that can be used instead of long Swifternalization

      +

      Handy typealias that can be used instead of longer Swifternalization

      @@ -401,7 +325,7 @@

      Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes.html index 8db924d..673c50a 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases
  • @@ -159,9 +170,9 @@

    Classes

  • @@ -169,19 +180,15 @@

    Classes

    -

    Class represents single expression that is added in curly -brackets inside key in Localizable.strings file or as a value in -Expressions.strings file.

    - -

    It is able to validate passed string using internal expression matcher.

    +

    Parses inequality expression patterns. e.g. ie:x=5.

    - See more + See more

    Declaration

    Swift

    -
    class Expression
    +
    class InequalityExpressionParser: ExpressionParser
    @@ -195,9 +202,9 @@

    Declaration

  • @@ -205,15 +212,15 @@

    Declaration

    -

    Parses inequality expression patterns. e.g. ie:x=5.

    +

    Parses inequality extended expressions. iex:5<x<10.

    - See more + See more

    Declaration

    Swift

    -
    class InequalityExpressionParser: ExpressionParser
    +
    class InequalityExtendedExpressionParser: InequalityExpressionParser
    @@ -227,9 +234,9 @@

    Declaration

  • @@ -237,15 +244,15 @@

    Declaration

    -

    Parses inequality extended expressions. iex:5<x<10.

    +

    Simple JSON file loader.

    - See more + See more

    Declaration

    Swift

    -
    class InequalityExtendedExpressionParser: InequalityExpressionParser
    +
    final class JSONFileLoader
    @@ -259,9 +266,9 @@

    Declaration

  • @@ -269,15 +276,16 @@

    Declaration

    -

    Class responsible for loading content from specified file type.

    +

    Translation processor which takes loaded translations and process them to make +them regular translation objects that can be used for further work.

    - See more + See more

    Declaration

    Swift

    -
    class LocalizableFilesLoader
    +
    class LoadedTranslationsProcessor
    @@ -309,7 +317,7 @@

    Declaration

    Declaration

    Swift

    -
    class Regex
    +
    final class Regex
    @@ -387,9 +395,41 @@

    Declaration

  • +
    +
    +
    +
    +
    +

    Used to load content from expressions.json file for specified language.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    final class SharedExpressionsLoader
    + +
    +
    +
    +
    +
  • + +
    +
    +
      +
    • +
      @@ -398,30 +438,30 @@

      Declaration

      Swifternalization contains some built-in country-related shared expressions. -Developer can create its own expressions in Expressions.strings file for -base and preferred language version of the file.

      +Developer can create its own expressions in expressions.json file for +base and preferred languages.

      -

      The class is responsible for proper loading the built-in shared ones and -those loaded from project’s files. It handles overriding of built-in and loads +

      The class is responsible for proper loading the built-in shared ones and +those loaded from project’s files. It handles overriding of built-in and loads those from Base and preferred language version.

      It always load base expressions, then looking for language specific. -If in Base file are some expressions that overrides built-in, that’s fine -the class adds or developer’s custom expressions and add only those of built-in +If in Base are some expressions that overrides built-ins, that’s fine. +The class adds developer’s custom expressions and adds only those of built-in that are not contained in the Base.

      -

      The same is for preferred languages but also here what is important is that -at first preferred language file expressions are loaded, then if something -different is defined in Base it will be also loaded and then the built-in +

      The same is for preferred languages but also here what is important is that +at first preferred language file expressions are loaded, then if something +different is defined in Base it will be also loaded and then the built-in expression that differs.

      - See more + See more

      Declaration

      Swift

      -
      class SharedExpressionsConfigurator
      +
      class SharedExpressionsProcessor
      @@ -478,35 +518,56 @@

      Declaration

      This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

      - -

      It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

      +that can be used to get localized strings.

      -

      Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

      +

      The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

      -

      It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

      +

      Before calling any method that return localized string call configure:.

      -

      It looks for content in the NSBundle you can provide and try to find:

      + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      final public class Swifternalization
      -
        -
      • Localizable.strings (Base),
      • -
      • Localizable.strings of preferred language, e.g. Localizable.strings (en)
      • -
      • Expressions.strings (Base),
      • -
      • Expressions.strings of preferred language, e.g. Expressions.strings (en)
      • -
      +
      +
      +
  • +
    +
  • + +
    +
    +
      +
    • +
      + + + + TranslationsLoader + +
      +
      +
      +
      +
      +
      +

      Class that gets dictionary of translations and turn it into LoadedTranslations.

      - See more + See more

      Declaration

      Swift

      -
      public class Swifternalization
      +
      final class TranslationsLoader
      @@ -518,7 +579,7 @@

      Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Expression.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Expression.html deleted file mode 100644 index 13546d2..0000000 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Expression.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - Expression Class Reference - - - - - - - - - -
    -
    -

    Swifternalization Docs (100% documented)

    -

    View on GitHub

    -
    -
    -
    - -
    -
    - -
    -
    -
    -

    Expression

    -

    Class represents single expression that is added in curly -brackets inside key in Localizable.strings file or as a value in -Expressions.strings file.

    - -

    It is able to validate passed string using internal expression matcher.

    - -
    -
    -
    -
      -
    • -
      - - - - pattern - -
      -
      -
      -
      -
      -
      -

      Pattern of expression passed during initialization.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      let pattern: ExpressionPattern
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - type - -
      -
      -
      -
      -
      -
      -

      Type of expression computed based on pattern.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private var type: ExpressionType!
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - matcher - -
      -
      -
      -
      -
      -
      -

      Matcher created based on pattern.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private var matcher: ExpressionMatcher!
      - -
      -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Return ExpressionPattern object if passed str parameter contains -some pattern. The pattern may not be one of the supported one. -If the syntax of the passed string is correct it is treated as pattern.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      class func parseExpressionPattern(str: String) -> ExpressionPattern?
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - str - - -
      -

      string which may or may not contain a pattern.

      - -
      -
      -
      -
      -

      Return Value

      -

      ExpressionPattern object or nil when pattern is not found.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Tries to parse passed str parameter and created Expression from it. -If passed string contains expression that is supported and inside logic -compute that this string is some supported pattern an Expression object -will be returned.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      class func expressionFromString(str: String) -> Expression?
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - str - - -
      -

      string that may or may not contain expression pattern

      - -
      -
      -
      -
      -

      Return Value

      -

      Expression object or nil if pattern is not supported.

      - -
      -
      -
      -
    • -
    • -
      - - - - init(pattern:) - -
      -
      -
      -
      -
      -
      -

      Initializer that takes expression pattern. It may fail when expression -type of passed expression is not supported.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      init?(pattern: ExpressionPattern)
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - pattern - - -
      -

      pattern of expression

      - -
      -
      -
      -
      -

      Return Value

      -

      Expression object or nil when pattern is not supported.

      - -
      -
      -
      -
    • -
    • -
      - - - - validate(_:) - -
      -
      -
      -
      -
      -
      -

      Method that validates passed string.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      func validate(value: String) -> Bool
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - value - - -
      -

      value that should be matched

      - -
      -
      -
      -
      -

      Return Value

      -

      true if value match expression, otherwise false.

      - -
      -
      -
      -
    • -
    -
    -
    - -
      -
    • -
      - - - - buildMatcher() - -
      -
      -
      -
      -
      -
      -

      Method used to create ExpressionMatcher instance that match expression -pattern of this Expression object.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private func buildMatcher()
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - getExpressionType(_:) - -
      -
      -
      -
      -
      -
      -

      Method used to get ExpressionType of passed ExpressionPattern.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func getExpressionType(pattern: ExpressionPattern) -> ExpressionType?
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - pattern - - -
      -

      expression pattern that will be checked.

      - -
      -
      -
      -
      -

      Return Value

      -

      ExpressionType if pattern is supported, otherwise nil.

      - -
      -
      -
      -
    • -
    -
    -
    -
    - -
    -
    - -
    - diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExpressionParser.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExpressionParser.html index 8fcb413..9ee4551 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExpressionParser.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExpressionParser.html @@ -30,9 +30,6 @@
  • + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases
  • @@ -170,7 +181,7 @@

    InequalityExpressionParser

    -

    Pattern of expression.

    +

    A pattern of expression.

    @@ -308,7 +319,7 @@

    Return Value

    -

    Get value - Int.

    +

    Get value - Double.

    @@ -473,7 +484,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExtendedExpressionParser.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExtendedExpressionParser.html index fbe1c5f..b370281 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExtendedExpressionParser.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/InequalityExtendedExpressionParser.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -333,7 +344,7 @@

    Return Value

    diff --git a/docs/framework/Classes/LocalizableFilesLoader.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/JSONFileLoader.html similarity index 58% rename from docs/framework/Classes/LocalizableFilesLoader.html rename to docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/JSONFileLoader.html index 01cbb70..d494697 100644 --- a/docs/framework/Classes/LocalizableFilesLoader.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/JSONFileLoader.html @@ -1,7 +1,7 @@ - LocalizableFilesLoader Class Reference + JSONFileLoader Class Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,8 +161,8 @@
    -

    LocalizableFilesLoader

    -

    Class responsible for loading content from specified file type.

    +

    JSONFileLoader

    +

    Simple JSON file loader.

    @@ -160,63 +171,9 @@

    LocalizableFilesLoader

  • - - - BaseLanguage - -
    -
    -
    -
    -
    -
    -

    Defines Base language - which is equal Base.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let BaseLanguage: Language = "Base"
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - bundle - -
    -
    -
    -
    -
    -
    -

    Bundle where files are located

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let bundle: NSBundle
    - -
    -
    -
    -
    -
  • -
  • -
    @@ -224,14 +181,14 @@

    Declaration

    -

    Creates LocalizableFilesLoader instance.

    +

    Loads translations dict for specified language.

    Declaration

    Swift

    -
    init(_ bundle: NSBundle)
    +
    class func loadTranslations(countryCode: CountryCode, bundle: NSBundle) -> JSONDictionary
    @@ -239,6 +196,19 @@

    Declaration

    Parameters

    + + + + @@ -255,15 +225,20 @@

    Parameters

    + + countryCode + + +
    +

    A country code.

    + +
    +
    @@ -247,7 +217,7 @@

    Parameters

    -

    A bundle where .strings files are located.

    +

    A bundle when file is located.

    +
    +

    Return Value

    +

    Returns json of file or empty dictionary if cannot load a file.

    + +
  • @@ -271,15 +246,14 @@

    Parameters

    -

    Loads content from files of specified type and language. -Converts them to BasePrefDicts instance.

    +

    Loads expressions dict for specified language.

    Declaration

    Swift

    -
    func loadContentFromFilesOfType(type: StringsFileType, language: Language) -> BasePrefDicts
    +
    class func loadExpressions(countryCode: CountryCode, bundle: NSBundle) -> Dictionary<String, String>
    @@ -290,12 +264,12 @@

    Parameters

    - type + countryCode
    -

    A type of files that will be loaded.

    +

    A country code.

    @@ -303,13 +277,12 @@

    Parameters

    - language + bundle
    -

    A preferred language - it will be used to find proper -file for specified file type.

    +

    A bundle when file is located.

    @@ -319,29 +292,18 @@

    Parameters

    Return Value

    -

    Returns content of files converted into key-value pairs.

    +

    dictionary with expressions or empty dictionary if cannot load a file.

  • - - -
    - - @@ -474,7 +485,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/RegexExpressionParser.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/RegexExpressionParser.html index 95c3009..20410d0 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/RegexExpressionParser.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/RegexExpressionParser.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -302,7 +313,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedBaseExpression.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedBaseExpression.html index e2ad24a..60cec49 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedBaseExpression.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedBaseExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -189,7 +200,7 @@

    Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsConfigurator.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsConfigurator.html deleted file mode 100644 index 32f23ed..0000000 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsConfigurator.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - SharedExpressionsConfigurator Class Reference - - - - - - - - - -
    -
    -

    Swifternalization Docs (100% documented)

    -

    View on GitHub

    -
    -
    -
    - -
    -
    - -
    -
    -
    -

    SharedExpressionsConfigurator

    -

    Swifternalization contains some built-in country-related shared expressions. -Developer can create its own expressions in Expressions.strings file for -base and preferred language version of the file.

    - -

    The class is responsible for proper loading the built-in shared ones and -those loaded from project’s files. It handles overriding of built-in and loads -those from Base and preferred language version.

    - -

    It always load base expressions, then looking for language specific. -If in Base file are some expressions that overrides built-in, that’s fine -the class adds or developer’s custom expressions and add only those of built-in -that are not contained in the Base.

    - -

    The same is for preferred languages but also here what is important is that -at first preferred language file expressions are loaded, then if something -different is defined in Base it will be also loaded and then the built-in -expression that differs.

    - -
    -
    -
    -
      -
    • - -
      -
      -
      -
      -
      -

      Method takes expression for both Base and preferred language localizations -and also internally loads built-in expressions, combine them and returns -tuple with expressions for Base and another array for preferred language -localization.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      class func configureExpressions(dicts: BasePrefDicts, language: Language) -> (base: [SharedExpression], pref: [SharedExpression])
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - dicts - - -
      -

      A BasePrefDicts tuple that contains two dicts of -shared expressions for Base and preferred language localization.

      - -
      -
      - - language - - -
      -

      A preferred user’s language.

      - -
      -
      -
      -
      -

      Return Value

      -

      Tuple with arrays of shared expressions for Base and preferred -language localizations.

      - -
      -
      -
      -
    • -
    -
    -
    - -
      -
    • -
      - - - - convert(_:) - -
      -
      -
      -
      -
      -
      -

      Converts dictionary with expressions to array of SharedExpression objects.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func convert(expressionsDict: KVDict) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - expressionsDict - - -
      -

      Dictionary with key-value pair of expression -from Expressions.strings.

      - -
      -
      -
      -
      -

      Return Value

      -

      Array of SharedExpression objects.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Method loads built-in framework’s built-in expressions for specific language.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func loadBuiltInExpressions(language: Language) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - -
      - - language - - -
      -

      A preferred user’s language.

      - -
      -
      -
      -
      -

      Return Value

      -

      Shared expressions for specific language. If there is no -expression for passed language empty array is returned.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Method that merges expressions. It takes two arrays, one is source and one -is additional. If the source does not contain some expression from -additional array this expression will be added to the source.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func mergeExpressions(var source: [SharedExpression], additional: [SharedExpression]) -> [SharedExpression]
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - source - - -
      -

      A source array with expressions.

      - -
      -
      - - additional - - -
      -

      Array with additional expressions that may or may not -be added to source array.

      - -
      -
      -
      -
      -

      Return Value

      -

      Array with expressions that contains all elements from source -and elements from additional that were not in source.

      - -
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      This is just helper method. It does the same like -mergeExpressions(source:additional:) but this one takes reference to -source array instead of pasing it by value.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func mergeExpressions(inout source: [SharedExpression], additional: [SharedExpression]) -> Void
      - -
      -
      -
      -

      Parameters

      - - - - - - - - - - - -
      - - source - - -
      -

      reference to source array.

      - -
      -
      - - additional - - -
      -

      Array of additional shared expressions.

      - -
      -
      -
      -
      -

      Return Value

      -

      merged array of shared expressions.

      - -
      -
      -
      -
    • -
    -
    -
    -
    - -
    -
    - -
    - diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsLoader.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsLoader.html new file mode 100644 index 0000000..1dd2dba --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsLoader.html @@ -0,0 +1,235 @@ + + + + SharedExpressionsLoader Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    SharedExpressionsLoader

    +

    Used to load content from expressions.json file for specified language.

    + +
    +
    +
    +
      +
    • +
      + + + + loadExpressions(_:) + +
      +
      +
      +
      +
      +
      +

      Loads expressions for specified language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func loadExpressions(json: Dictionary<String, String>) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + countryCode + + +
      +

      A country code

      + +
      +
      +
      +
      +

      Return Value

      +

      array of loaded expressions.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsProcessor.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsProcessor.html new file mode 100644 index 0000000..a352fdc --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedExpressionsProcessor.html @@ -0,0 +1,332 @@ + + + + SharedExpressionsProcessor Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    SharedExpressionsProcessor

    +

    Swifternalization contains some built-in country-related shared expressions. +Developer can create its own expressions in expressions.json file for +base and preferred languages.

    + +

    The class is responsible for proper loading the built-in shared ones and +those loaded from project’s files. It handles overriding of built-in and loads +those from Base and preferred language version.

    + +

    It always load base expressions, then looking for language specific. +If in Base are some expressions that overrides built-ins, that’s fine. +The class adds developer’s custom expressions and adds only those of built-in +that are not contained in the Base.

    + +

    The same is for preferred languages but also here what is important is that +at first preferred language file expressions are loaded, then if something +different is defined in Base it will be also loaded and then the built-in +expression that differs.

    + +
    +
    +
    +
      +
    • + +
      +
      +
      +
      +
      +

      Method takes expression for both Base and preferred language localizations +and also internally loads built-in expressions, combine them and returns +expressions for Base and prefered language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func processSharedExpression(preferedLanguage: CountryCode, preferedLanguageExpressions: [SharedExpression], baseLanguageExpressions: [SharedExpression]) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + + + + + +
      + + preferedLanguage + + +
      +

      A user device’s language.

      + +
      +
      + + preferedLanguageExpressions + + +
      +

      Expressions from expressions.json

      + +
      +
      + + baseLanguageExpressions + + +
      +

      Expressions from base section of +expression.json.

      + +
      +
      +
      +
      +

      Return Value

      +

      array of shared expressions for Base and preferred language.

      + +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Method loads built-in framework’s built-in expressions for specific language.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      private class func loadBuiltInExpressions(language: CountryCode) -> [SharedExpression]
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + language + + +
      +

      A preferred user’s language.

      + +
      +
      +
      +
      +

      Return Value

      +

      Shared expressions for specific language. If there is no

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedPolishExpression.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedPolishExpression.html index 23bf90d..0eb1601 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedPolishExpression.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/SharedPolishExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -189,7 +200,7 @@

    Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization.html index 484fba2..2be03ee 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -152,27 +163,16 @@

    Swifternalization

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    @@ -181,64 +181,9 @@

    Swifternalization

  • - - - Static - -
    -
    -
    -
    -
    -
    -

    Struct to keep shared instance of Swifternalization

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private struct Static
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - bundle - -
    -
    -
    -
    -
    -
    -

    Bundle when Localizable and Expressions .strings files are located.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    private let bundle: NSBundle
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - basePairs + + + sharedInstance
    @@ -246,14 +191,14 @@

    Declaration

    -

    key-value from base Localizable.strings file

    +

    Shared instance of Swifternalization used internally.

    Declaration

    Swift

    -
    private var basePairs = [TranslatablePair]()
    +
    private static let sharedInstance = Swifternalization()
    @@ -263,9 +208,9 @@

    Declaration

  • @@ -273,14 +218,14 @@

    Declaration

    -

    key-value airs from preferred language Localizable.strings file

    +

    Array of translations that contain expressions and localized values.

    Declaration

    Swift

    -
    private var preferredPairs = [TranslatablePair]()
    +
    private var translations = [Translation]()
    @@ -291,19 +236,19 @@

    Declaration

    • @@ -311,20 +256,14 @@

      Public methods

      -

      Swifternalization takes NSBundle when Localizable.strings file is located. -This method return instance of the class but you don’t need it because -shared instance is set automatically.

      - -

      It get Localizable.strings file version based on the first language from -the prefferedLocalizations property of NSBundle. If Localizable.strings for -preferred language isn’t exist then Base is used instead.

      +

      Call the method to configure Swifternalization.

      Declaration

      Swift

      -
      public init(bundle: NSBundle)
      +
      public class func configure(bundle: NSBundle = NSBundle.mainBundle())
      @@ -340,7 +279,7 @@

      Parameters

      -

      bundle when .strings files are located.

      +

      A bundle when expressions.json and other files are located.

      @@ -354,9 +293,9 @@

      Parameters

    • @@ -364,20 +303,14 @@

      Parameters

      -

      Returns localized string for simple key that does not contain any expression.

      - -
      I18n.localizedString("car")
      -I18n.localizedString("car", defaultValue: "Audi")
      -I18n.localizedString("car", defaultValue: "Audi", comment: "Comment")
      -I18n.localizedString("car", comment: "Comment")
      -
      +

      Get localized value for a key.

      Declaration

      Swift

      -
      public class func localizedString(key: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -393,7 +326,23 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -406,8 +355,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -420,8 +369,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description of -a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -431,9 +380,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise -returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    • @@ -442,9 +390,9 @@

      Return Value

    • @@ -452,20 +400,14 @@

      Return Value

      -

      Returns localized string for key which contains expression.

      - -
      I18n.localizedExpressionString("cars", value: "10")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "Few cars")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "10", comment: "This is a comment")
      -I18n.localizedExpressionString("cars", value: "10", comment: "This is a comment")
      -
      +

      Get localized value for a key and string value.

      Declaration

      Swift

      -
      public class func localizedExpressionString(key: String, value: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -481,7 +423,7 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      @@ -489,12 +431,28 @@

      Parameters

      - value + stringValue
      -

      value used when validating expressions.

      +

      A value that is matched by expressions.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -507,8 +465,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -521,8 +479,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description -of a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -532,8 +490,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

  • @@ -542,9 +500,9 @@

    Return Value

  • @@ -552,19 +510,100 @@

    Return Value

    -

    This method is just extension to method -localizedExpressionString(_:value:defaultValue:comment:) that takes -String as a value parameter.

    +

    Get localized value for a key and string value.

    Declaration

    Swift

    -
    public class func localizedExpressionString(key: String, value: Int, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + key + + +
    +

    A key to which localized string is assigned.

    + +
    +
    + + intValue + + +
    +

    A value that is matched by expressions.

    + +
    +
    + + fittingWidth + + +
    +

    A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

    + +
    +
    + + defaultValue + + +
    +

    A default value that is returned when there is no +localized value for passed key.

    + +
    +
    + + comment + + +
    +

    A comment about the key and localized value. Just for +developer use for describing key-value pair.

    + +
    +
    +
    +
    +

    Return Value

    +

    localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    + +
  • @@ -572,73 +611,19 @@

    Declaration

    • -
      -
      -
      -
      -
      -

      Method that set shared instance of Swifternalization

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func setSharedInstance(instance: Swifternalization)
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - sharedInstance() - -
      -
      -
      -
      -
      -
      -

      Method that returns shared instance of Swifternalization

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private class func sharedInstance() -> Swifternalization!
      - -
      -
      -
      -
      -
    • -
    • -
      - - - - load() + + + load(bundle:)
      @@ -646,52 +631,37 @@

      Declaration

      -

      Method responsible for loading content into Swifternaliztion library. -It takes preferred language of user’s device, and tries to find -Localizable.strings (Preferred Language) and Localizable.strings (Base) -to get keys and values of words to translate.

      - -

      It also tries to find and load Expressions.strings (Preferred Language) -and Expressions.strings (Base) files when shared expressions might be -and tries to combine them together with built-in shared expression, -and at the end enumeate through key-value pairs for translation and -changes keys that have only expression shortcuts to full expressions.

      +

      Loads expressions and translations from expression.json and translation +json files.

      Declaration

      Swift

      -
      private func load()
      +
      private func load(bundle: NSBundle = NSBundle.mainBundle())
      -
      -
      -
    • -
    • - -
      -
      -
      -
      -
      -

      Gets preferred language of user’s device

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      private func getPreferredLanguage() -> Language
      +
      +

      Parameters

      + + + + + + + +
      + + bundle + + +
      +

      A bundle when files are located.

      -
      + +
      @@ -699,9 +669,9 @@

      Declaration

    • @@ -709,18 +679,14 @@

      Declaration

      -

      Enumerate through translatable pairs dict and check if there are some shared -expression identifiers that needs to be replaced with full expressions.

      - -

      Next create translatable pairs with susch updated expressions to make it -ready to be used by framework.

      +

      Get preferred language of user’s device.

      Declaration

      Swift

      -
      private func createTranslablePairs(translatableDict: KVDict, expressions: [SharedExpression]) -> [TranslatablePair]
      +
      private func getPreferredLanguage(bundle: NSBundle) -> CountryCode
      @@ -732,7 +698,7 @@

      Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization/Static.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization/Static.html deleted file mode 100644 index 98701fb..0000000 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/Swifternalization/Static.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - Static Struct Reference - - - - - - - - - -
    -
    -

    Swifternalization Docs (100% documented)

    -

    View on GitHub

    -
    -
    -
    - -
    -
    - -
    -
    -
    -

    Static

    -

    Struct to keep shared instance of Swifternalization

    - -
    -
    -
    -
      -
    • -
      - - - - instance - -
      -
      -
      -
      -
      -
      -

      Instance of Swifternalization that might be nil if not set.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      static var instance: Swifternalization? = nil
      - -
      -
      -
      -
      -
    • -
    -
    -
    -
    - -
    -
    - - - diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/TranslationsLoader.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/TranslationsLoader.html new file mode 100644 index 0000000..389ec54 --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Classes/TranslationsLoader.html @@ -0,0 +1,247 @@ + + + + TranslationsLoader Class Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    TranslationsLoader

    +

    Class that gets dictionary of translations and turn it into LoadedTranslations.

    + +
    +
    +
    +
      +
    • +
      + + + + loadTranslations(_:) + +
      +
      +
      +
      +
      +
      +

      Converts dictionary into array of LoadedTranslations.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      class func loadTranslations(json: Dictionary<String, AnyObject>) -> [LoadedTranslation]
      + +
      +
      +
      +

      Return Value

      +

      Array of LoadedTranslation objects from specified file.

      + +
      +
      +
      +
    • +
    • +
      + + + + detectElementType(_:) + +
      +
      +
      +
      +
      +
      +

      Analyzes passed dictionary and checks its content to match it to some translation type.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      private class func detectElementType(element: JSONDictionary) -> LoadedTranslationType?
      + +
      +
      +
      +

      Return Value

      +

      translation type of a dictionary.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums.html index 461de92..a8b8023 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -159,9 +170,9 @@

    Enums

  • @@ -169,15 +180,15 @@

    Enums

    -

    Supported expression types

    +

    Supported expression types.

    - See more + See more

    Declaration

    Swift

    -
    enum ExpressionType: String
    +
    enum ExpressionPatternType: String
    @@ -255,9 +266,9 @@

    Declaration

  • @@ -265,15 +276,17 @@

    Declaration

    -

    Enum that represents file types used by LocalizableFilesLoader.

    +

    Specifies available translation types used by translation processor to have +knowledge about specifics of loaded translations. It helps later to decide +what is needed to do with LoadedTranslation’s content property.

    - See more + See more

    Declaration

    Swift

    -
    enum StringsFileType: String
    +
    enum LoadedTranslationType
    @@ -285,7 +298,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionType.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionPatternType.html similarity index 78% rename from docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionType.html rename to docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionPatternType.html index 8a0cb44..c05f06b 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionType.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/ExpressionPatternType.html @@ -1,7 +1,7 @@ - ExpressionType Enum Reference + ExpressionPatternType Enum Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
  • @@ -150,8 +161,8 @@
    -

    ExpressionType

    -

    Supported expression types

    +

    ExpressionPatternType

    +

    Supported expression types.

    @@ -160,9 +171,9 @@

    ExpressionType

  • @@ -170,14 +181,14 @@

    ExpressionType

    -

    works on Int only, e.g. 4<x<10, 1<=x<18

    +

    Works with Int/Float, e.g. x<5, x=3, x<4.5.

    Declaration

    Swift

    -
    case InequalityExtended = "iex"
    +
    case Inequality = "ie"
    @@ -187,9 +198,9 @@

    Declaration

  • @@ -197,14 +208,14 @@

    Declaration

    -

    regular expression, e.g. [02-9]+

    +

    Works on Int/Float only, e.g. 4<x<10, 1<=x<18, 1.3<=x<15.4.

    Declaration

    Swift

    -
    case Regex = "exp"
    +
    case InequalityExtended = "iex"
    @@ -214,9 +225,9 @@

    Declaration

  • - - - Inequality + + + Regex
    @@ -224,14 +235,14 @@

    Declaration

    -

    works on Int only, e.g. x<5, x=3

    +

    Regular expression, e.g. [02-9]+.

    Declaration

    Swift

    -
    case Inequality = "ie"
    +
    case Regex = "exp"
    @@ -243,7 +254,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InequalitySign.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InequalitySign.html index 671b6e5..64525a3 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InequalitySign.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InequalitySign.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -170,7 +181,7 @@

    InequalitySign

    -

    Inverts enum

    +

    Inverts enum.

    @@ -187,9 +198,9 @@

    Declaration

  • - - - LessThan + + + Equal
    @@ -197,14 +208,14 @@

    Declaration

    -

    Less than a value

    +

    Equal a value.

    Declaration

    Swift

    -
    case LessThan = "<"
    +
    case Equal = "="
    @@ -214,9 +225,9 @@

    Declaration

  • @@ -224,14 +235,14 @@

    Declaration

    -

    Greater than a value

    +

    Less than or equal a value.

    Declaration

    Swift

    -
    case GreaterThan = ">"
    +
    case LessThanOrEqual = "<="
    @@ -241,9 +252,9 @@

    Declaration

  • @@ -251,14 +262,14 @@

    Declaration

    -

    Less than or equal a value

    +

    Greater than or equal a value.

    Declaration

    Swift

    -
    case LessThanOrEqual = "<="
    +
    case GreaterThanOrEqual = ">="
    @@ -268,9 +279,9 @@

    Declaration

  • - - - Equal + + + GreaterThan
    @@ -278,14 +289,14 @@

    Declaration

    -

    Equal a value

    +

    Greater than a value.

    Declaration

    Swift

    -
    case Equal = "="
    +
    case GreaterThan = ">"
    @@ -295,9 +306,9 @@

    Declaration

  • @@ -305,14 +316,14 @@

    Declaration

    -

    Greater than or equal a value

    +

    Less than a value.

    Declaration

    Swift

    -
    case GreaterThanOrEqual = ">="
    +
    case LessThan = "<"
    @@ -324,7 +335,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InternalPattern.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InternalPattern.html index dbceacc..5632cba 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InternalPattern.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/InternalPattern.html @@ -30,9 +30,6 @@
  • + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases
  • @@ -160,9 +171,9 @@

    InternalPattern

  • @@ -170,14 +181,14 @@

    InternalPattern

    -

    Pattern that matches expressions.

    +

    Pattern that matches expression types.

    Declaration

    Swift

    -
    case Expression = "(?<=\\{)(.+)(?=\\})"
    +
    case ExpressionPatternType = "(^.{2,3})(?=:)"
    @@ -187,9 +198,9 @@

    Declaration

  • @@ -197,14 +208,14 @@

    Declaration

    -

    Pattern that matches expression types.

    +

    Pattern that matches expressions.

    Declaration

    Swift

    -
    case ExpressionType = "(^.{2,3})(?=:)"
    +
    case Expression = "(?<=\\{)(.+)(?=\\})"
    @@ -243,7 +254,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/LoadedTranslationType.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/LoadedTranslationType.html new file mode 100644 index 0000000..e8e5cd3 --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/LoadedTranslationType.html @@ -0,0 +1,327 @@ + + + + LoadedTranslationType Enum Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    LoadedTranslationType

    +

    Specifies available translation types used by translation processor to have +knowledge about specifics of loaded translations. It helps later to decide +what is needed to do with LoadedTranslation’s content property.

    + +
    +
    +
    +
      +
    • + +
      +
      +
      +
      +
      +

      Pair where value is dictionary that contains dictionary with expression and +length variations.

      + +
      "car-sentence": {
      +    "one": {
      +        "@100": "one car",
      +        "@200": "just one car",
      +        "@300": "you've got just one car"
      +    },
      +
      +    "more": {
      +        "@100": "%d cars",
      +        "@300": "you've got %d cars"
      +    },
      +
      +    "two": "two cars"
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithExpressionsAndLengthVariations
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + WithLengthVariations + +
      +
      +
      +
      +
      +
      +

      Pair where value is a dictionary with length variations.

      + +
      "forgot-password": {
      +    "@100": "Forgot Password? Help.",
      +    "@200": "Forgot Password? Get password Help.",
      +    "@300": "Forgotten Your Password? Get password Help."
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithLengthVariations
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + WithExpressions + +
      +
      +
      +
      +
      +
      +

      Pair where value is a dictionary with expressions.

      + +
      "cars": {
      +    "one": "1 car",
      +    "ie:x=2": "2 cars",
      +    "more": "%d cars"
      +}
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case WithExpressions
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + Simple + +
      +
      +
      +
      +
      +
      +

      Simple key-value pair.

      + +
      "welcome": "welcome"
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case Simple
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
    + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Functions.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Functions.html new file mode 100644 index 0000000..f190723 --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Functions.html @@ -0,0 +1,213 @@ + + + + Functions Reference + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    Functions

    +

    The following functions are available globally.

    + +
    +
    +
    +
      +
    • +
      + + + + <!(_:_:) + +
      +
      +
      +
      +
      +
      +

      Get Unique operator. It helps in getting unique shared expressions from two arrays. +Content of lhs array will be checked in terms on uniqueness. The operator does +check is there is any shared expression in lhs that is presented in rhs. +If element from lhs is not in rhs then this element is correct and is returned +in new array which is a result of this operation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      func <! (lhs: [SharedExpression], rhs: [SharedExpression]) -> [SharedExpression]
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
  • + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols.html index b93a562..38f4e93 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols.html @@ -29,9 +29,6 @@
  • + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases
  • @@ -222,39 +233,6 @@

    Declaration

    -
    -
      -
    • -
      - - - - KeyValue - -
      -
      -
      -
      -
      -
      -

      Protocol that defines properties and methods that need to be implemented by -objects that keeps key-value pair things.

      - - See more -
      -
      -

      Declaration

      -
      -

      Swift

      -
      protocol KeyValue
      - -
      -
      -
      -
      -
    • -
    -
    • @@ -270,8 +248,8 @@

      Declaration

      -

      Protocol that is implemented by classes that contains shared expressions. -Shared expressions are built-in expressions that user can easily use when +

      Protocol that is implemented by classes/structs that contains shared expressions. +Shared expressions are built-in expressions that user can easily use when localizing app.

      Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html

      @@ -294,7 +272,7 @@

      Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionMatcher.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionMatcher.html index 25d5b2a..17c3557 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionMatcher.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -215,7 +226,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionParser.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionParser.html index 5970ee7..55ccfe2 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionParser.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/ExpressionParser.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -173,7 +184,7 @@

    ExpressionParser

    -

    pattern of expression

    +

    Pattern of expression.

    @@ -273,7 +284,7 @@

    Parameters

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/SharedExpressionProtocol.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/SharedExpressionProtocol.html index 17bfa2b..98954d3 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/SharedExpressionProtocol.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Protocols/SharedExpressionProtocol.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -151,8 +162,8 @@

    SharedExpressionProtocol

    -

    Protocol that is implemented by classes that contains shared expressions. -Shared expressions are built-in expressions that user can easily use when +

    Protocol that is implemented by classes/structs that contains shared expressions. +Shared expressions are built-in expressions that user can easily use when localizing app.

    Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html

    @@ -193,7 +204,7 @@

    Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs.html index ddbb47f..27ccb86 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -154,6 +165,43 @@

    Structs

    +
    +
      +
    • +
      + + + + Expression + +
      +
      +
      +
      +
      +
      +

      This class contains pattern of expression and localized value as well as +length variations if any are associated. During instance initialization pattern +is analyzed and correct expression matcher is created. If no matcher matches +the expression pattern then when validating there is only check if passed value +is the same like pattern (equality). If there is matcher then its internal logic +validates passed value.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct Expression
      + +
      +
      +
      +
      +
    • +
    +
    • @@ -201,7 +249,7 @@

      Declaration

      -

      Validates inequality extended expressions

      +

      Validates inequality extended expressions.

      See more
      @@ -218,6 +266,71 @@

      Declaration

    +
    +
      +
    • +
      + + + + LengthVariation + +
      +
      +
      +
      +
      +
      +

      Length variation representation. It contains a width property which specifies +up to which width of a screen the text in value property should be presented.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct LengthVariation
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
      +
    • +
      + + + + LoadedTranslation + +
      +
      +
      +
      +
      +
      +

      Struct that represents loaded translation.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      struct LoadedTranslation
      + +
      +
      +
      +
      +
    • +
    +
    • @@ -288,9 +401,9 @@

      Declaration

    • @@ -298,17 +411,15 @@

      Declaration

      -

      Represents key-value pair from Localizable.strings files. -It contains key, value and expression if exists for the key. -It can also validate if text matches expression’s requirements.

      +

      Represents translation with expressions.

      - See more + See more

      Declaration

      Swift

      -
      struct TranslatablePair: KeyValue
      +
      struct Translation
      @@ -320,7 +431,7 @@

      Declaration

    diff --git a/docs/framework/Structs/TranslatablePair.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Expression.html similarity index 67% rename from docs/framework/Structs/TranslatablePair.html rename to docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Expression.html index 9f66b76..4aeb32f 100644 --- a/docs/framework/Structs/TranslatablePair.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Expression.html @@ -1,7 +1,7 @@ - TranslatablePair Struct Reference + Expression Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,10 +161,13 @@
    -

    TranslatablePair

    -

    Represents key-value pair from Localizable.strings files. -It contains key, value and expression if exists for the key. -It can also validate if text matches expression’s requirements.

    +

    Expression

    +

    This class contains pattern of expression and localized value as well as +length variations if any are associated. During instance initialization pattern +is analyzed and correct expression matcher is created. If no matcher matches +the expression pattern then when validating there is only check if passed value +is the same like pattern (equality). If there is matcher then its internal logic +validates passed value.

    @@ -162,9 +176,9 @@

    TranslatablePair

  • - - - key + + + pattern
    @@ -172,14 +186,14 @@

    TranslatablePair

    -

    Key from Localizable.strings.

    +

    Pattern of an expression.

    Declaration

    Swift

    -
    var key: Key
    +
    let pattern: ExpressionPattern
    @@ -189,9 +203,9 @@

    Declaration

  • - + - value + value
    @@ -199,14 +213,15 @@

    Declaration

    -

    Value from Localizable.strings.

    +

    A localized value. If length vartiations array is empty or you want to +get full localized value use this property.

    Declaration

    Swift

    -
    var value: Value
    +
    let value: String
    @@ -216,9 +231,9 @@

    Declaration

  • @@ -226,14 +241,14 @@

    Declaration

    -

    Expression which is parsed from key.

    +

    Array of length variations.

    Declaration

    Swift

    -
    var expression: Expression? = nil
    +
    let lengthVariations: [LengthVariation]
    @@ -243,9 +258,9 @@

    Declaration

  • @@ -253,14 +268,14 @@

    Declaration

    -

    Tells if pair has expression or not.

    +

    Expression matcher that is used in validation.

    Declaration

    Swift

    -
    var hasExpression: Bool { return expression != nil }
    +
    private var expressionMatcher: ExpressionMatcher? = nil
    @@ -270,9 +285,9 @@

    Declaration

  • @@ -280,17 +295,14 @@

    Declaration

    -

    It returns key without expression pattern. -If pair has expression set to nil it will return key. -If expression exist the key will be parsed and returned without -expression pattern.

    +

    Returns expression object.

    Declaration

    Swift

    -
    var keyWithoutExpression: String
    +
    init(pattern: String, value: String, lengthVariations: [LengthVariation] = [LengthVariation]())
    @@ -300,9 +312,9 @@

    Declaration

  • @@ -310,15 +322,14 @@

    Declaration

    -

    Creates TranslatablePair. It automatically tries to parse -expression from key - if there is any.

    +

    Method that validates passed string.

    Declaration

    Swift

    -
    init(key: Key, value: Value)
    +
    func validate(value: String) -> Bool
    @@ -326,19 +337,6 @@

    Declaration

    Parameters

    - - - - @@ -355,32 +353,10 @@

    Parameters

    - - key - - -
    -

    A key from Localizable.strings

    - -
    -
    @@ -347,7 +345,7 @@

    Parameters

    -

    A value from Localizable.strings

    +

    value that should be matched

    -
  • -
    -
  • -
  • -
    - - - - parseExpression() - -
    -
    -
    -
    -
    -
    -

    Method parses expression from the key property.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    mutating func parseExpression()
    +
    +

    Return Value

    +

    true if value match expression, otherwise false.

    -
    @@ -388,9 +364,9 @@

    Declaration

  • @@ -398,15 +374,14 @@

    Declaration

    -

    Validates string and check if matches expression’s requirements. -If pair has no expression it return false.

    +

    Method used to get ExpressionPatternType of passed expression pattern.

    Declaration

    Swift

    -
    func validate(value: String) -> Bool
    +
    private func getExpressionType(pattern: ExpressionPattern) -> ExpressionPatternType?
    @@ -417,12 +392,12 @@

    Parameters

    - value + pattern
    -

    A value that will be matched.

    +

    expression pattern that will be checked.

    @@ -432,7 +407,7 @@

    Parameters

    Return Value

    -

    true if value matches expression, otherwise false.

    +

    ExpressionPatternType if pattern is supported, otherwise nil.

  • @@ -443,7 +418,7 @@

    Return Value

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExpressionMatcher.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExpressionMatcher.html index baf8763..d592fa2 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExpressionMatcher.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -197,7 +208,7 @@

    Declaration

    -

    Value that will be used during validation to compare to passed one.

    +

    A value that will be used during validation to compare to passed one.

    @@ -334,7 +345,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExtendedExpressionMatcher.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExtendedExpressionMatcher.html index 63c7c68..4c1d6c0 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExtendedExpressionMatcher.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/InequalityExtendedExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -151,7 +162,7 @@

    InequalityExtendedExpressionMatcher

    -

    Validates inequality extended expressions

    +

    Validates inequality extended expressions.

    @@ -170,7 +181,7 @@

    InequalityExtendedExpressionMatcher

    -

    Matcher that validates left side of expressions

    +

    Matcher that validates left side of expressions.

    @@ -197,7 +208,7 @@

    Declaration

    -

    Matcher that validates right side of expressions

    +

    Matcher that validates right side of expressions.

    @@ -328,7 +339,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/StringsFileType.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LengthVariation.html similarity index 73% rename from docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/StringsFileType.html rename to docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LengthVariation.html index 6ad3929..4abb0db 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Enums/StringsFileType.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LengthVariation.html @@ -1,7 +1,7 @@ - StringsFileType Enum Reference + LengthVariation Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,8 +161,9 @@
    -

    StringsFileType

    -

    Enum that represents file types used by LocalizableFilesLoader.

    +

    LengthVariation

    +

    Length variation representation. It contains a width property which specifies +up to which width of a screen the text in value property should be presented.

    @@ -160,9 +172,9 @@

    StringsFileType

  • - - - Expressions + + + width
    @@ -170,14 +182,14 @@

    StringsFileType

    -

    Expressions.strings file.

    +

    Max width of a screen on which the value should be presented.

    Declaration

    Swift

    -
    case Expressions = "Expressions"
    +
    let width: Int
    @@ -187,9 +199,9 @@

    Declaration

  • - - - Localizable + + + value
    @@ -197,14 +209,14 @@

    Declaration

    -

    Localizable.strings file.

    +

    String with localized content in some language.

    Declaration

    Swift

    -
    case Localizable = "Localizable"
    +
    let value: String
    @@ -216,7 +228,7 @@

    Declaration

  • diff --git a/docs/framework/Protocols/KeyValue.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LoadedTranslation.html similarity index 72% rename from docs/framework/Protocols/KeyValue.html rename to docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LoadedTranslation.html index a44df05..96b77a5 100644 --- a/docs/framework/Protocols/KeyValue.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/LoadedTranslation.html @@ -1,7 +1,7 @@ - KeyValue Protocol Reference + LoadedTranslation Struct Reference @@ -9,8 +9,8 @@ - - + +

    Swifternalization Docs (100% documented)

    @@ -21,7 +21,7 @@
    @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -150,9 +161,8 @@
    -

    KeyValue

    -

    Protocol that defines properties and methods that need to be implemented by -objects that keeps key-value pair things.

    +

    LoadedTranslation

    +

    Struct that represents loaded translation.

    @@ -161,9 +171,9 @@

    KeyValue

  • - - - key + + + type
    @@ -171,14 +181,15 @@

    KeyValue

    -

    A key.

    +

    A type of translation. It is used later to convert object into translation +correclty.

    Declaration

    Swift

    -
    var key: Key {get set}
    +
    let type: LoadedTranslationType
    @@ -188,9 +199,9 @@

    Declaration

  • - - - value + + + key
    @@ -198,14 +209,14 @@

    Declaration

    -

    A value.

    +

    Key that identifies this translation.

    Declaration

    Swift

    -
    var value: Value {get set}
    +
    var key: String
    @@ -215,9 +226,9 @@

    Declaration

  • @@ -225,14 +236,14 @@

    Declaration

    -

    Initializer.

    +

    A content of translation just loaded from a file user in future processing.

    Declaration

    Swift

    -
    init(key: Key, value: Value)
    +
    let content: Dictionary<String, AnyObject>
    @@ -244,7 +255,7 @@

    Declaration

  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/RegexExpressionMatcher.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/RegexExpressionMatcher.html index aa7eb3b..8fb07f0 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/RegexExpressionMatcher.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/RegexExpressionMatcher.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -289,7 +300,7 @@

    Return Value

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/SharedExpression.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/SharedExpression.html index 787f52d..a9034f8 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/SharedExpression.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/SharedExpression.html @@ -30,9 +30,6 @@ + @@ -88,9 +102,6 @@ - @@ -99,12 +110,21 @@ @@ -120,7 +140,7 @@ Typealiases @@ -160,9 +171,9 @@

    SharedExpression

  • - - - key + + + identifier
    @@ -170,14 +181,14 @@

    SharedExpression

    -

    Key of expression.

    +

    Identifier of expression.

    Declaration

    Swift

    -
    let key: Key
    +
    let identifier: String
    @@ -204,7 +215,7 @@

    Declaration

    Declaration

    Swift

    -
    let pattern: ExpressionPattern
    +
    let pattern: String
    @@ -214,9 +225,9 @@

    Declaration

  • @@ -224,50 +235,17 @@

    Declaration

    -

    Creates shared expression.

    +

    Creates expression.

    Declaration

    Swift

    -
    init(key: Key, pattern: ExpressionPattern)
    +
    init(identifier: String, pattern: String)
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    A key of expression.

    - -
    -
    - - pattern - - -
    -

    A pattern of expression.

    - -
    -
    -
  • @@ -276,7 +254,7 @@

    Parameters

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Translation.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Translation.html new file mode 100644 index 0000000..348fc89 --- /dev/null +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Structs/Translation.html @@ -0,0 +1,304 @@ + + + + Translation Struct Reference + + + + + + + + + +
    +
    +

    Swifternalization Docs (100% documented)

    +

    View on GitHub

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    Translation

    +

    Represents translation with expressions.

    + +
    +
    +
    +
      +
    • +
      + + + + key + +
      +
      +
      +
      +
      +
      +

      Key that identifies a translation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      let key: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + expressions + +
      +
      +
      +
      +
      +
      +

      Expressions that are related to a translation.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      let expressions: [Expression]
      + +
      +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Validates passed text and uses fittingWidth for getting proper +localized string.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      func validate(text: String, fittingWidth: Int?) -> String?
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + +
      + + text + + +
      +

      A text that is matched.

      + +
      +
      + + fittingWidth + + +
      +

      A max width of a screen that text should match.

      + +
      +
      +
      +
      +

      Return Value

      +

      A localized string if any expression validates the text, +otherwise nil.

      + +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + +
    + diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Typealiases.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Typealiases.html index e955408..11ea5fc 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Typealiases.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/Typealiases.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -159,9 +170,9 @@

    Typealiases

  • @@ -169,14 +180,16 @@

    Typealiases

    -

    String that contains expression pattern, e.g. ie:x<5, exp:^1$.

    +

    https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 (lowercase) + base +Represents country codes in framework. +Country codes are used to get correct user device’s language.

    Declaration

    Swift

    -
    internal typealias ExpressionPattern = String
    +
    internal typealias CountryCode = String
    @@ -190,63 +203,9 @@

    Declaration

  • - - - KVDict - -
    -
    -
    -
    -
    -
    -

    Represent key-value pair of Key and Value from .strings file.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    internal typealias KVDict = Dictionary<Key, Value>
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Key - -
    -
    -
    -
    -
    -
    -

    Represents key from .strings file

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    internal typealias Key = String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - Value + + + ExpressionPattern
    @@ -254,14 +213,14 @@

    Declaration

    -

    Representes value from .strings file

    +

    String that contains expression pattern, e.g. ie:x<5, exp:^1$.

    Declaration

    Swift

    -
    internal typealias Value = String
    +
    internal typealias ExpressionPattern = String
    @@ -275,9 +234,9 @@

    Declaration

  • @@ -285,20 +244,14 @@

    Declaration

    -

    Type that contains two dictionaries.

    - -

    base - for Base localization

    - -

    pref - for preferred language localization

    - -

    It contains KVDict instanes so every one has key and value.

    +

    Represents json content.

    Declaration

    Swift

    -
    internal typealias BasePrefDicts = (base: KVDict, pref: KVDict)
    +
    internal typealias JSONDictionary = Dictionary<String, AnyObject>
    @@ -322,7 +275,7 @@

    Declaration

    -

    Type that represents pattern with regular expression

    +

    Type that represents pattern with regular expression.

    @@ -340,35 +293,6 @@

    Declaration

      -
    • -
      - - - - Language - -
      -
      -
      -
      -
      -
      -

      Defines language selected on the user’s device e.g. en, pl, ru. -Language can be also Base. It used used for finding right Localizable.strings -and Expression.strings and to load built-in shared expressions.

      - -
      -
      -

      Declaration

      -
      -

      Swift

      -
      internal typealias Language = String
      - -
      -
      -
      -
      -
    • @@ -382,7 +306,7 @@

      Declaration

      -

      Handy typealias that can be used instead of long Swifternalization

      +

      Handy typealias that can be used instead of longer Swifternalization

      @@ -401,7 +325,7 @@

      Declaration

    diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html index 0c2fe02..63e4263 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html @@ -29,9 +29,6 @@ + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases
  • @@ -153,62 +164,97 @@

    Swifternalization: localize apps smarter

    CocoaPods Status

    +

    Swifternalization

    -

    Swifternalization is library that helps in localizing apps. It is written in Swift.

    +

    Swift library that helps in localizing apps in a different, better, simpler, more powerful way than system localization does. It uses json files instead of strings files.

    Features

      -
    • [x] Pluralization support - Avoids using .stringdicts
    • -
    • [x] Expressions - inequality and regular expressions in Localizable.strings
    • -
    • [x] Shared expressions
    • -
    • [x] Built-in expressions
    • -
    • [x] Works similarly to NSLocalizedString() macro
    • -
    • [x] Uses Localizable.strings file as NSLocalizedString() macro does
    • +
    • [x] Pluralization support - Without using stringdict files
    • +
    • [x] Length variations support - Supported since iOS 8.0 (instead of iOS 9.0 like system does) and avoids using stringsdict files
    • +
    • [x] Expressions - inequality and regular expressions
    • +
    • [x] Shared Expressions
    • +
    • [x] Built-in Expressions
    • +
    • [x] Works similarly to NSLocalizedString()
    • +
    • [x] Uses JSON files to minimize boilerplate code
    • [x] Comprehensive Unit Test Coverage
    • [x] Full documentation
    -

    Swifternalization

    +

    Table of Contents

    -

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessary to solve Polish language internalization problems but it is universal and works with every language.

    -

    Installation

    + +

    Introduction

    -

    You can find Public API and Full documentation with docset here in docs directory.

    +

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessity to solve Polish language internalization problems but it is universal and works with every language very well.

    -

    It is also hosted on my blog: -- Public API documentation -- Full API documentation

    +

    It uses JSON files and expressions that avoid writing code to handle some cases that you have to write when not using this framework. It makes localizing process simpler.

    +

    Practical Usage Example

    -

    Docsets: -- Public API docset -- Full API docset

    -

    Real Example

    +

    Description of practical usage example will use things that are covered later in the document so keep reading it to the end and then read about details/features presented here.

    +

    Problem

    + +

    Let’s assume the app supports English and Polish languages. Naturally app contains two Localizable.strings files. One is for Base localization which contains English translation and one is Polish language.

    + +

    App displays label with information which says when object from the backend has been updated for the last time, e.g. 2 minutes ago, 3 hours ago, 1 minute ago, etc.

    +

    Analysis

    -

    Let’s take a look on practical usage of Swifternalization. App supports both English and Polish languages. Naturally app contains two Localizable.strings files - one is Base for English (or English for English) and one is Polish… for Polish, obviously :)

    +

    The label displays number and a hour/minute/second word in singular or plural forms with ago suffix. Different languages handles pluralization/cardinal numbering in slight different ways. Here we need to support English and Polish languages.

    -

    App displays label with information that says when objects from the backend has been updated for the last time, e.g. 2 minutes ago.

    +

    In English there are just two cases to cover per hour/minute/second word:

    + +
      +
    • 1 - one second ago
    • +
    • 0, 2, 3… %d seconds ago
    • +
    • Same with minutes and hours.
    • +
    -

    This shouldn’t be problem in English:

    +

    In Polish it is more tricky because the cardinal numbers are more complicated:

      -
    • 0, 2… second ago
    • -
    • 1 second ago
    • -
    • +
    • 1 - jedną sekundę temu
    • +
    • 0, (5 - 21) - %d sekund temu
    • +
    • (2 - 4), (22-24), (32-34), (42, 44), …, (162-164), … - %d sekundy temu
    • +
    • Same logic for minutes and hours.
    -

    The same with minutes and hours. This is easy. Localization file for English will looks like this one:

    +

    Following chapters will present solution without and with Swifternalization framework. Each solution describes Base (English) and Polish localizations.

    + +

    Here is a table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    Solution without Swifternalization

    Localizable.strings (Base)
     --------------------------
    -
     "one-second" = "1 second ago";
     "many-seconds" = "%d seconds ago";
     
    @@ -217,77 +263,84 @@
     
     "one-hour" = "1 hour ago";
     "many-hours" = "%d hours ago";
    -
    -

    Let’s try with Polish language. As mentioned - this is tricky.

    -
    Localizable.strings (Polish)
    -----------------------------
    -
    -"one-second" = "1 sekundę temu";
    -"few-seconds" = "%d sekundy temu";
    -"many-seconds" = "%d sekund temu";
    +Localizable.strings (Polish)
    +-------------------------               
    +"one-second" = "1 sekundę temu"
    +"few-seconds" = "%d sekundy temu"
    +"many-seconds" = "%d sekund temu""              
     
    -"one-minute" = "1 minutę temu";
    -"few-minutes" = "%d minuty temu";
    -"many-minutes" = "%d minut temu";
    +"one-minute" = "1 minutę temu"
    +"few-minutes" = "%d minuty temu "
    +"many-minutes" = "%d minut temu"            
     
    -"one-hours" = "1 hodzinę temu";
    -"few-hours" = "%d godziny temu";
    +"one-hours" = "1 godzinę temu"
    +"few-hours" = "%d godziny temu"
     "many-hours" = "%d godzin temu";
     
    -

    Okay… there is 9 cases for now. But this is not the only thing to deal with. It depends on the number of seconds/minutes/hours to select proper one. Without some logic additional logic to find out which case should be used this is impossible to use proper one.

    -
    - 0, (5 - 21) - "few-seconds"
    -- 1 - "one-second"
    -- (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "many-seconds"
    -
    - -

    The same logic for minutes and hours.

    - -

    Here is nice table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    There are 6 cases in English and 9 cases in Polish. Notice that without additional logic we’re not able to detect which version of a string for hour/minute/second the app should display. The logic differs among different languages. We would have to add some lines of code that handle the logic for all the languages we’re using in the app. What if there are more than 2 languages? Don’t even think about it - this might be not so easy.

    -

    With Swifternalization this can be solved e.g. in this way:

    -
    Localizable.strings (Base)
    ---------------------------
    -"time-seconds{one}" = "%d second ago";
    -"time-seconds{other}" = "%d seconds ago";
    +

    The logic is already implemented in Swifternalization framework and it fits to every language.

    +

    Solution with Swifternalization

    -"time-minutes{one}" = "%d minute ago"; -"time-minutes{other}" = "%d minutes ago"; - -"time-hours{one}" = "%d hour ago"; -"time-hours{other}" = "%d hours ago"; +

    This is how localizable files will look:

    +
    base.json
    +---------
    +"time-seconds": {
    +    "one": "%d second ago"
    +    "other": "%d seconds ago"
    +},
     
    +"time-minutes": {
    +    "one": "%d minute ago"
    +    "other": "%d minutes ago"
    +},
     
    +"time-hours": {
    +    "one": "%d hours ago"
    +    "other": "%d hours ago"
    +}
     
    -Localizable.strings (Polish)
    -----------------------------
    -"time-seconds{one}" = "%d sekundę temu";
    -"time-seconds{few}" = "%d sekundy temu";
    -"time-seconds{many}" = "%d sekund temu";
    -
    -"time-minutes{one}" = "%d minutę temu";
    -"time-minutes{few}" = "%d minuty temu";
    -"time-minutes{many}" = "%d minut temu";
    -
    -"time-hours{one}" = "%d godzinę temu";
    -"time-hours{few}" = "%d godziny temu";
    -"time-hours{many}" = "%d godzin temu";
    +pl.json
    +-------
    +"time-seconds": {
    +    "one": "1 sekundę temu",
    +    "few": "%d sekundy temu",
    +    "many": "%d sekund temu"
    +},
    +
    +"time-minutes": {
    +    "one": "1 minutę temu",
    +    "few": "%d minuty temu",
    +    "many": "%d minut temu"
    +},
    +
    +"time-hours": {
    +    "one": "1 godzinę temu",
    +    "few": "%d godziny temu",
    +    "many": "%d godzin temu"
    +}
     
    -

    So the logic is in Swifternalization and you don’t need write additional handling code for these cases.

    +
      +
    • one, few, many, other - those are shared expressions already built into Swifternalization - covered below.
    • +
    • You can add own expressions to handle specific cases - covered below.
    • +
    -

    And the call will look like this:

    -
    Swifternalization.localizedExpressionString("time-seconds", value: 10)
    +

    As mentioned the logic is implemented into framework so if you want to get one of a localized string you have to make a simple call.

    +
    Swifternalization.localizedString("time-seconds", intValue: 10)
     

    or with I18n typealias (I-18-letters-n, Internalization):

    -
    I18n.localizedExpressionString("time-seconds", value: 10)
    +
    I18n.localizedString("time-seconds", intValue: 10)
     
    -

    There is easy way to add you own expression to handle your specific case with Swifternalization.

    +

    The key and intValue parameters are validated by loaded expressions and proper version of a string is returned - covered below.

    +

    Features

    +

    Pluralization

    -

    Swifternalization also drops need for having .stringdicts files like this one:

    +

    Swifternalization drops necessity of using stringdicts files like following one to support pluralization in localized strings. Instead of that you can simply define expressions that cover such cases.

    <plist version="1.0">
         <dict>
             <key>%d file(s) remaining</key>
    @@ -309,49 +362,56 @@
         </dict>
     </plist>
     
    -

    Getting Started

    -

    Configuration is simple. The one thing that Swifternalization needs to works is NSBundle where Localizable.strings are placed.

    +

    No more stringsdict files!

    +

    Length Variations

    -

    Recommended is to configure it as fast as you can to be sure that before you want to get some localized key it will be able to return you something.

    -
        Swifternalization(bundle: NSBundle.mainBundle())
    -
    +

    iOS 9 provides new way to select proper localized string variation depending on a screen width. It uses stringsdict file with NSStringVariableWidthRuleType key.

    -

    This call will create instance (you can get handle to it but you don’t need it) and automatically set it as shared instance and you can easily work with it.

    +

    Swifternalization drops necessity of using such file and it is not necessary to use this new key to use the feature.

    -

    In Localizable.strings the syntax should looks like this:

    -
    "key" = "value";
    -"key{expression}" = "value";
    -
    -

    How to get localized string

    +

    With Swifternalization this length variations feature is available since iOS 8.0 because the framework has its own implementation of length variations.

    -

    Swifternalization allows developer to work with its class methods. There are few to use:

    -
    localizedString(key: String, defaultValue: String? = nil) -> String
    +

    To use length variations feature your translation file should has entries like this:

    +
    base.json
    +---------
    +"forgot-password": {
    +    "@200": "Forgot Password? Help.",
    +    "@300": "Forgot Your Password? Use Help.",
    +    "@400": "Do not remember Your Password?" Use Help.""
    +}
     
    -

    Allows to get value for simple key. Works similar to NSLocalizedString. key is the key placed in Localizable.strings and defaultValue is the value that will be returned when there is no translation found for passed key. If defaultValue is nil then key will be return in such case.

    +

    The number after @ sign is max width of a screen or bounds that a string fits to. E.g. the second string will be returned if passed fitting width as a paramter is be greater than 200 and less or equal 300.

    -

    The next one is for getting localized string with keys that contain some expressions:

    -
    localizedExpressionString(key: String, value: String, defaultValue: String? = nil) -> String
    +

    To get the second localized string the call looks like below:

    +
    I18n.localizedString("forgot-password", fittingWidth: 300) // 201 - 300
     
    -

    Similarly to the one above key is the key in Localizable.strings, defaultValue is also the same and methods behaves the same. There is additional parameter called value. The value is used for expression matchers to validate expressions and return proper localized value. We’ll cover it soon.

    +

    You can mix expressions with length variations. Following example shows it:

    +
    base.json
    +---------
    +"car": {
    +    "ie:x=1": {
    +        @100: "One car",
    +        @200: "You've got one car"
    +    },
     
    -

    As the method takes some String as a value and you probably will deal with Int there is alternative method to call:

    -
    localizedExpressionString(key: String, value: Int, defaultValue: String? = nil) -> String
    +    "more": "You've got few cars"
    +}
     

    Expressions

    -

    As mentioned there are few expression types. Every expression type has their own parser and matcher.

    +

    There are few expression types. Every expression type has their own parser and matcher but they work internally so you don’t need to worry about them.

    -

    There are 3 types:

    +

    There are 3 types of expressions:

      -
    • inequality - this type of expression handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on.
    • -
    • inequality extended - this is extended version of inequality with syntax like this: 2<x<10, 4<=x<6.
    • -
    • regex - this types of expression uses regular expression. This is the most powerful ;)
    • +
    • inequality - handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on. Work with integer and float numbers.
    • +
    • inequality extended - extended version of inequality with syntax like this: 2<x<10, 4<=x<6. Work with integer and float numbers.
    • +
    • regex - uses regular expression. This is the most powerful ;)
    -

    Inequality

    +

    Inequality Expressions

    It is composed of several elements:

    @@ -363,13 +423,15 @@

    Example:

    -
    "cars{ie:x=1}" = "1 car";
    -"cars{ie:x=0}" = "no cars";
    -"cars{ie:x>1}" = "%d cars";
    +
    "cars": {
    +    "ie:x=1": "1 car",
    +    "ie:x=0": "no cars",
    +    "ie:x>1": "%d cars"
    +}
     
    -

    Inequality Extended

    +

    Inequality Extended Expressions

    -

    This is a bit extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    +

    This is extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    • iex: - prefix of inequality extended expression
    • @@ -378,11 +440,13 @@

    Expample:

    -
    "tomatos{iex:2<x<10}" = "%d tomatos is between 2 and 10";
    +
    "tomatos": {
    +    "iex:2<x<10": "%d tomatos is between 2 and 10"
    +}
     
    -

    Regex

    +

    Regex Expressions

    -

    This is the most powerful type of expression and probably will be most used by developers. It takes regular expression ;)

    +

    This is the most powerful type of expression. It takes regular expression ;)

    • exp: - prefix of regex expression
    • @@ -390,108 +454,228 @@

    Example: (police cars in Polish language)

    -
    "police-cars{exp:^1$}" = "1 samochód policyjny";
    -"police-cars{exp:(((?!1).[2-4]{1})$)|(^[2-4]$)}" = "%d samochody policyjne";
    -"police-cars{exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])}" = "%d samochodów policyjnych";
    +
    "police-cars": {
    +    "exp:^1$": "1 samochód policyjny",
    +    "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)": "%d samochody policyjne",
    +    "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])": "%d samochodów policyjnych"
    +}
     

    Powerful stuff, isn’t it? :>

    -

    PS. There is built in solution for Polish language so you can use it with doing just this:

    -
    "police-cars{one}" = "1 samochód policyjny";
    -"police-cars{few}" = "%d samochody policyjne";
    -"police-cars{many}" = "%d samochodów policyjnych";
    +

    PS. There is built-in solution for Polish language so you can use it with doing just this:

    +
    "police-cars": {
    +    "one": "1 samochód policyjny",
    +    "few": "%d samochody policyjne",
    +    "many": "%d samochodów policyjnych"
    +}   
     
    -

    This feature is called Shared Expression and is covered below.

    -

    Shared Expressions

    +

    This is called Shared Built-In Expression and is covered below.

    +

    Shared Expressions

    + +

    Shared expressions are expressions available among all the localization files. They are declared in expressions.json file divided by language and you can use them in localization files.

    The functionality allows developer to observance of DRY principle and to avoid mistakes that exist because of reapeating the code in many places.

    -

    It is possible to create shared expression in your project and use it with no configuration with Swifternalization.

    -

    Getting Started of Shared Expressions

    +

    Normally you declare expression like this:

    +
    ...
    +"ie:x>1": "Localized String"
    +...
    +
    -
      -
    1. Create Expressions.strings file in the same bundle when Localizable.strings file is.
    2. -
    3. Add shortcuts for your expressions and add your expressions ;)
    4. -
    +

    If you want to use the same expression in multiple files there is no necessity to repeat the expression elsewhere. This is even problematic when you decide to improve/change expression to handle another cases you forget about - you would have to change expression in multiple places. Because of that there are Shared Expression. These feature allows you to create expression just in one place and use identifier of it in multiple places where you normally should put this expression.

    -

    Example:

    -
    Localizable.strings (Base)
    --------------------
    -"cars{custom-1}" = "%d car";
    -"cars{custom-2}" = "%d cars";
    +

    What you need to do is to create expressions.json file with following structure:

    +
    {
    +    "base": {
    +        "one": "ie:x>1"
    +    },
     
    +    "pl": {
    +        // ... other than "one" because "one" is available here too.
    +    }
    +}
    +
    -Localizable.strings (Polish) ----------------------------- -"cars{custom-1}" = "%d samochód"; -"cars{custom-2}" = "%d samochody"; -"cars{custom-3}" = "%d samochodów"; +

    Now in pl.json, en.json and so on you have to use it as below:

    +
    ...
    +"one": "Localized String"
    +...
    +
    +

    Before you decide to create your own expression take a look if there is no built-in one with the same name or whether there is such expression but named differently. Maybe you don’t need to do this at all and just use it.

    +

    Built-in expressions

    -Expressions.strings (Base) --------------------------- -"custom-1" = "ie:x=1"; -"custom-2" = "exp:(^[^1])|(^\\d{2,})"; +

    Built-in expressions as name suggest are shared expressions built into framework and available to use with zero configuration. They are separated by country and not all country have its own built-in expressions. For now there are e.g. Base built-in expressions and Polish built-in expressions. Base expressions are available in every country and there are very generic to match all countries pluralization/cardinal numbering logic.

    +

    List of supported built-in shared expressions:

    +
    Base (English fits to this completely)
    +- one - detects if number is equal 1
    +- >one - detects if number is greater than 1
    +- two - detects if number is equal 2
    +- other - detects if number is not 1, so 0, 2, 3 and so on.
     
    -Expressions.strings (Polish)
    ----------------------------
    -"custom-1" = "ie:x=1";
    -"custom-2" = "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)";
    -"custom-3" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])";
    +Polish
    +- few - matches (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    +- many - matches 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
     
    -

    Swifternalization load these Expressions.strings files and analyze them, and replace shortcuts for expressions with full expressions.

    +

    As you can see polish has no one, >one, etc. because it inherits from Base by default.

    +

    Getting Started

    -

    There is some duplication in Base and Polish version of expressions - custom-1. Instead of repeating this in entire language you want to cover you can keep it just in Base version of Expressions.strings file. Expressions that are find in Base and are not in preferred language file will be added to preferred language too to observance of DRY principle.

    +

    This chapter shows you how to start using Swifternalization and how to intergrate it with your code.

    +

    Documentation

    -

    Swifternalization also handles the case of overriding built-in expressions. It gives you just few expressions for now like: one, >one, two, other as base expressions and few and many for Polish. If any of your Expressions.strings version of file will override it Swifternalization will use your version.

    -

    Demo

    +

    Documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private.

    -

    There is demo project included in the repo. Just switch to proper target and run. It enumerated cars from 1 to 1000 and print them out to the console. Base (English) and Polish languages are supported. You can find there example of using simple primitive no-expression translation and also with experssions.

    -

    Contribution and change or feature requests

    +

    You can find Public API and Full documentation with docset here in docs directory.

    -

    Swifternalization is open sources so everyone may contribute if want to. If you want to develop it just fork the repo, do you work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Built-in expressions

    - -

    As mentioned in previous chapter Swifternalization has some built-in expressions and is ready to extend. If you want to add expressions specific for your country you can do it by creating class which conforms to SharedExpressionProtocol. Methods from protocol returns all expressions for your country. There is already SharedBaseExpression with some basic expressions and SharedPolishExpression with polish expressions for helping ordering numbers.

    - -

    Example of the file ready for pull request should looks like this:

    -
    class SharedPolishExpression: SharedExpressionProtocol {
    -    static func allExpressions() -> [SharedExpression] {
    -        return [
    -            /**
    -            (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    -
    -            e.g.
    -            - 22 samochody, 1334 samochody, 53 samochody
    -            - 2 minuty, 4 minuty, 23 minuty
    -            */
    -            SharedExpression(k: "few", e: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"),
    -
    -            /**
    -            0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
    -
    -            e.g.
    -            - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów
    -            - 5 minut, 18 minut, 117 minut, 1009 minut
    -            */
    -            SharedExpression(k: "many", e: "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"),
    -        ]
    -    }
    -}
    +

    It is also hosted on my blog: +- Public API documentation +- Full API documentation

    + +

    Docsets: +- Public API docset +- Full API docset

    +

    Instalation

    + +

    It works with iOS 8.0 and newer.

    + +

    With CocoaPods:

    +
    pod 'Swifternalization', '~> 1.2'
     
    -

    Also this is required to cover all shared expressions for a country with unit tests. You can find examples in the repo for e.g. Polish expressions.

    +

    If you are not using CocoaPods just import files from Swifternalization/Swifternalization directory to your project.

    + +

    Swifternalization also supports Carthage.

    +

    Configuration

    + +

    Before you get a first localized string you have to configure Swifternalization by passing to it the bundle where localized json files are placed.

    +
    I18n.configure() // for NSBundle.mainBundle() - Mostly you want to call it this way
    +I18n.configure(bundle) // if files are in another bundle
    +
    +

    Creating file with Shared Expressions

    + +

    Shared Expressions must be placed in expressions.json. Syntax of a file looks like below:

    +
    {
    +    "base": {
    +        "ten": "ie:x=10",
    +        ">20": "ie:x>20",
    +        "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"
    +    },
    +
    +    "pl": {
    +        "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)",
    +        "two": "ie:x=2",
    +        "three": "ie:x=3"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "language-1": {
    +        "shared-expression-key-1": "expression-1",
    +        "shared-expression-key-2": "expression-2"
    +    },
    +
    +    "language-2": {
    +        "shared-expression-key-1": "expression-1"
    +    }
    +}
    +
    + +

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    +

    Creating Localizable Files

    + +

    Localizable file contains translations for specific language. The files might look like below:

    +
    {
    +    "welcome-key": "welcome",
    +
    +    "cars": {
    +        "one": "one car",
    +        "ie:x>=2": "%d cars",
    +        "ie:x<=-2": "minus %d cars"
    +    }
    +}
    +
    + +

    Name of a file should be the same like country code. e.g. for English it is en.json, for Polish it is pl.json, for base localization it is base.json, etc.

    + +

    There are few things that you can place in such files. More complex file will look like below:

    +
    {
    +    "welcome": "welcome",
    +
    +    "cars": {
    +        "one": {
    +            "@100": "one car",
    +            "@200": "You have one car",
    +            "@400": "You have got one car"
    +        },
    +
    +        "other": "%d cars"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "key": "value",
    +
    +    "key": {
    +        "expression-1": {
    +            "length-variation-1": "value-1",
    +            "length-variation-2": "value-2",
    +            "length-variation-3": "value-3"
    +        },
    +
    +        "expression-2": "value"
    +    }
    +}
    +
    +

    Getting localized string

    + +

    Swifternalization allows you to work with its one class method which exposes all the methods you need to localize an app.

    + +

    These methods have many optional paramters and you can omit them if you want. There are few common parameters:

    + +
      +
    • key - A key of localized string.
    • +
    • fittingWidth - A width of a screen or place where you want to put a localized string. It is integer.
    • +
    • defaultValue - A value that will be returned if there is no localized string for a key passed to the method. If this is not specified then key is returned.
    • +
    • comment - A comment used just by developer to know a context of translation.
    • +
    + +

    First method called localizedString(_:fittingWidth:defaultValue:comment:) allows you to get value for simple key without expression.

    + +

    Examples:

    +
    I18n.localizedString("welcome")
    +I18n.localizedString("welcome", fittingWidth: 200)
    +I18n.localizedString("welcome", defaultValue: "Welcome", comment: "Displayed on app start")
    +
    + +

    Next method localizedString(_:stringValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for string value that match an expression. Actually the string value will contain number inside in most cases or some other string that you would like to match.

    +
    I18n.localizedString("cars", stringValue: "5")
    +// Other cases similar to above example
    +
    + +

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    +
    I18n.localizedString("cars", intValue: 5)
    +
    +

    Contribution and change or feature requests

    + +

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    + +

    There is no guide for contributors but if you added new functionality you must write unit tests for it.

    Swift 2

    -

    Swifternalization supports Swift 2 and works on Xcode 7 beta 2. Please check swift2 branch for that.

    -

    Things to do in future release:

    +

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    +

    Things to do in future releases:

    • Add more built-in expressions for another countries.
    • +
    • Add support for float numbers in built in expressions that uses regular expressions.

    LICENSE

    @@ -500,7 +684,7 @@
  • diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/docSet.dsidx b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/docSet.dsidx index 2b08965427a7af2014ca8d83b11e3da879c30b50..b021abbde0d7f348e6b9c6b3699e71c4faaef3ff 100644 GIT binary patch literal 53248 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 o!kg=SNpoEtQw!!xwu!$ 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 xG0LyvOj2!S`LRvaWnyHh&4( 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 GK>rI+fhZpU literal 77162 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 Classes
  • + @@ -87,9 +101,6 @@ - @@ -98,12 +109,21 @@ @@ -119,7 +139,7 @@ Typealiases @@ -153,62 +164,97 @@

    Swifternalization: localize apps smarter

    CocoaPods Status

    +

    Swifternalization

    -

    Swifternalization is library that helps in localizing apps. It is written in Swift.

    +

    Swift library that helps in localizing apps in a different, better, simpler, more powerful way than system localization does. It uses json files instead of strings files.

    Features

      -
    • [x] Pluralization support - Avoids using .stringdicts
    • -
    • [x] Expressions - inequality and regular expressions in Localizable.strings
    • -
    • [x] Shared expressions
    • -
    • [x] Built-in expressions
    • -
    • [x] Works similarly to NSLocalizedString() macro
    • -
    • [x] Uses Localizable.strings file as NSLocalizedString() macro does
    • +
    • [x] Pluralization support - Without using stringdict files
    • +
    • [x] Length variations support - Supported since iOS 8.0 (instead of iOS 9.0 like system does) and avoids using stringsdict files
    • +
    • [x] Expressions - inequality and regular expressions
    • +
    • [x] Shared Expressions
    • +
    • [x] Built-in Expressions
    • +
    • [x] Works similarly to NSLocalizedString()
    • +
    • [x] Uses JSON files to minimize boilerplate code
    • [x] Comprehensive Unit Test Coverage
    • [x] Full documentation
    -

    Swifternalization

    +

    Table of Contents

    -

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessary to solve Polish language internalization problems but it is universal and works with every language.

    -

    Installation

    + +

    Introduction

    -

    You can find Public API and Full documentation with docset here in docs directory.

    +

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessity to solve Polish language internalization problems but it is universal and works with every language very well.

    -

    It is also hosted on my blog: -- Public API documentation -- Full API documentation

    +

    It uses JSON files and expressions that avoid writing code to handle some cases that you have to write when not using this framework. It makes localizing process simpler.

    +

    Practical Usage Example

    -

    Docsets: -- Public API docset -- Full API docset

    -

    Real Example

    +

    Description of practical usage example will use things that are covered later in the document so keep reading it to the end and then read about details/features presented here.

    +

    Problem

    + +

    Let’s assume the app supports English and Polish languages. Naturally app contains two Localizable.strings files. One is for Base localization which contains English translation and one is Polish language.

    + +

    App displays label with information which says when object from the backend has been updated for the last time, e.g. 2 minutes ago, 3 hours ago, 1 minute ago, etc.

    +

    Analysis

    -

    Let’s take a look on practical usage of Swifternalization. App supports both English and Polish languages. Naturally app contains two Localizable.strings files - one is Base for English (or English for English) and one is Polish… for Polish, obviously :)

    +

    The label displays number and a hour/minute/second word in singular or plural forms with ago suffix. Different languages handles pluralization/cardinal numbering in slight different ways. Here we need to support English and Polish languages.

    -

    App displays label with information that says when objects from the backend has been updated for the last time, e.g. 2 minutes ago.

    +

    In English there are just two cases to cover per hour/minute/second word:

    + +
      +
    • 1 - one second ago
    • +
    • 0, 2, 3… %d seconds ago
    • +
    • Same with minutes and hours.
    • +
    -

    This shouldn’t be problem in English:

    +

    In Polish it is more tricky because the cardinal numbers are more complicated:

      -
    • 0, 2… second ago
    • -
    • 1 second ago
    • -
    • +
    • 1 - jedną sekundę temu
    • +
    • 0, (5 - 21) - %d sekund temu
    • +
    • (2 - 4), (22-24), (32-34), (42, 44), …, (162-164), … - %d sekundy temu
    • +
    • Same logic for minutes and hours.
    -

    The same with minutes and hours. This is easy. Localization file for English will looks like this one:

    +

    Following chapters will present solution without and with Swifternalization framework. Each solution describes Base (English) and Polish localizations.

    + +

    Here is a table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    Solution without Swifternalization

    Localizable.strings (Base)
     --------------------------
    -
     "one-second" = "1 second ago";
     "many-seconds" = "%d seconds ago";
     
    @@ -217,77 +263,84 @@
     
     "one-hour" = "1 hour ago";
     "many-hours" = "%d hours ago";
    -
    -

    Let’s try with Polish language. As mentioned - this is tricky.

    -
    Localizable.strings (Polish)
    -----------------------------
    -
    -"one-second" = "1 sekundę temu";
    -"few-seconds" = "%d sekundy temu";
    -"many-seconds" = "%d sekund temu";
    +Localizable.strings (Polish)
    +-------------------------               
    +"one-second" = "1 sekundę temu"
    +"few-seconds" = "%d sekundy temu"
    +"many-seconds" = "%d sekund temu""              
     
    -"one-minute" = "1 minutę temu";
    -"few-minutes" = "%d minuty temu";
    -"many-minutes" = "%d minut temu";
    +"one-minute" = "1 minutę temu"
    +"few-minutes" = "%d minuty temu "
    +"many-minutes" = "%d minut temu"            
     
    -"one-hours" = "1 hodzinę temu";
    -"few-hours" = "%d godziny temu";
    +"one-hours" = "1 godzinę temu"
    +"few-hours" = "%d godziny temu"
     "many-hours" = "%d godzin temu";
     
    -

    Okay… there is 9 cases for now. But this is not the only thing to deal with. It depends on the number of seconds/minutes/hours to select proper one. Without some logic additional logic to find out which case should be used this is impossible to use proper one.

    -
    - 0, (5 - 21) - "few-seconds"
    -- 1 - "one-second"
    -- (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "many-seconds"
    -
    - -

    The same logic for minutes and hours.

    - -

    Here is nice table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    There are 6 cases in English and 9 cases in Polish. Notice that without additional logic we’re not able to detect which version of a string for hour/minute/second the app should display. The logic differs among different languages. We would have to add some lines of code that handle the logic for all the languages we’re using in the app. What if there are more than 2 languages? Don’t even think about it - this might be not so easy.

    -

    With Swifternalization this can be solved e.g. in this way:

    -
    Localizable.strings (Base)
    ---------------------------
    -"time-seconds{one}" = "%d second ago";
    -"time-seconds{other}" = "%d seconds ago";
    +

    The logic is already implemented in Swifternalization framework and it fits to every language.

    +

    Solution with Swifternalization

    -"time-minutes{one}" = "%d minute ago"; -"time-minutes{other}" = "%d minutes ago"; - -"time-hours{one}" = "%d hour ago"; -"time-hours{other}" = "%d hours ago"; +

    This is how localizable files will look:

    +
    base.json
    +---------
    +"time-seconds": {
    +    "one": "%d second ago"
    +    "other": "%d seconds ago"
    +},
     
    +"time-minutes": {
    +    "one": "%d minute ago"
    +    "other": "%d minutes ago"
    +},
     
    +"time-hours": {
    +    "one": "%d hours ago"
    +    "other": "%d hours ago"
    +}
     
    -Localizable.strings (Polish)
    -----------------------------
    -"time-seconds{one}" = "%d sekundę temu";
    -"time-seconds{few}" = "%d sekundy temu";
    -"time-seconds{many}" = "%d sekund temu";
    -
    -"time-minutes{one}" = "%d minutę temu";
    -"time-minutes{few}" = "%d minuty temu";
    -"time-minutes{many}" = "%d minut temu";
    -
    -"time-hours{one}" = "%d godzinę temu";
    -"time-hours{few}" = "%d godziny temu";
    -"time-hours{many}" = "%d godzin temu";
    +pl.json
    +-------
    +"time-seconds": {
    +    "one": "1 sekundę temu",
    +    "few": "%d sekundy temu",
    +    "many": "%d sekund temu"
    +},
    +
    +"time-minutes": {
    +    "one": "1 minutę temu",
    +    "few": "%d minuty temu",
    +    "many": "%d minut temu"
    +},
    +
    +"time-hours": {
    +    "one": "1 godzinę temu",
    +    "few": "%d godziny temu",
    +    "many": "%d godzin temu"
    +}
     
    -

    So the logic is in Swifternalization and you don’t need write additional handling code for these cases.

    +
      +
    • one, few, many, other - those are shared expressions already built into Swifternalization - covered below.
    • +
    • You can add own expressions to handle specific cases - covered below.
    • +
    -

    And the call will look like this:

    -
    Swifternalization.localizedExpressionString("time-seconds", value: 10)
    +

    As mentioned the logic is implemented into framework so if you want to get one of a localized string you have to make a simple call.

    +
    Swifternalization.localizedString("time-seconds", intValue: 10)
     

    or with I18n typealias (I-18-letters-n, Internalization):

    -
    I18n.localizedExpressionString("time-seconds", value: 10)
    +
    I18n.localizedString("time-seconds", intValue: 10)
     
    -

    There is easy way to add you own expression to handle your specific case with Swifternalization.

    +

    The key and intValue parameters are validated by loaded expressions and proper version of a string is returned - covered below.

    +

    Features

    +

    Pluralization

    -

    Swifternalization also drops need for having .stringdicts files like this one:

    +

    Swifternalization drops necessity of using stringdicts files like following one to support pluralization in localized strings. Instead of that you can simply define expressions that cover such cases.

    <plist version="1.0">
         <dict>
             <key>%d file(s) remaining</key>
    @@ -309,49 +362,56 @@
         </dict>
     </plist>
     
    -

    Getting Started

    -

    Configuration is simple. The one thing that Swifternalization needs to works is NSBundle where Localizable.strings are placed.

    +

    No more stringsdict files!

    +

    Length Variations

    -

    Recommended is to configure it as fast as you can to be sure that before you want to get some localized key it will be able to return you something.

    -
        Swifternalization(bundle: NSBundle.mainBundle())
    -
    +

    iOS 9 provides new way to select proper localized string variation depending on a screen width. It uses stringsdict file with NSStringVariableWidthRuleType key.

    -

    This call will create instance (you can get handle to it but you don’t need it) and automatically set it as shared instance and you can easily work with it.

    +

    Swifternalization drops necessity of using such file and it is not necessary to use this new key to use the feature.

    -

    In Localizable.strings the syntax should looks like this:

    -
    "key" = "value";
    -"key{expression}" = "value";
    -
    -

    How to get localized string

    +

    With Swifternalization this length variations feature is available since iOS 8.0 because the framework has its own implementation of length variations.

    -

    Swifternalization allows developer to work with its class methods. There are few to use:

    -
    localizedString(key: String, defaultValue: String? = nil) -> String
    +

    To use length variations feature your translation file should has entries like this:

    +
    base.json
    +---------
    +"forgot-password": {
    +    "@200": "Forgot Password? Help.",
    +    "@300": "Forgot Your Password? Use Help.",
    +    "@400": "Do not remember Your Password?" Use Help.""
    +}
     
    -

    Allows to get value for simple key. Works similar to NSLocalizedString. key is the key placed in Localizable.strings and defaultValue is the value that will be returned when there is no translation found for passed key. If defaultValue is nil then key will be return in such case.

    +

    The number after @ sign is max width of a screen or bounds that a string fits to. E.g. the second string will be returned if passed fitting width as a paramter is be greater than 200 and less or equal 300.

    -

    The next one is for getting localized string with keys that contain some expressions:

    -
    localizedExpressionString(key: String, value: String, defaultValue: String? = nil) -> String
    +

    To get the second localized string the call looks like below:

    +
    I18n.localizedString("forgot-password", fittingWidth: 300) // 201 - 300
     
    -

    Similarly to the one above key is the key in Localizable.strings, defaultValue is also the same and methods behaves the same. There is additional parameter called value. The value is used for expression matchers to validate expressions and return proper localized value. We’ll cover it soon.

    +

    You can mix expressions with length variations. Following example shows it:

    +
    base.json
    +---------
    +"car": {
    +    "ie:x=1": {
    +        @100: "One car",
    +        @200: "You've got one car"
    +    },
     
    -

    As the method takes some String as a value and you probably will deal with Int there is alternative method to call:

    -
    localizedExpressionString(key: String, value: Int, defaultValue: String? = nil) -> String
    +    "more": "You've got few cars"
    +}
     

    Expressions

    -

    As mentioned there are few expression types. Every expression type has their own parser and matcher.

    +

    There are few expression types. Every expression type has their own parser and matcher but they work internally so you don’t need to worry about them.

    -

    There are 3 types:

    +

    There are 3 types of expressions:

      -
    • inequality - this type of expression handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on.
    • -
    • inequality extended - this is extended version of inequality with syntax like this: 2<x<10, 4<=x<6.
    • -
    • regex - this types of expression uses regular expression. This is the most powerful ;)
    • +
    • inequality - handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on. Work with integer and float numbers.
    • +
    • inequality extended - extended version of inequality with syntax like this: 2<x<10, 4<=x<6. Work with integer and float numbers.
    • +
    • regex - uses regular expression. This is the most powerful ;)
    -

    Inequality

    +

    Inequality Expressions

    It is composed of several elements:

    @@ -363,13 +423,15 @@

    Example:

    -
    "cars{ie:x=1}" = "1 car";
    -"cars{ie:x=0}" = "no cars";
    -"cars{ie:x>1}" = "%d cars";
    +
    "cars": {
    +    "ie:x=1": "1 car",
    +    "ie:x=0": "no cars",
    +    "ie:x>1": "%d cars"
    +}
     
    -

    Inequality Extended

    +

    Inequality Extended Expressions

    -

    This is a bit extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    +

    This is extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    • iex: - prefix of inequality extended expression
    • @@ -378,11 +440,13 @@

    Expample:

    -
    "tomatos{iex:2<x<10}" = "%d tomatos is between 2 and 10";
    +
    "tomatos": {
    +    "iex:2<x<10": "%d tomatos is between 2 and 10"
    +}
     
    -

    Regex

    +

    Regex Expressions

    -

    This is the most powerful type of expression and probably will be most used by developers. It takes regular expression ;)

    +

    This is the most powerful type of expression. It takes regular expression ;)

    • exp: - prefix of regex expression
    • @@ -390,108 +454,228 @@

    Example: (police cars in Polish language)

    -
    "police-cars{exp:^1$}" = "1 samochód policyjny";
    -"police-cars{exp:(((?!1).[2-4]{1})$)|(^[2-4]$)}" = "%d samochody policyjne";
    -"police-cars{exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])}" = "%d samochodów policyjnych";
    +
    "police-cars": {
    +    "exp:^1$": "1 samochód policyjny",
    +    "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)": "%d samochody policyjne",
    +    "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])": "%d samochodów policyjnych"
    +}
     

    Powerful stuff, isn’t it? :>

    -

    PS. There is built in solution for Polish language so you can use it with doing just this:

    -
    "police-cars{one}" = "1 samochód policyjny";
    -"police-cars{few}" = "%d samochody policyjne";
    -"police-cars{many}" = "%d samochodów policyjnych";
    +

    PS. There is built-in solution for Polish language so you can use it with doing just this:

    +
    "police-cars": {
    +    "one": "1 samochód policyjny",
    +    "few": "%d samochody policyjne",
    +    "many": "%d samochodów policyjnych"
    +}   
     
    -

    This feature is called Shared Expression and is covered below.

    -

    Shared Expressions

    +

    This is called Shared Built-In Expression and is covered below.

    +

    Shared Expressions

    + +

    Shared expressions are expressions available among all the localization files. They are declared in expressions.json file divided by language and you can use them in localization files.

    The functionality allows developer to observance of DRY principle and to avoid mistakes that exist because of reapeating the code in many places.

    -

    It is possible to create shared expression in your project and use it with no configuration with Swifternalization.

    -

    Getting Started of Shared Expressions

    +

    Normally you declare expression like this:

    +
    ...
    +"ie:x>1": "Localized String"
    +...
    +
    -
      -
    1. Create Expressions.strings file in the same bundle when Localizable.strings file is.
    2. -
    3. Add shortcuts for your expressions and add your expressions ;)
    4. -
    +

    If you want to use the same expression in multiple files there is no necessity to repeat the expression elsewhere. This is even problematic when you decide to improve/change expression to handle another cases you forget about - you would have to change expression in multiple places. Because of that there are Shared Expression. These feature allows you to create expression just in one place and use identifier of it in multiple places where you normally should put this expression.

    -

    Example:

    -
    Localizable.strings (Base)
    --------------------
    -"cars{custom-1}" = "%d car";
    -"cars{custom-2}" = "%d cars";
    +

    What you need to do is to create expressions.json file with following structure:

    +
    {
    +    "base": {
    +        "one": "ie:x>1"
    +    },
     
    +    "pl": {
    +        // ... other than "one" because "one" is available here too.
    +    }
    +}
    +
    -Localizable.strings (Polish) ----------------------------- -"cars{custom-1}" = "%d samochód"; -"cars{custom-2}" = "%d samochody"; -"cars{custom-3}" = "%d samochodów"; +

    Now in pl.json, en.json and so on you have to use it as below:

    +
    ...
    +"one": "Localized String"
    +...
    +
    +

    Before you decide to create your own expression take a look if there is no built-in one with the same name or whether there is such expression but named differently. Maybe you don’t need to do this at all and just use it.

    +

    Built-in expressions

    -Expressions.strings (Base) --------------------------- -"custom-1" = "ie:x=1"; -"custom-2" = "exp:(^[^1])|(^\\d{2,})"; +

    Built-in expressions as name suggest are shared expressions built into framework and available to use with zero configuration. They are separated by country and not all country have its own built-in expressions. For now there are e.g. Base built-in expressions and Polish built-in expressions. Base expressions are available in every country and there are very generic to match all countries pluralization/cardinal numbering logic.

    +

    List of supported built-in shared expressions:

    +
    Base (English fits to this completely)
    +- one - detects if number is equal 1
    +- >one - detects if number is greater than 1
    +- two - detects if number is equal 2
    +- other - detects if number is not 1, so 0, 2, 3 and so on.
     
    -Expressions.strings (Polish)
    ----------------------------
    -"custom-1" = "ie:x=1";
    -"custom-2" = "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)";
    -"custom-3" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])";
    +Polish
    +- few - matches (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    +- many - matches 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
     
    -

    Swifternalization load these Expressions.strings files and analyze them, and replace shortcuts for expressions with full expressions.

    +

    As you can see polish has no one, >one, etc. because it inherits from Base by default.

    +

    Getting Started

    -

    There is some duplication in Base and Polish version of expressions - custom-1. Instead of repeating this in entire language you want to cover you can keep it just in Base version of Expressions.strings file. Expressions that are find in Base and are not in preferred language file will be added to preferred language too to observance of DRY principle.

    +

    This chapter shows you how to start using Swifternalization and how to intergrate it with your code.

    +

    Documentation

    -

    Swifternalization also handles the case of overriding built-in expressions. It gives you just few expressions for now like: one, >one, two, other as base expressions and few and many for Polish. If any of your Expressions.strings version of file will override it Swifternalization will use your version.

    -

    Demo

    +

    Documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private.

    -

    There is demo project included in the repo. Just switch to proper target and run. It enumerated cars from 1 to 1000 and print them out to the console. Base (English) and Polish languages are supported. You can find there example of using simple primitive no-expression translation and also with experssions.

    -

    Contribution and change or feature requests

    +

    You can find Public API and Full documentation with docset here in docs directory.

    -

    Swifternalization is open sources so everyone may contribute if want to. If you want to develop it just fork the repo, do you work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Built-in expressions

    - -

    As mentioned in previous chapter Swifternalization has some built-in expressions and is ready to extend. If you want to add expressions specific for your country you can do it by creating class which conforms to SharedExpressionProtocol. Methods from protocol returns all expressions for your country. There is already SharedBaseExpression with some basic expressions and SharedPolishExpression with polish expressions for helping ordering numbers.

    - -

    Example of the file ready for pull request should looks like this:

    -
    class SharedPolishExpression: SharedExpressionProtocol {
    -    static func allExpressions() -> [SharedExpression] {
    -        return [
    -            /**
    -            (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    -
    -            e.g.
    -            - 22 samochody, 1334 samochody, 53 samochody
    -            - 2 minuty, 4 minuty, 23 minuty
    -            */
    -            SharedExpression(k: "few", e: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"),
    -
    -            /**
    -            0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
    -
    -            e.g.
    -            - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów
    -            - 5 minut, 18 minut, 117 minut, 1009 minut
    -            */
    -            SharedExpression(k: "many", e: "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"),
    -        ]
    -    }
    -}
    +

    It is also hosted on my blog: +- Public API documentation +- Full API documentation

    + +

    Docsets: +- Public API docset +- Full API docset

    +

    Instalation

    + +

    It works with iOS 8.0 and newer.

    + +

    With CocoaPods:

    +
    pod 'Swifternalization', '~> 1.2'
     
    -

    Also this is required to cover all shared expressions for a country with unit tests. You can find examples in the repo for e.g. Polish expressions.

    +

    If you are not using CocoaPods just import files from Swifternalization/Swifternalization directory to your project.

    + +

    Swifternalization also supports Carthage.

    +

    Configuration

    + +

    Before you get a first localized string you have to configure Swifternalization by passing to it the bundle where localized json files are placed.

    +
    I18n.configure() // for NSBundle.mainBundle() - Mostly you want to call it this way
    +I18n.configure(bundle) // if files are in another bundle
    +
    +

    Creating file with Shared Expressions

    + +

    Shared Expressions must be placed in expressions.json. Syntax of a file looks like below:

    +
    {
    +    "base": {
    +        "ten": "ie:x=10",
    +        ">20": "ie:x>20",
    +        "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"
    +    },
    +
    +    "pl": {
    +        "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)",
    +        "two": "ie:x=2",
    +        "three": "ie:x=3"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "language-1": {
    +        "shared-expression-key-1": "expression-1",
    +        "shared-expression-key-2": "expression-2"
    +    },
    +
    +    "language-2": {
    +        "shared-expression-key-1": "expression-1"
    +    }
    +}
    +
    + +

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    +

    Creating Localizable Files

    + +

    Localizable file contains translations for specific language. The files might look like below:

    +
    {
    +    "welcome-key": "welcome",
    +
    +    "cars": {
    +        "one": "one car",
    +        "ie:x>=2": "%d cars",
    +        "ie:x<=-2": "minus %d cars"
    +    }
    +}
    +
    + +

    Name of a file should be the same like country code. e.g. for English it is en.json, for Polish it is pl.json, for base localization it is base.json, etc.

    + +

    There are few things that you can place in such files. More complex file will look like below:

    +
    {
    +    "welcome": "welcome",
    +
    +    "cars": {
    +        "one": {
    +            "@100": "one car",
    +            "@200": "You have one car",
    +            "@400": "You have got one car"
    +        },
    +
    +        "other": "%d cars"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "key": "value",
    +
    +    "key": {
    +        "expression-1": {
    +            "length-variation-1": "value-1",
    +            "length-variation-2": "value-2",
    +            "length-variation-3": "value-3"
    +        },
    +
    +        "expression-2": "value"
    +    }
    +}
    +
    +

    Getting localized string

    + +

    Swifternalization allows you to work with its one class method which exposes all the methods you need to localize an app.

    + +

    These methods have many optional paramters and you can omit them if you want. There are few common parameters:

    + +
      +
    • key - A key of localized string.
    • +
    • fittingWidth - A width of a screen or place where you want to put a localized string. It is integer.
    • +
    • defaultValue - A value that will be returned if there is no localized string for a key passed to the method. If this is not specified then key is returned.
    • +
    • comment - A comment used just by developer to know a context of translation.
    • +
    + +

    First method called localizedString(_:fittingWidth:defaultValue:comment:) allows you to get value for simple key without expression.

    + +

    Examples:

    +
    I18n.localizedString("welcome")
    +I18n.localizedString("welcome", fittingWidth: 200)
    +I18n.localizedString("welcome", defaultValue: "Welcome", comment: "Displayed on app start")
    +
    + +

    Next method localizedString(_:stringValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for string value that match an expression. Actually the string value will contain number inside in most cases or some other string that you would like to match.

    +
    I18n.localizedString("cars", stringValue: "5")
    +// Other cases similar to above example
    +
    + +

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    +
    I18n.localizedString("cars", intValue: 5)
    +
    +

    Contribution and change or feature requests

    + +

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    + +

    There is no guide for contributors but if you added new functionality you must write unit tests for it.

    Swift 2

    -

    Swifternalization supports Swift 2 and works on Xcode 7 beta 2. Please check swift2 branch for that.

    -

    Things to do in future release:

    +

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    +

    Things to do in future releases:

    • Add more built-in expressions for another countries.
    • +
    • Add support for float numbers in built in expressions that uses regular expressions.

    LICENSE

    @@ -500,7 +684,7 @@ diff --git a/docs/public/Classes.html b/docs/public/Classes.html index 91516ba..5506c33 100644 --- a/docs/public/Classes.html +++ b/docs/public/Classes.html @@ -68,27 +68,16 @@

    Classes

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    See more
    @@ -96,7 +85,7 @@

    Classes

    Declaration

    Swift

    -
    public class Swifternalization
    +
    final public class Swifternalization
    @@ -108,7 +97,7 @@

    Declaration

    diff --git a/docs/public/Classes/Swifternalization.html b/docs/public/Classes/Swifternalization.html index 6565127..a1496aa 100644 --- a/docs/public/Classes/Swifternalization.html +++ b/docs/public/Classes/Swifternalization.html @@ -50,45 +50,34 @@

    Swifternalization

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    • @@ -96,20 +85,14 @@

      Public methods

      -

      Swifternalization takes NSBundle when Localizable.strings file is located. -This method return instance of the class but you don’t need it because -shared instance is set automatically.

      - -

      It get Localizable.strings file version based on the first language from -the prefferedLocalizations property of NSBundle. If Localizable.strings for -preferred language isn’t exist then Base is used instead.

      +

      Call the method to configure Swifternalization.

      Declaration

      Swift

      -
      public init(bundle: NSBundle)
      +
      public class func configure(bundle: NSBundle = NSBundle.mainBundle())
      @@ -125,7 +108,7 @@

      Parameters

      -

      bundle when .strings files are located.

      +

      A bundle when expressions.json and other files are located.

      @@ -139,9 +122,9 @@

      Parameters

    • @@ -149,20 +132,14 @@

      Parameters

      -

      Returns localized string for simple key that does not contain any expression.

      - -
      I18n.localizedString("car")
      -I18n.localizedString("car", defaultValue: "Audi")
      -I18n.localizedString("car", defaultValue: "Audi", comment: "Comment")
      -I18n.localizedString("car", comment: "Comment")
      -
      +

      Get localized value for a key.

      Declaration

      Swift

      -
      public class func localizedString(key: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -178,7 +155,23 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -191,8 +184,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -205,8 +198,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description of -a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -216,9 +209,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise -returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    • @@ -227,9 +219,9 @@

      Return Value

    • @@ -237,20 +229,14 @@

      Return Value

      -

      Returns localized string for key which contains expression.

      - -
      I18n.localizedExpressionString("cars", value: "10")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "Few cars")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "10", comment: "This is a comment")
      -I18n.localizedExpressionString("cars", value: "10", comment: "This is a comment")
      -
      +

      Get localized value for a key and string value.

      Declaration

      Swift

      -
      public class func localizedExpressionString(key: String, value: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -266,7 +252,7 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      @@ -274,12 +260,28 @@

      Parameters

      - value + stringValue
      -

      value used when validating expressions.

      +

      A value that is matched by expressions.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -292,8 +294,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -306,8 +308,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description -of a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -317,8 +319,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    @@ -327,9 +329,9 @@

    Return Value

  • @@ -337,19 +339,100 @@

    Return Value

    -

    This method is just extension to method -localizedExpressionString(_:value:defaultValue:comment:) that takes -String as a value parameter.

    +

    Get localized value for a key and string value.

    Declaration

    Swift

    -
    public class func localizedExpressionString(key: String, value: Int, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + key + + +
    +

    A key to which localized string is assigned.

    + +
    +
    + + intValue + + +
    +

    A value that is matched by expressions.

    + +
    +
    + + fittingWidth + + +
    +

    A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

    + +
    +
    + + defaultValue + + +
    +

    A default value that is returned when there is no +localized value for passed key.

    + +
    +
    + + comment + + +
    +

    A comment about the key and localized value. Just for +developer use for describing key-value pair.

    + +
    +
    +
    +
    +

    Return Value

    +

    localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    + +
  • @@ -358,7 +441,7 @@

    Declaration

    diff --git a/docs/public/Typealiases.html b/docs/public/Typealiases.html index 9de94b2..d8bfaf4 100644 --- a/docs/public/Typealiases.html +++ b/docs/public/Typealiases.html @@ -67,7 +67,7 @@

    Typealiases

    -

    Handy typealias that can be used instead of long Swifternalization

    +

    Handy typealias that can be used instead of longer Swifternalization

    @@ -86,7 +86,7 @@

    Declaration

    diff --git a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes.html b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes.html index 91516ba..5506c33 100644 --- a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes.html +++ b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes.html @@ -68,27 +68,16 @@

    Classes

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    See more
    @@ -96,7 +85,7 @@

    Classes

    Declaration

    Swift

    -
    public class Swifternalization
    +
    final public class Swifternalization
    @@ -108,7 +97,7 @@

    Declaration

    diff --git a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes/Swifternalization.html b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes/Swifternalization.html index 6565127..a1496aa 100644 --- a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes/Swifternalization.html +++ b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Classes/Swifternalization.html @@ -50,45 +50,34 @@

    Swifternalization

    This is the main class of Swifternalization library. It exposes methods -that can be used to get localized keys with or without expressions from -Localizable.strings files.

    +that can be used to get localized strings.

    -

    It also uses Expressions.strings files to manage shared expressions that -can have theirs identifiers placed in many versions of the Localizable.strings -file.

    +

    The framework uses json files and work with them. There are two types of files. +First is expressions.json that contains shared expressions used among other +localizable json files. The other files are files with translation for specific +languages. Each file is just for one language. E.g. you can have base.json, +en.json, pl.json which are accordingly used for Base, English and Polish +localizations.

    -

    Internal classes of the Swifternalization contains logic that is responsible -for detecting which value should be used for the given key and value.

    - -

    It is able to work with genstrings command-line tool like NSLocalizedString() -macro does.

    - -

    It looks for content in the NSBundle you can provide and try to find:

    - -
      -
    • Localizable.strings (Base),
    • -
    • Localizable.strings of preferred language, e.g. Localizable.strings (en)
    • -
    • Expressions.strings (Base),
    • -
    • Expressions.strings of preferred language, e.g. Expressions.strings (en)
    • -
    +

    Before calling any method that return localized string call configure:.

    • @@ -96,20 +85,14 @@

      Public methods

      -

      Swifternalization takes NSBundle when Localizable.strings file is located. -This method return instance of the class but you don’t need it because -shared instance is set automatically.

      - -

      It get Localizable.strings file version based on the first language from -the prefferedLocalizations property of NSBundle. If Localizable.strings for -preferred language isn’t exist then Base is used instead.

      +

      Call the method to configure Swifternalization.

      Declaration

      Swift

      -
      public init(bundle: NSBundle)
      +
      public class func configure(bundle: NSBundle = NSBundle.mainBundle())
      @@ -125,7 +108,7 @@

      Parameters

      -

      bundle when .strings files are located.

      +

      A bundle when expressions.json and other files are located.

      @@ -139,9 +122,9 @@

      Parameters

    • @@ -149,20 +132,14 @@

      Parameters

      -

      Returns localized string for simple key that does not contain any expression.

      - -
      I18n.localizedString("car")
      -I18n.localizedString("car", defaultValue: "Audi")
      -I18n.localizedString("car", defaultValue: "Audi", comment: "Comment")
      -I18n.localizedString("car", comment: "Comment")
      -
      +

      Get localized value for a key.

      Declaration

      Swift

      -
      public class func localizedString(key: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -178,7 +155,23 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -191,8 +184,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -205,8 +198,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description of -a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -216,9 +209,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise -returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    • @@ -227,9 +219,9 @@

      Return Value

    • @@ -237,20 +229,14 @@

      Return Value

      -

      Returns localized string for key which contains expression.

      - -
      I18n.localizedExpressionString("cars", value: "10")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "Few cars")
      -I18n.localizedExpressionString("cars", value: "10", defaultValue: "10", comment: "This is a comment")
      -I18n.localizedExpressionString("cars", value: "10", comment: "This is a comment")
      -
      +

      Get localized value for a key and string value.

      Declaration

      Swift

      -
      public class func localizedExpressionString(key: String, value: String, defaultValue: String? = nil, comment: String? = nil) -> String
      +
      public class func localizedString(key: String, stringValue: String, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
      @@ -266,7 +252,7 @@

      Parameters

      -

      key placed in Localizable.strings file.

      +

      A key to which localized string is assigned.

      @@ -274,12 +260,28 @@

      Parameters

      - value + stringValue
      -

      value used when validating expressions.

      +

      A value that is matched by expressions.

      + +
      + + + + + + fittingWidth + + + +
      +

      A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

      @@ -292,8 +294,8 @@

      Parameters

      -

      default value that will be returned when there is no -translation for passed key. Default is nil.

      +

      A default value that is returned when there is no +localized value for passed key.

      @@ -306,8 +308,8 @@

      Parameters

      -

      comment used by genstrings tool to generate description -of a key. Default is nil.

      +

      A comment about the key and localized value. Just for +developer use for describing key-value pair.

      @@ -317,8 +319,8 @@

      Parameters

      Return Value

      -

      Returns translation for passed key if found. If not found and -defaultValue is not nil it return defaultValue, otherwise returns key.

      +

      localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    @@ -327,9 +329,9 @@

    Return Value

  • @@ -337,19 +339,100 @@

    Return Value

    -

    This method is just extension to method -localizedExpressionString(_:value:defaultValue:comment:) that takes -String as a value parameter.

    +

    Get localized value for a key and string value.

    Declaration

    Swift

    -
    public class func localizedExpressionString(key: String, value: Int, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    public class func localizedString(key: String, intValue: Int, fittingWidth: Int? = nil, defaultValue: String? = nil, comment: String? = nil) -> String
    +
    +

    Parameters

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + key + + +
    +

    A key to which localized string is assigned.

    + +
    +
    + + intValue + + +
    +

    A value that is matched by expressions.

    + +
    +
    + + fittingWidth + + +
    +

    A max width that value should fit to. If there is no +value specified the full-length localized string is returned. If a +passed fitting width is greater than highest available then a value for +highest available width is returned.

    + +
    +
    + + defaultValue + + +
    +

    A default value that is returned when there is no +localized value for passed key.

    + +
    +
    + + comment + + +
    +

    A comment about the key and localized value. Just for +developer use for describing key-value pair.

    + +
    +
    +
    +
    +

    Return Value

    +

    localized string if found, otherwise defaultValue is returned if +specified or key if defaultValue is not specified.

    + +
  • @@ -358,7 +441,7 @@

    Declaration

    diff --git a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Typealiases.html b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Typealiases.html index 9de94b2..d8bfaf4 100644 --- a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Typealiases.html +++ b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/Typealiases.html @@ -67,7 +67,7 @@

    Typealiases

    -

    Handy typealias that can be used instead of long Swifternalization

    +

    Handy typealias that can be used instead of longer Swifternalization

    @@ -86,7 +86,7 @@

    Declaration

    diff --git a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/index.html b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/index.html index f20cac4..88df856 100644 --- a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/index.html +++ b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/Documents/index.html @@ -51,62 +51,97 @@

    Swifternalization: localize apps smarter

    CocoaPods Status

    +

    Swifternalization

    -

    Swifternalization is library that helps in localizing apps. It is written in Swift.

    +

    Swift library that helps in localizing apps in a different, better, simpler, more powerful way than system localization does. It uses json files instead of strings files.

    Features

      -
    • [x] Pluralization support - Avoids using .stringdicts
    • -
    • [x] Expressions - inequality and regular expressions in Localizable.strings
    • -
    • [x] Shared expressions
    • -
    • [x] Built-in expressions
    • -
    • [x] Works similarly to NSLocalizedString() macro
    • -
    • [x] Uses Localizable.strings file as NSLocalizedString() macro does
    • +
    • [x] Pluralization support - Without using stringdict files
    • +
    • [x] Length variations support - Supported since iOS 8.0 (instead of iOS 9.0 like system does) and avoids using stringsdict files
    • +
    • [x] Expressions - inequality and regular expressions
    • +
    • [x] Shared Expressions
    • +
    • [x] Built-in Expressions
    • +
    • [x] Works similarly to NSLocalizedString()
    • +
    • [x] Uses JSON files to minimize boilerplate code
    • [x] Comprehensive Unit Test Coverage
    • [x] Full documentation
    -

    Swifternalization

    +

    Table of Contents

    -

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessary to solve Polish language internalization problems but it is universal and works with every language.

    -

    Installation

    + +

    Introduction

    -

    You can find Public API and Full documentation with docset here in docs directory.

    +

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessity to solve Polish language internalization problems but it is universal and works with every language very well.

    -

    It is also hosted on my blog: -- Public API documentation -- Full API documentation

    +

    It uses JSON files and expressions that avoid writing code to handle some cases that you have to write when not using this framework. It makes localizing process simpler.

    +

    Practical Usage Example

    -

    Docsets: -- Public API docset -- Full API docset

    -

    Real Example

    +

    Description of practical usage example will use things that are covered later in the document so keep reading it to the end and then read about details/features presented here.

    +

    Problem

    -

    Let’s take a look on practical usage of Swifternalization. App supports both English and Polish languages. Naturally app contains two Localizable.strings files - one is Base for English (or English for English) and one is Polish… for Polish, obviously :)

    +

    Let’s assume the app supports English and Polish languages. Naturally app contains two Localizable.strings files. One is for Base localization which contains English translation and one is Polish language.

    -

    App displays label with information that says when objects from the backend has been updated for the last time, e.g. 2 minutes ago.

    +

    App displays label with information which says when object from the backend has been updated for the last time, e.g. 2 minutes ago, 3 hours ago, 1 minute ago, etc.

    +

    Analysis

    + +

    The label displays number and a hour/minute/second word in singular or plural forms with ago suffix. Different languages handles pluralization/cardinal numbering in slight different ways. Here we need to support English and Polish languages.

    + +

    In English there are just two cases to cover per hour/minute/second word:

    + +
      +
    • 1 - one second ago
    • +
    • 0, 2, 3… %d seconds ago
    • +
    • Same with minutes and hours.
    • +
    -

    This shouldn’t be problem in English:

    +

    In Polish it is more tricky because the cardinal numbers are more complicated:

      -
    • 0, 2… second ago
    • -
    • 1 second ago
    • -
    • +
    • 1 - jedną sekundę temu
    • +
    • 0, (5 - 21) - %d sekund temu
    • +
    • (2 - 4), (22-24), (32-34), (42, 44), …, (162-164), … - %d sekundy temu
    • +
    • Same logic for minutes and hours.
    -

    The same with minutes and hours. This is easy. Localization file for English will looks like this one:

    +

    Following chapters will present solution without and with Swifternalization framework. Each solution describes Base (English) and Polish localizations.

    + +

    Here is a table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    Solution without Swifternalization

    Localizable.strings (Base)
     --------------------------
    -
     "one-second" = "1 second ago";
     "many-seconds" = "%d seconds ago";
     
    @@ -115,77 +150,84 @@
     
     "one-hour" = "1 hour ago";
     "many-hours" = "%d hours ago";
    -
    -

    Let’s try with Polish language. As mentioned - this is tricky.

    -
    Localizable.strings (Polish)
    -----------------------------
    -
    -"one-second" = "1 sekundę temu";
    -"few-seconds" = "%d sekundy temu";
    -"many-seconds" = "%d sekund temu";
    +Localizable.strings (Polish)
    +-------------------------               
    +"one-second" = "1 sekundę temu"
    +"few-seconds" = "%d sekundy temu"
    +"many-seconds" = "%d sekund temu""              
     
    -"one-minute" = "1 minutę temu";
    -"few-minutes" = "%d minuty temu";
    -"many-minutes" = "%d minut temu";
    +"one-minute" = "1 minutę temu"
    +"few-minutes" = "%d minuty temu "
    +"many-minutes" = "%d minut temu"            
     
    -"one-hours" = "1 hodzinę temu";
    -"few-hours" = "%d godziny temu";
    +"one-hours" = "1 godzinę temu"
    +"few-hours" = "%d godziny temu"
     "many-hours" = "%d godzin temu";
     
    -

    Okay… there is 9 cases for now. But this is not the only thing to deal with. It depends on the number of seconds/minutes/hours to select proper one. Without some logic additional logic to find out which case should be used this is impossible to use proper one.

    -
    - 0, (5 - 21) - "few-seconds"
    -- 1 - "one-second"
    -- (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "many-seconds"
    -
    - -

    The same logic for minutes and hours.

    - -

    Here is nice table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    - -

    With Swifternalization this can be solved e.g. in this way:

    -
    Localizable.strings (Base)
    ---------------------------
    -"time-seconds{one}" = "%d second ago";
    -"time-seconds{other}" = "%d seconds ago";
    +

    There are 6 cases in English and 9 cases in Polish. Notice that without additional logic we’re not able to detect which version of a string for hour/minute/second the app should display. The logic differs among different languages. We would have to add some lines of code that handle the logic for all the languages we’re using in the app. What if there are more than 2 languages? Don’t even think about it - this might be not so easy.

    -"time-minutes{one}" = "%d minute ago"; -"time-minutes{other}" = "%d minutes ago"; +

    The logic is already implemented in Swifternalization framework and it fits to every language.

    +

    Solution with Swifternalization

    -"time-hours{one}" = "%d hour ago"; -"time-hours{other}" = "%d hours ago"; +

    This is how localizable files will look:

    +
    base.json
    +---------
    +"time-seconds": {
    +    "one": "%d second ago"
    +    "other": "%d seconds ago"
    +},
     
    +"time-minutes": {
    +    "one": "%d minute ago"
    +    "other": "%d minutes ago"
    +},
     
    +"time-hours": {
    +    "one": "%d hours ago"
    +    "other": "%d hours ago"
    +}
     
    -Localizable.strings (Polish)
    -----------------------------
    -"time-seconds{one}" = "%d sekundę temu";
    -"time-seconds{few}" = "%d sekundy temu";
    -"time-seconds{many}" = "%d sekund temu";
    -
    -"time-minutes{one}" = "%d minutę temu";
    -"time-minutes{few}" = "%d minuty temu";
    -"time-minutes{many}" = "%d minut temu";
    -
    -"time-hours{one}" = "%d godzinę temu";
    -"time-hours{few}" = "%d godziny temu";
    -"time-hours{many}" = "%d godzin temu";
    +pl.json
    +-------
    +"time-seconds": {
    +    "one": "1 sekundę temu",
    +    "few": "%d sekundy temu",
    +    "many": "%d sekund temu"
    +},
    +
    +"time-minutes": {
    +    "one": "1 minutę temu",
    +    "few": "%d minuty temu",
    +    "many": "%d minut temu"
    +},
    +
    +"time-hours": {
    +    "one": "1 godzinę temu",
    +    "few": "%d godziny temu",
    +    "many": "%d godzin temu"
    +}
     
    -

    So the logic is in Swifternalization and you don’t need write additional handling code for these cases.

    +
      +
    • one, few, many, other - those are shared expressions already built into Swifternalization - covered below.
    • +
    • You can add own expressions to handle specific cases - covered below.
    • +
    -

    And the call will look like this:

    -
    Swifternalization.localizedExpressionString("time-seconds", value: 10)
    +

    As mentioned the logic is implemented into framework so if you want to get one of a localized string you have to make a simple call.

    +
    Swifternalization.localizedString("time-seconds", intValue: 10)
     

    or with I18n typealias (I-18-letters-n, Internalization):

    -
    I18n.localizedExpressionString("time-seconds", value: 10)
    +
    I18n.localizedString("time-seconds", intValue: 10)
     
    -

    There is easy way to add you own expression to handle your specific case with Swifternalization.

    +

    The key and intValue parameters are validated by loaded expressions and proper version of a string is returned - covered below.

    +

    Features

    +

    Pluralization

    -

    Swifternalization also drops need for having .stringdicts files like this one:

    +

    Swifternalization drops necessity of using stringdicts files like following one to support pluralization in localized strings. Instead of that you can simply define expressions that cover such cases.

    <plist version="1.0">
         <dict>
             <key>%d file(s) remaining</key>
    @@ -207,49 +249,56 @@
         </dict>
     </plist>
     
    -

    Getting Started

    -

    Configuration is simple. The one thing that Swifternalization needs to works is NSBundle where Localizable.strings are placed.

    +

    No more stringsdict files!

    +

    Length Variations

    -

    Recommended is to configure it as fast as you can to be sure that before you want to get some localized key it will be able to return you something.

    -
        Swifternalization(bundle: NSBundle.mainBundle())
    -
    +

    iOS 9 provides new way to select proper localized string variation depending on a screen width. It uses stringsdict file with NSStringVariableWidthRuleType key.

    -

    This call will create instance (you can get handle to it but you don’t need it) and automatically set it as shared instance and you can easily work with it.

    +

    Swifternalization drops necessity of using such file and it is not necessary to use this new key to use the feature.

    -

    In Localizable.strings the syntax should looks like this:

    -
    "key" = "value";
    -"key{expression}" = "value";
    -
    -

    How to get localized string

    +

    With Swifternalization this length variations feature is available since iOS 8.0 because the framework has its own implementation of length variations.

    -

    Swifternalization allows developer to work with its class methods. There are few to use:

    -
    localizedString(key: String, defaultValue: String? = nil) -> String
    +

    To use length variations feature your translation file should has entries like this:

    +
    base.json
    +---------
    +"forgot-password": {
    +    "@200": "Forgot Password? Help.",
    +    "@300": "Forgot Your Password? Use Help.",
    +    "@400": "Do not remember Your Password?" Use Help.""
    +}
     
    -

    Allows to get value for simple key. Works similar to NSLocalizedString. key is the key placed in Localizable.strings and defaultValue is the value that will be returned when there is no translation found for passed key. If defaultValue is nil then key will be return in such case.

    +

    The number after @ sign is max width of a screen or bounds that a string fits to. E.g. the second string will be returned if passed fitting width as a paramter is be greater than 200 and less or equal 300.

    -

    The next one is for getting localized string with keys that contain some expressions:

    -
    localizedExpressionString(key: String, value: String, defaultValue: String? = nil) -> String
    +

    To get the second localized string the call looks like below:

    +
    I18n.localizedString("forgot-password", fittingWidth: 300) // 201 - 300
     
    -

    Similarly to the one above key is the key in Localizable.strings, defaultValue is also the same and methods behaves the same. There is additional parameter called value. The value is used for expression matchers to validate expressions and return proper localized value. We’ll cover it soon.

    +

    You can mix expressions with length variations. Following example shows it:

    +
    base.json
    +---------
    +"car": {
    +    "ie:x=1": {
    +        @100: "One car",
    +        @200: "You've got one car"
    +    },
     
    -

    As the method takes some String as a value and you probably will deal with Int there is alternative method to call:

    -
    localizedExpressionString(key: String, value: Int, defaultValue: String? = nil) -> String
    +    "more": "You've got few cars"
    +}
     

    Expressions

    -

    As mentioned there are few expression types. Every expression type has their own parser and matcher.

    +

    There are few expression types. Every expression type has their own parser and matcher but they work internally so you don’t need to worry about them.

    -

    There are 3 types:

    +

    There are 3 types of expressions:

      -
    • inequality - this type of expression handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on.
    • -
    • inequality extended - this is extended version of inequality with syntax like this: 2<x<10, 4<=x<6.
    • -
    • regex - this types of expression uses regular expression. This is the most powerful ;)
    • +
    • inequality - handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on. Work with integer and float numbers.
    • +
    • inequality extended - extended version of inequality with syntax like this: 2<x<10, 4<=x<6. Work with integer and float numbers.
    • +
    • regex - uses regular expression. This is the most powerful ;)
    -

    Inequality

    +

    Inequality Expressions

    It is composed of several elements:

    @@ -261,13 +310,15 @@

    Example:

    -
    "cars{ie:x=1}" = "1 car";
    -"cars{ie:x=0}" = "no cars";
    -"cars{ie:x>1}" = "%d cars";
    +
    "cars": {
    +    "ie:x=1": "1 car",
    +    "ie:x=0": "no cars",
    +    "ie:x>1": "%d cars"
    +}
     
    -

    Inequality Extended

    +

    Inequality Extended Expressions

    -

    This is a bit extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    +

    This is extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    • iex: - prefix of inequality extended expression
    • @@ -276,11 +327,13 @@

    Expample:

    -
    "tomatos{iex:2<x<10}" = "%d tomatos is between 2 and 10";
    +
    "tomatos": {
    +    "iex:2<x<10": "%d tomatos is between 2 and 10"
    +}
     
    -

    Regex

    +

    Regex Expressions

    -

    This is the most powerful type of expression and probably will be most used by developers. It takes regular expression ;)

    +

    This is the most powerful type of expression. It takes regular expression ;)

    • exp: - prefix of regex expression
    • @@ -288,108 +341,228 @@

    Example: (police cars in Polish language)

    -
    "police-cars{exp:^1$}" = "1 samochód policyjny";
    -"police-cars{exp:(((?!1).[2-4]{1})$)|(^[2-4]$)}" = "%d samochody policyjne";
    -"police-cars{exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])}" = "%d samochodów policyjnych";
    +
    "police-cars": {
    +    "exp:^1$": "1 samochód policyjny",
    +    "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)": "%d samochody policyjne",
    +    "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])": "%d samochodów policyjnych"
    +}
     

    Powerful stuff, isn’t it? :>

    -

    PS. There is built in solution for Polish language so you can use it with doing just this:

    -
    "police-cars{one}" = "1 samochód policyjny";
    -"police-cars{few}" = "%d samochody policyjne";
    -"police-cars{many}" = "%d samochodów policyjnych";
    +

    PS. There is built-in solution for Polish language so you can use it with doing just this:

    +
    "police-cars": {
    +    "one": "1 samochód policyjny",
    +    "few": "%d samochody policyjne",
    +    "many": "%d samochodów policyjnych"
    +}   
     
    -

    This feature is called Shared Expression and is covered below.

    -

    Shared Expressions

    +

    This is called Shared Built-In Expression and is covered below.

    +

    Shared Expressions

    + +

    Shared expressions are expressions available among all the localization files. They are declared in expressions.json file divided by language and you can use them in localization files.

    The functionality allows developer to observance of DRY principle and to avoid mistakes that exist because of reapeating the code in many places.

    -

    It is possible to create shared expression in your project and use it with no configuration with Swifternalization.

    -

    Getting Started of Shared Expressions

    +

    Normally you declare expression like this:

    +
    ...
    +"ie:x>1": "Localized String"
    +...
    +
    -
      -
    1. Create Expressions.strings file in the same bundle when Localizable.strings file is.
    2. -
    3. Add shortcuts for your expressions and add your expressions ;)
    4. -
    +

    If you want to use the same expression in multiple files there is no necessity to repeat the expression elsewhere. This is even problematic when you decide to improve/change expression to handle another cases you forget about - you would have to change expression in multiple places. Because of that there are Shared Expression. These feature allows you to create expression just in one place and use identifier of it in multiple places where you normally should put this expression.

    -

    Example:

    -
    Localizable.strings (Base)
    --------------------
    -"cars{custom-1}" = "%d car";
    -"cars{custom-2}" = "%d cars";
    +

    What you need to do is to create expressions.json file with following structure:

    +
    {
    +    "base": {
    +        "one": "ie:x>1"
    +    },
     
    +    "pl": {
    +        // ... other than "one" because "one" is available here too.
    +    }
    +}
    +
    -Localizable.strings (Polish) ----------------------------- -"cars{custom-1}" = "%d samochód"; -"cars{custom-2}" = "%d samochody"; -"cars{custom-3}" = "%d samochodów"; +

    Now in pl.json, en.json and so on you have to use it as below:

    +
    ...
    +"one": "Localized String"
    +...
    +
    +

    Before you decide to create your own expression take a look if there is no built-in one with the same name or whether there is such expression but named differently. Maybe you don’t need to do this at all and just use it.

    +

    Built-in expressions

    -Expressions.strings (Base) --------------------------- -"custom-1" = "ie:x=1"; -"custom-2" = "exp:(^[^1])|(^\\d{2,})"; +

    Built-in expressions as name suggest are shared expressions built into framework and available to use with zero configuration. They are separated by country and not all country have its own built-in expressions. For now there are e.g. Base built-in expressions and Polish built-in expressions. Base expressions are available in every country and there are very generic to match all countries pluralization/cardinal numbering logic.

    +

    List of supported built-in shared expressions:

    +
    Base (English fits to this completely)
    +- one - detects if number is equal 1
    +- >one - detects if number is greater than 1
    +- two - detects if number is equal 2
    +- other - detects if number is not 1, so 0, 2, 3 and so on.
     
    -Expressions.strings (Polish)
    ----------------------------
    -"custom-1" = "ie:x=1";
    -"custom-2" = "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)";
    -"custom-3" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])";
    +Polish
    +- few - matches (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    +- many - matches 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
     
    -

    Swifternalization load these Expressions.strings files and analyze them, and replace shortcuts for expressions with full expressions.

    +

    As you can see polish has no one, >one, etc. because it inherits from Base by default.

    +

    Getting Started

    -

    There is some duplication in Base and Polish version of expressions - custom-1. Instead of repeating this in entire language you want to cover you can keep it just in Base version of Expressions.strings file. Expressions that are find in Base and are not in preferred language file will be added to preferred language too to observance of DRY principle.

    +

    This chapter shows you how to start using Swifternalization and how to intergrate it with your code.

    +

    Documentation

    -

    Swifternalization also handles the case of overriding built-in expressions. It gives you just few expressions for now like: one, >one, two, other as base expressions and few and many for Polish. If any of your Expressions.strings version of file will override it Swifternalization will use your version.

    -

    Demo

    +

    Documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private.

    -

    There is demo project included in the repo. Just switch to proper target and run. It enumerated cars from 1 to 1000 and print them out to the console. Base (English) and Polish languages are supported. You can find there example of using simple primitive no-expression translation and also with experssions.

    -

    Contribution and change or feature requests

    +

    You can find Public API and Full documentation with docset here in docs directory.

    -

    Swifternalization is open sources so everyone may contribute if want to. If you want to develop it just fork the repo, do you work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Built-in expressions

    - -

    As mentioned in previous chapter Swifternalization has some built-in expressions and is ready to extend. If you want to add expressions specific for your country you can do it by creating class which conforms to SharedExpressionProtocol. Methods from protocol returns all expressions for your country. There is already SharedBaseExpression with some basic expressions and SharedPolishExpression with polish expressions for helping ordering numbers.

    - -

    Example of the file ready for pull request should looks like this:

    -
    class SharedPolishExpression: SharedExpressionProtocol {
    -    static func allExpressions() -> [SharedExpression] {
    -        return [
    -            /**
    -            (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    -
    -            e.g.
    -            - 22 samochody, 1334 samochody, 53 samochody
    -            - 2 minuty, 4 minuty, 23 minuty
    -            */
    -            SharedExpression(k: "few", e: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"),
    -
    -            /**
    -            0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
    -
    -            e.g.
    -            - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów
    -            - 5 minut, 18 minut, 117 minut, 1009 minut
    -            */
    -            SharedExpression(k: "many", e: "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"),
    -        ]
    -    }
    -}
    +

    It is also hosted on my blog: +- Public API documentation +- Full API documentation

    + +

    Docsets: +- Public API docset +- Full API docset

    +

    Instalation

    + +

    It works with iOS 8.0 and newer.

    + +

    With CocoaPods:

    +
    pod 'Swifternalization', '~> 1.2'
    +
    + +

    If you are not using CocoaPods just import files from Swifternalization/Swifternalization directory to your project.

    + +

    Swifternalization also supports Carthage.

    +

    Configuration

    + +

    Before you get a first localized string you have to configure Swifternalization by passing to it the bundle where localized json files are placed.

    +
    I18n.configure() // for NSBundle.mainBundle() - Mostly you want to call it this way
    +I18n.configure(bundle) // if files are in another bundle
    +
    +

    Creating file with Shared Expressions

    + +

    Shared Expressions must be placed in expressions.json. Syntax of a file looks like below:

    +
    {
    +    "base": {
    +        "ten": "ie:x=10",
    +        ">20": "ie:x>20",
    +        "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"
    +    },
    +
    +    "pl": {
    +        "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)",
    +        "two": "ie:x=2",
    +        "three": "ie:x=3"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "language-1": {
    +        "shared-expression-key-1": "expression-1",
    +        "shared-expression-key-2": "expression-2"
    +    },
    +
    +    "language-2": {
    +        "shared-expression-key-1": "expression-1"
    +    }
    +}
    +
    + +

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    +

    Creating Localizable Files

    + +

    Localizable file contains translations for specific language. The files might look like below:

    +
    {
    +    "welcome-key": "welcome",
    +
    +    "cars": {
    +        "one": "one car",
    +        "ie:x>=2": "%d cars",
    +        "ie:x<=-2": "minus %d cars"
    +    }
    +}
    +
    + +

    Name of a file should be the same like country code. e.g. for English it is en.json, for Polish it is pl.json, for base localization it is base.json, etc.

    + +

    There are few things that you can place in such files. More complex file will look like below:

    +
    {
    +    "welcome": "welcome",
    +
    +    "cars": {
    +        "one": {
    +            "@100": "one car",
    +            "@200": "You have one car",
    +            "@400": "You have got one car"
    +        },
    +
    +        "other": "%d cars"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "key": "value",
    +
    +    "key": {
    +        "expression-1": {
    +            "length-variation-1": "value-1",
    +            "length-variation-2": "value-2",
    +            "length-variation-3": "value-3"
    +        },
    +
    +        "expression-2": "value"
    +    }
    +}
    +
    +

    Getting localized string

    + +

    Swifternalization allows you to work with its one class method which exposes all the methods you need to localize an app.

    + +

    These methods have many optional paramters and you can omit them if you want. There are few common parameters:

    + +
      +
    • key - A key of localized string.
    • +
    • fittingWidth - A width of a screen or place where you want to put a localized string. It is integer.
    • +
    • defaultValue - A value that will be returned if there is no localized string for a key passed to the method. If this is not specified then key is returned.
    • +
    • comment - A comment used just by developer to know a context of translation.
    • +
    + +

    First method called localizedString(_:fittingWidth:defaultValue:comment:) allows you to get value for simple key without expression.

    + +

    Examples:

    +
    I18n.localizedString("welcome")
    +I18n.localizedString("welcome", fittingWidth: 200)
    +I18n.localizedString("welcome", defaultValue: "Welcome", comment: "Displayed on app start")
    +
    + +

    Next method localizedString(_:stringValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for string value that match an expression. Actually the string value will contain number inside in most cases or some other string that you would like to match.

    +
    I18n.localizedString("cars", stringValue: "5")
    +// Other cases similar to above example
    +
    + +

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    +
    I18n.localizedString("cars", intValue: 5)
     
    +

    Contribution and change or feature requests

    + +

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Also this is required to cover all shared expressions for a country with unit tests. You can find examples in the repo for e.g. Polish expressions.

    +

    There is no guide for contributors but if you added new functionality you must write unit tests for it.

    Swift 2

    -

    Swifternalization supports Swift 2 and works on Xcode 7 beta 2. Please check swift2 branch for that.

    -

    Things to do in future release:

    +

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    +

    Things to do in future releases:

    • Add more built-in expressions for another countries.
    • +
    • Add support for float numbers in built in expressions that uses regular expressions.

    LICENSE

    @@ -398,7 +571,7 @@ diff --git a/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/docSet.dsidx b/docs/public/docsets/Swifternalization Public API.docset/Contents/Resources/docSet.dsidx index 4c0c49e09b93c24a81146c366ffb7ea8a851e00f..320a0bf89b1735d1683d366a4cc1c19756c0801f 100644 GIT binary patch delta 709 zcmZojXh_(=Dj>}Ln1TNYzagJLZy-+;k30`6_v4L)r@1FP%2XJXu(C5Wa!NME=j10R z=44i-rUaK1W#*-8#9L+Nm4qecl%`sxWtNlxg~BsaN-`$TkZY2+fJp^s8X6(Xx(62q zXU0#Kl2c*v2CQZ`D;Xlm1_$FZUM-%PjiFq!$rICbWcL7F+aiQ4 zfN-Fo023R7p=4uWa(-S~W_oE+>SP~z!^zj>G}SEOLTb}^a6jQU;tSx3<_+3dc!qnjlTL+xDLXh|88Mv*3qEo} zR@H>Hv4$NSvRF(;3Osbfi40m*b(Y2iHc-%FF&;T6afYqFx_}@XC}bJo;Rp*_J>r5k Qp2><;R6Lb)@@aWf06(7i-~a#s delta 657 zcmZojXh_(=Dj>|`$iRPu-=43IcQ(%so?4z{9>TQ zWLBl7xK9rR1be zULd2z>oxn=tgAFDhmUj~7abn;};Xle#$K%BB zz&nR$Ctv->LM@)jPqZsE64^lE#|U=<6$4OLoo1$k!cSF-rO}=Z6n+TTqXZz2zPb=I b8z}e~afhE8{_u-uvSJk#Pvx9^THX`@3$Nk~ diff --git a/docs/public/docsets/Swifternalization Public API.tgz b/docs/public/docsets/Swifternalization Public API.tgz index b756f39859667531957e7b6abe7f5d4d99da749d..1cefaba0dc75e7ac997cd74043112b1df481d723 100644 GIT binary patch literal 52704 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 zjV0A{RUSwifternalization: localize apps smarter

    CocoaPods Status

    +

    Swifternalization

    -

    Swifternalization is library that helps in localizing apps. It is written in Swift.

    +

    Swift library that helps in localizing apps in a different, better, simpler, more powerful way than system localization does. It uses json files instead of strings files.

    Features

      -
    • [x] Pluralization support - Avoids using .stringdicts
    • -
    • [x] Expressions - inequality and regular expressions in Localizable.strings
    • -
    • [x] Shared expressions
    • -
    • [x] Built-in expressions
    • -
    • [x] Works similarly to NSLocalizedString() macro
    • -
    • [x] Uses Localizable.strings file as NSLocalizedString() macro does
    • +
    • [x] Pluralization support - Without using stringdict files
    • +
    • [x] Length variations support - Supported since iOS 8.0 (instead of iOS 9.0 like system does) and avoids using stringsdict files
    • +
    • [x] Expressions - inequality and regular expressions
    • +
    • [x] Shared Expressions
    • +
    • [x] Built-in Expressions
    • +
    • [x] Works similarly to NSLocalizedString()
    • +
    • [x] Uses JSON files to minimize boilerplate code
    • [x] Comprehensive Unit Test Coverage
    • [x] Full documentation
    -

    Swifternalization

    +

    Table of Contents

    -

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessary to solve Polish language internalization problems but it is universal and works with every language.

    -

    Installation

    + +

    Introduction

    -

    You can find Public API and Full documentation with docset here in docs directory.

    +

    Swifternalization helps in localizing apps in a smarter way. It has been created because of necessity to solve Polish language internalization problems but it is universal and works with every language very well.

    -

    It is also hosted on my blog: -- Public API documentation -- Full API documentation

    +

    It uses JSON files and expressions that avoid writing code to handle some cases that you have to write when not using this framework. It makes localizing process simpler.

    +

    Practical Usage Example

    -

    Docsets: -- Public API docset -- Full API docset

    -

    Real Example

    +

    Description of practical usage example will use things that are covered later in the document so keep reading it to the end and then read about details/features presented here.

    +

    Problem

    -

    Let’s take a look on practical usage of Swifternalization. App supports both English and Polish languages. Naturally app contains two Localizable.strings files - one is Base for English (or English for English) and one is Polish… for Polish, obviously :)

    +

    Let’s assume the app supports English and Polish languages. Naturally app contains two Localizable.strings files. One is for Base localization which contains English translation and one is Polish language.

    -

    App displays label with information that says when objects from the backend has been updated for the last time, e.g. 2 minutes ago.

    +

    App displays label with information which says when object from the backend has been updated for the last time, e.g. 2 minutes ago, 3 hours ago, 1 minute ago, etc.

    +

    Analysis

    + +

    The label displays number and a hour/minute/second word in singular or plural forms with ago suffix. Different languages handles pluralization/cardinal numbering in slight different ways. Here we need to support English and Polish languages.

    + +

    In English there are just two cases to cover per hour/minute/second word:

    + +
      +
    • 1 - one second ago
    • +
    • 0, 2, 3… %d seconds ago
    • +
    • Same with minutes and hours.
    • +
    -

    This shouldn’t be problem in English:

    +

    In Polish it is more tricky because the cardinal numbers are more complicated:

      -
    • 0, 2… second ago
    • -
    • 1 second ago
    • -
    • +
    • 1 - jedną sekundę temu
    • +
    • 0, (5 - 21) - %d sekund temu
    • +
    • (2 - 4), (22-24), (32-34), (42, 44), …, (162-164), … - %d sekundy temu
    • +
    • Same logic for minutes and hours.
    -

    The same with minutes and hours. This is easy. Localization file for English will looks like this one:

    +

    Following chapters will present solution without and with Swifternalization framework. Each solution describes Base (English) and Polish localizations.

    + +

    Here is a table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    +

    Solution without Swifternalization

    Localizable.strings (Base)
     --------------------------
    -
     "one-second" = "1 second ago";
     "many-seconds" = "%d seconds ago";
     
    @@ -115,77 +150,84 @@
     
     "one-hour" = "1 hour ago";
     "many-hours" = "%d hours ago";
    -
    -

    Let’s try with Polish language. As mentioned - this is tricky.

    -
    Localizable.strings (Polish)
    -----------------------------
    -
    -"one-second" = "1 sekundę temu";
    -"few-seconds" = "%d sekundy temu";
    -"many-seconds" = "%d sekund temu";
    +Localizable.strings (Polish)
    +-------------------------               
    +"one-second" = "1 sekundę temu"
    +"few-seconds" = "%d sekundy temu"
    +"many-seconds" = "%d sekund temu""              
     
    -"one-minute" = "1 minutę temu";
    -"few-minutes" = "%d minuty temu";
    -"many-minutes" = "%d minut temu";
    +"one-minute" = "1 minutę temu"
    +"few-minutes" = "%d minuty temu "
    +"many-minutes" = "%d minut temu"            
     
    -"one-hours" = "1 hodzinę temu";
    -"few-hours" = "%d godziny temu";
    +"one-hours" = "1 godzinę temu"
    +"few-hours" = "%d godziny temu"
     "many-hours" = "%d godzin temu";
     
    -

    Okay… there is 9 cases for now. But this is not the only thing to deal with. It depends on the number of seconds/minutes/hours to select proper one. Without some logic additional logic to find out which case should be used this is impossible to use proper one.

    -
    - 0, (5 - 21) - "few-seconds"
    -- 1 - "one-second"
    -- (2 - 4), (22-24), (32-34), (42, 44), ..., (162-164), ... - "many-seconds"
    -
    - -

    The same logic for minutes and hours.

    - -

    Here is nice table with Language Plural Rules which covers cardinal forms of numbers in many languages - Many language handle plurality in their own way.

    - -

    With Swifternalization this can be solved e.g. in this way:

    -
    Localizable.strings (Base)
    ---------------------------
    -"time-seconds{one}" = "%d second ago";
    -"time-seconds{other}" = "%d seconds ago";
    +

    There are 6 cases in English and 9 cases in Polish. Notice that without additional logic we’re not able to detect which version of a string for hour/minute/second the app should display. The logic differs among different languages. We would have to add some lines of code that handle the logic for all the languages we’re using in the app. What if there are more than 2 languages? Don’t even think about it - this might be not so easy.

    -"time-minutes{one}" = "%d minute ago"; -"time-minutes{other}" = "%d minutes ago"; +

    The logic is already implemented in Swifternalization framework and it fits to every language.

    +

    Solution with Swifternalization

    -"time-hours{one}" = "%d hour ago"; -"time-hours{other}" = "%d hours ago"; +

    This is how localizable files will look:

    +
    base.json
    +---------
    +"time-seconds": {
    +    "one": "%d second ago"
    +    "other": "%d seconds ago"
    +},
     
    +"time-minutes": {
    +    "one": "%d minute ago"
    +    "other": "%d minutes ago"
    +},
     
    +"time-hours": {
    +    "one": "%d hours ago"
    +    "other": "%d hours ago"
    +}
     
    -Localizable.strings (Polish)
    -----------------------------
    -"time-seconds{one}" = "%d sekundę temu";
    -"time-seconds{few}" = "%d sekundy temu";
    -"time-seconds{many}" = "%d sekund temu";
    -
    -"time-minutes{one}" = "%d minutę temu";
    -"time-minutes{few}" = "%d minuty temu";
    -"time-minutes{many}" = "%d minut temu";
    -
    -"time-hours{one}" = "%d godzinę temu";
    -"time-hours{few}" = "%d godziny temu";
    -"time-hours{many}" = "%d godzin temu";
    +pl.json
    +-------
    +"time-seconds": {
    +    "one": "1 sekundę temu",
    +    "few": "%d sekundy temu",
    +    "many": "%d sekund temu"
    +},
    +
    +"time-minutes": {
    +    "one": "1 minutę temu",
    +    "few": "%d minuty temu",
    +    "many": "%d minut temu"
    +},
    +
    +"time-hours": {
    +    "one": "1 godzinę temu",
    +    "few": "%d godziny temu",
    +    "many": "%d godzin temu"
    +}
     
    -

    So the logic is in Swifternalization and you don’t need write additional handling code for these cases.

    +
      +
    • one, few, many, other - those are shared expressions already built into Swifternalization - covered below.
    • +
    • You can add own expressions to handle specific cases - covered below.
    • +
    -

    And the call will look like this:

    -
    Swifternalization.localizedExpressionString("time-seconds", value: 10)
    +

    As mentioned the logic is implemented into framework so if you want to get one of a localized string you have to make a simple call.

    +
    Swifternalization.localizedString("time-seconds", intValue: 10)
     

    or with I18n typealias (I-18-letters-n, Internalization):

    -
    I18n.localizedExpressionString("time-seconds", value: 10)
    +
    I18n.localizedString("time-seconds", intValue: 10)
     
    -

    There is easy way to add you own expression to handle your specific case with Swifternalization.

    +

    The key and intValue parameters are validated by loaded expressions and proper version of a string is returned - covered below.

    +

    Features

    +

    Pluralization

    -

    Swifternalization also drops need for having .stringdicts files like this one:

    +

    Swifternalization drops necessity of using stringdicts files like following one to support pluralization in localized strings. Instead of that you can simply define expressions that cover such cases.

    <plist version="1.0">
         <dict>
             <key>%d file(s) remaining</key>
    @@ -207,49 +249,56 @@
         </dict>
     </plist>
     
    -

    Getting Started

    -

    Configuration is simple. The one thing that Swifternalization needs to works is NSBundle where Localizable.strings are placed.

    +

    No more stringsdict files!

    +

    Length Variations

    -

    Recommended is to configure it as fast as you can to be sure that before you want to get some localized key it will be able to return you something.

    -
        Swifternalization(bundle: NSBundle.mainBundle())
    -
    +

    iOS 9 provides new way to select proper localized string variation depending on a screen width. It uses stringsdict file with NSStringVariableWidthRuleType key.

    -

    This call will create instance (you can get handle to it but you don’t need it) and automatically set it as shared instance and you can easily work with it.

    +

    Swifternalization drops necessity of using such file and it is not necessary to use this new key to use the feature.

    -

    In Localizable.strings the syntax should looks like this:

    -
    "key" = "value";
    -"key{expression}" = "value";
    -
    -

    How to get localized string

    +

    With Swifternalization this length variations feature is available since iOS 8.0 because the framework has its own implementation of length variations.

    -

    Swifternalization allows developer to work with its class methods. There are few to use:

    -
    localizedString(key: String, defaultValue: String? = nil) -> String
    +

    To use length variations feature your translation file should has entries like this:

    +
    base.json
    +---------
    +"forgot-password": {
    +    "@200": "Forgot Password? Help.",
    +    "@300": "Forgot Your Password? Use Help.",
    +    "@400": "Do not remember Your Password?" Use Help.""
    +}
     
    -

    Allows to get value for simple key. Works similar to NSLocalizedString. key is the key placed in Localizable.strings and defaultValue is the value that will be returned when there is no translation found for passed key. If defaultValue is nil then key will be return in such case.

    +

    The number after @ sign is max width of a screen or bounds that a string fits to. E.g. the second string will be returned if passed fitting width as a paramter is be greater than 200 and less or equal 300.

    -

    The next one is for getting localized string with keys that contain some expressions:

    -
    localizedExpressionString(key: String, value: String, defaultValue: String? = nil) -> String
    +

    To get the second localized string the call looks like below:

    +
    I18n.localizedString("forgot-password", fittingWidth: 300) // 201 - 300
     
    -

    Similarly to the one above key is the key in Localizable.strings, defaultValue is also the same and methods behaves the same. There is additional parameter called value. The value is used for expression matchers to validate expressions and return proper localized value. We’ll cover it soon.

    +

    You can mix expressions with length variations. Following example shows it:

    +
    base.json
    +---------
    +"car": {
    +    "ie:x=1": {
    +        @100: "One car",
    +        @200: "You've got one car"
    +    },
     
    -

    As the method takes some String as a value and you probably will deal with Int there is alternative method to call:

    -
    localizedExpressionString(key: String, value: Int, defaultValue: String? = nil) -> String
    +    "more": "You've got few cars"
    +}
     

    Expressions

    -

    As mentioned there are few expression types. Every expression type has their own parser and matcher.

    +

    There are few expression types. Every expression type has their own parser and matcher but they work internally so you don’t need to worry about them.

    -

    There are 3 types:

    +

    There are 3 types of expressions:

      -
    • inequality - this type of expression handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on.
    • -
    • inequality extended - this is extended version of inequality with syntax like this: 2<x<10, 4<=x<6.
    • -
    • regex - this types of expression uses regular expression. This is the most powerful ;)
    • +
    • inequality - handles simple inequalities like: x<3, x>10, x=5, x<=3, and so on. Work with integer and float numbers.
    • +
    • inequality extended - extended version of inequality with syntax like this: 2<x<10, 4<=x<6. Work with integer and float numbers.
    • +
    • regex - uses regular expression. This is the most powerful ;)
    -

    Inequality

    +

    Inequality Expressions

    It is composed of several elements:

    @@ -261,13 +310,15 @@

    Example:

    -
    "cars{ie:x=1}" = "1 car";
    -"cars{ie:x=0}" = "no cars";
    -"cars{ie:x>1}" = "%d cars";
    +
    "cars": {
    +    "ie:x=1": "1 car",
    +    "ie:x=0": "no cars",
    +    "ie:x>1": "%d cars"
    +}
     
    -

    Inequality Extended

    +

    Inequality Extended Expressions

    -

    This is a bit extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    +

    This is extended version of inequality expression. It is composed of 2 values, one value marker and two inequality signs.

    • iex: - prefix of inequality extended expression
    • @@ -276,11 +327,13 @@

    Expample:

    -
    "tomatos{iex:2<x<10}" = "%d tomatos is between 2 and 10";
    +
    "tomatos": {
    +    "iex:2<x<10": "%d tomatos is between 2 and 10"
    +}
     
    -

    Regex

    +

    Regex Expressions

    -

    This is the most powerful type of expression and probably will be most used by developers. It takes regular expression ;)

    +

    This is the most powerful type of expression. It takes regular expression ;)

    • exp: - prefix of regex expression
    • @@ -288,108 +341,228 @@

    Example: (police cars in Polish language)

    -
    "police-cars{exp:^1$}" = "1 samochód policyjny";
    -"police-cars{exp:(((?!1).[2-4]{1})$)|(^[2-4]$)}" = "%d samochody policyjne";
    -"police-cars{exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])}" = "%d samochodów policyjnych";
    +
    "police-cars": {
    +    "exp:^1$": "1 samochód policyjny",
    +    "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)": "%d samochody policyjne",
    +    "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])": "%d samochodów policyjnych"
    +}
     

    Powerful stuff, isn’t it? :>

    -

    PS. There is built in solution for Polish language so you can use it with doing just this:

    -
    "police-cars{one}" = "1 samochód policyjny";
    -"police-cars{few}" = "%d samochody policyjne";
    -"police-cars{many}" = "%d samochodów policyjnych";
    +

    PS. There is built-in solution for Polish language so you can use it with doing just this:

    +
    "police-cars": {
    +    "one": "1 samochód policyjny",
    +    "few": "%d samochody policyjne",
    +    "many": "%d samochodów policyjnych"
    +}   
     
    -

    This feature is called Shared Expression and is covered below.

    -

    Shared Expressions

    +

    This is called Shared Built-In Expression and is covered below.

    +

    Shared Expressions

    + +

    Shared expressions are expressions available among all the localization files. They are declared in expressions.json file divided by language and you can use them in localization files.

    The functionality allows developer to observance of DRY principle and to avoid mistakes that exist because of reapeating the code in many places.

    -

    It is possible to create shared expression in your project and use it with no configuration with Swifternalization.

    -

    Getting Started of Shared Expressions

    +

    Normally you declare expression like this:

    +
    ...
    +"ie:x>1": "Localized String"
    +...
    +
    -
      -
    1. Create Expressions.strings file in the same bundle when Localizable.strings file is.
    2. -
    3. Add shortcuts for your expressions and add your expressions ;)
    4. -
    +

    If you want to use the same expression in multiple files there is no necessity to repeat the expression elsewhere. This is even problematic when you decide to improve/change expression to handle another cases you forget about - you would have to change expression in multiple places. Because of that there are Shared Expression. These feature allows you to create expression just in one place and use identifier of it in multiple places where you normally should put this expression.

    -

    Example:

    -
    Localizable.strings (Base)
    --------------------
    -"cars{custom-1}" = "%d car";
    -"cars{custom-2}" = "%d cars";
    +

    What you need to do is to create expressions.json file with following structure:

    +
    {
    +    "base": {
    +        "one": "ie:x>1"
    +    },
     
    +    "pl": {
    +        // ... other than "one" because "one" is available here too.
    +    }
    +}
    +
    -Localizable.strings (Polish) ----------------------------- -"cars{custom-1}" = "%d samochód"; -"cars{custom-2}" = "%d samochody"; -"cars{custom-3}" = "%d samochodów"; +

    Now in pl.json, en.json and so on you have to use it as below:

    +
    ...
    +"one": "Localized String"
    +...
    +
    +

    Before you decide to create your own expression take a look if there is no built-in one with the same name or whether there is such expression but named differently. Maybe you don’t need to do this at all and just use it.

    +

    Built-in expressions

    -Expressions.strings (Base) --------------------------- -"custom-1" = "ie:x=1"; -"custom-2" = "exp:(^[^1])|(^\\d{2,})"; +

    Built-in expressions as name suggest are shared expressions built into framework and available to use with zero configuration. They are separated by country and not all country have its own built-in expressions. For now there are e.g. Base built-in expressions and Polish built-in expressions. Base expressions are available in every country and there are very generic to match all countries pluralization/cardinal numbering logic.

    +

    List of supported built-in shared expressions:

    +
    Base (English fits to this completely)
    +- one - detects if number is equal 1
    +- >one - detects if number is greater than 1
    +- two - detects if number is equal 2
    +- other - detects if number is not 1, so 0, 2, 3 and so on.
     
    -Expressions.strings (Polish)
    ----------------------------
    -"custom-1" = "ie:x=1";
    -"custom-2" = "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)";
    -"custom-3" = "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])";
    +Polish
    +- few - matches (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    +- many - matches 0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
     
    -

    Swifternalization load these Expressions.strings files and analyze them, and replace shortcuts for expressions with full expressions.

    +

    As you can see polish has no one, >one, etc. because it inherits from Base by default.

    +

    Getting Started

    -

    There is some duplication in Base and Polish version of expressions - custom-1. Instead of repeating this in entire language you want to cover you can keep it just in Base version of Expressions.strings file. Expressions that are find in Base and are not in preferred language file will be added to preferred language too to observance of DRY principle.

    +

    This chapter shows you how to start using Swifternalization and how to intergrate it with your code.

    +

    Documentation

    -

    Swifternalization also handles the case of overriding built-in expressions. It gives you just few expressions for now like: one, >one, two, other as base expressions and few and many for Polish. If any of your Expressions.strings version of file will override it Swifternalization will use your version.

    -

    Demo

    +

    Documentation covers 100% of the code, Yay! There are two types of documentation. First covers only public API which is for those who only want to use the framework without looking inside. The second one covers all the API - public, internal and private.

    -

    There is demo project included in the repo. Just switch to proper target and run. It enumerated cars from 1 to 1000 and print them out to the console. Base (English) and Polish languages are supported. You can find there example of using simple primitive no-expression translation and also with experssions.

    -

    Contribution and change or feature requests

    +

    You can find Public API and Full documentation with docset here in docs directory.

    -

    Swifternalization is open sources so everyone may contribute if want to. If you want to develop it just fork the repo, do you work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Built-in expressions

    - -

    As mentioned in previous chapter Swifternalization has some built-in expressions and is ready to extend. If you want to add expressions specific for your country you can do it by creating class which conforms to SharedExpressionProtocol. Methods from protocol returns all expressions for your country. There is already SharedBaseExpression with some basic expressions and SharedPolishExpression with polish expressions for helping ordering numbers.

    - -

    Example of the file ready for pull request should looks like this:

    -
    class SharedPolishExpression: SharedExpressionProtocol {
    -    static func allExpressions() -> [SharedExpression] {
    -        return [
    -            /**
    -            (2-4), (22-24), (32-4), ..., (..>22, ..>23, ..>24)
    -
    -            e.g.
    -            - 22 samochody, 1334 samochody, 53 samochody
    -            - 2 minuty, 4 minuty, 23 minuty
    -            */
    -            SharedExpression(k: "few", e: "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)"),
    -
    -            /**
    -            0, (5-9), (10-21), (25-31), ..., (..0, ..1, ..5-9)
    -
    -            e.g.
    -            - 0 samochodów, 10 samochodów, 26 samochodów, 1147 samochodów
    -            - 5 minut, 18 minut, 117 minut, 1009 minut
    -            */
    -            SharedExpression(k: "many", e: "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"),
    -        ]
    -    }
    -}
    +

    It is also hosted on my blog: +- Public API documentation +- Full API documentation

    + +

    Docsets: +- Public API docset +- Full API docset

    +

    Instalation

    + +

    It works with iOS 8.0 and newer.

    + +

    With CocoaPods:

    +
    pod 'Swifternalization', '~> 1.2'
    +
    + +

    If you are not using CocoaPods just import files from Swifternalization/Swifternalization directory to your project.

    + +

    Swifternalization also supports Carthage.

    +

    Configuration

    + +

    Before you get a first localized string you have to configure Swifternalization by passing to it the bundle where localized json files are placed.

    +
    I18n.configure() // for NSBundle.mainBundle() - Mostly you want to call it this way
    +I18n.configure(bundle) // if files are in another bundle
    +
    +

    Creating file with Shared Expressions

    + +

    Shared Expressions must be placed in expressions.json. Syntax of a file looks like below:

    +
    {
    +    "base": {
    +        "ten": "ie:x=10",
    +        ">20": "ie:x>20",
    +        "custom-pl-few": "exp:(.*(?=1).[0-9]$)|(^[05-9]$)|(.*(?!1).[0156789])"
    +    },
    +
    +    "pl": {
    +        "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)",
    +        "two": "ie:x=2",
    +        "three": "ie:x=3"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "language-1": {
    +        "shared-expression-key-1": "expression-1",
    +        "shared-expression-key-2": "expression-2"
    +    },
    +
    +    "language-2": {
    +        "shared-expression-key-1": "expression-1"
    +    }
    +}
    +
    + +

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    +

    Creating Localizable Files

    + +

    Localizable file contains translations for specific language. The files might look like below:

    +
    {
    +    "welcome-key": "welcome",
    +
    +    "cars": {
    +        "one": "one car",
    +        "ie:x>=2": "%d cars",
    +        "ie:x<=-2": "minus %d cars"
    +    }
    +}
    +
    + +

    Name of a file should be the same like country code. e.g. for English it is en.json, for Polish it is pl.json, for base localization it is base.json, etc.

    + +

    There are few things that you can place in such files. More complex file will look like below:

    +
    {
    +    "welcome": "welcome",
    +
    +    "cars": {
    +        "one": {
    +            "@100": "one car",
    +            "@200": "You have one car",
    +            "@400": "You have got one car"
    +        },
    +
    +        "other": "%d cars"
    +    }
    +}
    +
    + +

    In pseudo-language:

    +
    {
    +    "key": "value",
    +
    +    "key": {
    +        "expression-1": {
    +            "length-variation-1": "value-1",
    +            "length-variation-2": "value-2",
    +            "length-variation-3": "value-3"
    +        },
    +
    +        "expression-2": "value"
    +    }
    +}
    +
    +

    Getting localized string

    + +

    Swifternalization allows you to work with its one class method which exposes all the methods you need to localize an app.

    + +

    These methods have many optional paramters and you can omit them if you want. There are few common parameters:

    + +
      +
    • key - A key of localized string.
    • +
    • fittingWidth - A width of a screen or place where you want to put a localized string. It is integer.
    • +
    • defaultValue - A value that will be returned if there is no localized string for a key passed to the method. If this is not specified then key is returned.
    • +
    • comment - A comment used just by developer to know a context of translation.
    • +
    + +

    First method called localizedString(_:fittingWidth:defaultValue:comment:) allows you to get value for simple key without expression.

    + +

    Examples:

    +
    I18n.localizedString("welcome")
    +I18n.localizedString("welcome", fittingWidth: 200)
    +I18n.localizedString("welcome", defaultValue: "Welcome", comment: "Displayed on app start")
    +
    + +

    Next method localizedString(_:stringValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for string value that match an expression. Actually the string value will contain number inside in most cases or some other string that you would like to match.

    +
    I18n.localizedString("cars", stringValue: "5")
    +// Other cases similar to above example
    +
    + +

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    +
    I18n.localizedString("cars", intValue: 5)
     
    +

    Contribution and change or feature requests

    + +

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    -

    Also this is required to cover all shared expressions for a country with unit tests. You can find examples in the repo for e.g. Polish expressions.

    +

    There is no guide for contributors but if you added new functionality you must write unit tests for it.

    Swift 2

    -

    Swifternalization supports Swift 2 and works on Xcode 7 beta 2. Please check swift2 branch for that.

    -

    Things to do in future release:

    +

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    +

    Things to do in future releases:

    • Add more built-in expressions for another countries.
    • +
    • Add support for float numbers in built in expressions that uses regular expressions.

    LICENSE

    @@ -398,7 +571,7 @@ diff --git a/example/base.json b/example/base.json deleted file mode 100644 index e0b4524..0000000 --- a/example/base.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "welcome": "welcome", - - "cars": { - "one": "1 car", - "ie:x=2": "2 cars", - "more": "%d cars" - }, - - "forgot-password": { - "@100": "Forgot Password? Help.", - "@200": "Forgot Password? Get password Help.", - "@300": "Forgotten Your Password? Get password Help." - }, - - "car-sentence": { - "one": { - "@100": "one car", - "@200": "just one car", - "@300": "you've got just one car" - }, - - "more": { - "@100": "%d cars", - "@300": "you've got %d cars" - } - } -} diff --git a/example/expressions.json b/example/expressions.json deleted file mode 100644 index 05f0b5d..0000000 --- a/example/expressions.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "base": { - "one": "ie:x=1", - "two": "ie:x=2", - "more": "exp:(^[^1])|(^\\d{2,})" - }, - - "pl": { - "few": "exp:(((?!1).[2-4]{1})$)|(^[2-4]$)" - } -} diff --git a/example/pl.json b/example/pl.json deleted file mode 100644 index ebea036..0000000 --- a/example/pl.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "welcome": "witaj", - - "cars": { - "one": "1 samochód", - "ie:x=2": "2 samochody", - "more": "%d samochodów" - }, - - "forgot-password": { - "@100": "Zapomniałeś hasła? Pomoc.", - "@200": "Zapomniałeś hasła? Skorzystaj z Pomocy.", - "@300": "Zapomniałeś swojego hasła? Skorzystaj z pomocy." - }, - - "car-sentence": { - "one": { - "@100": "jeden samochód", - "@200": "tylko jeden samochód", - "@300": "posiadasz tylko jeden samochód" - }, - - "more": { - "@100": "%d samochodów", - "@300": "posiadasz %d samochodów" - } - } -} From abbaf9828d8370d14c0e20f51b44ee246da71679 Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 2 Aug 2015 11:25:23 +0200 Subject: [PATCH 25/27] Fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb8f1bd..023a6e6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Swift library that helps in localizing apps in a different, better, simpler, mor - [x] Full documentation # Table of Contents -- [Introduction](#introdcution) +- [Introduction](#introduction) - [Practical Usage Example](#practical-usage-example) - [Features](#features) - [Pluralization](#pluralization) From 6c96a7ad67ad9542b2d6cd10919b59895999794a Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 2 Aug 2015 11:26:28 +0200 Subject: [PATCH 26/27] Fix link --- README.md | 14 +++++++------- .../Contents/Resources/Documents/index.html | 16 ++++++++-------- docs/framework/docsets/Swifternalization.tgz | Bin 81217 -> 81182 bytes docs/framework/index.html | 16 ++++++++-------- .../Contents/Resources/Documents/index.html | 16 ++++++++-------- .../docsets/Swifternalization Public API.tgz | Bin 52704 -> 52678 bytes docs/public/index.html | 16 ++++++++-------- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 023a6e6..2804784 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ Swift library that helps in localizing apps in a different, better, simpler, mor # Table of Contents - [Introduction](#introduction) - [Practical Usage Example](#practical-usage-example) -- [Features](#features) +- [Features](#features-1) - [Pluralization](#pluralization) - - [Length variation](#length-variation) + - [Length variations](#length-variations) - [Expressions](#expressions) - [Inequality Expressions](#inequality-expressions) - [Inequality Extended Expressions](#inequality-extended-expressions) @@ -37,7 +37,7 @@ Swift library that helps in localizing apps in a different, better, simpler, mor - [Getting localized string](#getting-localized-string) - [Contribution](#contribution) - [Swift 2](#swift-2) -- [Things To Do](#things-to-do) +- [Things To Do](#things-to-do-in-future-releases) - [License](#license) ## Introduction @@ -355,7 +355,7 @@ Docsets: - [Public API docset](http://szulctomasz.com/docs/swifternalization/public/docsets/Swifternalization%20Public%20API.docset.zip) - [Full API docset](http://szulctomasz.com/docs/swifternalization/framework/docsets/Swifternalization.docset.zip) -### Instalation +### Installation It works with iOS 8.0 and newer. With CocoaPods: @@ -406,7 +406,7 @@ In pseudo-language: Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in *base* variant because if expression is in *base* it is also available in every other language too. So, "ten" is available in "pl", but "three" is not available in "base". -### Creating Localizable Files +### Creating file with localization per country Localizable file contains translations for specific language. The files might look like below: @@ -483,7 +483,7 @@ The last method `localizedString(_:intValue:fittingWidth:defaultValue:comment:)` I18n.localizedString("cars", intValue: 5) -## Contribution and change or feature requests +## Contribution Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it. There is no guide for contributors but if you added new functionality you must write unit tests for it. @@ -491,7 +491,7 @@ There is no guide for contributors but if you added new functionality you must w ## Swift 2 Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out *swift2* branch. -## Things to do in future releases: +## Things to do in future releases - Add more built-in expressions for another countries. - Add support for float numbers in built in expressions that uses regular expressions. diff --git a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html index 63e4263..0b63271 100644 --- a/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html +++ b/docs/framework/docsets/Swifternalization.docset/Contents/Resources/Documents/index.html @@ -183,13 +183,13 @@

    Table of Contents

  • Contribution
  • Swift 2
  • -
  • Things To Do
  • +
  • Things To Do
  • License
  • Introduction

    @@ -538,7 +538,7 @@

    Docsets: - Public API docset - Full API docset

    -

    Instalation

    +

    Installation

    It works with iOS 8.0 and newer.

    @@ -587,7 +587,7 @@

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    -

    Creating Localizable Files

    +

    Creating file with localization per country

    Localizable file contains translations for specific language. The files might look like below:

    {
    @@ -663,7 +663,7 @@
     

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    I18n.localizedString("cars", intValue: 5)
     
    -

    Contribution and change or feature requests

    +

    Contribution

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    @@ -671,7 +671,7 @@

    Swift 2

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    -

    Things to do in future releases:

    +

    Things to do in future releases

    • Add more built-in expressions for another countries.
    • diff --git a/docs/framework/docsets/Swifternalization.tgz b/docs/framework/docsets/Swifternalization.tgz index b451cd1017a0ae6c9f300e1166aa0bee9ec3fe5e..9bdd277bba71811e4748a76a7ce554159183d262 100644 GIT binary patch delta 67918 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 delta 67979 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

    Introduction

    @@ -538,7 +538,7 @@

    Docsets: - Public API docset - Full API docset

    -

    Instalation

    +

    Installation

    It works with iOS 8.0 and newer.

    @@ -587,7 +587,7 @@

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    -

    Creating Localizable Files

    +

    Creating file with localization per country

    Localizable file contains translations for specific language. The files might look like below:

    {
    @@ -663,7 +663,7 @@
     

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    I18n.localizedString("cars", intValue: 5)
     
    -

    Contribution and change or feature requests

    +

    Contribution

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    @@ -671,7 +671,7 @@

    Swift 2

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    -

    Things to do in future releases:

    +

    Things to do in future releases

    Introduction

    @@ -425,7 +425,7 @@

    Docsets: - Public API docset - Full API docset

    -

    Instalation

    +

    Installation

    It works with iOS 8.0 and newer.

    @@ -474,7 +474,7 @@

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    -

    Creating Localizable Files

    +

    Creating file with localization per country

    Localizable file contains translations for specific language. The files might look like below:

    {
    @@ -550,7 +550,7 @@
     

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    I18n.localizedString("cars", intValue: 5)
     
    -

    Contribution and change or feature requests

    +

    Contribution

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    @@ -558,7 +558,7 @@

    Swift 2

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    -

    Things to do in future releases:

    +

    Things to do in future releases

    • Add more built-in expressions for another countries.
    • diff --git a/docs/public/docsets/Swifternalization Public API.tgz b/docs/public/docsets/Swifternalization Public API.tgz index 1cefaba0dc75e7ac997cd74043112b1df481d723..ed641d0ef4bcaf482092f97d8fd17bdc935c8ea3 100644 GIT binary patch delta 52139 zcmZshWlWw8(4}#AcQ5V^MT-?H#frPTyWF@G=fR!g?q1xT;_mM5@a+3-HrZ^lKPQt+ za*~tG9RMoDH;Sq0V8(5Ym#nwdi+5JpCS*9{3-a<1ZGyMuo~-iZ4^)VrhR>3sd_ zedWjMw42x(k73r5&>RuL|K#$x($erG@>OPs0@WOw4he9a1=w>e*^caSyjO!$l`{R$ zzlg8CZj%BR?r)z%VbZ{r$FYOY1?$^5plxAiviJGg*);Xhq3u)ES-1Hjw&uAM#&(xU zSH=}wU3lXuD)MS%B{ITGkfn-F9{&D@f4kN;@G2TjXT>fHYJm-@r~$9Ou6AZV0OZ|F zRp7Z%3i}^G0lRF?Jlw=^XNN~>HDFQYQd9fsv2@x0+5!}E9QgwWXWa|JWHPHp?DB>e zcAay!XWHyH#-dp$b{}U;N<_J*8y7k{O)_j`U7wc+CNU`nJ}(bP(>WsZ_tF&)hJrCc zzs~-h$*hiBD0%4H{FJG9cy6mcDd5i5u3G%}Pk9dL)m3C8GZ`v;aK=*#*7CkZVYCqo zEY5uufeP3Y<{n#N_htu?v<} zrg{T9%HnB4&WZo1SL0rSPHf2JF2Ft+E%RSI({BdQs-Uw}u^#Jf{1&XX2R! z%9=lWf!FPRbkXOgVsR|vy>9jTPt^A(kFmG#)Y8vI_!#ibKT+cy176YR128gp{oUT5 zuRC*V^6JpzuH$9n|CUb{;J5y*_xD`?hnsNVHR8TxYJhmKfyU9C3{Y+Ryy(}6Bme7P z>G$z|a@FNuDeU)oeA7ZSUexJX%^=Y9IzR|toRXukvsDI)jlS>C1z>u`@_to+cGi4QadjNmD3{m^ILzT8d`~~aZ4Ywz#b9M*!oY-Yq_9`ey2R~oz z{MO|vMTNYU+YIEYUmKN=XFxUXs}4DTH&dpJOt&owjZiwz=saNWADG3PvF4H>l!eJ_CLe? zEa`q(fzLMhIO&O^3wGqsg>_4{9rO>m1ZdZ`Ke~o9yj!t>SKsjT(oV0Bq1WsGls^GT zIqo9aLOptV>zm8wQWqX|5M;OD57GB0bpM+MgHND)=5wbT?0g~G^)7WqZtyYx{p|m7 z2k?K~(z#Ldh#3JU&!Gl`KWapvZovIWFW{p^8H!KfnfDev{mBg$hy;^=s*i%nKdf$C zu)s`VAKhK=U=QH68wlG42DiZzaPQ63?`xtT4x$qO4G<-N0XtO2iM@l-9ln6Oi@?JL z%b9U(XV|`O;9MUY4~O^eEnxgAd&wZ+O~6jq@8fDqr~3x%&(?jW4SeI4U*+EdSIl*7 zwPhNBA0CHRW57#3FJ`eDtpRH-&DU{w0>>Yu3Tbx_^p3ul7nxyJIJS2@ON|=66lt*7 zY|GYlI4Mj>bZ@K75w^mzN`EK_Va3ZqAL&&ky-usQiIPjw4Tgqipj%i{VC~Lz=J}pi zNp7TDOWr)E>Pnm(?XdT}Pymf?Qn<8dIKj@LNwfNkRO#A6TZ^{wV)Od)(bSAitn{4e z*#Y5vertb&k#eaL|IfFb{6iy10>-u9F$L*d*>GHX`+>T1cBMIr-w!vca?i@D^Cbj$ zD;B<9Jc`Btf#@prB=G%i#r4@C4e?MYyXPAJh zYKu0=7@mra$c^(jiLon@MB5RcUl+$8sefEmszF=VX04K@{Bwrqq zkhmR$5iMkYo2&>fhh0tv!BAsWaI38XO(76K?t+dGC;@v)9}$l*&0&*aVr9zn zJ2C{WB%U0Yj1d^GjQ(PGtZ6H2nJJ@yV3$D65#{t%7LqH;7l^Aldg8}_^dVsgq2BsJ z{ntPLArdq3J`2p7TgUB~q(xh<8Vudj!{^R&kGZ`G1sID_LcmhTy3^8;AT7g82f2!5 zCgJ7Wx=hfznE-fO^--uPA`U4GvJsQw*B$LeQ&^*f9W<=WwayC0G zpU_vwskSUX1?3^qm!s_Rv&@h#J22@B39rmAu4bc#=KV=xIc9*fk+1w^gV1VmsZz>Y zja^2yUB(4HVSDDF+r#w}KJt_zRh88+Y_AUVo_F!i9w)TUs(1lutBXa9J(3C2(BI== z{a}G+SP8t$dx#x2mU_fImc%?}C_{)7^x{LtOHY)v(WR0@MTGxLlRd?E&v3Q^f7XCs zw!jbFmf;tN+6}>ed^|^4k9h*t1%yNRyw^xviGPK&2YqVIC;z7D>1vIP@t*`;w=Nh9 z!hS-|K%LlcN`vpFUPaZqbZ3Tij)6748-uOsDs;m#i*6r_ zzS*DjkxWXC6Nk(hO#?HM?lN!sZJ$I^ee>~!$_W`p|9K`uus<&l1L3bS!>MQR4r>^I zmPKFH^OBpW=xXX6LP zU1x~z>W|aHyO0*jp54dcqR*{EY@bZnrnV}9h8e5qZ(HcJywb4K6#ze~V_{gXjRizY zoL~ftL#j^cYOd!%Fm(w{zPS1CK4lD`mnKw2{rlJe!`r^4KR<;+Mn(p4;m?Y)FFT$@ zJJ8m*jaI%m6L7Viu_Wb=f4p(S0sGrR>78;<@#jo5b0~0)Rx-B|sBjONu zUy&1Saz(Xn9kCoNVi6JK13rd6u){h%qxrkynT?R0tp&&(L8 zf_jIh?c|uS3mxT^218B-nN>>n?4)+N(68r+4^5QuY%IQY1%9ixHg0BruGcKCS&*Kf zIo9}l;mTsN`}yxdDshP}9rv2b-F$}1LBRqje8Txe7QK#S0$l1cxon;NuFr9)MSuu; zK_ldKBlj@hjjgEo2vUCjF-#o5l5-`*q}fCC!f8NF|6zVK=z=Eq6K+|d6pFWtxW8nD z+gU{{sE&MJUL<;WCQg}Mwy$68%^RzD^^8Jx=go*6`W!UIsJHhPIg9vVP9djU zvN;gUN3y0TTA8A=?5}7+1%dNzfcaYujWT&S$QAF+C3ZTO(IHv`$7BkSQ@Gh?43V;& zz?K~AtqayM*z#wfb+-aB zm^tl~mn%c)ZcX)`CU%(<7bP&gaj%JmT9;=`ElAJW>Ut5Bn)L@8M&v7nsoJ%Urw)Miv6iBMD3Tv0G4c$n ztM$Go2$9CrF~t7S@q%g8+SU_6^kS^~!_;2;()z;`ZES;I^Wi07Fm|~fe-b2oBT?J; ziE%ZGIsR6C3uR7T#gJw2LtiBKW>1%^-ff^YCJTnY2f{lvFUaD1;1L90c%ucDCqgvC zzYCh!fYRH38>bMUI$ehholbLBkEX^EQlQq{5r zn9b1E89u3Suf`W+bIY4pkpE`FM%o)_!&Gods7l6pL{(k}__F%+zlJc|SO|QoDI=Q8^kb#u^dC=t4fN_~A{;el85ju$j#91}#4zI}|#A}A zT18+QLUT|3x3~2r9)X-{Sn8q*21GiDA}r608eLW|l=Sq0?BxKle@slyX!YsOAwjGp=-~!};viBANkx zy%a)#y>(oOWo|E-OlV|^NuM(GSph226|$Y0Wjq=jCn{tj5=1xKKmK!ih(A3O(;>D# ztP>V#zEoVrvlG#k^%(C0?y-cwD636Jqc#6Z_u{&XRfYArGh{e-}A4F^R~{YesYpf`vR# z>Fb*pH4DNYnuY|0*Fz)Qqm+#6w{?y-wF_L7J~S=&3jbhc4A7H>kzJh2Pwayx7`^rZ z1=TF-Kf+lk6mJ|B^kEg|5$Qcm* z#uU1By}=BZt{)+sqj6G9dh*(OC5YPWcxh)e#{ZNQLW}3J&d`5Gw8=n1H6NF*o21Bs- z%jD!L1&zP7xAu4SO2(!#Mq-zj{BYCu38L8uHRVWOxuH*}M#4WXKM>A+B~e$dq$CfL zU(&)h?_Tn@Ni}{WtdxcEkT&`(_BmJDlYdt55@sjgzkIdCngr710wEUyIgR4v zS!Z-CF>R3Gk;40q1#VEa3*WCeZ4;UATi zR*pZIqJ@!_54puK+~Fv}faC}`n&H!SwRFHJ9uPVBa!Olwdz63u6w}QnxjD}M#4?y_ zj6-f!z8Bz#5`mGi=f^N4rH8d3uLIUWqLR6h_(LZDwMbQY{qj1|$nIOZ5F#XP>IlYZ z71tde$N`54VWmoEJ$z^J0F;1(3k=Swh;0@v;tc5hOABgX%W9z7WaUZjUp;76%R-&g zmRwcwQRMucq?Ygf5}iDXug!*agb_Y~C?SD6#SNtuFGsfE=mh)6@;eV|M9z&em&r`` z-Ve42)r6JtvU$#hbjL~~Q!`n&#Bd-0O&;4;;2KvgdW|=HHZKq*6jC2135b$wqqayy z8m7fLj+jXz(NlXMEkB4SP=45ln)^F;$(9L$6MVPN7g{!Yt)N|Er}op5LQsJ{55^q* zTU?=`Y$y-nT;2T5+MM*!oV29OwXBJ;wGtidJ-k;m(+;=oLpFR+2x3PIc~Zb@Rkv=!Ya2ArQWdvWmCDFa)u(s!{MI6 zobh|ct;JR<&-e7{s*d_``SwZg4rogG^q(~> zF(BTNcwG1yAV$CFd;>WBADR=#d8Dn9DRRIW;BO9%PRC8rce%p~n_Bj|YD|Vk=D9bt zTtltyuA27ZngQ?nbr6`A3CyMOeI#N20n67B={1ll$W%$D0yhjCxb^;GT62cGT&@vG zqnSG#gP22o<0u;`Qy3+m*Ng#~?TE%mQghguA-E7TKVvnj1AtjXClwdnPw2i#D%x>5 zyns6XMw12zMYAo4Bd*!v#xB@?NLk;vOzRj%b)4k$HBVuhzhlpsUeJ8%$T=K-o}a;- zO!Ya5e69mVre&0Q=zoE9pRdG|w<6hji%drhvDiaASj+wa((pk@yAWG?&Ljg3Mp?YY zKMBy8IF0s}Y=8@1Born3;KxLZV`~Gre~kv~LeernfN%kpQW zB3_!tjZYt#=lp=`AT}i-{|?FTSxHOJ!;S(MBDe#@6e+4(S-{sYkWv*svqI{gIF*z3u7_LaHt$IRdYtyzeMIGm}^ z)Q0xS0~k&k7uX)9SxJ$W|G-SaMPt&qb8_H?Ay~2R%bY^Ti>Nx`6dw!U`Ynd&6(9fg z)EJyAH{#6f#Lm+iM*7IBv{_a@wX^5;C;*bwXA}<<=MejrUA^n^~{VrmdCBb89{Ep45 z{__Z8<}h(^;x#eXPGGKlY&3F)B#0h2PX)yE3T?$WnRVK_!m^DQ-j^usdm zW9Is|oV;xY6dGvAdd~h@NqIv^%267;EnXhrS@e?pQzj52|GTOo*{D}yO@)c|S#h(G z^;5m7zw#*Mn3=)~oF3jD^;gn*(%lIoE3Bel@tHY#k*CKYC*w5ISM)HUezbU&SX`@l zG#DZ~a;s6t(rE02aGn5h{-4e$mj0-4s#Gam#vFQw3ZCZGNLl&uUpd_K4Sv_w2`S^Y0a~{c@_|J^Q2fOrn zOkt)#(n2=Kuc5xI(A-A7Q@_Li*fBH!5Ms%1btrpQ4_48|`yoKRgqN3?jV_{`re2JY z`9QD#)=LQaKY_hv;*S>asZG+8se>~WJJn&u(%FXgu;t?-k;hv}HrF1ui8456Q-otc zfK(mxjjsMTg@)AKk4_sC(u*Mrzh7OQz2qD*rw$&xmmA>w2LHNQ@S{OoeU2AUU~P3@ z1QAA)jSfc(Ave??W~I?U5V|LHHcpD!g2or>D476f5JCT&_AHA}S_=u$bmW{WKw}8k zoHkNJ9Zs!A(cQJ(S1$>A{(!3{r+_tEZbXw|t)Vk4(XPjlkizxyrk}(kr#$xSM6^<# z2o?HKR$r~lC^>GE8n^OEEsYW&%@`|Z@j@M;pgvfsRG1!NEt4%pC6te(sVQilkbr+7 zi6B`9eJ`OVTF=D}>B^tUIG?{?lvP%0cSy}YMMuW9Kv|=RbAo+5PFV@Z$Qj5Ir58hlMNGFfSNr?!&e&w$;KelEKB|fvW2JJhlU9OqpmYa6@@*?E54U`lg=9)d z&dR#gp-halN9^#go;l`tNOpQz7wxh1J>w-2!|k8>bt-6o*bnXUeTb^av-M z8bR7GbMndv@-+#3OPQhNjEd%v2%s)JEgh3P66!lFC z&SxvaMLK`x+QgXF6rOE4JLJ4ULMiXuh0Uy&Gc}0umonjyQ@KMffK*uIFFGrwndnS3 zWIdx?Rjg6;EoL#1h{>j-6#nQDiecW(l}amktUlXkfiUoJYQijZDtFlG9yDdl4qd)r ztyjdr2xWObX4YyogDamO_RbhO6&mHDv)j1;!Hq|&!AdpynJiEBr~P$c_@lbQV#H-f}`8VZs?@8-jZB&(6?to5DX*m^8#+%uA>#n)bBLlDG%*x_W-d4chRQ zUUSC!ntGR!0N0`t?&Lg2oEVC+|4yX|zzwERStE?|q^z0tYGfKawXLwI8YW8^&iSZrkbl z!8QcgRb7kpFEcv^VushVw!7?$i`UsXiwARhS%_HW^}yH&P;*20YtD*qQBdth+N85U zZFy)I?K4<_DSkhgrlAWNN(HUQc{`sfuQTYy=`jecUImFpgwz}>t$ zl_^Cg``9=|DK3MjD(B*)ImUk_b#kGw{f&($;ff=UZXL-AY~_F>gskNa<%Cf^9=wix7`U6F6f(WDWPidKE$083)iWXuUM1#-)86iW(>|ZZ2xS`K{sa zh!s3`Rn<^_){;#nOGDn}w0+FTtTD(^+a6rprJ+Cb4OP@Y0RXeN{%Q zT{2c(nQcjaxrxem+3fOe+nj+?^fLQ!rX~A#PIo0j)*Ov1&I0zTnv_OD*BUuIMBi!T zhXt-*+K0Lt`PvQ_UUp*eROanxl_Q@- zoy?`)fTbj)^AeU?rp;_~O1>Bw)F>eb-QWK*utS1X8-rEfi@`kg#K`G=hFSJLA<9M3 zr3GnyMpbd~KnXD)1QpU@SIe+`kz*|L>Vxs}Usn_vPI`I3mp6d3Zu~NhwqJne`|jou zt@h7`VZkAZXC!tO4RvHHDR+NL4Nfuvx)vn2`YzSU9kCM@>7Tpa+9_ry=@k*P4{!jo z%hoVCxOU&U>X#I3$3VhwB6(+_l78qe-=myJ?j}-mIw|Rb`CWBpX0V2OXL0Yow|b3x zNoQqWQTqb2k1W;rtoN3_%-_FLEmsxr?1XW|bESddho9islw(X;kr5`;f!$HfIs?upI*Z3hgqZeP-|Q^ zwwE2_oN#h~c&*O!F@348ePExIR0xrjKc=$jF-fGR?%;(T-uE{(cT_7!wQ`^fqg85z z8TJla8P%mC#kPfsT09XvO~ZAD2rqCJU#Lqp!L5ROgsm{2Ag$n8SavZ`_zmte870W2 z8qgHpj2&|w_xuqikl+~k*HuV$WZK)MRu3zAd^Xye^}(EIV)Gh1>4{R7#qP~oImJxq z9~25Bq&Qc}APvxM2S-uEbq&x`eYeOKO_p@Sk<=GL!#L9=pV~>*Pp8S=Qm>zRAH{rZ zTHQcEZ?`Gg_@=2dHL2g(&_}p8e8Q}KVnF8-7#M+un>fA9Tj7bOkMUYVE^K?Fi^xAp5|ZmUTKVfz3}heA0=@t_uM z1Ji)2_0D|`g%?hOYOqP5YunMKQVfg(#`c?JKzU`fhf6}ufN7pbwkA#;>Ks( z+EIt7T(0`?O-o>J)Irx>ernKZCw|5=Xf8EZ8{ef$=!+M(KG(O-YP2qFkh~sQ2Y5Tsu z^_ik{IOSTn6p57x9Pp;Pgi2lkHkkbMqix%RCgw%#zf9tIoBjg)Q0q?wO?;d~W(;F6 zETo|$rU{8etI$YIp&XS+pkirHlpZED%Kg9 zF1P$ST6044!KcbK(iSQg$MVfo%=55lIq*)qPam~Kfn=y@O|VOl&dI-e*q|6E3$c(I z=lyh34HL_Sk8**u#MH)0)28xP*~@~^774lL?KI|mTppd#xZHGcXLj7KhIw6%<~d>} z{4v>V?Wm_QPDzcs4rn`v1|4HG4br0lJh2R2Z z#@f6tuzmWdT9jYM=5ZFS_tyNRo_ zCO8c)&ILNo8GSOO@(c^aR%!g4F|K}QFi-sb<=M3BDuq!wF;AF)s(%**!D76xzcy=1 znaTHChsHvmzDDJ*yU)AsL8uOK>Xr-w2<=#KHnN`zr) zQY^KL8gsoWca}rehZcK`qH7J+SH%<6fsOuROKadOGTNbd;+SBhVjd}+&vfsF^vQk@ zISeMVWnOz)s4S<&OyIv+FNA;dJJ-p0P3h5MXh*Fz9N{DeLVHV8&d|TegJBF^r~ZOO zixd1vJF*;Omk9P2#AT~H`}Fo57OVZ5q&in(GRK!RGdgeoj;Y7Rzp}t$x5kOvkX+cS z`p5zvPS%_B*M`&E43yIPy@@tL)f*^p>Lm24=-sk=eE!k)Hs{vUe7n}uHn{;&tJoTm z&g8BTWMg@eD8_v-9nTPX;ZWzl1d}nLzXU-bh_Zz~_0Cdp^BqnOM)og;`sfL)KPG;6 zl5G$qBI*9PUp-Qt@Ax=B=^viG;IkGc1-1aeA9Wq{z+x|j8^xe_fsDvT)@e2H!3;r+wN4YQCSMz*b_P{Q^zWsk5zT}EixBe$% z-xjrqyp<_JJVb>ChO1JD-{=tb?jglkNug^2$D0}yQ2G+W)1hVL_=-*ql=MboisPu9 zwvX)DAKulA$B!6PAm(%PYG&(kRqrts+&Hy5H1zS{W0U>n(e$|5=|fCRZb_Va4E_o} z9Ep|C;J0L(gkOORb~)YIiwD5p{IC9g)(qN_zsq(1<~0<#-019Z{SPzg^bzg;Jj?E| zc@mjfy)uFN99se^9*+n6m$o{6D>;(tLV*z|`0M$Qh#@>+Rw&kSmK}D#pt7yF6KTYB z0=Ze6Ir%`cab!j>F(-gV6e&oJE?hxJ#whRLOQcxl^$5E&NThkQ9*Mzuzk-ljG;B!~ zig~T&=p^sv#4cxmHGDBK-Qp}@K|n+TKq3|NExce9*gOEQ^t!Y#ATG1hr~g&bJKe~53F)AFxL zrZ2nUhka9DKF9T)sfUfWVQ_^1nO5*ML2S6~e-|nb2~P{+^+oWqEB424_ztvMIi4R(YLz!-Gj4K}yYbclxF6c8CLK)7pfG+ePDuCd7 zSFfogxo8yYWD}T)q`u4J9dvUZ7m3^V3UXN{nA&~e9=-Eb)*SoS6M*-kYdl0|{L4#` z2^d!#t2ZYu1D(@_-erBcFj-WYa%N)`QcSHhD+?#;4(0>HN(CMWRP(D#VU<=O+;%Ey z)$sx3Em##U-Q3C^%E$$G;~kq`Q}gpxc&@*?9K+P3p*!)2Ne0xfSt8kgV^9a=bmE~S z$z;E~_WOuHqjS)k9sSVnY{r3_FR?JPwO@W6Jr2al zPt{>E)`aYqEVuVr88+lfI8%=V`U?pit4k&!?CWqfh3P&unKN$X%Ol zdpMHkw@;iRVbjwVzQ|@HZNE}EC-iSf=Tgo_9XPO579t#rQfK_x@bmES@mF={_+vI8 zwdXpOO*(-3zu^){zV-hNmppv@2bcUB5jS3^$cddR2rB=h+xSgh-|9!>)!7;|1k4hb zRgvjI{a`ss*)k?>WL=jX8;g3Kw|cRv{IKWcx10v(a*PYugR#bPg(^m*Ez8+`1bc3) zr99cU@%PS#PYaRJ!a;NItY3{zK;UOdC0OTby|cx=0PIEHeKrF|A|0vcWt{=LU#2d0 zcltYgn15^yqtj4{idb{L+uD@0tYmC8HwI#V>0POE%v(qpg?NlTk~-9@SXs3bs-wc6 zT!YhtqrebX6%sOk!s9{g1|d%~;>^_st;HMIW%d)fLQb#S*SU+wqJ;kdh^8}otft6N z>R9Vx%35L*P_06T7PO01@Oc%7D6lJ&>^*p!xXI9{4{h!rDl7=>91CBJb|>40Paqt& zzvp%8N7HWT>f@Jex-F`b{Hprn7Hm-Bcs#*f7+kZ*R)?$eR_MQW!ja*rqk~H- zbB0(9*Q;C6^6+UFyb+cG;My1W;6RA#F9|Qtya}4|ceud;F9@>Lv-Ij{Sc(M6bqT^f zG$aL~5N6@9X{i094Tb>)3z-VMTp4Qe3zpM_%ng1f8)IQVf~A=RPucy=NCT|NZzz=F z%{n*mM(0EEgCTV!I~zEJ7p1gOT*Npe?8=FG`XAO-Fd&@mbgCeLab;>cbJQIUjU4&9MSaZ!%Jf^wn9a_d71Z zHof5T)xUpYTyDUtAOCT5+6kXEH7yR;;J4?d%lXF6t({k^@N9GV&l;A-c8k21y|75* ze!YZGm@CmQ1RPC333d%4;mg=?gHaSQv!#c2i!~N9f3Z#0XpyZsiw={-6Swg`!^uEj$!oGj|Hin576RFG^LJ77Pt1{ zygkopryj6n^*+NV_RfV~PVACDY_JzNC_#sKI!6UzS}TLL^EShTEJ=KRq)5Zx!ocIIR#&E-tW-={ zlcoZ5O{>mb$RGF37ms7VJ1*RO8hiA+9UfjaKL!j0<=PCRq^#^Pqxp3;p}8QrO?#!o zM9n#i%7Xd41HM?-fBm#wb$Zvb0zDI8A8qnMyFg^>|1hoDFkm*6i91KL^?T7m@tdx8 zEY5g{@V#Qay?^xCl5nz{imfhibYy*wk@Z9+o6qL)E8gprBf;4hfWI+ zGBzFmD&@UGH4ChP3J6>2TTlv|f#@LC$x=J6c(8n6q9f(8c=poei!Y%gPxiKX!ZeiC z!*ll` zRhnKk*uEV&Uh~^-?$1OErH%EQxJ8kVYC|c0>DXibg3n^g+r-&{V7Ua{eul+-wL9}5*m(s zCeI9vQe!Of^;j@!%x48_e9ag;8WtKGtXtiACy%HJ!`@RQf_@5dc#7b)BBzKEMHSva zj>9h!8Va|4`M?+TTqaT>k7Vcy8+@29=pv`QgYP8_KLSr23D2Bqml-`x2hnViYU$V`oeO*{&h!5TuJk0RO%0UWPQhy;(^T4# z#q=S-bzgu`xp`n+T7{QMKr-9{M5<6P0gx}Du#)&MUBQ^)^xhK^Tc>FO4{=PO3~mKh z8M7`g+?RBjA4p6?!aS89T<0RPEU!n=vI{v}OQN)c*2acJcKnRhok(GEaU~!e9u}Y> zsN)vtUA$$nPOw$#3p8X$vyP1pp**Y<|II>roSc2+h8>O!5h+vy*SUgmYFerS6}ybN z2a!5|y(a_Yi$w40M`%_I^sMil1`cWtlQ*xO4jL$=; zGzi+_!3Vt8E{n(GzeGCOqbap#66W}tpY7}8J|h=P1;En@qdG_O&W{oi zPG#E5$o){*!Ly4V9zL(98WpeE?Om_F>U?>OpPLYg%dlKOQl2Ih@E`=OEj#ro~ZI&zEfkZ*9v&|NgpcWbpoiCn9# zG42IN;|`IMeu*kIx2~;Z6p897}OPw!dGG zmrNt+HJrli7yi@An=QSgE~6xUB~3jNEL~tAI+4 z-dZ_^Er}7%cR2mb;DC$BaN?7XnF&C|ux2ps=J1*{_;r zMz;$sU`V)0sZ9S2ae85Lu7@oY$L!WzZ##z3%U;H)O(6H;{%^DsFYlM}b4Yc;uONky zo}v}l*Qrw?h)4?<5!ya~Q!;939ghyya)_XGehnRF-LshhuiO9#RAFdVOwVY<`yLtP z5gI#q3&sPl5Sj#i#$hJ%kH0D#+i^kN$iPN?!JAC)cl zF29x;0?k2oEVODgi=XJ2am;!$7)EMs6>{Q499wv|j*I&(Qn=x$=eVus_+Lh4CBJ#N zFnu|jMg43*&_N9fcnET7e7>h$ zM-_Nmv+Y6>;s`pq2Cw--=ps^o|9gnXYmP~32`IXulNE^RQ)+tThrXGi8>62DJhmG8 zPMXs=ay1GcAT$-~)sB@gY5R<_Tjj^3+>Q2d9bGvS7{5(MK#pULJ_r)@8hvPHHi&** z{&Vs|VA2!^ZMGef$}sNs@j~o=a596Wt$eJ4?+UsBEvQWaeM4b3om-0?9?jTo10CJX zge$XCds4hpTMLHb|nQt& zf>Ghvd+b)(XZtL~4BAl9OlDnSXXGlGSpI4ugsKlNfKi(WQ_p;+6yW`HKFKr46KVwI zLYxdG3<9q9Ho!RiL!^@QTcBRW+cSJ_Ak`k&G^%I^i~?W+h8xKnCNV&5Zb0g z{oWRWDt=SUPG#DhqM(VZz_NnTKA)k$yq z_UhzruIcvU;{c&1UyIii7kg~TDTD+$rK=(Kpg7bx;HEm`Z&VGruaU-8Nf7@RTgEZu zq~Vc`B9fLFL5eKd6c#bDQBiNFE_43(}xgKbVWd zeRc*%rsn!XBMn09w}iD5UOh|NWUxR#2l=+B0`myFY{He4R;m7Sq1SzHBhD&+e!R0j z&X0?=05(ksWkq_W6P+l(fuC^q>DRK6bBjK5dRr2`U6Q0sOz^huZfcs37YZ7YKzqNQ zil+J^eS3m>lV(IYXO-m=m6#rT(M)1Hb2WZ-DT6!|JDUR0n!9*HjCm!fPAe(Iu}=KE z5s~R>8k)2lB{>l{@k z-?6?-9+Jw_Iiu!%r+pTux<#&40K{zfWTT|kg^<=i*gqI^@v(<)&~Xj+%Cls53Y2M6 z^s%cJOd#AsNX8uKX6=Lb$+>*4zVN`P6RkzZRhge1USZ&&okI)U{MNq+KrTSuhc&05 z(%4Tg^U~#@=6D^YtiwFt80SYE^;}a>&Mh!CNu?s(Kw6in_ouye&8E_^0P-#O4~uvh z3YQBzDi&<6*~&wF2Z@S*J(Pdi zSXkt7ZIM1|C0>n2nf&ceA1{;yzb7azKYs~9jpEc}HfLhu3IlYZzr)NDeVWI&$JkX! za)?SKj~XinI$|370c9N*6d9u;rmq&GR!(0!oxD>fBW->xhd{s}jx}u*w_$pgIg7BR z;Tt`nE##$XmF~Xx!90q^s|&05?n2lZ1YrA4n9ELD;W-m+1WZ9$tM+amC_d+{hjGLnv;Re3CM|Hy`#B|Sy|tWGGFiR zgan~lK=QCC8n`VdP0>--qJ1sZWT@c7Ny%>J*v1MV#Q^@^|0Ag?<_N28b8um915t?v z{Xrr`n_nAAGyL}9(jN^YfzGMy5P%~5FJ=>>NxD^1atuB!{}`XtK2!0O2HP>ahoZ1m za;+G`jhh`*1N;-|?qly?bc?))!NLCahcuE3mZEifITx){<=e_pYXAH_z_v-b*M9GJiO9qT52e zBbVlRq<(IQMQIEgCs-_iEkUI#IuZnhL4ubko)m-K43H~-kvl@OTXWY^NxIk{WCe{; z#S%-cHbCp&kbrmlE&Zy!Tvm5N@+?np~W*{UQo4jYS=t%W|$oTYTq_#4*1y**3rd{XwiVWnA6Trx#g{_ z{GMe;B{jhCuW@4N8aA#5gdd8vEY!{|7J-CEQPpaPDqGbXx`irtfTA*55gta| z-oPd-Li|_;-oUl%X#b*qZ`?%>Ae~$HP*22sB4P9SoOH{7;8Clo5`hBP*pra;BWKiT z5Af)rt1FaEufX;PdC_8qhQrIwxXi?^g)1wy5mM&^X3iiuhN*EHNhu(*qu#WSa_6WU zE#G1@vippRtZS{a8QW8srAb8ApAIw`udL)oawfMpG!omrVfR2G>efLMXP%z>cnp&m zr~CFnZw?55ua1An6>HDl2QHWlJtp=ZA~4%NT3OixN??w2AJ+P2gzV~pViePx^yO$* zS0OQi|HG)Eg|R)c+Xkvurt!2+b{_eGuB@!Cjv+hAfT!3CyLSZ3HtugS2LZ`@B;h_QW-ijBvi(J-oyR%J4+PPR zG^-lgzL2T|v)4ylRW$BZqS>I%3Y_4QDYX1 zKf(vcCBql;geI+jHTx;f1jx4w1VjFMab^(7_@PQAuY5_v7Z>{vd?dH%2PtGKTr+r* zI3PMR#Zz_PR2wGsO;$kDVP+NbK?LZ3qnfMoQgS)DjtXGfx>x1F%KA{{J1eP3lUgVD zToweoYHWYtn^n#!I`iTLR%QOKA~Q4mdBOWs0gd5vO3H;6E`*fZho`7LpYWW6y1LFi&t$%#n$ICOvfg z^HyEUqnsdZmunLaFP`1rbjcskUNfBHIkW@iLHa(gc;|xRQE4bSpFLalb+gexMrTu0 zpqRe#H^u7xZs1b~AqVk_W&ARU@dXO17n=tEDN-;Iy)8Lzy@zj)Q+Q9SeU zIUM8N%&?=3jQeu?cI4I-|LD_yWbTebR27*Rmn$vjAG7?zu55~Coo7>5iB=*EGm@;w zIkt<0>Zyy|lgol&g+BxZ^|EvW%zV4-AB2RCe&5oksoS>prQUNBm=|(x1VO-WN_CcC zWQ@~#4Ftq`VDB)4?U6wmk$}vmN4%NJlzQ1(4AJpTOr%kR=Dr2aQMR~P?7=Hwc1Xad zdVt?9?#0mL2O7X)E@>SPpFHl@hnFY_=0nyKf=ZN+1_Y49a>W%j15M?1$aCAgs(E>*P%;ngg3xFJ*wWeB z%F0N}wj)%FDtc{{b?p(V6R`@ELiSAhR_=>p!OpzseL7!)= zAng_^7qb>qw*phbN(;a3&0yQ#^Z{g;C_oWY6m7h$XYQ)X98niaI)E2OP-U2b;-1Z% zmR%XJg`A6-q=UG^s}HCigN!}-S&6wvCIie-LWzhXyKty1E?V%XU6i+l@649mhj&6=Jy9XGLH6r2ez()U< z+5p{grxwiA)Et9sjMbYn+v@_T)dyY2ia{zjy1l+Nv<}F`8@Tg2y3|rmSoS>|3eU_5 zywvwhoLEfQxA*dZu#d811UPD+W^~q@z!Qp(2mC2A$WMWP9seNj%^t7;sB~Z2jO6Q^ zvHJGd8XUkIRJ0ior^AfD_R!J%g5vc(I}qpR12ZzLF@6WJ4_(ZAd)Un({=q)7Z91b- zN+ObOEn9gY2U`hBS%VsR1zjrZ^GmkrEyeIKuA` z6(%wX2_gW0G};2Rak{GQVHs;V!V>^9T9WCvGC~7MzC1TTnyLc9rRVz>BPijab`8CU z0BrDF!N_24pJ%rs{$QN-HwW9{X2^p%=Y+u_Q}QC4M$D8__NvaeFPEWZ*#HO|I@YZbJadYD1g zao+bsYD*M9l5ISdEg~0lpOryXGb)tj`@n>Z*pX?zEtj8!JvzXjuW{$&^sBb1OYE+ECBV$!+16}AYm!s?lVaUN!e#u1CeiJ z518>3^s$o5=GF!jjpTvCW7lAS>=bNBUVRxn3Bw>5@W2Lm#eggTr>JR^_5@ZSo;IO> zT4?VWX&X|!fjly|9mRAw%nsA@9=1e5v8zgm2fHfP5_&qtHqIt=*q+E{kgDK-0yO=C zVIp%S-0f%$_v{$Cr&u}b1R5{aU=29Ur;l)V*Z1iN!Qg_mG`we?C8{sg6nBV3GkbJK zO=yNp`s~b5zrHVUmMS3dgK{)40;&yvDP~O?mIa#=I>gQC<;)Ca>x%uv(T1opmjj9H{T#YPE_ZCR!JvyG#bz0wJ0{`6xF={@wJRfc z+}smXHZ(gpNTF{hgoqBbPSI5qp~VcunG1)n`UC_y0H)Xg{*FwEFdWnQ&XiDp%bBSf z*Z}(Cj(NHv%xk#uD)}kQ&9ae>U8@1SqN+7#&$z@bq;rmWXtjf4g&}sO4~)FK_cq-c zq?`K3Ohu%4yX194m>*?S5H($eO^fs|QsUCzCSRAQ~7>rUQ(e4KWIPEiN-3&zc|%7OEe%f<<;HdD#fmaF8lxlpgH zU%GHS;8&qrnbA4#*|SogSm>?N3a}tBwRX2K<7l?K%o@&Dtl@ms8V<0xdxi#nW7_j} zLO~#>Gds!bp!laAa6c@lG;48&>O@u`m1$6rMt7ou(3E(3=!Y66W zn8-)6GfYJzgmiK^4%n5}_gfSd0%J$Cy4&@xwYA&Y?QAbFHpC$$H+$b(AMf^dn`Zyc zC#_6{aU|vy&`>VGlq*kE#!Zazl{5WN804CW{$`?$1zJj+5gBqzcAXXVj7#ff8-oZPtr3QPGk(7~HPs#eO z$_$GRRw*PHYBV{0R2HBdJE9m<82$SAlq|qB%@Z~H*AFa)44{Yl zR&V!c_nZFew$b~4e!G8X!Q;x@p)fKJY#9)|nu}(kR8?T8+^)jrccj;8UZ~Y)*kC>>hs(HX`uB-@dvF75bFbf~vMa|H$ z!9mBcN->*VnMNuf5yS*oO}eVinRpaxzKc8Da!oa^H@r}PX%@J+uiXbf~ zw9w|9n<-*zkS%;QuWUH-tN5H>2ZZ1YU|vZ<`d8AathDLT1&7~sV8 z$+t1%0Z&O+5Tc8$r$j(GmXoAJUb8#2Yf*}j?vQHOa{I*0Z72Njsp-uu$|)fH&IiK6Si4&TfzuI($b)VI^JP-$gEztA!*Umc?mBqm6@&Gw^ zSXQ8HgY4u)(qYujknsUDU1^2Ut%$>$abp}Om`LRa2M8`r;CWORTYSYWo*T#`dr)2Y z`Pzddg!k#dhC&BcK`${vi|P1&VKtX9J7R2qxyv2^&4TDl%V$vhRH*Lr;w2Gkg^*qs z0gnrJ3|NJC0kfzTRil?aV%_v(;GC5m5Bx!=rtT}{sGY}OJ*}`b5e6P*_fVI+YpFzz zdkk^RRyE2oo?fCmt#Wi}$P$DDmzi)^6-9GFh7l{;!kNe!*cxXHYc9)MG$iJA15liQ zJCuqhE-JF5rOS<9ZI31ZYrEvs6-}kFI7iY;){Cd27hyK>cu$0(=nzn%G9|@2w;omX zfQ1BUt1KiG3fT8>FrzRnT~2%_sp5MlK7GS4n&T;!*p7f@9W@#tx@qLLHPR6@_?2Ik z!(HWe6gLw#bei&JoZ|t9-<0>5w6E$s z0Yp*kain&p3wYxp$oDR^nh#D45^ouQKcRtP<5tG8oSEELG8fAjX<~?O*)ec`+;sf1 zm(?FJf{Nx8<6s=mPF7YTAgpNggGYlH?Hh@#u-GtNNZQ26;XKAS8u0f9;^ZtsLJ}eG zO_qz~Lzu?X7n3J=hoNzb#x9(`(d{B5jF!<2rS!g64~4oJgv%~=-P#uhj$}W!Z=dFf z2#ba&7&Fn$O?mT`Mxo>RQn-bG>v&ip9*PM-W@Tj`jXUY8l#w9Q7Fn2z zj%REJdT5|42{159v~LsLNu=2eU4UZy<{mPDDy2uaJ&f&EaYxPw83+bYG6;EvHMuaf z#bF6b?wKPzDFL3u7)e)5RDfg1>0lds9N-BSJq;QSRGhNj6gfc2QK-s)oDp$5wK|VJ zDGmsVHk{c=ggYv0gRgF7#?ba`#z^NgS6m`g8=aX^tTF?#o>0^QthfSAyh9-Q*m=4UfCR02yEf1lVpUL5J3zH;+wJ^m)H2+Ad!9 zq?D`bb{&0DWIjZjWqysoW$^!@-G;L^K|vLvx|5DAvbG)iIU}QCQzTO;RVVnlca2 zUv%W0Bo%2Gfo&2f8?uSrt(Rss9jOFYM3|J>GSn_+Q;E1&jb`|NVdK!wOV=pupjRev zltB*zy!Pv*W``8Mt?I(BO@8@ORv!w;;}CFcS3cGaHfXQO7mN=Y-*F+3M`u${q6{tv zKq_>m-Cxxa^7KBnOSsU>YGoJf4cS#5vP&2r&~#WRN+ZjR>=ZqocR8E{Qq*@_0KkAA zGCB$ZaY%NcxHx8id5)cIjxn?>5IhVNyXmuOwJCreQW|XSlu2_f8+m3B&+j z*61A(0|?2f_&#x~Hfyn!$W+KABrWN^6RNU#obsB8|-T7F4)G%}5vU9nHb zEUDNnM?hA8hUDcXXdRP^eB*?v1$MDSDeUT#a_r6e36Hf~h`;&h z#aGsjph7{=>2crsu*)!A9VP0C_eP^TZ{B63onh^ToZXEFrh`x~sPkc(GhDArlO zfnL|k-CitMi-?TyyYLG^QveKlhzTcbrgyMal!Bh3Vude1*@fi*E`v#SJXre@`Tj5M z9I{w{UVjw?2=CQ$_l@$W^Jcu9SD9{R?srm*fx2YI3%SIMv!F{hzn#vr#fdrz(9#?1 zq!xpbrC2!LkY^92IdBA*xU-9 zoz(Xn_EC--mzWo3uDt2xPDj*`t%xFYvPEL9dg%$qoT3@A8lS;$dW-=q)YyT(Q3xey%D|F; ztlb~tcnlNtTkL$XE5K6VjW-sXxlML~IW#CcGzIWQjeci(%sYoqSf$m`mruE4uhXe_ zOqZR1g9VXmsC!DE><-2BRfC25WZ+ZcGpi?dlixLJ+aNPhwA9D!T2AA%@Bvi<2(~p9^NBGN0^-AREEHiVPa{oe8tamPuXGao!zs=ZcjY-@g(=T-KzHGG`R! zg<|fJ9R(GmrpwDdd>CRwU(G^?Jva3R8uu1l0O_o^&~4u+pI>_%k=S}Vn-fNV_UOK> z0qSA+Ysb=Y5!f3AfLZlcb@saISzn2OFI|2Z`}OIe zlaul?b3jyX&_t_0ng9ykji+PH+$E(0IDh=cP{09w zy9!9%F2wu1 zgBJ$UL}K_zkOQm&@&<{f220NFBJL{n(e&8tn(ni+Odw%1eu|bmSg&=eqe3G{H62gg z^3R-}N0SjA?*6u%i^!dHVjR%kKJ!*71-k|Z>z0W^?ZQQ+kcVlCH<8B*% zI_W5>=1OBXM>PebzVy^}$4#BRnY9`Ql6gs<-+sQ%ocQ}Yf$+Gx_E9mf%=wB(J$!SC z>P(Wm-dw6m%3D0IFSQ|!iD);7gWe%-w1Hw6yQ$JBCg>^WDEyd(@ce+8+YOXcZGgEq zERBv5lfXyHl%R}%1>|Q~Ht`?!Jm#^64^51xY_`d0PX-ZV#E>2SsC&id`qT~B884V4 z9Xcx-C{I@Qfo4f)YxqO?re$_COj;#{HR2;?svHbUQlZ$mxFB7iYz?~DU9>FZy>tvE zxOmli=g+arO=N|#9x`|kz;G}R=0OSKaz?!l-tkWUeS>3vyuXf$po&#YCq<>f!-|^E zZsZg<#CLOfaz;CN)4Po64%|7p9LT}?LAhVXDcz}so#3L@xJkKK`_8)?hyiUk)aO2*!&Ky3Kc zs@w>9aFJVXGY=YkhnRd}p-<{^l09CVl!5|YvMp7A0k9Ws&5BBptj>OSZao}O*)~Am z+A77l>SWb1gS`HQA$YWYQVgO->V{}Ed<eL1%Ui}PvWBJ0dg&>z8zDboUgCrWj zNK~eigJFY7_5`Ph%U4%T{zjmwxH^xZ`+=&a;Cj&=ylS!0@1g+a-`Q<0j9q^fUV6df zJ|)0^>%wU9E2YfWc29pDPjyp&h2lN@{X9<7cnlBEP>a$xzg~9zF5n+D1;L{)fRHgt z@GNRQ$?q5psixSg6@nF$xyJ8p#)u|KL1``y;<=xHmBTndAQ|^c(v+P^8>az^*C;NH zkW2Io(G1l-GCknCOgyfK7GzAg95ap;OfB^DPkfi5XUX&vwo7 z^^_tE04qVUojtmcfE(9J*LRm7!s zZ;>C@_EwAk6<$#-=k~UPeo@Ssh;AaWZ;P(qM(3h^000LyKi`Z(;J~IJ@^h7r;z=Zh zm;Gjn>N2~wtci07qoY5F zCQFkC2~gO>?BwhN0M(3vqoj#4S->EFelau*TsLJ%DTGL`aA^^=E-I6sym-bboy630QJy?za($B#A9Jo#g`S{9R`v+vi%4L1TNA^$%^z-SbYsZvlrJuQ zX46>g!?GTUvC3hP9vUag&EM!%cZ-T@54hs}+>KtvN;+s@RO$$ns>8m0DESP?ad>E|g15r$R^Clo=*!^ofds%zHwd%(tgK6hdNC3@fXm_`Xj z-Mq`5XX=Om3fX-{2tr|m3xZ_3WNi9EM@h8k+jFiOraOl5F5>;*q(zqvL(XwLPlClX z4kxf77TnMnhC4SFH1#dIiWo9~@{zGi(&=B=LvA#mp>~n(B<_9R?BAhFlAHA=Ue~NT zmN~8(&RBkSmCwrEh#r1cF7Z6cN43k&8AymH=s6umvFk)A#24*%b0KcC*h*%^0?e8R z<}4Ew2$Rn6qL`69whL_!kZ&ly08bN29|5|GY##b(UPBMIP>>lWP_G$(>t4cWnRsJi zY%o#>tI{A%n|P}_T;&W8h93PbKPczNX6!VPV$v~OCY&He4rC9=Nst+7>^Gt3W98ug z%w(Tzx)*{ZEx?zGIiRkixk5q5Ga^5Deg`nd#bkzqv3)={0h?2suFz$bacxmk_z)Xr zOo0$Y7s>I`iF$D#jZC3`5>y4!ULC>)MCxMgHC+(BNgcgd#OBhnQKln2rSMO&8N(D& z9S2Nb;+uRSEui|aR2Ew}Q!J?!z)yf$i9%Xa?jPMm3*Qjdd14Qt zceuvviQ?GE(LIdk0KRQ=4{^bKiUD5eA@if0n)~QUS?fRz<)g%ZZpG+}@ zu{a@s={NadOWC;a(mR;z*vMkGu%BUQ``KPZS$+we?-k*Ek41GFWuU&Nfcl7o`sh4R z$B5F)A>Hprc}S0c@{m5U_bE5UaT?L}0fhkVySq(8Ulm{nZ-9fFcXm+{L4`>jpD<)Q zMw_rF`Qf1<<1pzJtwDDBS6yT@P04QWIv9cnbq%s48w?Th@CYc&Ag049;A7mfQE}lh! zsVQ+T-%?}M90J27WI2Qi1s8yeElB-iq~H67SW;FTORget5Ue6V7uDSRq$^}KiY7`* z4V{6gPL~RB+@24`R@w-B8`wRVE7Fmq}wD8 zfh;65dCWjU9*{81@E{CK-~+-okU;W6SOQ_2gyDM_m;`2k7xQ4g$>U|n`}>{ zPiQBKwDa&hcg>5cv+h1en*#5048iquSSe3?t!5s>%hx@IE(dZ#2?r4OQtKDih#U!{ z?-}ubh}Th4kt3vg7ej>U;Dw{JmI5t(1}$j8w8;9jh}R(1_~+#zQr?+|VjK-RVTN7KU}osyPpNbQp=2S^B$-LK~9`}C(97qO(;X?QWQ7gUxHyW6MW1c4sl~ur+HF6%xKq5`n$1?jvwz?Ij56vJ*At@z$AT02FtjB5b^YMOE=zzd1o)$ zs5VK`l_|*sN+IWz$37W=Vbu-!$VygtTs5j}DL?I`e3VtO@GnT7)5;4?VPS8*RyaMT4!#22dg2T2Bvg{pQp0GCmZgkf?ENdw{WM0^sl{#q*5AF8}#g^_Y19Vrq} zxGcSf+rVwb(;scCm24gkva?fTsze}@(`X@oM=J{rz?#WKui?E`GM0ROOJW+nmV4Oz zvKC$8bQCGnJks0l52Yb7JW8z(@x^xIPiP6DQA>4%Dm9Zo2|?nEebslGDxp3ap52O_Xyo4$UGdP5zj$^U!i=)oP=?S>9k^V7 zx`pZwUCVEeAYU$E*;y_`?;UO2&be?jXyYdX2IlDSp98)TFC?jr9JzgqY9a%XqL|Em z%#B)|Xldkk`ne4tNYI0E*t9fd7)P!m%Aw+=5#2){Cpr9e3Cm?*E3d|nu!%)07=&$q+tId?>=#3k9RGo7YqwxpE{MMm=jwxAM=WWM zbvZAHzYpgCOhy;z_rtkiIEbR|1@ZUc+^8o?U4MAG21p>F8byoM+FnS^0W~dsXQJGg zdQJKmYELC8OY&=!P=O|(g;x`fs57o?2AyUAOGH}EcsnOJVm+a37dRJ}(Q@g32^5}^ zrb*TQuu=Q@Uf-yk-V=4ld9-)*Co|<2jeSdGTRxEpP$fQ!&iFHco;p|4{IOe`UrwzeIA6i8<_snzKj&0-1Re+D0^DP0pJ)lP4HWZ`8wpv9e9 z((KrowiMTUz__ZbCg=$@%$_yb)d$uq{o(N4htVNPz+caFUM#a?`4&nNf(Wp7wBF@q z(y12HU@LZy!_*7P43X|?b^W%y*-4}nxplgvQsq#+&Kb5k>utjdQi$?@VjFuQsnxI$ zDvgk|!gpH3B~b&#axhv2__}Q`)T#+KA|J^x$!YPo5PYDm+5~PXOwoe~FLn8lmuYKL zP2N6dn%Ll6W?9~lQe85(c5L{~mq-hSsSWxp5*iR3Qu-c>QB{*%m}!@TtUkGb;~`9R zZDKgQ;>^`NnfZ-kCr!qGDo7Vi#EJ?!hZ55B>nTr=DoF!2>5n{H@yjDqU>;WtD#3USwqHRNl#Hx|vP4n<{Ej>o)oRh_V zarzEi6lO5DUfR4jJ^Kq2r#c`zizItfP4hdhImx&Uodg4PGd)Rvk1Xy>qFU*7!C#;_W9|N5a3`BJ4A5d!VFxCo&f?3ET+TS zk-rhiWIzv&xQP|n#Tyq8FQSP2MA#_l)+8aRSn;kIh%9iV*b5$+*-6>$hOsBe1RX&t z>xy|7R9SusS#j2X<HsMJWyRq+K_ENE>$y7yAur|owXugxr?iD3dAy7ef z2hBsdAXX=i3~X!X3{Io5`l)lKGGwSR{De*2=iB%07>XIEli z=0p{n$Xue%&1Gqe>vNb|s=!fT-n@FCm0Cz*;9w;KUNCvjG>a*w5O`NEcMX-rBaDEP zQuOfIAji?NI%w=ywr466VC79UTc|9cO;TKWgf7%G*%+-W+Ko0=LT(I9C4D2LWJe>}luqKg=Ehhs7)#$RoKbYqmOwA#aQz10YPiZ_gp>w{=q@AXiHf0kqM zj++f&&HCexKUf=gBK*+xH^zW%P%&3p^^AuRO~rV)-hrG~#wes1Z$LMCKv)D`DYtq* zI=8)8oAIoCmEh1AUTmw>a2kQ9E1(>P@77Ed%vkpF#f!tz;)(?4bZnU*W)e{iLm{YeC@v|UvJ zxv`f1wvXVQ78On=tAavKz%k2+=JCk2qwew+$oT~kTMoz)LR1(J5WS0NyfV^g)w;Y$ zSMGD`4h)&-Hx(gJSvvy7g}l4wsy9Fn9K!Fezv56ZHZ^w}1nM7>@x>*NtE9duW12+F zNDYyh{()(qS+>!af3$~c-ChaG49K7Gjy6hw)sPi3Y=2C0Ik^QzLA;GtUEeap$&xfH z&+Fj{KkF2w$bRNQmD)~i8= zuX?#(U?ZW~IysxyBt@*VePY9J1JfDA#=aDpR{yHI06QWYe@eyp#dV9dw#{qcScBVX zcroW&Cx?GRlY4>P>Pim}QmM8Pn^`QVNE%H#B7!+vg3Z~euW)T`zjgWGr zpnr(P1 zX{Wg{&7lmdg?l?%0#i77Z2{PE0!JUCh!j%|5h&0)Y|ZFStrh`z;stWYM)S)rT_wvczp0ttV&?I_vkcyd*=)EmY`Z~r01!#eo+a^Uv0oxD&Gyt~E z%rxv8f5ntPN^v?V5p`=Ra0b=V^qS)_S3o-OTxUjI4FDlW;#!E!C%9KvAewvR$YX|1 z;o|tGR}t4vwWgdM-cXsZkx=;`UE8r<}Bp9v>9Aatom$D*l*fzeY5 zf2b3U0Bht4x&=0IYdl_4l~omu4_~nV#>cYTbelRQ6S0e&nA}TyVbaK=RZq_fw4ftt z767=b=~}RA{p7Bq!%eyFO+BuH(N8LpS)*^gr?lnD<=_i?M9@Gj-FlZMm)Ardm{ zm~TKQ2sZgjzjrF3BfRul3p6al*?^@2lb zDCz}kj(@Kj@y%&V_g>h(WTt9KDu;NfRBFCuyI71TR!w+@xOy}p+RSTM2AAllUekuomde&-rp!1iP^}S5<3J;$b|;o z7CZ9;AR3Z0Sd)K19#KKxFadp$at7FnMRl42xl)8Rkl_{hMC6fC5zC3lENR|A^B*Lm zj7?QsUpUY~&sDz5Vt;ZAnRO#emFm9YwjE?0ZV)2!i4&lWOcO&uOf}sDRKmBPb{S@b z(^SNrqU!1a#<$jGVHvS%VVkL_=VTOVNPi-yryJ(i9q2vG0c58lQT?4rR78S4DNkim zpkx+DRywuCa3RF=!IKp7Bqb#!`NSp*XpDhzHRo%BmLR8Sr~*6Dmw%Zu z+a@JL+55!NL6bDTWyN4(!pXW*iFKW6etp}Xe4VEhhJkFkjB%nalTL7X#p`R9X(3*B!uF>qVl{2i z=v-sJ+T`%0Q#3eD0pC-!3G6`;WPcO=qiDPdTb5-o73fTbDPi3E4H0Jz*1x4u%v%8& z(MsshUR+&RB_lJKaljxw>2#Ce5?b4n=0P0+CvzwbmR-jvfjkSzSwJiOnzKs7tqV54 zu`HV_RFZ?_J~o^y0osg~7wW1%4XjGi?=_u%pNh3646&$z&M@K?js=aTP=C!Lf-DaX z+GJ%cp7|rv#c?fpYzmMys)T5q7rrWBz}_e%?72*E5-o>4wkS8S1}lA$stf~&^F7z% zCri?5m?R!8N2eZKp@t<>j}X~n5%pq8Sc0(;Ee)<@=&U9}@%iZ(suVSqT)8#9c(E3m ziyljGf!2Us*VKRkZxGT-$$vLcm;VvyH79_| zRh_T_K=)R$bo!)rlW+wN?Qsl@qb|lNM%W1!U<*fsY(0x#js+h41Ak0O=lZ9=l#<^itBO^2npp(|rC z2;R5Wew-5D+c%YnT9J-W5&M4KmafBw`P`REIVJF>TlW0e&On? zR%Z4H1&z~zb9dm}gT?c(M&kjl*Ek@jpkrJvbfiX5!2k!<{D0{nOG7MqPpLW3NYw!+ zg`J^84^l2PO;M81Qx)%Z012msU_Go77(FKxjOoS#cF%Z>VX9mi))3X_5S%oK?VEfz zL7vh&+#E+yhUpakh5%dMC7uIzz1`%+qv)TVR+poU)bYjN;+HsGOO@rk2KHanA=7Gl znaI&fq3dMDsDCu74Yx64S=+XoimM}?El9XNX|42ZPkt4&>1O5*{g9w55)3B>v)Uw= zG5Tyy-IJV7+;MF5vEF5)DQFO8lOzFBox<0as*$WL|Esze>j z#p!Mq`>m~r%x7mTJaVEyO|Lm~rh%yO*GD}|S0lUE(0>oO2Kz9{w1l3YSAk-t01GI6 zHEKWz5DD{RX%)uX3+xFhmfI;TV#KngJETc4{3-X+iuaD$XvY^g2US-E>OjYa3S`&Y zJ%8llbT9OP(QS>cxW)bG8C1M^RvF$YK)gsn`-tK2B$o50rb6}NNOXWczEQCT;BLFs z_bd&bTYo9%p4mmnH7WX{ChmtemRS!m##v>dAbejUOyY5jm^c9mVoEvFyLvR0_z)p4 zl^(mOUvpq*wOhCe~*TjB^sJPs$&0 z{s?(>uKEli9`YzXu~d^wH4QDyma&0bUz4%0ZhwSU;TAJV741`YY8IhVz>Zd4wIKe8 z0U?2sCR^q(H8}?s-tSTPX$n7>!rkOE+p4?@QEEQ@8F&*y?d&WEBLLJr3mU@hzH@f& z6Xk`Hj2T2hgBPy|Msor(s!{W6}60mHk-6B#dgrU4C3fV!xE}u1jEq%JR z27m0Nqh_f+Ie>pP1o$<281a)(qZFE+t%;z0R6~D`$R8ypc!vp4Q$>s&DlwpthGaSf z(@xPD!|TetRtS${-N1Jc`X_nZF3{PH9A%HE5paC{#m3Z!>& zxEJ-2`J-KO&JwAP10T~u0aafJmm`10E}|;*xe2s?ajz{&{`-?zFiwE&iJT(S1A%u< zK?N%Y3mdIrD`>S5TU$*+NtPp5)}m8jZ97X)7|5DYG+4qx!1CkPMvCkqhNRXU#ZVNEc^rX2){c+Yb8b!55@|7A{?63Iux-`*xk&LQNE0@=0SiILI^%1T_{x$ zAp`)^&SwORB6{gOMv9sVon#AAdnOiQye5;ke;|Loc1=l8*o&3E%^eH^1|@)8P(l%Q z-<>!jwnOwp;=}axgXBwZr&gVlLB19nwAmPhHS(~WlLvgOw>uSS4sH{*MAcA;g=(5i z2qk|kZY@uql0a)EH2^CkHDd;Xs+cCr<+@_Ynx=UAP++>aI47=Cf>>3mdd43S-<2;r zH2{A*1q=;Vw(XgGja}^mRJPq9uJb~m8Yn6FHKH8$ zQA{=&F-%kz3$GOF#IBVWF(|m8cnSDK-rd}(I-H8~qV|=jAqnPnCO1-dCecC-iSxYH zA4!;FT*HX&bmIylPs8}=-dM$ojArC!Ls3Q6*1{Vin|NDYZF4vjZnq?}WiqP@!b*Rx zmBwm`%u28;VqaPn%SE>c+YZ!w-+CtiTz@SD6w&FD??8%1&}z5Oinj2z9c|q;=xxg9 zC6an_*aC$Y;0=Sho>FhUxB{jq=mD^0cZiR$A9|bgboDfP@1rLa!J!K%=-N4QF?Nl4 zP0Nk>$EqctVCHDr`2`(*Txn-n8?t}d;J$<|=%K{BFBx4;4Kf+bu%!ekgn4KeQb(7Y zD`fJoy?YPh-Biv9v-77;wMC5?d{LRL+%Pr#9%?0rdn>$E+H; zEvS&Lqf~5EEfOWgoQ%Bt1(*zNHXqDKsNz_$_eKB`XSCfaPZf&Jfa>qk?y!G2hQ<_$ z^97uepQgy&7Zu~J#Pbl&cm!wD3^_u0Mn+{4mMZ7WF++}?F7h}m560h&2IyhInabmw z3=?yLAUY(!;Uh95t|7uAS720LhH_oS8*SQDlRQdguC}s}xc)keU2!9;ZLTdw1eHr# z#@o$!H*maA0k=@$84%;3%VI^8-eP>uHnAH$H zL88UCOC~q*Ro=42JTnk;46|2KS8m$yyd*5r?R&%OSpiMcF{fZFvI8d#7gU+NhkOqN zPFfoA07F2$zp$K(ZQE7P$~V9;qaW-ptTsJOI7KcfkrD8{D!_5g`65sax&yO+-Lfq_ z%m`>FHwR6R5{G5LYtfba#QY!;Wy(aW2@$Dqe<*gEc(v)awpuJ#_G;_R>_Kr)1yG6Q z1_bggD>Xw3U&!5CONeF3ra#^m9@%UTFU z)NUsqEP53oK112DB~--GZX?!zx<14A&L}TRC|^<>@RX4fwTL2sPDTx+uT4jbJ~26>Ym3YJNCf zbmTfGY@Z^i`TTNi1*@;a-|-lJ7iQ^iS!0*Cox3zmsC=Sl2*16D0z0Wan*`XCmBkWQ z9ykyr$tiwv9Lt!8TN4DP7JWDptWAA604`)j9eI3XR4a#4m$Y2$BD$(wIXR?oA(7}d zYQ3ctr54FoS`)~rG=zJ9jZiNo)gs{0vn0PF4_eyy8jW^V&_w`MJyem>!@!C_u-I^3 zs1suY@;PbZ{~$L~?eJe&PQG$cilP~F`>!=vDJepZHsM3;{|-=oYoz4^LihY;6m`AV z29BQ31ERDGTXCHt>%d1Wm*M;a2xit==YRh06JKu-$LtKFBALVSKqgt!FtzNe<6{-USpingVB5XWE5-b1M)+M8um18%i0vK zNPXXA?!CTKb}HF_-)vVZ3B#=B2V(5?vd>F``iS^0#VJB(L`#!7 ztCHi$`yvzB|Ohjk9BsB5)7U3tj48B zMgk(&5Ow>5EZtBaW*EUs#<$}B0!gL99xUBndOUa9F3nQS^(`;8=^V8ZD9y$#v?p*b z0V)YOrXB--{xQPjF+yStr5A(=(Qwrj(&4c!CCRKn5{r{Vs4os_3zgJDZ@Cq&I8o?b zv-Vo<@+9vq(yjy{cU}duW`GHZHI#BWasR)(ZzTaR{TN^Zs!n`jG2ImLIWs5#l@;># z55@kmQK7E_O!+LKdLZ2!&=3|wTR8yH_zK;LC=y41!<#{i(1ltrM|Oo4URgIvUel5L zQr7D3=Iqz1o`f2 zW5(dW-5LCi3T7*sj$l2E5k73?^Q{iL`P32g9_a!IsHQ!ECw$Y!Qn*HFMpV8gZbD(7 zqL~GMMWbZtB~WwGLNw#e76Lo;&ZC82ZCMH$XE5x-ax5~0S6?y4q=G7$fttAZLW|e9 z8_iHsXPT9nmybeXjh9z!8D;=dV8^ZCjzM{3KfpG%sD-8WZq^y*PPHV6g zrpQ6VpjBvD1BKXEjL&655cD8YhgxJujI51+N6oTrBDE`#o+9)WPt9-h#^Byv-LKh_~pKOcH+ki+VjIJY*3hx2RLm{MW|kX zt!k66->{6wsD~QgcDSpK;HR)yTV6!yTszATZIa(|R#Mfx4+DwZ>tBIWMFG>P4mj9m z{nfr;Tit$qSz|D8WHcm)Mxn~oRH#d~w4Nx+jkDHED;?L0niJYo>R&4QmxuK)vn$)S z=H!7a)6vIAZ3VCh1Jj6&EoI1C{6tWH(_-|3vxp)9fPa^(IF<wkf|hsLF+ac^FXSxIvLqY&YweI~WlKrEfBuH+9)|E>}^5T=GHPVhuHa#C4x& z_*Jq+PWn3&f~l~53be@Zz`35SGn74)>}_i+*|QrS0M*M?oJ+S1EKnB!E`EVE$YUZ3 zM2VKZOuk1D@yHbMSG$b?_#%%sz9Bv^yppV%XoSIO>>R~sxU36KL!tn9H(}@(CuYIx zeBDBEUiqVkasoI)BA7e5qOZ$;@QIiqAy2Kb0F@;fBtu11-w>wCDCtyXYb$X#>o)fm zLsQ6TG{D`%QzUjEcG!#Jaj&J(2;rq#C=GTH100X)(O`f<05T4<=t+yHifxLbX=sN> zwhUmrAaP^h?E&bD$RBt(?Iv@T*A2;lnN`JG6`1TQ#VJU0)V)ffM)%o&EpN3rLJBQ5 zmlJ{xnZDduL3Pc1>}#S42Wr%+UqNFFn1SV0SZ`c~$|CBTRYNzA2KmEYdm~a=R>;}z zHh}de60)5Z>;jI8TphH7em93HWYzOG{0)d-O(FD!*b0>-wAeCLov4g6R%I!ZK*_`n>3 znnyMB!aEkwMC6cOYf#eDXww)}6HDb1EQ}0apBzAIs;mtE)G~&=j1|qRQ+k;kfFv#^ zBUm+39cj(820MmUjM{>;zm_>}+oZ~Bp(4vxfqIr@V(LuxitV9))|_UDk?0KDHrWh` z1TdCy{3=7RQW4;bP`P-JSKFuM$i3!wTOFVXh{|zA4fL{2k&31FMA?>nw;5K8RD70$ z4tt$Go?*LK_gih51E1A-7is*4!mK0E3EFV6zo#_}O@a%Ra))V5fsa0iFchH2l(36N zpt0a}A!*j+7ZaX;sMz%uFmD9dLNW+l7Vkg^a(L8$&FC|uDFxM9s4@cYUlYC!d?6Bt zO%TaWreFpfhdZol&&zRtE)1%w(qm6D!Ygo1G792> zuHV>XkKdVF0Pd+>!vK?FTAcV%M;6AIsf>*mE$}xMg<)k>HLCd3A|~~T*%9ezth#Lq z*y}#f_r2@*=M{$f586agE42crEQukcFc1OK#s|nRFaCpVhp*wb9r104fIcS8=iB7S zjV70a7b6{ikff$OK+-fqbC;nONIr^P%&{x*daK(SHt9|&ps*Wx^0+N;+>89(Yhe7_ zdc0_rYAhaa2w6GLt;pmw>?CA2&PCdyouh&*sK5vK=AaJ)*_*T_y7Kyq&_R0s#Vf-p zcK)k%%SXq-Tb8rDGQk-zcY>61gfI{4nXzJC)}pz8Cn!7ATr7B zG2dWs4Kmasl@pPEA zbeQQ(svj`XM0UTy%7XZ-BPHYZi6!o^a2Z#BV&Q~n1GKynoHau9n1EsH__|8FlO499 zf5*{HK4cQCu|tUu_9=<_FOtslWgRAqVZyWm{5{=`^}4u@ZAw48KGHr=P5ePr6n_)d zZC~FsIt|?AC<6(qX?VOFQ7u*wT;W<0_@W;WwQWNoP>kh}3!w^>N|dVaA-CoDDEPpC zCqBA?+*VvWGq98ojO}$O` zKp7Y~e>iey;X&Oh&6UdnbHn-ZuVhDmV)KdUF{l-kr2FKuB@kFaLll~ZjfSo?VKWL? z7sBlE$UMT)QF?RmtqhJ}~3sex}CGDXc0$`i@Btpue?(uG9Bm`=)o-B~Mt;tbtK znq#f2K)qWfpEb?R0+@>x_Hfe9b}4=?xi2N4eWuY#HjEMc1Dw+J#JPyXPl{#`&;3yJdw5P1QwCjvW!U z2J54z*U0>ya_=gww%egsx;Q9*H5Lql8nR?v$CKA@8T5eEQIpPdl&8-GHe;y`zaAY2Ino5-UGF*q_na#tfX(_CyVv{;@LvP;OR{8XqKo1Sj*MMK_z9Mu@A zxWYYpm^l3MJFE|CBx>&6pUixEo zy{huEt@&1^YZhLB00*kPX~d|DG!4ua$tr&$;F^Rnsu)7om4M$C4t+YI(+!NV>xm5o>I%X9C|6_?hG)IaF>Zlu+klMA3N0UWCg=mLw2GgvvgFS zxRAyh8&E0LFY|^ZzA|2jjV|hJYXN^|NjF3GL5qM9Q#y!sZU8hJIO_t;bLWMfa z4Qz!{xiUAh6*!_whpJNDmZPTZ7DUAfq69`igTzg!;uvb{L%o@>%;{(&0Sdin67nb5 zd)f4~dBrbcA%$L@eGB7U;wqeMZIK)~bS6w?Xjcc}^z>yE==MXEMmKrrFLGJG8?*#R zQ?O=2d&QG~nL2VR2`xJ5AQYDYdAcM>l*{XKuOO{r(Wf9bvr(dYl-qHQ z4p$N>d1NTcR1@dhWk1%GyN)<$(maRq98R2^z+TC$9T!oKCa(b#vD7;)ZsxwCj^xdd z3?9aq$HffA`NvwYVQzH777~{%2CyW5D50&*skv$BJJD|$0chf_6_Q6s9$|pAzoa7B znkYzrBS_euL5z5NVX;+%wWNS~_&4nJ2DM@KJ<&pPjq;hJ3T#{14pV2U;;$|zNu!e$&8VX8@BgZkFG?t-F#=N9szD^|KX(aOdW6Xd_CcT7k@jCPpD;w`jFP$^1 zF=T5J^pa35`w9SwZesKO6(O>8G$&DzDoF4E4H6TSm8c#qUVCU0jLue1!jr?IF|-nY zRwA-(CpF%L!ecfi)?iAk!IW4-Qet(l!tT|k3>u4cpaN~=2>k;x3X5$EPN3mP%ZLnXS?ky=pDr0 zx|hxzZtJMs7CuJA+c}|R3`KgI@TrUGy|#E-A4X^VK0J`~(4%ZS??~JE69N=}Cpk~K zcUc5w$aY9}Ml&gdlxh~CfD&z5@X(`fG2~hYNVI%$ba8O8d$GQf6ac~qj^Qz#Bn8ys zYv|6OSJPH^cq=Q_AvJ;E4vw60PYGF{>_%P}1qw?i1xBQpY}MxbJi0ozY{e&_^2kDK zu_;4%h`6oI6=1X~p6-vIRDe}~y;W3Knb8u^tTJ{fybeS@+7aR}RNj+eO;BmDPOL|4 z6^`bZRQhlkbth&#ep_fri_sdY->QZfw&{HVAZPly7hZ+Fs5K&cWd>*lmedcnxbv%%`Ge6fhLz#vsn>KBQ-34Qn$f@?ob_r zw6Wete2{G2O}v>V7%NkiOBdJ6#npP5H-+@(;v$O~*cyK$6t3g`$qmbzV4{8!z!)1q z1UN?9TwEQD=Qrf$fklgq!2nkh#lsC)EjEvS1MV?y&pGIv+27c!5bN4px}-i`x@0AH zZsOfDUxBz{Hks}qCfCY;rtcI|LX0U}gze!*^J$}{!$(L(N-Z)B>dNVM4^Ubr?|dfI zo+r7a)a@-VNF`qlJz7C~eUPgILpJ2!sG^L2d+VcIyXV((gJ=kpM2^L@x%Dn3hV53k zma7GAe(3Z7CDwqq^?;@VAc|lK24CM}t8R4ih5Wtq}mhbxTuE%u+ru^)g~!IYoh&`Lxkhw1_Veio+#C zf(Ldgx>6GdxI@_Tc*H0)jt7mZKp4paX~IeDyzCEOmobm-tanpI5(VmYRe~Z(aLD=V zXxP!PrK;R|cS0$DRi=`GhN@cg)?SJaEEAw=jQqRTVle{|F&m*&)NnTOBC}d@w%~8x zvB&v~%j0YCXM4P2k1dL;ZC$*GBfrE(P`OV1%#h%NJ{Yp?;)ra^>3Gw!PQ7 zINa-2#~4t1T=xNhklNO`P|a7T$`@<)#o7!;(JsQ@v5j?qM<-U$NU=%@L`)G`e)rPZ z`#c>zA`erFrY{Op-?Sl6Hm{YA@Al|XLl*j>n=*IA;v zP*#Aay0q275WF}lEOP|9un}DlTA9|F7QmxnuRbawlX&(FN*0R#@JcuIELv_v+X$-Q zGm3EHh%%v6;nC$T3D?)K8sP-h3F_^DCQdV4OHCzzG|MyTglsl@C3LQa&Joz!cNF*T zDLF@RlP_1NSL{pP@*~qLdrQtSk}MSWR&94VcX70W?iy*g@3k)us(VU}j&ocJ9|C}* zW53_We}&$)f> zlXw_v%CjUzM0eQg$S2b@G)R>S@FAXq#5cO<4Rr&lGhtGhnY^eV5pz0>=c9r5uK2yB zpbz?vBBf5E34C%bwQNWUM3GgmDU_fmDG0BQ$Mx|zT3%hL)~i$bmbWSeel7;i02&H^ z$(f-V+N$j|@E4lx*iK7bTkDz8FbjP~k}<;cbc422Jns9FhGBU5w(4&!MWYe)ZCI?g z{gE)zLFcx*En=+A0TT<0pO4@#z*3GQat**I&U!#`#AAS=8w??Rpob(sn{D-r4vkno zFeKk?YH)%wh9L>}vv#Y7$|6|#sEHKJswhTIZacGl!V*Q@#Z6c2oQI_?NM3d10rQdq2iruSTO zlWT#jV7ssBQ!7pvhl*aVti)5Hci3KCZY1R~=70d0cx!S|Wav|y2!VlEfs?JMT^n+6 z&u}>-9X63o!I5i7H((b}k~ISfHuVIglGG3=oN(RInx}}yv{&l5Fe+Fvq~SEOdP<#R zmwKzqEn18^8i&E@p+{$`5l1tBU8OyNBGH(9Zo*UvuWxY`ODB0h#)ZumjeN8abaY_jURxiuT|o78DQp3zwVc-QUch;6_qdSZUI}q7 z9FB&C-X{Bl9yU$^aS@~-9fJ=7Vw|U{P(!@MsI#a?AlqQ{)oA$fAv zGGL0xvFdO5B3#+F>^roMbzepMBil+-r~s4$TXy0 zxm|H^aop4~%VpA%3CkRf6sMP8u@+?T6}d0calCF!1_LUM(Fm`9vRt7p^fJ1w_QCpu z`=6i+n2L~>ZY0DXSq%eKY?;dLu%9tPXkrz7pQ*{!T79Mq|5U0g7K4{;p3f8&-dLL#+8qBFZgrJcG;&r15xN=0xmQq>QM)h}P2W;18lq3jSoU^=G=|jc z$;E z9v<&?0MHXxBiSYDe}ZFjjO-q2ykh7pq626cz$C+eZs)gICUVG;lGPlI`Ys7$(W8)c zsBF2`+}u8(KG8+Sl~lGXseIHr4sd#Ut*BwbSPZV13z<*Mh)2-bmlK4OjQUl$LC^}u zIfL-hSp#x$!*=+jOP`4gO;mv-1ne}~|0~9XM?^m}Lj*pBb(bPPivS_o66(H|o*2cq~sk3OjU9<&tO3Y@npsjb<{Nu~ZJSW~fVwhvN6@BoJ9tZWNR7DQ}&w z7i6BoQVSro<)liI1RCh4ohrEx4kG$13!7GdoEv(TkM~PFkNBq1OJpmF$4pDJvs*H_ zCda77cCrc_A-c-ak1DY*YehkEEt_9fsuRD=2*TY=u38QdsdtAn)`itf<;xqBF$PE=a;^`6)xa?0}z4B#H+u3l|Y>y|AkB&M!0>s*X z3xI&>(NZ0`CZMQW5wv*4QFiu~ZF>XS2Tn|MqUD`I+C>~t0O-XSgL@S`mfgewq{ywy z(6bfXnnWh!tdH0PY_UunOSY3T*=tK=wrteX7B4mnr$*?)4dxGs{(-Y4S&mI64+xXl z#HHjm7q;?+ih~~c2!GKVvK)#mvovRaq+2G(y6Oy^p|kF6I$K__fa)(uJdM0r^e1DO zv^9|X6WTj<@{a>_jy_5LV!1j8#Lg-_d9!60|Gsj05pdML@*IlhNjr4iJB!{kHpr*< zx_1s=*5P&4c2=wT#0lR7gqI;$xEBtuI@16GH?x3&w+SDz%j4Nq#l%5|>;RR2xuY;N zlt&_hU|3n*LLXc*8i6au!>OZ=?1-D+^45u9h=4X+XZSdT2iklvEmK33oDID#2Ql~5 z7G4-a9fDnisUmCYGU6afc)+a5N75C;E1E4t^)OpK!;?wdS%z)mY^>OBPxocg#x1(# z3|Hbxkx0s;>n6FJ#^zqF^7a;gET~IwC%BKe+o9!wi`DhXGn&>2q+vkn#5p-8j+{Cx zg@QzGB&yCrZ9OtAQ9gx(}a`Sie%;Ma{<0uVZX5|q^vrr|Lu z;KXz1Bqm;8LSu2A7?P#cu#y**S(kI7Gcve#R_=@evLHeYQck=SHB^)^0^pdC#2`;= zC){*w4*Wg_!qyx8V)aWzU8Zh{R3;H7ib{ztKOuU%jgjKZb{r;qL1r`c#q@CBs&XeF zL&BuVFkQY2Hqh51!b?Mcg*YX)S@4bYIAD+r_{kbVlNIoy_4OuBeDn++Ccz%)ixdbg zYF*5TysmLsrnAtR!$dT6^Mz1P_`W)aGGyf@NiLJGYrXOzBy$WWwu@F1uQ_jo84VqB z(uj*2`_WqMwYquWO=HQ*{XlTcSV_zT;JrmU&lwOyiV4`cyUfVr!gFLMO#(TYs6rSB zGN_4!xGaJtRNb6zx+Ro9^32x7as*$A>J|jCV+ASJ^N>%rMNqhGPJ1LOX#i zTYR;mOR%D-=sXlBGE&Ga;YFQ|8n z^ye7a<}m^?7P^duu{1(DZOTV1TX=1VK4td6?``+BAwkxMP|@!KX+%66CaRK~vvMY>>h=0i``naS5%yn-g-B6=ZsVJfhZ*3s6N-9Kzs!Sw z?yZtP2+vGs!AwilgK0u8vj!W}v#jkV05e}6U(!X07pz*Do!#e=ll*+sn=Q}TZqwt2 zs&<(X2G3*^yp5VKYz}$^~#JJKy{tW%%iwBjxT+koN+Ha zAw3E^ndw`F_=|CUELuY~G^}%s*=$ZMC9Zqv-d0qR#Z+~K$o~Z5Gjz_i9U~8Q&6-!t zvBBs_BFblZJDFXxQED>)@WZDxCiRi7uDjp;$X(a3=dKyRWvdOTAK zA$ayfQ+@!epfUTCjSW`8I$jEq7l5vp@}7OMny-4(<30BHV)bIRv|t=M6s`4L zD-^fntn|>Am*(l%PM1P~Y~LLP-9MvBv{2B=AizZAjh*YMH}HLl&nYz2v{J~?ah z9!_Lu<_-^-rjjPkE9)`1f+(<53MClo-ejd4EEoyVpR=YKili+1 z0KlLcLZc=jrV}nvYuPgX27s8na(t>wg_WxS1bQ6uLClrV^g>4$l3Ym94T^xQ)r|I9 z!-rtJy-k$1`R=^ov!@z67HLJBNQ5({|DiIVGo2o`?sYd22tBbk{1ZnVp(xN={}5W zEvg+O-AxD50~)&pyoNqY%MOmfEpNvTE=9;U^Tvp6WDl`B#(Hx9W4v9UN`T}ZlAO7k zm-1s|4v%77aF~)~?H1&H0KgNV^Lq&RP56%5RiRg-h%nSq+66P@->(rp%mX6_#4?ni zT|ml+Du6fI0W>t;HnQY}WJ!!Rs1?OmEW2&HwQWc4!-OHPag%Va6c=j19?G(2!OaWt zCNJtaae?CmdMaVP*pr*C9WfzKT1Gycpg5yg96mEtcR>x11JIqvYu+ij7~IH6DkP>8=}pZPTf^+E%D$uXdc}sGZ8`Vh^XHKC=k9^swaL?1hvc z@Ywt`9Z0z*g;;fuFwsI1ctq8DmDEb>Rdf{Z)+gdLj8BD@Q$#i9A-g(k`4I4Y-PVo} z?9|8!tC6dvpGS_$rYbN@6FFc{!Zw1hNhk8C7(mEm=g9RG%exN;t9?#?tdwJN$;wp~ z9D=(8_VjluP#)SyWE`U!sB#9wR=TmXC0kopzLs>Qt0#vJfh#;frCqmkArpoo{x#H_(4!tzdJyvYoF zAY}&gEpFVFt4a30e7JF74b-paIBo1V1ml1NPQ6-lr7Tx%PHDJ~HvJiFqvnx7Ab7u9 zTZ-CsW;I|B97W#Ek|niJHW5(6fNlA2(eKpcbKc@xLktAsq?_!22vi;+QM!6R=5*@# zyeD3$ZtqyHTL()oUD&MAW6eh}Q@{ceWkLQFx5K)n}}sgE{Pl z-Meo6d=36AZMNzoyj*Ixu5ys2bL#asMzl|vfC(sm&*tD@6XZSgBAK)o}J^r_%k;UGw7n^!01gKYKs4)yR*a_a9_$u!7I^I!k`_dUGpt$%Usg?m1>Yjo##PUW7t+pfIv z>yO|0(%=55^`2Atdtc$dVDLi!_-Eb`KJSj7-}NKkUJmws*GrDQ;`0Cd_6x^e@weYr zu6%p>pS-nyaTI?4s|!zjEoj$d3 zxVqH`Iw>0Ef(Qsy&-&~y{IZp6)jaE={b$N&`p2SX>(tdjv~=~p^WoLCaK2_$7juWV zT&SRb6ODYdJ?#$NEzhEQU3kXil9i(@qczXsPF*;2E_bXqh;sXj2MS@iGM76zU##pu zaB%*hlbeP2Qu$D6wp=LByZiUK<=LG0v7og{G@0L-aNL_DwQ@jH;bG5ioHRj zRGFWjFO_FYv$F-rQ5at7j{L1ccX)@a;dsPO1EiBHgXdC1k%tYij4Bm=Sv8o)vLh<&i#p+_T_IvDT`& zClAgYI=TPYk$p#J%LfisDu<37IDY)((W7&7`}WVCDDRhbWR;zt+dqHc=%Km&m4lUk zO6A1d{DI@;6Nio*JAU-&i6cjkS7h1KT{v3ZFp_0kaoK%4mUWNe?Lu#G9vCFK1I{&j zqu#LD>*tOg%jM6Y6H9!zaeYKrqd{vUs-cIS98Iv>%IHA7ey~=b3+D@SbMsMQZvUZu z1%G}nDwLyx_1Peb$_Mt>Em6LfHE~COP98tLZ(n7uGJ60AaA^O@@`>{C`PqH@kIWuB zId7GqL#4!ykx8%(@lox6cu^+^gGO5eI3K5e@3FV<+I3qb(C+8I@P+EH-uaB_Z@=w1 z<>%h}%s>0W`47JC^`EQU{@G{z*(-B@nET6ToO{Wx%D&rspFg|(#Eakjfn7g;@v`6l z<`=x}H~;iGe|FCoTF?BJU6)@r_1xFL{{^>K|MBd?@7(!ox8?r**M0a>>o4D2KKi0v zel_=x$L@IUFWwgTM_-hC`OL1%h1-Am)sMgVHLsoD_3a;d>~p)m`MJONoM7V}uYK)H zkCz_%em){n=`nB(WeB+-ycIZWShtGV`hY#&~=q-0STf08|8<&5r^?N^B zyznoNU3~i^FFN$5V;A4`$cwhVJ*55C~i@BhxP-ge~B zhkKv+)!W|tvilFc=;vmBYUB00e(Lt%BU8Wp@}E2X?O*rj&v@hC+t2ua+bf6v;ZJ_| zeX|cd`_DF-PrUN+`wzbO&98gI-&v1+%WVbsh5!7$A9>=h9^3oo|HuFC7r*hD?qgeD z|1;lm+mF2E9p|6;r*FRJEv@hVyhqu4`ozl)|L}8v;K!c(>>rtX^VUy(;q@PQ?GvB*f$zWi!{7fm zA6b6!(a-Mv?)SZa$$H7%uX^VA)PyXHSKKh5B{ou2{|F^&A zx9Wd4`^00l&-~VJ9xQ$KZ?65-UHAW&SN+tVKf3$gH+;vV&w9b&uV4MckIi0qZs|jh z{G0dfdG8B<@Nv6&{9`}%q4^*DoB#IFXAd8Le)Hsi{jraK?H_#K?%b#U;MczI!Jq!r zJKp{BBd_?m54>aTqq|@6+53k}AO6|t$DjAZ4?q7uKYrrw`=|b*yZn!@{2%{t_JiMf z?!O%P=s$dD?sLEXk8b{3U-(16`Acv9=)J>#@zMS5<)3`^#RtCSBky|uzK=h4=Y@sa zKKj=$t^Uw|@Bh&EKk~tU`K|Mxu6&}g{o)T@`<<8kp9kh&_A@{7r{Ck9zVo&}e&9JP zPyGDBAA8P!cb@h6t?xSjEw}xLZ+hN)U-_Qz`qcMXx1D&^-`@7Q|NG8ef3Wq#AAaj% z=Nra{de8pH*N4CIh41;p!K0Vn^?6N$$LHR3`h%~({m|e4 z@$#`JUihqEfBXl&`FV%z`O|l)-zGFM4+Kr{A~d(R-f% z@i)BfTR-!SKmMvuz3llHmfkS>>~cT%=woZAf9B+yPQ2{%Z=HY5d%pGP8-C#jf6M;( zAH3~G5xT*Pkx^`?Jk=-S*H2SKj}d^Ot|7`OP2wKR)!> z<^Sz_KmB*(rBna*&weTQ`ti*3OEb^?yCc}?XxfAUxTKmGK7 zp8F4<{JKAS`6Ive@hAT9@NfLdJ&(Wilb`;L-w2;r{?u!(JaOzT4}Ng{iOR=*^i%i0 z|9zi->-eqTc;VCU{-*!S?%A){_0C6se{p>yje^XEy^f8lUg zYPK59Hr}4$B}}>vpob33%}t{JD!@zaPtbpr{j-%@^xv1`r?fYR1~IJ>dV)i8VF>g> zqct2A8?8}uJs^@{(}y`)pPL2ZsXTXoX!g*-+}_geqFVLbwcWWKR@Cd@p1_avP6prV z?Vjig+O4Sfv;Am*B!Ef~#fxXw+oM*y)s0eL&J7~;h?7cRYIPg!boM0-ALO@H`dv{M z=!dX)oGd(QlhV0dy%nZ1oapqM=|p3cG8JtlpjWB%)1*0_(YX9Dx%IQ_BMhN`oJ>9k z#85w-yfhm0GTL8S51JFYc7EW85h|l5m#hc?0I94|>cjCUY~$Sz!j0s&bHix8)=MP= z42cFC+62fCNXe?ids_gM{C4p^3WA$TW(RgEnc~B$P%LKh(e)N=r>>buZs`+A$uUyu ziSDR1x{^rSLcupu~iE0?vY8}~RU7j3F za4lWD_vDI)ohLaDVLzjPg8BIy??C0rc3^l@9k}6o8?_x~He-G3^&0$S)>GWN*{Xc_ z$*3y2-eO;GOsPHVQw8aa&Fo1{XKdZv&}ucDr_6e8c9wh}=P9GA<0F8V?8y+6BcuHgKM#t1XVYA#MgG($Tw0xIZZcPKDp_CZnk`KqUPrI?>8XC z&gGL3u3XOE%1Jmii<#Kk$ zB$%0>zo`Z5)}Lhg?hT&HZgOXoCyi+12Ir`^`Q-L$vUos08GAB0MUysVCkcorW=sR( z2^rJ-Wx{z#?%>RS12If)-|SpT7?hoB_J(pLw`X>)Ksgb#l!L{M!fr=A2yL z$@xwWt<0TV)NyjHPQ%HqL#cMl$x&=q_`@}f2JnyJS4@Svkc`e9*uVxqk$HlfBV55o z3*x1>K0?8tc#}pJ{D#KftHuK;RD}%!~(PH#VPWHf9lK@jtABuu|RcwAxqP z+^ML&5n+vgPOd(Pq9JtA$sIx0S1<|iQb0dgbyW;a1l(L@uD^vzfZKw#7F0%`I=$Yg zNj)8|2V}TX@X5na3;IZW6iT=X^wiXs%Oi`gSX>IZIj*MaFKOg$WYdL`)s}kXs+Rl!_HIVXPs7 zLt%)2+0kK3p%#Tb7O7%q#Qt7qq#)WTkj-7LvR~BKX550Oq0p3DfHn+%h4U)5(^i43 znrMH#iqB2E7p`4i?~NjT$8q(EGYEx@L2;(JcsGP0vC+Gw8w;Ayel*Z(#%jGxAPas-U_>nX zAg3&z@c}n0T1`Wv!kqk~7T^GnOI4a=hwAmZ@m6eR!56hR1@i3#+QqFw}|PxVsd&PHg-+{1|P-HPJDC1+HB1#%H% zHp`UMf&th?+D`T2{t#OwBTY2dtJ4-spbZ9)>L@%lO~`@9r#anZ3=lTiF<=W8*IMo8uaFWhaL=$ls z=n~7#ts~750^yx?Xaqp4Y4_HDi3!kq*Xp*Aicio%b2xtbr7+B1x$i`@I(4I~Rmg6+!Rb2sY-L3bXr65=@oZmqaMi)sMa=6+!pQexz9S6on*5 zu*3PDu$?rzQ9=uW{3;seDjYO9YV9Nsdg#C~0nQ3B^*^`27By_j)9GE^IYV#9&pUm~ zM#OU7W)-xg5ha#S?M+FU$+g;Vf2@^)yVqSNadc8-fDjfY`(Ob1iR*b|+bq@jNqtTA zHVf++$+BH}7;3GJBFP_31OH0U+tQCR)YA6*LjZU3DY?G9ma=Hypi~r>b;%>e%a?3ubFXD>NBhx!s-t$bDdJn8!%(n^DrR7K=C zlG@n${%8N(+<}9M`=7bl1La%yKVOcY!&Ar49y|Z=xf29j7Iz=Ue?Pgl-)(qS)U_7j zxEw~Xv`FylFd@pt^rm6m#0M~zanuc?!zIqhg@BD;L(;*shz%GvqiAI1*dsVOO%I1d zE7u&Lv0Jfd-Y8nR(idNgVV4lsL^g%eb@!{+NA<#?SBp==kuW;%#!6P9C*-ZE2*|d$ z3U$M3yoN;m~f9ijBfx{&^p@;F>O4Q-!kPGsxQVHG9Mweh5r6JBUpRn}#vT`AQ z@GOLHN4jCNH?W``U)4uT;91vQEWH$CphY24K!>lgl?1f4WFE-XUxe1w#FL1Z4!@PL zQDW$l0~j*ImUDTehjP*yFRIyxOMSh1*#%wPZN+WEBg*ULf9%Hw$yR9*{jcVr*}Gb! zQ|ke?rqzA8gbrbUE@0MFf6mN~Oh9%S>znE%&UU+%=y++pZ45n;E8g)y&pkJl|{$bMXJD8Rf7WTyL&d&p5{IRxt1Ze;4IWk8;r#P_fZ4$Kle4yGKnQ z{n>YOL6ie7wT9+gjcAl>V;C4hkv8Z9-&_9DHSO0K8frfj^*&>MReR94Tl&ViX7b^E?r*C zaUX>@f8pE^5HNI0%gG)0J7|W`BkPOZqWfeS6L*e!BC?3yLZA4FyNiOJHqTnUH^>d8 zP1SCsY}Wk-)^WlQn^Xw)To@?ZX9n{ zOMv%&J0A9Cvjq)d5B)F%-U_WluJBl~!J|-{f0H`_i7nt$aN5uH=ur)xf^5a>#$h)o zcmWzg`Q0mG7w`lNH;~DNr~J9YDE?f8HK?~5>x0NWT%rU~xqgsteDCMd66oY{(6CCJ zISXVYGa{6W8OV$hI_1Q;^i{LvGkhpC!e&yD6tT6$0}yrPWKBELTB*tz?A?*kp^W?p ze=YVUrc^+8vl9S6_GLDh)ejA)Yl$Yb6p1lNNA^2=jx#C~Uugeie^RqNbYFY&SO&Py zNSarB^W>SO@}-mK4+QJoT01(n)H`(F(ouT8bpF!B+23(-c_K@yi9FZs+BdVl)Pmfj zD5@}TyJ0QzDVk8q&F%~gP@c)m3_WYnyU~)#g2l{^!U&NXoLmxzXxs7& zAADH4K{RoDUT=e5Z};xJQ_o>Y*$CsbTxX+_o`@WUxIfr=vyNgZ$>cnPoikvT+V!&R zh|P>_QAh0~c)Wi8iGbP6b^R{hf7mE?TrDkMfAL6;#{ooY)XgQScA`d;WHu6(=%-*O zP^b^;20C;7kwcChR~WwUyh|VhZoDsI_h*vA*D06gR4MgoMQK&`D;9w*(*+^2v{ad` zwGD$RQe{>~G{O+Xn6`B3V91U(`0dit(vzaf61~Y^a`j%ge|qz-X!t8Z zxP3{c?TZ@wEjsWiqyz6VWF-`5o49j7w-#NwMfN>i$iBouxmn^5*7Bw)za3k0vU>_m z=?D0x+dSPJO(;{nNdh#q7Q3zX&Gp_|)B(;@J7Hgr{%;_AKtS?o+;@9YWrgX+XyFDs zc#H1;veW$pLcZFGei#Hwe>zw)ostx{fY1jnpwWH0QTbny1vdftSNqC${4G|J;>!Xi zbcW5|dbv5a4tm;AX|e11P`z?-5e4Y(QuUWH~dzcz{Lo;QePv-cBk{Gf4WqYxAv@wzrWh| ztQd{h2DERrMa*P==M* z5XIcx^163-E!v3MJ=7mvAM#GAkz?-w;S`iF@D}R(t-;f4uY3tbUN=JcJs?6$GKkrc z^y@FCF+?4vdc9k7e^|V@*@C&}Eo*`{|ltTLSDG6ky+yUuS=O8q2S5iLbRUUpDdep;nita;f!n5nan3eYMH0 zpPH$Ba`SOZV0}wqy;JqK1lCV0fwer^Pod=amZXs^5}Rf8UZ*-;z`Boaw7_$KH}t-;z_`l2hN3Q{R$PKdt4|Nk@Shg)^GUN|NF` zbjis+rA;r4{l)39*T1rmn=M!NYl2vGUn}LX&C#eobW5d1Yt&p1ig27t!u@cTo7a&) zXhb8=x)ij1tb=^uMq8ZAT0GzD_`|EYrK{`z|8kN+f1{#$3XVBBkh9i7(OH~YR0e7r zX!s-ob7E;h3h3|-E3oTy4J>pGj5N^(yTIdtd8tJpb)Xh1(Ov+yFLH7rC*KluG+33w zqnWE7&0MWeW}s)JX9#k;4zLA++diOmU_10O^9foI1X2h-UACYoKMNRVdZ>rWqQ@*y uDdbE@CWrt$b=c@bGCx!Rm;=GbBoFkl&7)uxjDk@>F#rI3dsGzwAOirz-brr& delta 52195 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

      $gBJAf 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 diff --git a/docs/public/index.html b/docs/public/index.html index 88df856..9d53d67 100644 --- a/docs/public/index.html +++ b/docs/public/index.html @@ -70,13 +70,13 @@

      Table of Contents

    • Contribution
    • Swift 2
    • -
    • Things To Do
    • +
    • Things To Do
    • License

    Introduction

    @@ -425,7 +425,7 @@

    Docsets: - Public API docset - Full API docset

    -

    Instalation

    +

    Installation

    It works with iOS 8.0 and newer.

    @@ -474,7 +474,7 @@

    Expressions from the files may be used inside localizable files. All the shared expressions for different languages are placed in the same file because there will be just few expressions for every language. Mostly the expression will be defined in base variant because if expression is in base it is also available in every other language too. So, ten is available in pl, but three is not available in base.

    -

    Creating Localizable Files

    +

    Creating file with localization per country

    Localizable file contains translations for specific language. The files might look like below:

    {
    @@ -550,7 +550,7 @@
     

    The last method localizedString(_:intValue:fittingWidth:defaultValue:comment:) allows you to get a localized string for int value. This method calls the above one and just turn the int value into string because all the framework operates on strings.

    I18n.localizedString("cars", intValue: 5)
     
    -

    Contribution and change or feature requests

    +

    Contribution

    Swifternalization is open sourced so everyone may contribute if want to. If you want to develop it just fork the repo, do your work and create pull request. If you have some idea or question feel free to create issue and add proper tag for it.

    @@ -558,7 +558,7 @@

    Swift 2

    Swifternalization supports Swift 2 and works on Xcode 7 beta 4. Please check out swift2 branch.

    -

    Things to do in future releases:

    +

    Things to do in future releases

    • Add more built-in expressions for another countries.
    • From 99b6106a8990f02d86db4a2677210183c1f852aa Mon Sep 17 00:00:00 2001 From: Tomasz Szulc Date: Sun, 2 Aug 2015 11:47:02 +0200 Subject: [PATCH 27/27] Update podspec --- Swifternalization.podspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Swifternalization.podspec b/Swifternalization.podspec index 39363dd..e74494f 100644 --- a/Swifternalization.podspec +++ b/Swifternalization.podspec @@ -1,19 +1,19 @@ Pod::Spec.new do |s| s.name = "Swifternalization" - s.version = "1.1" - s.summary = "Library that helps in localizing apps. It is written in Swift." + s.version = "1.2" + s.summary = "Swift Framework which helps in localizing apps." s.homepage = "https://github.com/tomkowz/Swifternalization" s.license = { :type => 'MIT', :file => 'LICENSE' } - s.author = { "Tomasz Szulc" => "szulctomasz@me.com" } + s.author = { "Tomasz Szulc" => "mail@szulctomasz.com" } s.social_media_url = "http://twitter.com/tomkowz" s.platform = :ios, '8.0' - s.source = { :git => "https://github.com/tomkowz/Swifternalization.git", :tag => "v1.1" } + s.source = { :git => "https://github.com/tomkowz/Swifternalization.git", :tag => "v1.2" } s.documentation_url = 'http://szulctomasz.com/docs/swifternalization/public/'