@@ -2,40 +2,48 @@ import Foundation
22
33/// This helper class allows us to map WordPress.com LanguageID's into human readable language strings.
44///
5- public class WordPressComLanguageDatabase : NSObject {
5+ public struct WordPressComLanguageDatabase {
6+
7+ public static let shared = WordPressComLanguageDatabase ( )
8+
69 // MARK: - Public Properties
710
811 /// Languages considered 'popular'
912 ///
10- public let popular : [ Language ]
13+ public let popular : [ WPComLanguage ]
1114
1215 /// Every supported language
1316 ///
14- public let all : [ Language ]
17+ public let all : [ WPComLanguage ]
1518
16- /// Returns both, Popular and All languages, grouped
17- ///
18- public let grouped : [ [ Language ] ]
19+ /// Allow mocking the device language code for testing purposes
20+ private let _deviceLanguageCode : String ?
1921
2022 // MARK: - Public Methods
2123
2224 /// Designated Initializer: will load the languages contained within the `Languages.json` file.
2325 ///
24- public override init ( ) {
26+ private init ( ) {
27+ // Parse the json file
28+ let path = Bundle . wordPressSharedBundle. path ( forResource: " Languages " , ofType: " json " )
29+ let data = try ! Data ( contentsOf: URL ( fileURLWithPath: path!) )
30+ let bundle = try ! JSONDecoder ( ) . decode ( WPComLanguageBundle . self, from: data)
31+
32+ self . popular = bundle. popular
33+ self . all = bundle. all
34+ self . _deviceLanguageCode = nil
35+ }
36+
37+ /// Specifically marked internal for used by test code
38+ internal init ( deviceLanguageCode: String ) {
2539 // Parse the json file
26- let path = Bundle . wordPressSharedBundle. path ( forResource: filename, ofType: " json " )
27- let raw = try ! Data ( contentsOf: URL ( fileURLWithPath: path!) )
28- let parsed = try ! JSONSerialization . jsonObject ( with: raw, options: [ . mutableContainers, . mutableLeaves] ) as? NSDictionary
29-
30- // Parse All + Popular: All doesn't contain Popular. Otherwise the json would have dupe data. Right?
31- let parsedAll = Language . fromArray ( parsed![ Keys . all] as! [ [ String : Any ] ] )
32- let parsedPopular = Language . fromArray ( parsed![ Keys . popular] as! [ [ String : Any ] ] )
33- let merged = parsedAll + parsedPopular
34-
35- // Done!
36- popular = parsedPopular
37- all = merged. sorted { $0. name < $1. name }
38- grouped = [ popular] + [ all]
40+ let path = Bundle . wordPressSharedBundle. path ( forResource: " Languages " , ofType: " json " )
41+ let data = try ! Data ( contentsOf: URL ( fileURLWithPath: path!) )
42+ let bundle = try ! JSONDecoder ( ) . decode ( WPComLanguageBundle . self, from: data)
43+
44+ self . popular = bundle. popular
45+ self . all = bundle. all
46+ self . _deviceLanguageCode = deviceLanguageCode. lowercased ( )
3947 }
4048
4149 /// Returns the Human Readable name for a given Language Identifier
@@ -44,7 +52,7 @@ public class WordPressComLanguageDatabase: NSObject {
4452 ///
4553 /// - Returns: A string containing the language name, or an empty string, in case it wasn't found.
4654 ///
47- @ objc public func nameForLanguageWithId( _ languageId: Int ) -> String {
55+ public func nameForLanguageWithId( _ languageId: Int ) -> String {
4856 return find ( id: languageId) ? . name ?? " "
4957 }
5058
@@ -54,34 +62,14 @@ public class WordPressComLanguageDatabase: NSObject {
5462 ///
5563 /// - Returns: The language with the matching Identifier, or nil, in case it wasn't found.
5664 ///
57- public func find( id: Int ) -> Language ? {
65+ public func find( id: Int ) -> WPComLanguage ? {
5866 return all. first ( where: { $0. id == id } )
5967 }
6068
61- /// Returns the current device language as the corresponding WordPress.com language ID.
62- /// If the language is not supported, it returns 1 (English).
63- ///
64- /// This is a wrapper for Objective-C, Swift code should use deviceLanguage directly.
65- ///
66- @objc ( deviceLanguageId)
67- public func deviceLanguageIdNumber( ) -> NSNumber {
68- return NSNumber ( value: deviceLanguage. id)
69- }
70-
71- /// Returns the slug string for the current device language.
72- /// If the language is not supported, it returns "en" (English).
73- ///
74- /// This is a wrapper for Objective-C, Swift code should use deviceLanguage directly.
75- ///
76- @objc ( deviceLanguageSlug)
77- public func deviceLanguageSlugString( ) -> String {
78- return deviceLanguage. slug
79- }
80-
8169 /// Returns the current device language as the corresponding WordPress.com language.
8270 /// If the language is not supported, it returns English.
8371 ///
84- public var deviceLanguage : Language {
72+ public var deviceLanguage : WPComLanguage {
8573 let variants = LanguageTagVariants ( string: deviceLanguageCode)
8674 for variant in variants {
8775 if let match = self . languageWithSlug ( variant) {
@@ -93,101 +81,36 @@ public class WordPressComLanguageDatabase: NSObject {
9381
9482 /// Searches for a WordPress.com language that matches a language tag.
9583 ///
96- fileprivate func languageWithSlug( _ slug: String ) -> Language ? {
84+ fileprivate func languageWithSlug( _ slug: String ) -> WPComLanguage ? {
9785 let search = languageCodeReplacements [ slug] ?? slug
98-
99- // Use lazy evaluation so we stop filtering as soon as we got the first match
100- return all. lazy. filter ( { $0. slug == search } ) . first
101- }
102-
103- /// Overrides the device language. For testing purposes only.
104- ///
105- @objc func _overrideDeviceLanguageCode( _ code: String ) {
106- deviceLanguageCode = code. lowercased ( )
86+ return all. first { $0. slug == search }
10787 }
10888
109- // MARK: - Public Nested Classes
89+ // MARK: - Private Variables
11090
111- /// Represents a Language supported by WordPress.com
91+ /// The device's current preferred language, or English if there's no preferred language.
11292 ///
113- public class Language : Equatable {
114- /// Language Unique Identifier
115- ///
116- public let id : Int
117-
118- /// Human readable Language name
119- ///
120- public let name : String
121-
122- /// Language's Slug String
123- ///
124- public let slug : String
93+ /// Specifically marked internal for used by test code
94+ internal var deviceLanguageCode : String {
12595
126- /// Localized description for the current language
127- ///
128- public var description : String {
129- return ( Locale . current as NSLocale ) . displayName ( forKey: NSLocale . Key. identifier, value: slug) ?? name
96+ // Return the mocked language code, if set
97+ if let _deviceLanguageCode {
98+ return _deviceLanguageCode
13099 }
131100
132- /// Designated initializer. Will fail if any of the required properties is missing
133- ///
134- init ? ( dict: [ String : Any ] ) {
135- guard let unwrappedId = ( dict [ Keys . identifier] as? NSNumber ) ? . intValue,
136- let unwrappedSlug = dict [ Keys . slug] as? String ,
137- let unwrappedName = dict [ Keys . name] as? String else {
138- id = Int . min
139- name = String ( )
140- slug = String ( )
141- return nil
142- }
143-
144- id = unwrappedId
145- name = unwrappedName
146- slug = unwrappedSlug
101+ guard let preferredLanguage = Locale . preferredLanguages. first else {
102+ return " en "
147103 }
148104
149- /// Given an array of raw languages, will return a parsed array.
150- ///
151- public static func fromArray( _ array: [ [ String : Any ] ] ) -> [ Language ] {
152- return array. compactMap {
153- return Language ( dict: $0)
154- }
155- }
156-
157- public static func == ( lhs: Language , rhs: Language ) -> Bool {
158- return lhs. id == rhs. id
159- }
105+ return preferredLanguage. lowercased ( )
160106 }
161107
162- // MARK: - Private Variables
163-
164- /// The device's current preferred language, or English if there's no preferred language.
165- ///
166- fileprivate lazy var deviceLanguageCode : String = {
167- return NSLocale . preferredLanguages. first? . lowercased ( ) ?? " en "
168- } ( )
169-
170- // MARK: - Private Constants
171- fileprivate let filename = " Languages "
172-
173108 // (@koke 2016-04-29) I'm not sure how correct this mapping is, but it matches
174109 // what we do for the app translations, so they will at least be consistent
175110 fileprivate let languageCodeReplacements : [ String : String ] = [
176111 " zh-hans " : " zh-cn " ,
177112 " zh-hant " : " zh-tw "
178113 ]
179-
180- // MARK: - Private Nested Structures
181-
182- /// Keys used to parse the raw languages.
183- ///
184- fileprivate struct Keys {
185- static let popular = " popular "
186- static let all = " all "
187- static let identifier = " i "
188- static let slug = " s "
189- static let name = " n "
190- }
191114}
192115
193116/// Provides a sequence of language tags from the specified string, from more to less specific
@@ -210,3 +133,48 @@ private struct LanguageTagVariants: Sequence {
210133 }
211134 }
212135}
136+
137+ // MARK: - Public Nested Classes
138+
139+ public struct WPComLanguageBundle : Codable {
140+ let popular : [ WPComLanguage ]
141+ let others : [ WPComLanguage ]
142+
143+ enum CodingKeys : String , CodingKey {
144+ case popular = " popular "
145+ case others = " all "
146+ }
147+
148+ var all : [ WPComLanguage ] {
149+ ( popular + others) . sorted { $0. name < $1. name }
150+ }
151+ }
152+
153+ /// Represents a Language supported by WordPress.com
154+ ///
155+ public struct WPComLanguage : Codable , Equatable {
156+
157+ enum CodingKeys : String , CodingKey {
158+ case id = " i "
159+ case name = " n "
160+ case slug = " s "
161+ }
162+
163+ /// Language Unique Identifier
164+ ///
165+ public let id : Int
166+
167+ /// Human readable Language name
168+ ///
169+ public let name : String
170+
171+ /// Language's Slug String
172+ ///
173+ public let slug : String
174+
175+ /// Localized description for the current language
176+ ///
177+ public var description : String {
178+ return ( Locale . current as NSLocale ) . displayName ( forKey: NSLocale . Key. identifier, value: slug) ?? name
179+ }
180+ }
0 commit comments