diff --git a/Package.swift b/Package.swift index 4d907e4..afd910f 100644 --- a/Package.swift +++ b/Package.swift @@ -16,5 +16,6 @@ let package = Package( .target(name: "W3WSwiftCore", dependencies: []), .testTarget(name: "w3w-swift-typesTests", dependencies: ["W3WSwiftCore"]), .testTarget(name: "w3w-swift-Tests", dependencies: ["W3WSwiftCore"]), + .testTarget(name: "languages-Tests", dependencies: ["W3WSwiftCore"]) ] ) diff --git a/Sources/W3WSwiftCore/Localization/W3WTranslationsProtocol.swift b/Sources/W3WSwiftCore/Localization/W3WTranslationsProtocol.swift index 259726a..8e45cd1 100644 --- a/Sources/W3WSwiftCore/Localization/W3WTranslationsProtocol.swift +++ b/Sources/W3WSwiftCore/Localization/W3WTranslationsProtocol.swift @@ -6,7 +6,7 @@ // -public protocol W3WTranslationsProtocol { +public protocol W3WTranslationsProtocol: W3WAvailableLanguageProtocol { /// given a translation id return the translation for the given device locale /// - Parameters: @@ -21,6 +21,15 @@ public protocol W3WTranslationsProtocol { } +public protocol W3WLanguageSelectionProtocol { + /// to set RFCLanguage + func set(language: W3WRfcLanguage) +} + +/// list out all available RFCLanguages +public protocol W3WAvailableLanguageProtocol { + func availableLanguages() -> [W3WRfcLanguage] +} public extension W3WTranslationsProtocol { @@ -42,3 +51,10 @@ public extension W3WTranslationsProtocol { return String(format: localized, arguments) } } + +public extension W3WAvailableLanguageProtocol { + /// default convenience func for available languages, should override when in need + func availableLanguages() -> [W3WRfcLanguage] { + return [] + } +} diff --git a/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WLanguage.swift b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WLanguage.swift new file mode 100644 index 0000000..7b379e8 --- /dev/null +++ b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WLanguage.swift @@ -0,0 +1,29 @@ +// +// File.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 16/9/25. +// + +import Foundation + +public extension W3WRfcLanguageProtocol { + /// convert W3WRfcLanguage to W3WLanguage + func toW3wLanguage() -> W3WLanguage? { + guard let code, !code.isEmpty else { + return nil + } + return W3WBaseLanguage(locale: self.shortIdentifier) + } +} + +@available(iOS 13.0.0, *) +@available(watchOS 6.0.0, *) +extension W3WBaseLanguage: W3WRfcLanguageConvertable { + /// convert W3WBaseLanguage to any W3WRfcLanguageProtocol + public func toRfcLanguage() -> some W3WRfcLanguageProtocol { + let parsedLocale = Locale(identifier: locale) + return W3WRfcLanguage(locale: parsedLocale) + } +} + diff --git a/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WSdkLanguage.swift b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WSdkLanguage.swift new file mode 100644 index 0000000..f1937ac --- /dev/null +++ b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage+W3WSdkLanguage.swift @@ -0,0 +1,31 @@ +// +// File.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 21/11/25. +// + +import Foundation +#if canImport(w3w) +import w3w + +public extension W3WRfcLanguageProtocol { + /// convert W3WRfcLanguage to W3WSdkLanguage + func toW3wSdkLanguage() throws -> W3WSdkLanguage? { + guard let code, !code.isEmpty else { + return nil + } + return try W3WSdkLanguage(shortIdentifier) + } +} + +@available(iOS 13.0.0, *) +@available(watchOS 6.0.0, *) +extension W3WSdkLanguage: W3WRfcLanguageConvertable { + /// convert W3WSdkLanguage to any W3WRfcLanguageProtocol + public func toRfcLanguage() -> some W3WRfcLanguageProtocol { + return W3WRfcLanguage(locale: Locale(identifier: locale)) + } +} +#endif + diff --git a/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.md b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.md new file mode 100644 index 0000000..da2e337 --- /dev/null +++ b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.md @@ -0,0 +1,29 @@ +# W3WRfcLanguage + +A lightweight representation of an RFC 5646 language tag used in w3w-swift-core. It models: + +- language code (ISO 639; prefer 2-letter when available) +- optional script code (4 letters, Titlecase) +- optional region code (2 letters or 3 digits) + +It provides: +- A protocol `W3WRfcLanguageProtocol` that any type can conform to +- A concrete struct `W3WRfcLanguage` +- An extension that makes `Locale.Language` conform to the protocol on iOS 16+/watchOS 9+ +- Utilities to build identifiers and parse from strings + +## Availability + +- `Locale.Language` conformance requires iOS 16+ or watchOS 9+. +- `W3WRfcLanguage` itself is available on all supported platforms; when running on iOS < 16/watchOS < 9, script extraction from `Locale` may be unavailable and remain `nil`. + +## Types + +### W3WRfcLanguageProtocol +```swift +public protocol W3WRfcLanguageProtocol: Equatable { + var code: String? { get } // ISO 639 language code + var scriptCode: String? { get } // 4-letter Titlecase script code (e.g. "Latn", "Hans") + var regionCode: String? { get } // 2-letter or 3-digit region (e.g. "US", "419") +} + diff --git a/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.swift b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.swift new file mode 100644 index 0000000..2a14558 --- /dev/null +++ b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguage.swift @@ -0,0 +1,150 @@ +// +// File.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 15/8/25. +// + +import Foundation + +/// language[-script][-region] +/// - language = ISO 639 +/// Prefer ISO 639-1 (2-letter) whenever it exists +/// Only fall back to ISO 639-2 (3-letter) if a language has no 2-letter code +/// - script (optional, 4 letters, titlecased) +/// - region (optional, 2 letters or 3-digit number) +public struct W3WRfcLanguage: W3WRfcLanguageProtocol { + /// default language is "en". name's default can be changed externally to use a different language by an app if nessesary + public static var `default` = W3WRfcLanguage(code: "en") + + public var code: String? + public var scriptCode: String? + public var regionCode: String? + + public init(locale: Locale) { + if #available(iOS 16, *), #available(watchOS 9, *) { + // from iOS 16 supports getting script + self.init(from: locale.language) + } else { + // no script for iOS < 16 + self.init(code: locale.languageCode, + scriptCode: locale.scriptCode, + regionCode: locale.regionCode) + } + } + + public init( + code: String? = nil, + scriptCode: String? = nil, + regionCode: String? = nil + ) { + self.code = code + self.scriptCode = scriptCode + self.regionCode = regionCode + } + + @available(iOS 16, *) + @available(watchOS 9, *) + public init(from language: Locale.Language) { + self.code = language.code + self.scriptCode = language.scriptCode + self.regionCode = language.regionCode + } +} + +public extension W3WRfcLanguage { + /// Initialize from a string + init(from string: String) { + // Normalize separators + let normalized = string + .trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: "_", with: "-") + + // Handle “Base” or empty strings, check if string is a valid locale string + guard !normalized.isEmpty, string.isValidLocale else { + self.init(code: nil, scriptCode: nil, regionCode: nil) + return + } + + if #available(iOS 16, *), #available(watchOS 9, *) { + self.init(from: Locale.Language(identifier: normalized)) + } else { + // Split into parts (e.g. zh-Hans-CN → ["zh", "Hans", "CN"]) + let parts = normalized.split(separator: "-").map(String.init) + + var langCode: String? + var script: String? + var region: String? + + if parts.count > 0 { + langCode = parts[0] + } + + if parts.count > 1 { + let part = parts[1] + // Detect if this is a script code (Titlecase and 4 letters) + if part.count == 4, part.first?.isUppercase == true { + script = part + if parts.count > 2 { + region = parts[2] + } + } else { + region = part + } + } + + self.init(code: langCode, scriptCode: script, regionCode: region) + } + } +} + +public extension W3WRfcLanguageProtocol { + /// full version: code - script - region + var identifier: String { + return [code, scriptCode, regionCode] + .compactMap { $0 } + .joined(separator: "-") + } + /// short : code - region + var shortIdentifier: String { + return [code, regionCode] + .compactMap { $0 } + .joined(separator: "-") + } +} + +@available(watchOS 9, *) +@available(iOS 16, *) +extension Locale.Language : W3WRfcLanguageProtocol { + public var code: String? { + return languageCode?.identifier + } + + public var scriptCode: String? { + return self.script?.identifier + } + + public var regionCode: String? { + return region?.identifier + } +} + +extension W3WRfcLanguage { + public var nativeName: String? { + return LanguageUtils.getLanguageName(forLocale: identifier, inLocale: identifier) + } + + public var name: String? { + return LanguageUtils.getLanguageName(forLocale: identifier, inLocale: Self.default.identifier) + } + + /// get name of the language in any particular locale + func name(in locale: String) -> String? { + return LanguageUtils.getLanguageName(forLocale: identifier, inLocale: locale) + } + + /// get name of the language in any other language + func name(in language: W3WRfcLanguage) -> String? { + return name(in: language.identifier) + } +} diff --git a/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguageProtocol.swift b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguageProtocol.swift new file mode 100644 index 0000000..41ea5bb --- /dev/null +++ b/Sources/W3WSwiftCore/RfcLanguage/W3WRfcLanguageProtocol.swift @@ -0,0 +1,20 @@ +// +// File.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 21/11/25. +// + +import Foundation + +public protocol W3WRfcLanguageProtocol: Equatable { + var code: String? { get } + var scriptCode: String? { get } + var regionCode: String? { get } +} + +/// protocol to convert any language to W3WRfcLanguage +public protocol W3WRfcLanguageConvertable { + associatedtype Language: W3WRfcLanguageProtocol + func toRfcLanguage() -> Language +} diff --git a/Sources/W3WSwiftCore/Types/Util/LanguageUtils.swift b/Sources/W3WSwiftCore/Types/Util/LanguageUtils.swift new file mode 100644 index 0000000..cd6be1d --- /dev/null +++ b/Sources/W3WSwiftCore/Types/Util/LanguageUtils.swift @@ -0,0 +1,35 @@ +// +// LanguageUtils.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 14/11/25. +// +import Foundation + +public class LanguageUtils { + /// gets the langauge name for a locale in the language specified by 'inLocale' + /// Parameters + /// - forLocale: locale to find the language of + /// - inLocale: locale to translate the langauge name into + public static func getLanguageName(forLocale: String, inLocale: String) -> String? { + let inLocaleObj = NSLocale(localeIdentifier: inLocale) + let forLocaleObj = NSLocale(localeIdentifier: forLocale) + + var languageName = inLocaleObj.localizedString(forLanguageCode: forLocale) ?? "" + + if forLocale.count > 2 { + if let countryCode = forLocaleObj.countryCode { + languageName += " (" + (inLocaleObj.localizedString(forCountryCode: countryCode) ?? "") + ")" + } + } + + return languageName + } +} + +extension String { + var isValidLocale: Bool { + let normalized = self.replacingOccurrences(of: "-", with: "_") + return Locale.availableIdentifiers.contains(normalized) + } +} diff --git a/Sources/W3WSwiftCore/Types/Values/W3WLanguage.swift b/Sources/W3WSwiftCore/Types/Values/W3WLanguage.swift index acbf9e5..cbd2b4d 100644 --- a/Sources/W3WSwiftCore/Types/Values/W3WLanguage.swift +++ b/Sources/W3WSwiftCore/Types/Values/W3WLanguage.swift @@ -32,12 +32,19 @@ public protocol W3WLanguage { extension W3WLanguage { + /// To get the first 2 (or 3 max) in a locale string separated by "-", i.e: en-US / en + static func extractCode(from string: String) -> String { + let parts = string.split(separator: "-").map(String.init) + if !parts.isEmpty { + return parts[0] + } + return "" + } public static func getLanguageCode(from: String) -> String { - return String(from.prefix(2)) + return extractCode(from: from) } - public static func getLanguageRegion(from: String) -> String { if from.count == 5 { if String(Array(from)[2]) == "_" { @@ -51,35 +58,14 @@ extension W3WLanguage { return "" } - - /// gets the langauge name for a locale in the language specified by 'inLocale' - /// Parameters - /// - forLocale: locale to find the language of - /// - inLocale: locale to translate the langauge name into - public static func getLanguageName(forLocale: String, inLocale: String) -> String? { - let inLocaleObj = NSLocale(localeIdentifier: inLocale) - let forLocaleObj = NSLocale(localeIdentifier: forLocale) - - var langaugeName = inLocaleObj.localizedString(forLanguageCode: forLocale) ?? "" - - if forLocale.count > 2 { - if let countryCode = forLocaleObj.countryCode { - langaugeName += " (" + (inLocaleObj.localizedString(forCountryCode: countryCode) ?? "") + ")" - } - } - - return langaugeName - } - - public func getDeviceLanguages() -> [W3WLanguage] { var langauges = [W3WLanguage]() for locale_code in NSLocale.availableLocaleIdentifiers.sorted() { let language = W3WBaseLanguage( locale: locale_code, - name: Self.getLanguageName(forLocale: locale_code, inLocale: self.locale) ?? "", - nativeName: Self.getLanguageName(forLocale: locale_code, inLocale: locale_code) ?? "" + name: LanguageUtils.getLanguageName(forLocale: locale_code, inLocale: self.locale) ?? "", + nativeName: LanguageUtils.getLanguageName(forLocale: locale_code, inLocale: locale_code) ?? "" ) langauges.append(language) } @@ -133,19 +119,19 @@ public struct W3WBaseLanguage: W3WLanguage, ExpressibleByStringLiteral { /// - nativeName: Name of the langiage in that language public init(code: String, name: String = "", nativeName: String = "") { if name == "" { - self.name = Self.getLanguageName(forLocale: code, inLocale: "en") + self.name = LanguageUtils.getLanguageName(forLocale: code, inLocale: "en") } else { self.name = name } if nativeName == "" { - self.nativeName = Self.getLanguageName(forLocale: code, inLocale: code) + self.nativeName = LanguageUtils.getLanguageName(forLocale: code, inLocale: code) } else { self.nativeName = nativeName } - self.code = String(code.prefix(2)) - self.locale = String(code.prefix(2)) + self.code = Self.extractCode(from: code) + self.locale = Self.extractCode(from: code) } @@ -156,26 +142,41 @@ public struct W3WBaseLanguage: W3WLanguage, ExpressibleByStringLiteral { /// - nativeName: Name of the langiage in that language public init(locale: String, name: String = "", nativeName: String = "") { if name == "" { - self.name = Self.getLanguageName(forLocale: locale, inLocale: "en") + self.name = LanguageUtils.getLanguageName(forLocale: locale, inLocale: "en") } else { self.name = name } if nativeName == "" { - self.nativeName = Self.getLanguageName(forLocale: locale, inLocale: locale) + self.nativeName = LanguageUtils.getLanguageName(forLocale: locale, inLocale: locale) } else { self.nativeName = nativeName } - self.code = String(locale.prefix(2)) + self.code = Self.extractCode(from: locale) self.locale = locale } + public init(code: String, locale: String, name: String = "", nativeName: String = "") { + self.code = Self.extractCode(from: code) + self.locale = Self.extractCode(from: locale) + if name == "" { + self.name = LanguageUtils.getLanguageName(forLocale: locale, inLocale: "en") + } else { + self.name = name + } + + if nativeName == "" { + self.nativeName = LanguageUtils.getLanguageName(forLocale: locale, inLocale: locale) + } else { + self.nativeName = nativeName + } + } + public init(stringLiteral: String) { self.init(locale: stringLiteral) } - static public let english = W3WBaseLanguage(locale: "en_gb") } diff --git a/Tests/languages-Tests/W3wRfcLanguage_w3wLanguage_tests.swift b/Tests/languages-Tests/W3wRfcLanguage_w3wLanguage_tests.swift new file mode 100644 index 0000000..62c4c6d --- /dev/null +++ b/Tests/languages-Tests/W3wRfcLanguage_w3wLanguage_tests.swift @@ -0,0 +1,145 @@ +// +// W3wRfcLanguage_w3wLanguage_tests.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 21/11/25. +// + +import Testing +@testable import W3WSwiftCore +import w3w + +@Suite +struct W3wRfcLanguage_W3wLanguage_Tests { + + @Test(arguments: ["fr", "en", "ja", "ko", "pt", "de"]) + func convertLanguageToRfcLanguage_LanguageOnly_Success(lang: String) { + let testLang = W3WBaseLanguage(locale: lang) + let rfcLang = testLang.toRfcLanguage() + + #expect(rfcLang.regionCode == nil) + #expect(rfcLang.code == testLang.code) + + let rfcLang2 = W3WRfcLanguage(from: lang) + #expect(rfcLang2.code == rfcLang.code) + #expect(rfcLang2.regionCode == rfcLang.regionCode) + #expect(rfcLang2.scriptCode == rfcLang.scriptCode) + } + + @Test(arguments: [ + "en-US", "en-GB", "fr-CA", "pt-BR", "pt-PT", + "es-MX", "es-ES", "vi-VN", "ja-JP", "ko-KR" + ]) + func convertLanguageToRfcLangage_LanguageRegion_Success(lang: String) { + let testLang = W3WBaseLanguage(locale: lang) + let rfcLang = testLang.toRfcLanguage() + + #expect(rfcLang.regionCode != nil) + #expect(rfcLang.code == testLang.code) + + let rfcLang2 = W3WRfcLanguage(from: lang) + #expect(rfcLang2.code == rfcLang.code) + #expect(rfcLang2.regionCode == rfcLang.regionCode) + #expect(rfcLang2.scriptCode == rfcLang.scriptCode) + + } + + @Test(arguments: [ + "zh-Hans", "zh-Hant", "sr-Cyrl", "sr-Latn", + "uz-Cyrl", "uz-Latn" + ]) + func convertLanguageToRfcLangage_LanguageScript_Success(lang: String) { + let testLang = W3WBaseLanguage(locale: lang) + let rfcLang = testLang.toRfcLanguage() + + #expect(rfcLang.regionCode == nil) + if #available(iOS 16, *) { + #expect(rfcLang.scriptCode != nil) + } + + let rfcLang2 = W3WRfcLanguage(from: lang) + #expect(rfcLang2.code == rfcLang.code) + #expect(rfcLang2.regionCode == rfcLang.regionCode) + if #available(iOS 16, *) { + #expect(rfcLang2.scriptCode == rfcLang.scriptCode) + } + } + + @Test(arguments: [ + "zh-Hans-CN", "zh-Hant-TW", "sr-Cyrl-RS", + "sr-Latn-BA", "uz-Latn-UZ" + ]) + func convertLanguageToRfcLangage_LanguageScriptRegion_Success(lang: String) { + let testLang = W3WBaseLanguage(locale: lang) + let rfcLang = testLang.toRfcLanguage() + + #expect(rfcLang.regionCode != nil) + if #available(iOS 16, *) { + #expect(rfcLang.scriptCode != nil) + } + #expect(rfcLang.code == testLang.code) + + let rfcLang2 = W3WRfcLanguage(from: lang) + #expect(rfcLang2.code == rfcLang.code) + #expect(rfcLang2.regionCode == rfcLang.regionCode) + if #available(iOS 16, *) { + #expect(rfcLang2.scriptCode == rfcLang.scriptCode) + } + } + + @Test(arguments: [ + "vi", "af", "id", "ms", "de", + "en-AU", "en-GB", "en-US", "en-IN", + "es-ES", "es-MX", "sw", "nl", + "pt-BR", "pt-PT", "ro", "fi", + "sv", "xh", "zu", "bg", "mn", + "ru", "kk", "ko", "ja", "ar" + ]) + func convertLanguageToRfcLangage_CurrentLanguageList_Success(lang: String) { + let rfcLang = W3WRfcLanguage(from: lang) + + #expect(rfcLang.code != nil) + if #available(iOS 16, *) { + #expect(rfcLang.scriptCode != nil) + } + if lang.count > 3 { + #expect(rfcLang.regionCode != nil) + } + } + + @Test(arguments: [ + "en_US", "fr_FR", "pt_BR", "zh_CN", "zh_TW", + "sr_Cyrl_RS" + ]) + func convertLanguageToRfcLangage_UnderscoreFormats_AppleLegacy_Success(lang: String) { + let rfcLang = W3WRfcLanguage(from: lang) + let w3wLang = rfcLang.toW3wLanguage() + + #expect(w3wLang?.code == rfcLang.code) + } + + @Test(arguments: [ + "haw-US", + "yue-Hant", + "gsw-CH" + ]) + func convertLanguageToRfcLanguage_3CharactersCode_Success(lang: String) { + let rfcLang = W3WRfcLanguage(from: lang) + #expect(rfcLang.code?.count == 3) + } + + @Test(arguments: [ + "xx", "xxx", "123", + "en-ABCDE", "sr-OOO", "hr-12ab", + "en-USS", "fr-1234", "es-1X", "zh-Hans-CNN", + "CN-zh", "Cyrl-sr", "US-en", + "en-US-extra", "sr-Cyrl-RS-foo", + "-en", "en--US", "en-", "en- -US" + ]) + func convertLanguageToRfcLanguage_InvalidLanguage_Failure(lang: String) { + let rfcLang = W3WRfcLanguage(from: lang) + #expect(rfcLang.code == nil) + #expect(rfcLang.scriptCode == nil) + #expect(rfcLang.regionCode == nil) + } +} diff --git a/Tests/languages-Tests/W3wRfcLanguage_w3wSdkLanguage_tests.swift b/Tests/languages-Tests/W3wRfcLanguage_w3wSdkLanguage_tests.swift new file mode 100644 index 0000000..fcafa0b --- /dev/null +++ b/Tests/languages-Tests/W3wRfcLanguage_w3wSdkLanguage_tests.swift @@ -0,0 +1,89 @@ +// +// w3wRfcLanguage_w3wSdkLanguage_tests.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 21/11/25. +// + +import Testing +@testable import W3WSwiftCore +import w3w + +@Suite +struct W3wRfcLanguage_W3wSdkLanguage_Tests { + + @Test + func convertSdkLanguageToRfcLanguage_Success() throws { + let sdkLanguage = try W3WSdkLanguage("fr_fr") + let convertedRfcLanguage = try #require(sdkLanguage.toRfcLanguage() as? W3WRfcLanguage) + let rfcLanguage = W3WRfcLanguage(from: "fr_FR") + + #expect(convertedRfcLanguage == rfcLanguage) + #expect(convertedRfcLanguage.code == "fr") + #expect(convertedRfcLanguage.regionCode == "FR") + + if #available(iOS 16, *) { + #expect(convertedRfcLanguage.scriptCode != nil) + } + } + + @Test + func convertSdkLanguageToRfcLanguage_CodeOnly_Success() throws { + let sdkLanguage = try W3WSdkLanguage("ja") + let convertedRfcLanguage = try #require(sdkLanguage.toRfcLanguage() as? W3WRfcLanguage) + let rfcLanguage = W3WRfcLanguage(from: "ja") + + #expect(convertedRfcLanguage == rfcLanguage) + #expect(convertedRfcLanguage.code == "ja") + #expect(convertedRfcLanguage.regionCode == nil) + + if #available(iOS 16, *) { + #expect(convertedRfcLanguage.scriptCode != nil) + } + } + + @Test + func convertRfcLanguageToSdkLanguage_Success() throws { + let rfcLanguage = W3WRfcLanguage(from: "zh-Hans") + let sdkLanguage = try rfcLanguage.toW3wSdkLanguage() + + #expect(sdkLanguage != nil) + #expect(sdkLanguage?.code == "zh") + } + + @Test + func convertRfcLanguageToSdkLanguage_Code3Characters_Failure() { + let rfcLanguage = W3WRfcLanguage(from: "yue-Hans") + + #expect(throws: Error.self) { + _ = try rfcLanguage.toW3wSdkLanguage() + } + } + + @Test + func convertSdkLanguageToRfcLanguage_CodeScriptRegion_Failure() { + let rfcLanguage = W3WRfcLanguage(from: "zh-Hans-CN") + + #expect(throws: Error.self) { + _ = try rfcLanguage.toW3wSdkLanguage() + } + } + + /// Compare W3WBaseLanguage & W3WSdkLanguage by converting to W3WRfcLanguage + @Test + func matchingRfcLanguage() throws { + let string = "sv-SE" + let sdkString = "sv_se" + + let w3wBaseLanguage = W3WBaseLanguage(locale: string) + let w3wSdkLanguage = try W3WSdkLanguage(sdkString) + + let firstLang = try #require(w3wBaseLanguage.toRfcLanguage() as? W3WRfcLanguage) + let secondLang = try #require(w3wSdkLanguage.toRfcLanguage() as? W3WRfcLanguage) + + #expect(firstLang == secondLang) + #expect(firstLang.code == secondLang.code) + #expect(firstLang.regionCode == secondLang.regionCode) + } +} + diff --git a/Tests/languages-Tests/w3wRfcLanguage_tests.swift b/Tests/languages-Tests/w3wRfcLanguage_tests.swift new file mode 100644 index 0000000..940f2aa --- /dev/null +++ b/Tests/languages-Tests/w3wRfcLanguage_tests.swift @@ -0,0 +1,29 @@ +// +// w3wRfcLanguage_tests.swift +// w3w-swift-core +// +// Created by Kaley Nguyen on 3/12/25. +// + +import Testing +@testable import W3WSwiftCore +import w3w + +@Suite +struct w3wRfcLanguage_tests { + + @Test func initRfcLanguage() async throws { + let lang = W3WRfcLanguage(locale: Locale(identifier: "fr-FR")) + #expect(lang.code == "fr") + #expect(lang.nativeName == "français (France)") + #expect(lang.name == "French (France)") + #expect(lang.name(in: "ja") == "フランス語 (フランス)") + } + + @Test func initRfcLanguage_changeDefault() async throws { + let lang = W3WRfcLanguage(locale: Locale(identifier: "fr-FR")) + W3WRfcLanguage.default = W3WRfcLanguage(from: "de") + #expect(lang.code == "fr") + #expect(lang.name == "Französisch (Frankreich)") + } +}