Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation
/// 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 {
public enum InterpolationArgument: Hashable {

/// Holds an integer value
case int(Int)
Expand Down
10 changes: 3 additions & 7 deletions Sources/HTMLKit/Framework/Localization/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,7 @@ public struct Localization: Sendable {
throw Error.missingKey(string.key.value, currentLocale.tag)
}

if let interpolation = string.key.interpolation {
interpolate(arguments: interpolation, to: &translation, for: currentLocale)
}
interpolate(arguments: string.key.arguments, to: &translation, for: currentLocale)

return translation

Expand All @@ -242,9 +240,7 @@ public struct Localization: Sendable {

if var translation = translationTable.retrieve(for: string.key.value) {

if let interpolation = string.key.interpolation {
interpolate(arguments: interpolation, to: &translation, for: currentLocale)
}
interpolate(arguments: string.key.arguments, to: &translation, for: currentLocale)

return translation
}
Expand Down Expand Up @@ -276,7 +272,7 @@ public struct Localization: Sendable {
return try recover(from: error, with: string)

default:
return string.key.literal
return string.key.fallback
}
}
}
Expand Down
209 changes: 115 additions & 94 deletions Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,123 +5,144 @@ import Foundation
public struct LocalizedStringKey {

/// The key value
internal let value: String
///
/// ```
/// Hello %@
/// ```
internal var value: String

/// A fallback literal string
///
/// ```
/// Hello World
/// ```
///
/// > 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
internal var fallback: String

/// The arguments for the interpolation
internal var interpolation: [InterpolationArgument]?
internal var arguments: [InterpolationArgument]

/// Initializes a string key for localization
///
/// - Parameters:
/// - 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) {
public init(value: String, fallback: String, arguments: [InterpolationArgument] = []) {

self.value = value
self.literal = literal
self.interpolation = interpolation
self.fallback = fallback
self.arguments = arguments
}
}

extension LocalizedStringKey: ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
extension LocalizedStringKey: ExpressibleByStringLiteral {

public init(stringLiteral: String) {
self.init(value: stringLiteral, literal: stringLiteral)

self.value = stringLiteral
self.fallback = stringLiteral
self.arguments = []
}
}

extension LocalizedStringKey: ExpressibleByStringInterpolation {

public init(stringInterpolation: LocalizedStringKey) {

self.value = stringInterpolation.value
self.fallback = stringInterpolation.fallback
self.arguments = stringInterpolation.arguments
}
}

extension LocalizedStringKey: StringInterpolationProtocol {

public init(literalCapacity: Int, interpolationCount: Int) {

self.value = ""
self.fallback = ""
self.arguments = []
}

public mutating func appendLiteral(_ literal: String) {

self.value += literal

self.fallback += literal
}

public mutating func appendInterpolation(_ value: String) {

let argument = InterpolationArgument.string(value)

self.value += argument.placeholder

self.fallback += value

self.arguments.append(argument)
}

public mutating func appendInterpolation(_ value: Int) {

let argument = InterpolationArgument.int(value)

self.value += argument.placeholder

self.fallback += String(value)

self.arguments.append(argument)
}

public mutating func appendInterpolation(_ value: Double) {

let argument = InterpolationArgument.double(value)

self.value += argument.placeholder

self.fallback += String(value)

self.arguments.append(argument)
}

public init(stringInterpolation: StringInterpolation) {
self.init(value: stringInterpolation.key,
literal: stringInterpolation.literal,
interpolation: stringInterpolation.arguments)
public mutating func appendInterpolation(_ value: Float) {

let argument = InterpolationArgument.float(value)

self.value += argument.placeholder

self.fallback += String(value)

self.arguments.append(argument)
}

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)
}
public mutating func appendInterpolation(_ value: Date) {

let argument = InterpolationArgument.date(value)

self.value += argument.placeholder

let formatter = DateFormatter()

self.fallback += formatter.string(from: value)

self.arguments.append(argument)
}
}

extension LocalizedStringKey: Hashable {

/// Compare two string keys.
public static func == (lhs: LocalizedStringKey, rhs: LocalizedStringKey) -> Bool {
return lhs.fallback == rhs.fallback
}
}

extension LocalizedStringKey: CustomStringConvertible {

public var description: String {
return self.value
}
}
6 changes: 3 additions & 3 deletions Sources/HTMLKit/Framework/Rendering/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,12 @@ public struct Renderer: Sendable {

guard let localization = localization else {
// Bail early with the fallback since the localization is not in use
return string.key.literal
return string.key.fallback
}

if !localization.isConfigured {
// Bail early, since the localization is not properly configured
return string.key.literal
return string.key.fallback
}

do {
Expand Down Expand Up @@ -348,7 +348,7 @@ public struct Renderer: Sendable {
return try localization.recover(from: error, with: string)

default:
return string.key.literal
return string.key.fallback
}
}
}
Expand Down
17 changes: 1 addition & 16 deletions Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,8 @@ extension Application {

extension Request {

/// The accept language header of the request
private var acceptLanguage: String? {

if let languageHeader = headers.first(name: .acceptLanguage) {
return languageHeader.components(separatedBy: ",").first
}

return nil
}

/// Access to the view renderer
public var htmlkit: ViewRenderer {

if let acceptLanguage = acceptLanguage {
application.htmlkit.environment.upsert(HTMLKit.Locale(tag: acceptLanguage), for: \HTMLKit.EnvironmentKeys.locale)
}

public var htmlkit: ViewRenderer {
return .init(eventLoop: eventLoop, configuration: application.htmlkit.configuration, logger: logger)
}
}
Expand Down
37 changes: 36 additions & 1 deletion Tests/HTMLKitTests/LocalizationTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTMLKit
@testable import HTMLKit
import XCTest

final class LocalizationTests: XCTestCase {
Expand Down Expand Up @@ -100,6 +100,41 @@ final class LocalizationTests: XCTestCase {
XCTAssertEqual(localizationError.description, "Unable to find translation table 'unknown.table' for the locale 'en-GB'.")
}
}

/// Test the correct string interpolation of a localized string key
func testLocalizedStringKeyInterplation() throws {

let string: LocalizedStringKey = "Hallo \("World")"

XCTAssertEqual(string.value, "Hallo %@")
XCTAssertEqual(string.fallback, "Hallo World")
XCTAssertEqual(string.arguments.count, 1)

let integer: LocalizedStringKey = "Hallo \(941)"

XCTAssertEqual(integer.value, "Hallo %lld")
XCTAssertEqual(integer.fallback, "Hallo 941")
XCTAssertEqual(integer.arguments.count, 1)

let float: LocalizedStringKey = "Hallo \(9.41)"

XCTAssertEqual(float.value, "Hallo %f")
XCTAssertEqual(float.fallback, "Hallo 9.41")
XCTAssertEqual(float.arguments.count, 1)
}

/// Test the correct camparsion of the localized string key
func testLocalizedStringKeyComparison() throws {

let lhs: LocalizedStringKey = "Hallo \("Universe")"
let rhs: LocalizedStringKey = "Hallo \("World")"

XCTAssertEqual(lhs.value, rhs.value)
XCTAssertNotEqual(lhs.fallback, rhs.fallback)
XCTAssertEqual(lhs.arguments.count, rhs.arguments.count)

XCTAssertNotEqual(lhs, rhs)
}
}

extension LocalizationTests {
Expand Down
2 changes: 2 additions & 0 deletions Tests/HTMLKitVaporTests/Localization/de-DE/web.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* A string key with a namespace pattern */
"hello.world" = "Hallo Welt";
Loading
Loading