@@ -2,40 +2,52 @@ 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+ // /// Returns both, Popular and All languages, grouped
20+ // ///
21+ // public let grouped: [[WPComLanguage]]
22+
23+ /// Allow mocking the device language code for testing purposes
24+ private let _deviceLanguageCode : String ?
1925
2026 // MARK: - Public Methods
2127
2228 /// Designated Initializer: will load the languages contained within the `Languages.json` file.
2329 ///
24- public override init ( ) {
30+ private init ( ) {
31+ // Parse the json file
32+ let path = Bundle . wordPressSharedBundle. path ( forResource: " Languages " , ofType: " json " )
33+ let data = try ! Data ( contentsOf: URL ( fileURLWithPath: path!) )
34+ let bundle = try ! JSONDecoder ( ) . decode ( WPComLanguageBundle . self, from: data)
35+
36+ self . popular = bundle. popular
37+ self . all = bundle. all
38+ self . _deviceLanguageCode = nil
39+ }
40+
41+ /// Specifically marked internal for used by test code
42+ internal init ( deviceLanguageCode: String ) {
2543 // 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]
44+ let path = Bundle . wordPressSharedBundle. path ( forResource: " Languages " , ofType: " json " )
45+ let data = try ! Data ( contentsOf: URL ( fileURLWithPath: path!) )
46+ let bundle = try ! JSONDecoder ( ) . decode ( WPComLanguageBundle . self, from: data)
47+
48+ self . popular = bundle. popular
49+ self . all = bundle. all
50+ self . _deviceLanguageCode = deviceLanguageCode. lowercased ( )
3951 }
4052
4153 /// Returns the Human Readable name for a given Language Identifier
@@ -44,7 +56,7 @@ public class WordPressComLanguageDatabase: NSObject {
4456 ///
4557 /// - Returns: A string containing the language name, or an empty string, in case it wasn't found.
4658 ///
47- @ objc public func nameForLanguageWithId( _ languageId: Int ) -> String {
59+ public func nameForLanguageWithId( _ languageId: Int ) -> String {
4860 return find ( id: languageId) ? . name ?? " "
4961 }
5062
@@ -54,34 +66,14 @@ public class WordPressComLanguageDatabase: NSObject {
5466 ///
5567 /// - Returns: The language with the matching Identifier, or nil, in case it wasn't found.
5668 ///
57- public func find( id: Int ) -> Language ? {
69+ public func find( id: Int ) -> WPComLanguage ? {
5870 return all. first ( where: { $0. id == id } )
5971 }
6072
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-
8173 /// Returns the current device language as the corresponding WordPress.com language.
8274 /// If the language is not supported, it returns English.
8375 ///
84- public var deviceLanguage : Language {
76+ public var deviceLanguage : WPComLanguage {
8577 let variants = LanguageTagVariants ( string: deviceLanguageCode)
8678 for variant in variants {
8779 if let match = self . languageWithSlug ( variant) {
@@ -93,101 +85,36 @@ public class WordPressComLanguageDatabase: NSObject {
9385
9486 /// Searches for a WordPress.com language that matches a language tag.
9587 ///
96- fileprivate func languageWithSlug( _ slug: String ) -> Language ? {
88+ fileprivate func languageWithSlug( _ slug: String ) -> WPComLanguage ? {
9789 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 ( )
90+ return all. first { $0. slug == search }
10791 }
10892
109- // MARK: - Public Nested Classes
93+ // MARK: - Private Variables
11094
111- /// Represents a Language supported by WordPress.com
95+ /// The device's current preferred language, or English if there's no preferred language.
11296 ///
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
97+ /// Specifically marked internal for used by test code
98+ internal var deviceLanguageCode : String {
12199
122- /// Language's Slug String
123- ///
124- public let slug : String
125-
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
130- }
131-
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
100+ // Return the mocked language code, if set
101+ if let _deviceLanguageCode {
102+ return _deviceLanguageCode
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- }
105+ guard let preferredLanguage = Locale . preferredLanguages. first else {
106+ return " en "
155107 }
156108
157- public static func == ( lhs: Language , rhs: Language ) -> Bool {
158- return lhs. id == rhs. id
159- }
109+ return preferredLanguage. lowercased ( )
160110 }
161111
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-
173112 // (@koke 2016-04-29) I'm not sure how correct this mapping is, but it matches
174113 // what we do for the app translations, so they will at least be consistent
175114 fileprivate let languageCodeReplacements : [ String : String ] = [
176115 " zh-hans " : " zh-cn " ,
177116 " zh-hant " : " zh-tw "
178117 ]
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- }
191118}
192119
193120/// Provides a sequence of language tags from the specified string, from more to less specific
@@ -210,3 +137,48 @@ private struct LanguageTagVariants: Sequence {
210137 }
211138 }
212139}
140+
141+ // MARK: - Public Nested Classes
142+
143+ public struct WPComLanguageBundle : Codable {
144+ let popular : [ WPComLanguage ]
145+ let others : [ WPComLanguage ]
146+
147+ enum CodingKeys : String , CodingKey {
148+ case popular = " popular "
149+ case others = " all "
150+ }
151+
152+ var all : [ WPComLanguage ] {
153+ ( popular + others) . sorted { $0. name < $1. name }
154+ }
155+ }
156+
157+ /// Represents a Language supported by WordPress.com
158+ ///
159+ public struct WPComLanguage : Codable , Equatable {
160+
161+ enum CodingKeys : String , CodingKey {
162+ case id = " i "
163+ case name = " n "
164+ case slug = " s "
165+ }
166+
167+ /// Language Unique Identifier
168+ ///
169+ public let id : Int
170+
171+ /// Human readable Language name
172+ ///
173+ public let name : String
174+
175+ /// Language's Slug String
176+ ///
177+ public let slug : String
178+
179+ /// Localized description for the current language
180+ ///
181+ public var description : String {
182+ return ( Locale . current as NSLocale ) . displayName ( forKey: NSLocale . Key. identifier, value: slug) ?? name
183+ }
184+ }
0 commit comments