diff --git a/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift b/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift index 7458e4c7..faf307f8 100644 --- a/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/BodyElements.swift @@ -1745,8 +1745,8 @@ extension Heading1: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading1: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -2027,8 +2027,8 @@ extension Heading2: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading2: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -2309,8 +2309,8 @@ extension Heading3: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading3: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -2591,8 +2591,8 @@ extension Heading4: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading4: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -2873,8 +2873,8 @@ extension Heading5: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading5: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -3155,8 +3155,8 @@ extension Heading6: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribute extension Heading6: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -4537,8 +4537,8 @@ extension Paragraph: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribut extension Paragraph: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -5368,8 +5368,8 @@ extension Blockquote: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribu extension Blockquote: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -6977,8 +6977,8 @@ extension Anchor: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Anchor: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -7809,8 +7809,8 @@ extension Small: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes { extension Small: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -8015,8 +8015,8 @@ extension StrikeThrough: GlobalAttributes, GlobalEventAttributes { extension StrikeThrough: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -12612,8 +12612,8 @@ extension Italic: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes extension Italic: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -12894,8 +12894,8 @@ extension Bold: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes { extension Bold: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -13176,8 +13176,8 @@ extension Underline: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribut extension Underline: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } diff --git a/Sources/HTMLKit/Abstraction/Elements/FormElements.swift b/Sources/HTMLKit/Abstraction/Elements/FormElements.swift index a5caa066..fd830dec 100644 --- a/Sources/HTMLKit/Abstraction/Elements/FormElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/FormElements.swift @@ -631,8 +631,8 @@ extension Label: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Label: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } @@ -1549,8 +1549,8 @@ extension Button: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttributes, extension Button: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } diff --git a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift index 0a7a5e94..5d813cb0 100644 --- a/Sources/HTMLKit/Abstraction/Elements/TableElements.swift +++ b/Sources/HTMLKit/Abstraction/Elements/TableElements.swift @@ -2478,7 +2478,7 @@ extension HeaderCell: GlobalAttributes, GlobalEventAttributes, GlobalAriaAttribu extension HeaderCell: Localizable { - public init(_ localizedKey: String, tableName: String? = nil, interpolation: Any...) { - self.content = [LocalizedStringKey(key: localizedKey, table: tableName, interpolation: interpolation)] + public init(_ localizedKey: LocalizedStringKey, tableName: String? = nil) { + self.content = [LocalizedString(key: localizedKey, table: tableName)] } } diff --git a/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift b/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift new file mode 100644 index 00000000..415c7513 --- /dev/null +++ b/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift @@ -0,0 +1,45 @@ +import Foundation + +/// An enum that represents the data types of arguments used in interpolation. +/// +/// Each case corresponds to a specific data type and provides a placeholder +/// that can be used for replacing values in the localized string. +@_documentation(visibility: internal) +public enum InterpolationArgument { + + /// Holds an integer value + case int(Int) + + /// Holds a string value + case string(String) + + /// Holds a double value + case double(Double) + + /// Holds a float value + case float(Float) + + /// Holds a date value + case date(Date) + + /// The placeholder used for string interpolation + internal var placeholder: String { + + switch self { + case .int(_): + return "%lld" + + case .string(_): + return "%@" + + case .double(_): + return "%f" + + case .float(_): + return "%f" + + case .date(_): + return "%@" + } + } +} diff --git a/Sources/HTMLKit/Framework/Localization/Localizable.swift b/Sources/HTMLKit/Framework/Localization/Localizable.swift index c02329c1..f1e551d5 100644 --- a/Sources/HTMLKit/Framework/Localization/Localizable.swift +++ b/Sources/HTMLKit/Framework/Localization/Localizable.swift @@ -7,6 +7,5 @@ public protocol Localizable { /// - Parameters: /// - localizedKey: The string key to be translated /// - tableName: The name of the translation table - /// - interpolation: A variadic list of values used to replace placeholders within the translation string - init(_ localizedKey: String, tableName: String?, interpolation: Any...) + init(_ localizedKey: LocalizedStringKey, tableName: String?) } diff --git a/Sources/HTMLKit/Framework/Localization/Localization.swift b/Sources/HTMLKit/Framework/Localization/Localization.swift index f74727cd..74db7d84 100644 --- a/Sources/HTMLKit/Framework/Localization/Localization.swift +++ b/Sources/HTMLKit/Framework/Localization/Localization.swift @@ -137,46 +137,52 @@ public class Localization { return localizationTables } + /// Replace the value with the placeholder + /// + /// - Parameters: + /// - placeholder: The placeholder to be replaced in + /// - value: The value to replace the placeholder with + /// - translation: The string in which the replacement will occur + private func replace(placeholder: String, with value: String, on translation: inout String) { + + if let range = translation.range(of: placeholder) { + translation = translation.replacingCharacters(in: range, with: value) + } + } + /// Apply interpolation values to the translation for the given locale /// /// - Parameters: - /// - arguments: An array of values used to replace placeholders within the translation string - /// - translation: The translation string - /// - locale: The locale - private func interpolate(arguments: [Any], to translation: inout String, for locale: Locale) { + /// - arguments: The arguments to replace the placeholders with + /// - translation: The string in which the interpolation will occur + /// - locale: The locale to respect during interpolation + private func interpolate(arguments: [InterpolationArgument], to translation: inout String, for locale: Locale) { for argument in arguments { - + switch argument { - case let stringValue as String: + case .int(let int): - if let range = translation.range(of: "%st") { - translation = translation.replacingCharacters(in: range, with: stringValue) - } + replace(placeholder: argument.placeholder, with: String(int), on: &translation) - case let dateValue as Date: + case .string(let string): - let formatter = DateFormatter() - formatter.dateFormat = locale.dateFormat + replace(placeholder: argument.placeholder, with: string, on: &translation) - if let range = translation.range(of: "%dt") { - translation = translation.replacingCharacters(in: range, with: formatter.string(from: dateValue)) - } + case .double(let double): - case let doubleValue as Double: + replace(placeholder: argument.placeholder, with: String(double), on: &translation) - if let range = translation.range(of: "%do") { - translation = translation.replacingCharacters(in: range, with: String(doubleValue)) - } + case .float(let float): - case let intValue as Int: + replace(placeholder: argument.placeholder, with: String(float), on: &translation) - if let range = translation.range(of: "%in") { - translation = translation.replacingCharacters(in: range, with: String(intValue)) - } + case .date(let date): - default: - break + let formatter = DateFormatter() + formatter.dateFormat = locale.dateFormat + + replace(placeholder: argument.placeholder, with: formatter.string(from: date), on: &translation) } } } @@ -188,7 +194,7 @@ public class Localization { /// - locale: The locale to use when retrieving the translation /// /// - Returns: The translation - public func localize(key: LocalizedStringKey, for locale: Locale? = nil) throws -> String { + public func localize(string: LocalizedString, for locale: Locale? = nil) throws -> String { guard let fallback = self.locale else { throw Errors.noFallback @@ -204,17 +210,17 @@ public class Localization { throw Errors.missingTable(currentLocale.tag) } - if let table = key.table { + if let table = string.table { guard let translationTable = translationTables.first(where: { $0.name == table }) else { throw Errors.unknownTable(table, currentLocale.tag) } - guard var translation = translationTable.retrieve(for: key.key) else { - throw Errors.missingKey(key.key, currentLocale.tag) + guard var translation = translationTable.retrieve(for: string.key.value) else { + throw Errors.missingKey(string.key.value, currentLocale.tag) } - if let interpolation = key.interpolation { + if let interpolation = string.key.interpolation { interpolate(arguments: interpolation, to: &translation, for: currentLocale) } @@ -224,9 +230,9 @@ public class Localization { for translationTable in translationTables { - if var translation = translationTable.retrieve(for: key.key) { + if var translation = translationTable.retrieve(for: string.key.value) { - if let interpolation = key.interpolation { + if let interpolation = string.key.interpolation { interpolate(arguments: interpolation, to: &translation, for: currentLocale) } @@ -234,7 +240,6 @@ public class Localization { } } - throw Errors.missingKey(key.key, currentLocale.tag) + throw Errors.missingKey(string.key.value, currentLocale.tag) } } - diff --git a/Sources/HTMLKit/Framework/Localization/LocalizedString.swift b/Sources/HTMLKit/Framework/Localization/LocalizedString.swift new file mode 100644 index 00000000..fa00f711 --- /dev/null +++ b/Sources/HTMLKit/Framework/Localization/LocalizedString.swift @@ -0,0 +1,23 @@ +import Foundation + +/// A type thats holds the information for the localization +@_documentation(visibility: internal) +public struct LocalizedString: Content { + + /// The key of the translation value + internal let key: LocalizedStringKey + + /// The name of the translation table + internal let table: String? + + /// Initializes a localized string with context + /// + /// - Parameters: + /// - key: The string key to be translated + /// - table: The table where the string key should be looked up. Default is nil. + public init(key: LocalizedStringKey, table: String? = nil) { + + self.key = key + self.table = table + } +} diff --git a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift index f9734271..39050e60 100644 --- a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift +++ b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift @@ -1,28 +1,126 @@ import Foundation -/// A type thats holds the information for the localization -@_documentation(visibility: internal) -public struct LocalizedStringKey: Content { - - /// The key of the translation value - internal let key: String +/// A string key for the localization +public struct LocalizedStringKey { + + /// The key value + internal let value: String - /// The name of the translation table - internal let table: String? + /// A fallback literal string + /// + /// > Note: This literal is not intended for lookup in the translation table. Instead, it serves as + /// > a default value if localization is not set up or if the key is not found at all. + internal let literal: String - /// The interpolation for the translation string - internal var interpolation: [Any]? + /// The arguments for the interpolation + internal var interpolation: [InterpolationArgument]? - /// Initializes a localized string key with a context - /// + /// Initializes a string key for localization + /// /// - Parameters: - /// - key: The string key to be translated - /// - table: The table where the string key should be looked up. Default is nil. - /// - interpolation: An array of values that will replace placeholders within the translation string. - public init(key: String, table: String? = nil, interpolation: [Any]? = nil) { + /// - value: The key value + /// - literal: The default value + /// - interpolation: The arguments toreplace placeholders within the translation string + public init(value: String, literal: String, interpolation: [InterpolationArgument]? = nil) { - self.key = key - self.table = table + self.value = value + self.literal = literal self.interpolation = interpolation } } + +extension LocalizedStringKey: ExpressibleByStringLiteral, ExpressibleByStringInterpolation { + + public init(stringLiteral: String) { + self.init(value: stringLiteral, literal: stringLiteral) + } + + public init(stringInterpolation: StringInterpolation) { + self.init(value: stringInterpolation.key, + literal: stringInterpolation.literal, + interpolation: stringInterpolation.arguments) + } + + public struct StringInterpolation: StringInterpolationProtocol { + + /// The key to be localized + var key = "" + + /// The arguments for the interpolation + var arguments: [InterpolationArgument] = [] + + /// The string literal + var literal = "" + + public init(literalCapacity: Int, interpolationCount: Int) { + + key.reserveCapacity(literalCapacity + interpolationCount * 2) + + arguments.reserveCapacity(interpolationCount) + } + + public mutating func appendLiteral(_ literal: String) { + + self.literal += literal + + key.append(literal) + } + + public mutating func appendInterpolation(_ value: String) { + + literal += value + + let argument = InterpolationArgument.string(value) + + key += argument.placeholder + + arguments.append(argument) + } + + public mutating func appendInterpolation(_ value: Int) { + + literal += String(value) + + let argument = InterpolationArgument.int(value) + + key += argument.placeholder + + arguments.append(argument) + } + + public mutating func appendInterpolation(_ value: Double) { + + literal += String(value) + + let argument = InterpolationArgument.double(value) + + key += argument.placeholder + + arguments.append(argument) + } + + public mutating func appendInterpolation(_ value: Float) { + + literal += String(value) + + let argument = InterpolationArgument.float(value) + + key += argument.placeholder + + arguments.append(.float(value)) + } + + public mutating func appendInterpolation(_ value: Date) { + + let formatter = DateFormatter() + + literal += formatter.string(from: value) + + let argument = InterpolationArgument.date(value) + + key += argument.placeholder + + arguments.append(argument) + } + } +} diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 7e395bb8..91f698f7 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -24,9 +24,6 @@ public final class Renderer { /// Indicates a missing environment value. case environmentValueNotFound - - /// Indicates a missing localization configuration. - case missingLocalization /// A brief error description. public var description: String { @@ -43,9 +40,6 @@ public final class Renderer { case .environmentObjectNotFound: return "Unable to retrieve environment object." - - case .missingLocalization: - return "The localization seem to missing." } } } @@ -130,8 +124,8 @@ public final class Renderer { result += try render(element: element) } - if let stringkey = content as? LocalizedStringKey { - result += try render(stringkey: stringkey) + if let string = content as? LocalizedString { + result += try render(localized: string) } if let modifier = content as? EnvironmentModifier { @@ -205,8 +199,8 @@ public final class Renderer { result += try render(element: element) } - if let stringkey = content as? LocalizedStringKey { - result += try render(stringkey: stringkey) + if let string = content as? LocalizedString { + result += try render(localized: string) } if let modifier = content as? EnvironmentModifier { @@ -327,8 +321,8 @@ public final class Renderer { result += try render(element: element) } - if let stringkey = content as? LocalizedStringKey { - result += try render(stringkey: stringkey) + if let string = content as? LocalizedString { + result += try render(localized: string) } if let value = content as? EnvironmentValue { @@ -357,29 +351,28 @@ public final class Renderer { } /// Renders a localized string key. - private func render(stringkey: LocalizedStringKey) throws -> String { + private func render(localized string: LocalizedString) throws -> String { guard let localization = self.localization else { - throw Errors.missingLocalization + // Bail early with the fallback since the localization isn't set up + return string.key.literal } do { - return try localization.localize(key: stringkey, for: environment.locale) + return try localization.localize(string: string, for: environment.locale) } catch Localization.Errors.missingKey(let key, let locale) { + logger.warning("Unable to find translation key '\(key)' for the locale '\(locale)'.") + // Check if the fallback was already in charge if environment.locale != nil { - logger.warning("Unable to find translation key '\(key)' for the locale '\(locale)'.") - // Seems not, let's try to recover by using the fallback - return try localization.localize(key: stringkey) - - } else { - // Recovery didn't work out. Let's face the truth - throw Localization.Errors.missingKey(key, locale) + return try localization.localize(string: string) } + + return string.key.literal } } diff --git a/Sources/HTMLKitComponents/Components/Text.swift b/Sources/HTMLKitComponents/Components/Text.swift index 700e526d..74b76b5e 100644 --- a/Sources/HTMLKitComponents/Components/Text.swift +++ b/Sources/HTMLKitComponents/Components/Text.swift @@ -25,9 +25,9 @@ public struct Text: View, Actionable, Modifiable { self.classes = ["text", "alignment:\(alignment.value)"] } - public init(_ localizedStringKey: String, alignment: Tokens.TextAlignment = .leading) { + public init(_ localizedStringKey: LocalizedStringKey, alignment: Tokens.TextAlignment = .leading) { - self.content = [LocalizedStringKey(key: localizedStringKey)] + self.content = [LocalizedString(key: localizedStringKey)] self.classes = ["text", "alignment:\(alignment.value)"] } diff --git a/Tests/HTMLKitTests/Localization/en-GB/mobile.strings b/Tests/HTMLKitTests/Localization/en-GB/mobile.strings index 9c0a22e6..2e66f69f 100644 --- a/Tests/HTMLKitTests/Localization/en-GB/mobile.strings +++ b/Tests/HTMLKitTests/Localization/en-GB/mobile.strings @@ -1,5 +1,2 @@ -/* A friendly greeting. */ -"greeting.world" = "Hello World"; - -/* A friendly greeting. */ -"greeting.person" = "Hello %st"; +/* A string key with a namespace pattern */ +"hello.world" = "Hello World"; diff --git a/Tests/HTMLKitTests/Localization/en-GB/web.strings b/Tests/HTMLKitTests/Localization/en-GB/web.strings index 929b84c6..a32cbf1d 100644 --- a/Tests/HTMLKitTests/Localization/en-GB/web.strings +++ b/Tests/HTMLKitTests/Localization/en-GB/web.strings @@ -1,8 +1,24 @@ -/* A friendly greeting. */ -"greeting.world" = "Hello World"; +/* String key with namespace pattern */ +"hello.world" = "Hello World"; -/* A friendly greeting. */ -"greeting.person" = "Hello %st"; +/* String key with namespace pattern and string interpolation */ +"cheers.person %@" = "Cheers %@"; -/* A personal indroduction. */ -"personal.intro" = "Hello, I am %st, and I am %in years old. I have a dog named %st. He is %in and %do inches tall."; +/* Interpolation with one value */ +"Hello %@" = "Hello %@"; + +/* Interpolation with multiple values */ +"Hello %@ and %@" = "Hello %@ and %@"; +"Do you %lld have time at %@?" = "Do you %lld have time at %@?"; + +/* Interpolation with a string value */ +"String: %@" = "String: %@"; + +/* Interpolation with a integer value */ +"Integer: %lld" = "Integer: %lld"; + +/* Interpolation with a double value */ +"Double: %f" = "Double: %f"; + +/* Interpolation with a date value */ +"Date: %@" = "Date: %@"; diff --git a/Tests/HTMLKitTests/Localization/fr/web.strings b/Tests/HTMLKitTests/Localization/fr/web.strings index 9aeb015b..53ac872d 100644 --- a/Tests/HTMLKitTests/Localization/fr/web.strings +++ b/Tests/HTMLKitTests/Localization/fr/web.strings @@ -1,2 +1,2 @@ -/* A friendly greeting. */ -"greeting.world" = "Bonjour le monde"; +/* String key with namespace pattern */ +"hello.world" = "Bonjour le monde"; diff --git a/Tests/HTMLKitTests/LocalizationTests.swift b/Tests/HTMLKitTests/LocalizationTests.swift index 5c269ebb..c7b5eb91 100644 --- a/Tests/HTMLKitTests/LocalizationTests.swift +++ b/Tests/HTMLKitTests/LocalizationTests.swift @@ -1,8 +1,3 @@ -/* - Abstract: - The file tests the localization. - */ - import HTMLKit import XCTest @@ -24,7 +19,7 @@ final class LocalizationTests: XCTestCase { struct MainView: View { var body: Content { - Heading1("greeting.world") + Heading1("hello.world") } } @@ -44,18 +39,24 @@ final class LocalizationTests: XCTestCase { struct TestView: View { var body: Content { - Heading1("greeting.person", interpolation: "John Doe") + Paragraph("String: \("John Doe")") + Paragraph("Integer: \(31)") + Paragraph("Double: \(12.5)") + Paragraph("Date: \(Date(timeIntervalSince1970: 50000))") } } XCTAssertEqual(try renderer!.render(view: TestView()), """ -
String: John Doe
\ +Integer: 31
\ +Double: 12.5
\ +Date: 01/01/1970
""" ) } - /// Tests the localization of string interpolation with multiple arguments + /// Tests the localization of string interpolation with multiple arguments and various data types /// /// The test expects the key to exist in the default translation table, to be correctly formatted /// with the arguments in the proper order, and to be rendered accurately. @@ -64,13 +65,17 @@ final class LocalizationTests: XCTestCase { struct TestView: View { var body: Content { - Paragraph("personal.intro", interpolation: "John Doe", 31, "Mozart", 5, 21.5) + Paragraph("Hello \("Jane") and \("John Doe")") + Paragraph("Do you \(2) have time at \(Date(timeIntervalSince1970: 50000))?") + Paragraph("cheers.person \("Jean")") } } XCTAssertEqual(try renderer!.render(view: TestView()), """ -Hello, I am John Doe, and I am 31 years old. I have a dog named Mozart. He is 5 and 21.5 inches tall.
+Hello Jane and John Doe
\ +Do you 2 have time at 01/01/1970?
\ +Cheers Jean
""" ) } @@ -83,7 +88,7 @@ final class LocalizationTests: XCTestCase { struct TestView: View { var body: Content { - Paragraph("greeting.world", tableName: "web") + Paragraph("hello.world", tableName: "web") } } @@ -120,7 +125,7 @@ final class LocalizationTests: XCTestCase { var body: Content { MainView { - Heading1("greeting.world") + Heading1("hello.world") .environment(key: \.locale) } } @@ -138,9 +143,9 @@ final class LocalizationTests: XCTestCase { /// Tests the behavior when a localization key is missing /// /// A key is considered as missing if it cannot be found in the translation table. In this case, - /// the renderer is expected to throw an error. + /// the renderer is expected to use the fallback literal string. func testMissingKey() throws { - + struct MainView: View { var body: Content { @@ -148,15 +153,11 @@ final class LocalizationTests: XCTestCase { } } - XCTAssertThrowsError(try renderer!.render(view: MainView())) { error in - - guard let localizationError = error as? Localization.Errors else { - return XCTFail("Unexpected error type: \(error)") - } - - XCTAssertEqual(localizationError, .missingKey("unknown.key", "en-GB")) - XCTAssertEqual(localizationError.description, "Unable to find translation key 'unknown.key' for the locale 'en-GB'.") - } + XCTAssertEqual(try renderer!.render(view: MainView()), + """ +