From b16041315c4d0c3d5eb88861e41f790cc096ff44 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Wed, 22 Mar 2023 21:53:23 +0100 Subject: [PATCH] Upgrade the package (#121) * Add the css for the textpad component * Improve the text component * Fix the form validation * Change some of css * Undo the package reference in the package manifest * Add a test for string interpolation in a localized string * Replace the lingo implementation * Revise the new localization implementation * Integrate the storage manager of the environment into the environment class * Remove the localization configuration and replace it with the new localization class * Improve the locale struct --- Package.swift | 7 +- .../Abstraction/Elements/BodyElements.swift | 112 ++----- .../Abstraction/Elements/FormElements.swift | 16 +- .../Abstraction/Elements/TableElements.swift | 10 +- .../Framework/Environment/Environment.swift | 28 +- .../Environment/EnvironmentKeys.swift | 2 +- .../Framework/Environment/Manager.swift | 29 -- .../Framework/Localization/Locale.swift | 304 ++++++++++++++++++ .../Framework/Localization/Localizable.swift | 11 +- .../Framework/Localization/Localization.swift | 223 +++++++++++++ .../Localization/LocalizedStringKey.swift | 12 +- .../Localization/TranslationTable.swift | 33 ++ .../Framework/Rendering/Renderer.swift | 48 ++- .../HTMLKitComponents/Components/Form.swift | 24 +- .../HTMLKitComponents/Resources/css/form.css | 8 +- .../HTMLKitComponents/Resources/css/group.css | 76 +++++ .../Resources/css/symbol.css | 2 +- .../Resources/css/textpad.css | 72 +++++ Sources/HTMLKitComponents/Resources/js/all.js | 2 +- Sources/HTMLKitVapor/Environment.swift | 25 -- .../Extensions/Vapor+HTMLKit.swift | 24 +- Sources/HTMLKitVapor/Localization.swift | 52 --- Sources/HTMLKitVapor/ViewRenderer.swift | 5 +- .../Localization/en-GB/mobile.strings | 5 + .../Localization/en-GB/web.strings | 8 + Tests/HTMLKitTests/Localization/en.json | 3 - Tests/HTMLKitTests/Localization/fr.json | 3 - .../HTMLKitTests/Localization/fr/web.strings | 2 + Tests/HTMLKitTests/LocalizationTests.swift | 60 +++- .../Localization/en-GB/mobile.strings | 5 + .../Localization/en-GB/web.strings | 6 + Tests/HTMLKitVaporTests/Localization/en.json | 3 - Tests/HTMLKitVaporTests/Localization/fr.json | 3 - .../Localization/fr/web.strings | 2 + Tests/HTMLKitVaporTests/ProviderTests.swift | 4 +- 35 files changed, 933 insertions(+), 296 deletions(-) delete mode 100644 Sources/HTMLKit/Framework/Environment/Manager.swift create mode 100644 Sources/HTMLKit/Framework/Localization/Locale.swift create mode 100644 Sources/HTMLKit/Framework/Localization/Localization.swift create mode 100644 Sources/HTMLKit/Framework/Localization/TranslationTable.swift create mode 100644 Sources/HTMLKitComponents/Resources/css/textpad.css delete mode 100644 Sources/HTMLKitVapor/Environment.swift delete mode 100644 Sources/HTMLKitVapor/Localization.swift create mode 100644 Tests/HTMLKitTests/Localization/en-GB/mobile.strings create mode 100644 Tests/HTMLKitTests/Localization/en-GB/web.strings delete mode 100644 Tests/HTMLKitTests/Localization/en.json delete mode 100644 Tests/HTMLKitTests/Localization/fr.json create mode 100644 Tests/HTMLKitTests/Localization/fr/web.strings create mode 100644 Tests/HTMLKitVaporTests/Localization/en-GB/mobile.strings create mode 100644 Tests/HTMLKitVaporTests/Localization/en-GB/web.strings delete mode 100644 Tests/HTMLKitVaporTests/Localization/en.json delete mode 100644 Tests/HTMLKitVaporTests/Localization/fr.json create mode 100644 Tests/HTMLKitVaporTests/Localization/fr/web.strings diff --git a/Package.swift b/Package.swift index c80cdbac..14edca0c 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,6 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/miroslavkovac/Lingo.git", from: "3.1.0"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.1"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/vapor/vapor.git", from: "4.65.2") @@ -31,7 +30,6 @@ let package = Package( .target( name: "HTMLKit", dependencies: [ - .product(name: "Lingo", package: "Lingo"), .product(name: "Collections", package: "swift-collections") ], exclude: ["Abstraction/README.md", "Framework/README.md"] @@ -56,7 +54,6 @@ let package = Package( dependencies: [ .target(name: "HTMLKit"), .product(name: "Vapor", package: "vapor"), - .product(name: "Lingo", package: "lingo") ] ), .testTarget( @@ -65,7 +62,7 @@ let package = Package( .target(name: "HTMLKit") ], resources: [ - .process("Localization") + .copy("Localization") ] ), .testTarget( @@ -92,7 +89,7 @@ let package = Package( .product(name: "XCTVapor", package: "vapor") ], resources: [ - .process("Localization") + .copy("Localization") ] ), .executableTarget( diff --git a/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift b/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift index b301a538..f4e1f9f7 100644 --- a/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift @@ -1563,12 +1563,8 @@ extension Heading1: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading1: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -1818,12 +1814,8 @@ extension Heading2: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading2: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -2073,12 +2065,8 @@ extension Heading3: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading3: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -2328,12 +2316,8 @@ extension Heading4: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading4: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -2583,12 +2567,8 @@ extension Heading5: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading5: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -2838,12 +2818,8 @@ extension Heading6: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading6: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -4069,12 +4045,8 @@ extension Paragraph: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribut extension Paragraph: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -4811,12 +4783,8 @@ extension Blockquote: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribu extension Blockquote: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -6090,12 +6058,8 @@ extension Anchor: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Anchor: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -6833,12 +6797,8 @@ extension Small: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes { extension Small: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -7012,12 +6972,8 @@ extension StrikeThrough: GlobalAttributes, GlobalEventAttributes { extension StrikeThrough: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -10938,12 +10894,8 @@ extension Italic: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes extension Italic: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -11193,12 +11145,8 @@ extension Bold: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes { extension Bold: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -11448,12 +11396,8 @@ extension Underline: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribut extension Underline: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } diff --git a/Sources/HTMLKit/Abstraction/Elements/FormElements.swift b/Sources/HTMLKit/Abstraction/Elements/FormElements.swift index 6a98307a..a988ac0f 100644 --- a/Sources/HTMLKit/Abstraction/Elements/FormElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/FormElements.swift @@ -521,12 +521,8 @@ extension Label: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Label: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: B) where B : Encodable { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } @@ -1288,12 +1284,8 @@ extension Button: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Button: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } diff --git a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift index a75831e2..3d4a0634 100644 --- a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift @@ -2184,13 +2184,9 @@ extension HeaderCell: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribu } } -extension HeaderCell: Localizable { +extension HeaderCell: Localizable { - public init(_ localizedKey: String) { - self.content = [LocalizedStringKey(key: localizedKey)] - } - - public init(_ localizedKey: String, with context: some Encodable) { - self.content = [LocalizedStringKey(key: localizedKey, context: context)] + public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { + self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] } } diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift index 3f298aec..32f89fac 100644 --- a/Sources/HTMLKit/Framework/Environment/Environment.swift +++ b/Sources/HTMLKit/Framework/Environment/Environment.swift @@ -1,7 +1,16 @@ import Foundation /// A type that represents the environment -public struct Environment { +public class Environment { + + /// The storage of the environment + private var storage: [AnyKeyPath: Any] + + /// Initiates a manager + public init() { + + self.storage = [:] + } /// The current time zone of the environment public var timeZone: TimeZone? @@ -10,8 +19,23 @@ public struct Environment { public var calendar: Calendar? /// The current local of the environment - public var locale: String? + public var locale: Locale? /// The current color scheme of the environment public var colorScheme: String? + + /// Retrieves an item from storage by its path + public func retrieve(for path: AnyKeyPath) -> Any? { + + if let value = self.storage[path] { + return value + } + + return nil + } + + /// Adds und updates an item to the storage + public func upsert(_ value: T, for path: AnyKeyPath) { + self.storage[path] = value + } } diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift index eea6e65a..df68b701 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift @@ -6,7 +6,7 @@ public struct EnvironmentKeys: Hashable { public var timeZone: TimeZone - public var locale: String + public var locale: Locale public var colorScheme: String } diff --git a/Sources/HTMLKit/Framework/Environment/Manager.swift b/Sources/HTMLKit/Framework/Environment/Manager.swift deleted file mode 100644 index 0927214b..00000000 --- a/Sources/HTMLKit/Framework/Environment/Manager.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -/// A type, that manages the environment storage -public class Manager { - - /// The storage of the environment - private var storage: [AnyKeyPath: Any] - - /// Initiates a manager - public init() { - - self.storage = [:] - } - - /// Retrieves an item from storage by its path - public func retrieve(for path: AnyKeyPath) -> Any? { - - if let value = self.storage[path] { - return value - } - - return nil - } - - /// Adds und updates an item to the storage - public func upsert(_ value: T, for path: AnyKeyPath) { - self.storage[path] = value - } -} diff --git a/Sources/HTMLKit/Framework/Localization/Locale.swift b/Sources/HTMLKit/Framework/Localization/Locale.swift new file mode 100644 index 00000000..2f5b52b3 --- /dev/null +++ b/Sources/HTMLKit/Framework/Localization/Locale.swift @@ -0,0 +1,304 @@ +/// A type that represents a locale +public struct Locale: Hashable { + + /// A enumeration of possible tags + public enum Tag: String { + + case arabic = "ar" + case belarusian = "be" + case bulgarian = "bg" + case catalan = "ca" + case czech = "cs" + case danish = "da" + case german = "de" + case greek = "el" + case english = "en" + case spanish = "es" + case finnish = "fi" + case french = "fr" + case icelandic = "is" + case italian = "it" + case japanese = "ja" + case hebrew = "he" + case hindi = "hi" + case croation = "hr" + case hungarian = "hu" + case korean = "ko" + case lithuanian = "lt" + case latvian = "lv" + case macedonian = "mk" + case norwegian = "nb" + case dutch = "nl" + case polish = "pl" + case portuguese = "pt" + case romanian = "ro" + case russian = "ru" + case serbian = "sr" + case slovak = "sk" + case slovenian = "sl" + case albanian = "sq" + case swedish = "sv" + case thai = "th" + case turkish = "tr" + case ukrainian = "uk" + case chinese = "zh" + } + + /// The language code + public var language: String? { + return tag.components(separatedBy: "-").first + } + + /// The region code + public var region: String? { + + let components = tag.components(separatedBy: "-") + + if components.count > 1 { + return components.last + } + + return nil + } + + /// The currency code + public var currencyCode: String? { + return currencyCodes[tag] + } + + /// The currency symbol + public var currencySymbol: String? { + return currencySymbols[tag] + } + + /// The decimal seperator + public var decimalSeparator: String? { + return decimalSeparators[tag] + } + + /// The date format + public var dateFormat: String? { + return dateFormats[tag] + } + + /// The time format + public var timeFormat: String? { + return timeFormats[tag] + } + + /// The locale identifier + public let tag: String + + /// Initiates a locale + public init(tag: String) { + self.tag = tag + } + + /// Initiates a locale with a predefined tag + public init(tag: Tag) { + self.tag = tag.rawValue + } +} + +extension Locale { + + internal var currencyCodes: [String: String] { + return [ + "ar-AE": "AED‏", + "ar_QA": "QAR", + "be-BY": "BYR", + "bg-BG": "BGN", + "ca-ES": "EUR", + "cs-CZ": "CZK", + "da-DK": "DKK", + "de-DE": "EUR", + "el-GR": "EUR", + "en-GB": "GBP", + "en-US": "USD", + "es-ES": "EUR", + "fi-FI": "EUR", + "fr-FR": "EUR", + "is-IS": "ISK", + "it-IT": "EUR", + "ja-JP": "JPY", + "he-IL": "ILS", + "hi-IN": "INR", + "hr-HR": "HRK", + "hu-HU": "HUF", + "ko-KP": "KPW", + "ko-KR": "KRW", + "lt-LT": "EUR", + "lv-LV": "EUR", + "mk-MK": "MKD", + "nl-NL": "EUR", + "nb-NO": "NOK", + "pl-PL": "PLN", + "pt-PT": "EUR", + "ro-RO": "RON", + "ru-RU": "RUB", + "sr_RS": "RSD", + "sr_XK": "EUR", + "sk-SK": "EUR", + "sl-SI": "EUR", + "sq-AL": "ALL", + "sv-SE": "SEK", + "th-TH": "THB", + "tr-TR": "TRY", + "uk-UA": "UAH", + "zh-CN": "CNY", + "zh-HK": "HKD", + ] + } + + internal var currencySymbols: [String: String] { + return [ + "ar-AE": "د.إ.‏", + "ar_QA": "ر.ق.‏", + "be-BY": "Br", + "bg-BG": "лв.", + "ca-ES": "€", + "cs-CZ": "Kč", + "da-DK": "kr.", + "de-DE": "€", + "el-GR": "€", + "en-GB": "£", + "en-US": "$", + "es-ES": "€", + "fi-FI": "€", + "fr-FR": "€", + "is-IS": "ISK", + "it-IT": "€", + "ja-JP": "¥", + "he-IL": "₪", + "hi-IN": "₹", + "hr-HR": "HRK", + "hu-HU": "Ft", + "ko-KP": "KPW", + "ko-KR": "₩", + "lt-LT": "€", + "lv-LV": "€", + "mk-MK": "ден", + "nl-NL": "€", + "nb-NO": "kr", + "pl-PL": "zł", + "pt-PT": "€", + "ro-RO": "RON", + "ru-RU": "₽", + "sr_RS": "RSD", + "sk-SK": "€", + "sl-SI": "€", + "sq-AL": "Lekë", + "sv-SE": "kr", + "th-TH": "THB", + "tr-TR": "₺", + "uk-UA": "₴", + "zh-CN": "¥", + "zh-HK": "HK$", + ] + } + + internal var decimalSeparators: [String: String] { + return [ + "ar-AE": ",", + "ar_QA": ",", + "be-BY": ",", + "bg-BG": ",", + "ca-ES": ",", + "cs-CZ": ",", + "da-DK": ",", + "de-DE": ",", + "el-GR": ",", + "en-GB": ".", + "en-US": ".", + "es-ES": ",", + "fi-FI": ",", + "fr-FR": ",", + "is-IS": ",", + "it-IT": ",", + "ja-JP": ".", + "he-IL": ".", + "hi-IN": ".", + "hr-HR": ",", + "hu-HU": ",", + "ko-KP": ".", + "ko-KR": ".", + "lt-LT": ",", + "lv-LV": ",", + "mk-MK": ",", + "nl-NL": ",", + "nb-NO": ",", + "pl-PL": ",", + "pt-PT": ",", + "ro-RO": ",", + "ru-RU": ",", + "sr_RS": ",", + "sk-SK": ",", + "sl-SI": ",", + "sq-AL": ",", + "sv-SE": ",", + "th-TH": ".", + "tr-TR": ",", + "uk-UA": ",", + "zh-CN": ".", + "zh-HK": ".", + ] + } + + internal var dateFormats: [String: String] { + return [ + + "ar-AE": "dd‏/MM‏/yyyy", + "ar_QA": "dd‏/MM‏/yyyy", + "be-BY": "dd.MM.yyyy", + "bg-BG": "dd.MM.yyyy", + "ca-ES": "dd/MM/yyyy", + "cs-CZ": "dd. MM. yyyy", + "da-DK": "yyyy-mm-dd", + "de-DE": "dd.MM.yyyy", + "el-GR": "dd/MM/yyyy", + "en-GB": "dd/MM/yyyy", + "en-US": "M/D/yyyy", + "es-ES": "dd/MM/yyyy", + "fi-FI": "dd.MM.yyyy", + "fr-FR": "dd/MM/yyyy", + "is-IS": "dd.MM.yyyy", + "it-IT": "dd/MM/yyyy", + "ja-JP": "yyyy/MM/dd", + "he-IL": "dd.MM.yyyy", + "hi-IN": "dd/MM/yyyy", + "hr-HR": "dd.MM.yyyy.", + "hu-HU": "yyyy. MM. dd.", + "ko-KP": "yyyy. MM. dd.", + "ko-KR": "yyyy. MM. dd.", + "lt-LT": "yyyy-MM-dd", + "lv-LV": "dd.MM.yyyy", + "mk-MK": "dd.MM.yyyy", + "nl-NL": "dd-MM-yyyy", + "nb-NO": "dd.MM.yyyy", + "pl-PL": "dd.MM.yyyy", + "pt-PT": "dd/MM/yyyy", + "ro-RO": "dd.MM.yyyy", + "ru-RU": "dd.MM.yyyy", + "sr_RS": "dd.MM.yyyy.", + "sk-SK": "dd. MM. yyyy", + "sl-SI": "dd. MM. yyyy", + "sq-AL": "dd.MM.yyyy", + "sv-SE": "yyyy-MM-dd", + "th-TH": "dd/MM/y", + "tr-TR": "dd.MM.yyyy", + "uk-UA": "dd.MM.yyyy", + "zh-CN": "yyyy/MM/dd", + "zh-HK": "dd/MM/yyyy", + ] + } + + internal var timeFormats: [String: String] { + return [ + "en-GB": "HH:mm:ss", + "en-US": "h:mm:ss tt", + "de-DE": "HH:mm:ss", + "fr-FR": "HH:mm:ss", + "ru-RU": "H:mm:ss", + ] + } +} diff --git a/Sources/HTMLKit/Framework/Localization/Localizable.swift b/Sources/HTMLKit/Framework/Localization/Localizable.swift index 5f24918d..296c918d 100644 --- a/Sources/HTMLKit/Framework/Localization/Localizable.swift +++ b/Sources/HTMLKit/Framework/Localization/Localizable.swift @@ -6,14 +6,5 @@ /// The protocol defines public protocol Localizable { - init(_ localizedKey: String) - - init(_ localizedKey: String, with context: some Encodable) -} - -extension Localizable { - - public init(_ localizedKey: String, with context: some Encodable) { - self.init(localizedKey, with: context) - } + init(_ localizedKey: String, tableName: String?, interpolation: Any...) } diff --git a/Sources/HTMLKit/Framework/Localization/Localization.swift b/Sources/HTMLKit/Framework/Localization/Localization.swift new file mode 100644 index 00000000..319e8730 --- /dev/null +++ b/Sources/HTMLKit/Framework/Localization/Localization.swift @@ -0,0 +1,223 @@ +import Foundation + +/// A type that represents the localization +public class Localization { + + /// A enumeration of various errors + public enum Errors: Error { + + case missingKey + case missingTable + case missingTables + case unkownTable + case noFallback + + public var description: String { + + switch self { + case .missingKey: + return "Unable to find a translation for the key." + + case .missingTable: + return "Unable to find a translation table for the locale." + + case .missingTables: + return "Unable to find any localization tables." + + case .unkownTable: + return "Unkown table name." + + case .noFallback: + return "The fallback needs to be set up first." + } + } + } + + /// The localization tables + internal var tables: [Locale: [TranslationTable]]? + + /// The default locale + internal var locale: Locale? + + /// Initiates a localization + public init() { + } + + /// Initiates a localization + public init(source: URL, locale: Locale) { + + self.locale = locale + self.tables = load(source: source) + } + + /// Sets the root path + public func set(source: URL) { + self.tables = load(source: source) + } + + /// Sets the default locale indentifier + public func set(locale: String) { + self.locale = Locale(tag: locale) + } + + /// Loads the tables from a specific path + internal func load(source: URL) -> [Locale: [TranslationTable]] { + + var localizationTables = [Locale: [TranslationTable]]() + + if let enumerator = FileManager.default.enumerator(at: source, includingPropertiesForKeys: nil) { + + for case let path as URL in enumerator { + + if !path.hasDirectoryPath { + + if !path.isFileURL { + enumerator.skipDescendants() + + } else { + + let locale = Locale(tag: path.deletingPathExtension().deletingLastPathComponent().lastPathComponent) + + if var translationTables = localizationTables[locale] { + + if let translations = NSDictionary(contentsOf: path) as? [String: String] { + translationTables.append(TranslationTable(name: path.deletingPathExtension().lastPathComponent, translations: translations)) + } + + localizationTables[locale] = translationTables + } + } + + } else { + localizationTables[Locale(tag: path.lastPathComponent)] = [TranslationTable]() + } + } + } + + return localizationTables + } + + /// Retrieves a value for a specific key from the tables + public func localize(key: String, locale: Locale? = nil, interpolation: [Any]? = nil) throws -> String { + + guard let fallback = self.locale else { + throw Errors.noFallback + } + + guard let localizationTables = self.tables else { + throw Errors.missingTables + } + + let currentLocale = locale ?? fallback + + if let translationTables = localizationTables[currentLocale] { + + for translationTable in translationTables { + + if var translation = translationTable.retrieve(for: key) { + + if let interpolation = interpolation { + + for argument in interpolation { + + switch argument { + case let stringValue as String: + translation = translation.replacingOccurrences(of: "%st", with: stringValue) + + case let dateValue as Date: + + let formatter = DateFormatter() + formatter.dateFormat = currentLocale.dateFormat + + translation = translation.replacingOccurrences(of: "%dt", with: formatter.string(from: dateValue)) + + case let doubleValue as Double: + translation = translation.replacingOccurrences(of: "%do", with: String(doubleValue)) + + case let intValue as Int: + translation = translation.replacingOccurrences(of: "%in", with: String(intValue)) + + default: + break + } + } + } + + return translation + + } else { + continue + } + } + + } else { + throw Errors.missingTable + } + + throw Errors.missingKey + } + + /// Retrieves a value for a specific key from a specific table + public func localize(key: String, table: String, locale: Locale? = nil, interpolation: [Any]? = nil) throws -> String { + + guard let fallback = self.locale else { + throw Errors.noFallback + } + + guard let localizationTables = self.tables else { + throw Errors.missingTables + } + + let currentLocale = locale ?? fallback + + if let translationTables = localizationTables[currentLocale] { + + for translationTable in translationTables { + + if translationTable.name == table { + + if var translation = translationTable.retrieve(for: key) { + + if let interpolation = interpolation { + + for argument in interpolation { + + switch argument { + case let stringValue as String: + translation = translation.replacingOccurrences(of: "%st", with: stringValue) + + case let dateValue as Date: + + let formatter = DateFormatter() + formatter.dateFormat = currentLocale.dateFormat + + translation = translation.replacingOccurrences(of: "%dt", with: formatter.string(from: dateValue)) + + case let doubleValue as Double: + translation = translation.replacingOccurrences(of: "%do", with: String(doubleValue)) + + case let intValue as Int: + translation = translation.replacingOccurrences(of: "%in", with: String(intValue)) + + default: + break + } + } + } + + return translation + + } else { + throw Errors.missingKey + } + } + } + + } else { + throw Errors.missingTable + } + + throw Errors.unkownTable + } +} + diff --git a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift index a5f4de87..be96e267 100644 --- a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift +++ b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift @@ -8,14 +8,20 @@ import Foundation /// The struct thats contains the information for the localization public struct LocalizedStringKey: Content { + /// The key of the translation value public let key: String - public let context: Encodable? + /// The name of the translation table + public let table: String? + + /// The interpolation for the translation string + public var interpolation: [Any]? /// Initiates a localized string key with a context - public init(key: String, context: Encodable? = nil) { + public init(key: String, table: String? = nil, interpolation: [Any]? = nil) { self.key = key - self.context = context + self.table = table + self.interpolation = interpolation } } diff --git a/Sources/HTMLKit/Framework/Localization/TranslationTable.swift b/Sources/HTMLKit/Framework/Localization/TranslationTable.swift new file mode 100644 index 00000000..1ecd03d8 --- /dev/null +++ b/Sources/HTMLKit/Framework/Localization/TranslationTable.swift @@ -0,0 +1,33 @@ +import Foundation + +/// A type that represents a translation table +public class TranslationTable { + + /// The name of the table + public let name: String + + /// The translations in the table + private var translations: [String: String] + + /// Initiates a translation table + public init(name: String, translations: [String: String]) { + + self.name = name + self.translations = translations + } + + /// Retrieves a translation + public func retrieve(for key: String) -> String? { + + if let translation = self.translations[key] { + return translation + } + + return nil + } + + /// Adds und updates a translation + public func upsert(_ translation: String, for key: String) { + self.translations[key] = translation + } +} diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 9e769183..c4ec67fd 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -4,7 +4,6 @@ */ import Foundation -import Lingo /// A struct containing the different formulas for the different views. public class Renderer { @@ -16,7 +15,7 @@ public class Renderer { case unindendedEnvironmentKey case environmentObjectNotFound case environmentValueNotFound - case missingLingoConfiguration + case missingLocalization public var description: String { @@ -33,33 +32,28 @@ public class Renderer { case .environmentObjectNotFound: return "Unable to retrieve environment object." - case .missingLingoConfiguration: - return "The lingo configuration seem to missing." + case .missingLocalization: + return "The localization seem to missing." } } } private var environment: Environment - private var manager: Manager - - /// The localization to use when rendering. - private var lingo: Lingo? + private var localization: Localization? /// Initiates the renderer. - public init(lingo: Lingo? = nil) { + public init(localization: Localization? = nil) { self.environment = Environment() - self.manager = Manager() - self.lingo = lingo + self.localization = localization } /// Initiates the renderer. - public init(lingo: Lingo? = nil, manager: Manager) { + public init(localization: Localization? = nil, environment: Environment) { - self.environment = Environment() - self.manager = manager - self.lingo = lingo + self.environment = environment + self.localization = localization } /// Renders a view @@ -262,38 +256,32 @@ public class Renderer { /// Renders a localized string key internal func render(stringkey: LocalizedStringKey) throws -> String { - guard let lingo = self.lingo else { - throw Errors.missingLingoConfiguration + guard let localization = self.localization else { + throw Errors.missingLocalization } - if let context = stringkey.context { - - if let data = try? JSONEncoder().encode(context) { - - if let dictionary = try? JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any] { - return lingo.localize(stringkey.key, locale: lingo.defaultLocale, interpolations: dictionary) - } - } + if let table = stringkey.table { + return try localization.localize(key: stringkey.key, table: table, locale: environment.locale, interpolation: stringkey.interpolation) } - return lingo.localize(stringkey.key, locale: environment.locale ?? lingo.defaultLocale) + return try localization.localize(key: stringkey.key, locale: environment.locale, interpolation: stringkey.interpolation) } /// Renders a environment modifier internal func render(modifier: EnvironmentModifier) throws -> String { if let value = modifier.value { - self.manager.upsert(value, for: modifier.key) + self.environment.upsert(value, for: modifier.key) } else { - if let value = manager.retrieve(for: modifier.key) { + if let value = self.environment.retrieve(for: modifier.key) { if let key = modifier.key as? PartialKeyPath { switch key { case \.locale: - self.environment.locale = value as? String + self.environment.locale = value as? Locale case \.calender: self.environment.calendar = value as? Calendar @@ -317,7 +305,7 @@ public class Renderer { /// Renders a environment value internal func render(value: EnvironmentValue) throws -> String { - guard let parent = manager.retrieve(for: value.parentPath) else { + guard let parent = self.environment.retrieve(for: value.parentPath) else { throw Errors.environmentObjectNotFound } diff --git a/Sources/HTMLKitComponents/Components/Form.swift b/Sources/HTMLKitComponents/Components/Form.swift index 51093492..8ab651d9 100644 --- a/Sources/HTMLKitComponents/Components/Form.swift +++ b/Sources/HTMLKitComponents/Components/Form.swift @@ -743,6 +743,12 @@ public struct TextPad: View { /// The identifier of the textpad. internal let name: String + /// The placeholder for the field value. + internal let prompt: String? + + /// The number of lines. + internal var rows: Int = 1 + /// The content of the textpad. internal var content: [String] @@ -750,17 +756,20 @@ public struct TextPad: View { internal var classes: [String] /// Creates a textpad - public init(name: String, @ContentBuilder content: () -> [String]) { + public init(name: String, prompt: String? = nil, @ContentBuilder content: () -> [String]) { self.name = name + self.prompt = prompt self.content = content() self.classes = ["textpad"] } /// Creates a textpad. - internal init(name: String, content: [String], classes: [String]) { + internal init(name: String, prompt: String?, rows: Int, content: [String], classes: [String]) { self.name = name + self.prompt = prompt + self.rows = rows self.content = content self.classes = classes } @@ -799,9 +808,18 @@ public struct TextPad: View { } .id(name) .name(name) - .rows(5) + .rows(rows) .class("textpad-content") } .class(classes.joined(separator: " ")) } + + /// Sets the limit of the maximum lines. + public func lineLimit(_ value: Int) -> TextPad { + + var newSelf = self + newSelf.rows = value + + return newSelf + } } diff --git a/Sources/HTMLKitComponents/Resources/css/form.css b/Sources/HTMLKitComponents/Resources/css/form.css index 1db797af..87b1be1c 100644 --- a/Sources/HTMLKitComponents/Resources/css/form.css +++ b/Sources/HTMLKitComponents/Resources/css/form.css @@ -52,6 +52,10 @@ box-shadow: 0 0 0 4px var(--inputFocusColor); } +.input:invalid { + border-color: var(--redColor); +} + .input.type\:textfield { display: block; width: 100%; @@ -140,7 +144,3 @@ width: 100%; -webkit-appearance: none; } - -.input:invalid { - border: 1px solid red !important; -} diff --git a/Sources/HTMLKitComponents/Resources/css/group.css b/Sources/HTMLKitComponents/Resources/css/group.css index 2b3ce2c7..a7504571 100644 --- a/Sources/HTMLKitComponents/Resources/css/group.css +++ b/Sources/HTMLKitComponents/Resources/css/group.css @@ -29,3 +29,79 @@ .group > * + .symbol { margin-left: 15px; } + +.group.color\:black { + color: var(--blackColor); +} + +.group.color\:blue { + color: var(--blueColor); +} + +.group.color\:brown { + color: var(--brownColor); +} + +.group.color\:cyan { + color: var(--cyanColor); +} + +.group.color\:gray { + color: var(--grayColor); +} + +.group.color\:green { + color: var(--greenColor); +} + +.group.color\:indigo { + color: var(--indigoColor); +} + +.group.color\:mint { + color: var(--mintColor); +} + +.group.color\:orange { + color: var(--orangeColor); +} + +.group.color\:pink { + color: var(--pinkColor); +} + +.group.color\:purple { + color: var(--purpleColor); +} + +.group.color\:red { + color: var(--redColor); +} + +.group.color\:teal { + color: var(--tealColor); +} + +.group.color\:white { + color: var(--whiteColor); +} + +.group.color\:yellow { + color: var(--yellowColor); +} + +.group.color\:silver { + color: var(--silverColor); +} + +.group.color\:highlight { + color: var(--highlightColor); +} + +.group.color\:primary { + color: var(--primaryColor); +} + +.group.color\:secondary { + color: var(--secondaryColor); +} diff --git a/Sources/HTMLKitComponents/Resources/css/symbol.css b/Sources/HTMLKitComponents/Resources/css/symbol.css index f8c32218..6444ae7e 100644 --- a/Sources/HTMLKitComponents/Resources/css/symbol.css +++ b/Sources/HTMLKitComponents/Resources/css/symbol.css @@ -4,7 +4,7 @@ .symbol { display: inline-block; - width: 1em; + width: 1.25em; height: 1em; } diff --git a/Sources/HTMLKitComponents/Resources/css/textpad.css b/Sources/HTMLKitComponents/Resources/css/textpad.css new file mode 100644 index 00000000..0a70d84e --- /dev/null +++ b/Sources/HTMLKitComponents/Resources/css/textpad.css @@ -0,0 +1,72 @@ +/** + textpad component + */ + +.textpad { + display: flex; + flex-direction: column; +} + +.textpad:focus-within { + border-radius: var(--inputBorderRadius); + box-shadow: 0 0 0 4px var(--inputFocusColor); +} + +.textpad:has(textarea:invalid) .textpad-toolbar { + border-color: var(--redColor); +} + +.textpad:has(textarea:invalid) .textpad-content { + border-color: var(--redColor); +} + +.textpad-toolbar { + display: flex; + flex-direction: row; + align-items: center; + gap: 20px; + padding-top: var(--inputPaddingY); + padding-right: var(--inputPaddingX); + padding-bottom: var(--inputPaddingY); + padding-left: var(--inputPaddingX); + border-top: var(--inputBorderWidth); + border-left: var(--inputBorderWidth); + border-right: var(--inputBorderWidth); + border-bottom: var(--inputBorderWidth); + border-style: solid; + border-color: var(--inputBorderColor); + border-top-left-radius: var(--inputBorderRadius); + border-top-right-radius: var(--inputBorderRadius); + list-style: none; +} + +.textpad-toolbar > .toolbar-tool { + width: 20px; + height: 20px; + text-align: center; + cursor: pointer; +} + +.textpad-content { + display: block; + width: 100%; + min-height: 35px; + resize: none; + padding-top: var(--inputPaddingY); + padding-right: var(--inputPaddingX); + padding-bottom: var(--inputPaddingY); + padding-left: var(--inputPaddingX); + border-top: 0; + border-left: var(--inputBorderWidth); + border-right: var(--inputBorderWidth); + border-bottom: var(--inputBorderWidth); + border-style: solid; + border-color: var(--inputBorderColor); + border-bottom-left-radius: var(--inputBorderRadius); + border-bottom-right-radius: var(--inputBorderRadius); + font-size: 1.0rem; + font-weight: 400; + color: var(--inputTextColor); + background-color: var(--inputBackgroundColor); + outline: 0; +} diff --git a/Sources/HTMLKitComponents/Resources/js/all.js b/Sources/HTMLKitComponents/Resources/js/all.js index f6ee7b4c..a0d1877e 100644 --- a/Sources/HTMLKitComponents/Resources/js/all.js +++ b/Sources/HTMLKitComponents/Resources/js/all.js @@ -81,7 +81,7 @@ var $ = (function () { /** * Performs when the target is submitted. */ - Self.prototype.onSubmit = function (callback) { + Self.prototype.onSubmit = function (callback, validate) { if (validate) { this.elems[0].setAttribute("novalidate", "novalidate"); diff --git a/Sources/HTMLKitVapor/Environment.swift b/Sources/HTMLKitVapor/Environment.swift deleted file mode 100644 index e37342d9..00000000 --- a/Sources/HTMLKitVapor/Environment.swift +++ /dev/null @@ -1,25 +0,0 @@ -/* - Abstract: - The file contains the configuration for the environment. - */ - -import Foundation -import HTMLKit - -/// The environment -final public class Environment { - - /// The storage manager - internal var manager: Manager - - /// Initiates a environment - public init() { - - self.manager = Manager() - } - - /// Adds an encodable object to the storage - public func add(object: T) where T: Encodable { - manager.upsert(object, for: \T.self) - } -} diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 78e83d00..1e406205 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -18,16 +18,16 @@ extension Application { internal struct LocalizationStorageKey: StorageKey { - public typealias Value = Localization + public typealias Value = HTMLKit.Localization } internal struct EnvironmentStorageKey: StorageKey { - public typealias Value = Environment + public typealias Value = HTMLKit.Environment } /// The view localization - public var localization: Localization { + public var localization: HTMLKit.Localization { if let configuration = self.application.storage[LocalizationStorageKey.self] { return configuration @@ -41,7 +41,7 @@ extension Application { } /// The view environment - public var environment: Environment { + public var environment: HTMLKit.Environment { if let configuration = self.application.storage[EnvironmentStorageKey.self] { return configuration @@ -56,6 +56,7 @@ extension Application { /// The view renderer internal var renderer: ViewRenderer { + return .init(eventLoop: application.eventLoopGroup.next(), localization: localization, environment: environment) @@ -74,8 +75,23 @@ extension Application { extension Request { + /// The accept language header of the request + private var acceptLanguage: String? { + + if let languageHeader = self.headers.first(name: .acceptLanguage) { + return languageHeader.components(separatedBy: ",").first + } + + return nil + } + /// Access to the view renderer public var htmlkit: ViewRenderer { + + if let acceptLanguage = self.acceptLanguage { + self.application.htmlkit.localization.set(locale: acceptLanguage) + } + return .init(eventLoop: self.eventLoop, localization: self.application.htmlkit.localization, environment: self.application.htmlkit.environment) diff --git a/Sources/HTMLKitVapor/Localization.swift b/Sources/HTMLKitVapor/Localization.swift deleted file mode 100644 index b88d7e3a..00000000 --- a/Sources/HTMLKitVapor/Localization.swift +++ /dev/null @@ -1,52 +0,0 @@ -/* - Abstract: - The file contains the configuration for the localization. - */ - -import Foundation -import Lingo - -/// The localization -public class Localization { - - /// A enumeration of possible locale identifier - public enum Locale: String { - - case arabic = "ar" - case english = "en" - case french = "fr" - case german = "de" - case hindi = "es" - case bengali = "bn" - case russian = "ru" - case portuguese = "pt" - case indonesian = "id" - } - - /// The path of the source directory - internal var defaultDirectory: String - - /// The default locale indentifier - internal var defaultLocale: String - - internal var lingo: Lingo? { - return try? Lingo(rootPath: defaultDirectory, defaultLocale: defaultLocale) - } - - /// Creates a configuration - public init() { - - self.defaultDirectory = "Resources/Localization" - self.defaultLocale = "en" - } - - /// Sets the root path - public func set(source: URL) { - self.defaultDirectory = source.path - } - - /// Sets the default locale indentifier - public func set(locale: Locale) { - self.defaultLocale = locale.rawValue - } -} diff --git a/Sources/HTMLKitVapor/ViewRenderer.swift b/Sources/HTMLKitVapor/ViewRenderer.swift index 90344549..d913814b 100644 --- a/Sources/HTMLKitVapor/ViewRenderer.swift +++ b/Sources/HTMLKitVapor/ViewRenderer.swift @@ -5,7 +5,6 @@ import HTMLKit import Vapor -import Lingo /// The view renderer public class ViewRenderer { @@ -17,10 +16,10 @@ public class ViewRenderer { internal var renderer: Renderer /// Creates the view renderer - public init(eventLoop: EventLoop, localization: Localization, environment: Environment) { + public init(eventLoop: EventLoop, localization: HTMLKit.Localization, environment: HTMLKit.Environment) { self.eventLoop = eventLoop - self.renderer = Renderer(lingo: localization.lingo, manager: environment.manager) + self.renderer = Renderer(localization: localization, environment: environment) } /// Renders a layout and its context diff --git a/Tests/HTMLKitTests/Localization/en-GB/mobile.strings b/Tests/HTMLKitTests/Localization/en-GB/mobile.strings new file mode 100644 index 00000000..9c0a22e6 --- /dev/null +++ b/Tests/HTMLKitTests/Localization/en-GB/mobile.strings @@ -0,0 +1,5 @@ +/* A friendly greeting. */ +"greeting.world" = "Hello World"; + +/* A friendly greeting. */ +"greeting.person" = "Hello %st"; diff --git a/Tests/HTMLKitTests/Localization/en-GB/web.strings b/Tests/HTMLKitTests/Localization/en-GB/web.strings new file mode 100644 index 00000000..671db72f --- /dev/null +++ b/Tests/HTMLKitTests/Localization/en-GB/web.strings @@ -0,0 +1,8 @@ +/* A friendly greeting. */ +"greeting.world" = "Hello World"; + +/* A friendly greeting. */ +"greeting.person" = "Hello %st"; + +/* A personal indroduction. */ +"personal.introduction" = "Hello, I am %st and %in years old."; diff --git a/Tests/HTMLKitTests/Localization/en.json b/Tests/HTMLKitTests/Localization/en.json deleted file mode 100644 index db488087..00000000 --- a/Tests/HTMLKitTests/Localization/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Hallo Welt": "Hello World", -} diff --git a/Tests/HTMLKitTests/Localization/fr.json b/Tests/HTMLKitTests/Localization/fr.json deleted file mode 100644 index 66141827..00000000 --- a/Tests/HTMLKitTests/Localization/fr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Hallo Welt": "Bonjour le monde", -} diff --git a/Tests/HTMLKitTests/Localization/fr/web.strings b/Tests/HTMLKitTests/Localization/fr/web.strings new file mode 100644 index 00000000..9aeb015b --- /dev/null +++ b/Tests/HTMLKitTests/Localization/fr/web.strings @@ -0,0 +1,2 @@ +/* A friendly greeting. */ +"greeting.world" = "Bonjour le monde"; diff --git a/Tests/HTMLKitTests/LocalizationTests.swift b/Tests/HTMLKitTests/LocalizationTests.swift index b6b09f17..fd95ad22 100644 --- a/Tests/HTMLKitTests/LocalizationTests.swift +++ b/Tests/HTMLKitTests/LocalizationTests.swift @@ -4,7 +4,6 @@ */ import HTMLKit -import Lingo import XCTest final class LocalizationTests: XCTestCase { @@ -22,7 +21,7 @@ final class LocalizationTests: XCTestCase { struct MainView: View { var body: Content { - Heading1("Hallo Welt") + Heading1("greeting.world") } } @@ -33,6 +32,55 @@ final class LocalizationTests: XCTestCase { ) } + func testLocalizationWithStringInterpolation() throws { + + struct TestView: View { + + var body: Content { + Heading1("greeting.person", interpolation: "John Doe") + } + } + + XCTAssertEqual(try renderer!.render(view: TestView()), + """ +

Hello John Doe

+ """ + ) + } + + func testStringInterpolationWithMultipleArguments() throws { + + struct TestView: View { + + var body: Content { + Heading1("personal.introduction", interpolation: "John Doe", 31) + } + } + + XCTAssertEqual(try renderer!.render(view: TestView()), + """ +

Hello, I am John Doe and 31 years old.

+ """ + ) + } + + func testLocaliationWithTable() throws { + + struct TestView: View { + + var body: Content { + Heading1("personal.introduction", tableName: "web", interpolation: "John Doe", 31) + } + } + + XCTAssertEqual(try renderer!.render(view: TestView()), + """ +

Hello, I am John Doe and 31 years old.

+ """ + ) + } + + func testEnvironmentLocalization() throws { struct MainView: View { @@ -47,7 +95,7 @@ final class LocalizationTests: XCTestCase { Division { content } - .environment(key: \.locale, value: "fr") + .environment(key: \.locale, value: Locale(tag: .french)) } } @@ -55,7 +103,7 @@ final class LocalizationTests: XCTestCase { var body: Content { MainView { - Heading1("Hallo Welt") + Heading1("greeting.world") .environment(key: \.locale) } } @@ -79,8 +127,8 @@ extension LocalizationTests { let currentDirectory = currentFile.appendingPathComponent("Localization") - let lingo = try! Lingo(rootPath: currentDirectory.path, defaultLocale: "en") + let localization = Localization(source: currentDirectory, locale: Locale(tag: "en-GB")) - self.renderer = Renderer(lingo: lingo) + self.renderer = Renderer(localization: localization) } } diff --git a/Tests/HTMLKitVaporTests/Localization/en-GB/mobile.strings b/Tests/HTMLKitVaporTests/Localization/en-GB/mobile.strings new file mode 100644 index 00000000..9c0a22e6 --- /dev/null +++ b/Tests/HTMLKitVaporTests/Localization/en-GB/mobile.strings @@ -0,0 +1,5 @@ +/* A friendly greeting. */ +"greeting.world" = "Hello World"; + +/* A friendly greeting. */ +"greeting.person" = "Hello %st"; diff --git a/Tests/HTMLKitVaporTests/Localization/en-GB/web.strings b/Tests/HTMLKitVaporTests/Localization/en-GB/web.strings new file mode 100644 index 00000000..86fc468b --- /dev/null +++ b/Tests/HTMLKitVaporTests/Localization/en-GB/web.strings @@ -0,0 +1,6 @@ +/* A friendly greeting. */ +"greeting.world" = "Hello World"; + +/* A friendly greeting. */ +"greeting.person" = "Hello %st"; + diff --git a/Tests/HTMLKitVaporTests/Localization/en.json b/Tests/HTMLKitVaporTests/Localization/en.json deleted file mode 100644 index db488087..00000000 --- a/Tests/HTMLKitVaporTests/Localization/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Hallo Welt": "Hello World", -} diff --git a/Tests/HTMLKitVaporTests/Localization/fr.json b/Tests/HTMLKitVaporTests/Localization/fr.json deleted file mode 100644 index 66141827..00000000 --- a/Tests/HTMLKitVaporTests/Localization/fr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Hallo Welt": "Bonjour le monde", -} diff --git a/Tests/HTMLKitVaporTests/Localization/fr/web.strings b/Tests/HTMLKitVaporTests/Localization/fr/web.strings new file mode 100644 index 00000000..9aeb015b --- /dev/null +++ b/Tests/HTMLKitVaporTests/Localization/fr/web.strings @@ -0,0 +1,2 @@ +/* A friendly greeting. */ +"greeting.world" = "Bonjour le monde"; diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift index 82aa6c20..f2c88f72 100644 --- a/Tests/HTMLKitVaporTests/ProviderTests.swift +++ b/Tests/HTMLKitVaporTests/ProviderTests.swift @@ -63,7 +63,7 @@ final class ProviderTests: XCTestCase { var body: HTMLKit.Content { MainView { - Paragraph("Hallo Welt") + Paragraph("greeting.world") } } } @@ -158,7 +158,7 @@ final class ProviderTests: XCTestCase { defer { app.shutdown() } app.htmlkit.localization.set(source: currentDirectory) - app.htmlkit.localization.set(locale: .french) + app.htmlkit.localization.set(locale: "fr") app.get("test") { request async throws -> Vapor.View in