Skip to content

Commit

Permalink
Fix localization fallback behaviour (#163)
Browse files Browse the repository at this point in the history
* Check if localization is properly configured

* Test the error reporting for localization through the provider

* Prevent the default locale from being overwritten
  • Loading branch information
mattesmohr authored Dec 28, 2024
1 parent bbf9093 commit e0a2c3e
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 6 deletions.
10 changes: 10 additions & 0 deletions Sources/HTMLKit/Framework/Localization/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ public class Localization {
}
}

/// Indicates whether the localization is properly configured
internal var isConfigured: Bool {

if self.tables != nil && self.locale != nil {
return true
}

return false
}

/// The translations tables
internal var tables: [Locale: [TranslationTable]]?

Expand Down
7 changes: 6 additions & 1 deletion Sources/HTMLKit/Framework/Rendering/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,12 @@ public final class Renderer {
private func render(localized string: LocalizedString) throws -> String {

guard let localization = self.localization else {
// Bail early with the fallback since the localization isn't set up
// Bail early with the fallback since the localization is not in use
return string.key.literal
}

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

Expand Down
2 changes: 1 addition & 1 deletion Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ extension Request {
public var htmlkit: ViewRenderer {

if let acceptLanguage = self.acceptLanguage {
self.application.htmlkit.localization.set(locale: acceptLanguage)
self.application.htmlkit.environment.locale = HTMLKit.Locale(tag: acceptLanguage)
}

return .init(eventLoop: self.eventLoop, configuration: self.application.htmlkit.configuration, logger: self.logger)
Expand Down
148 changes: 144 additions & 4 deletions Tests/HTMLKitVaporTests/ProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,162 @@ final class ProviderTests: XCTestCase {
)
}
}


/// Tests the setup of localization through Vapor
func testLocalizationIntegration() throws {

let currentFile = URL(fileURLWithPath: #file).deletingLastPathComponent()
guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
return
}

let app = Application(.testing)

defer { app.shutdown() }

app.htmlkit.localization.set(source: source)
app.htmlkit.localization.set(locale: "fr")

app.get("test") { request async throws -> Vapor.View in

return try await request.htmlkit.render(TestPage.ChildView())
}

try app.test(.GET, "test") { response in
XCTAssertEqual(response.status, .ok)
XCTAssertEqual(response.body.string,
"""
<!DOCTYPE html>\
<html>\
<head>\
<title>TestPage</title>\
</head>\
<body>\
<p>Bonjour le monde</p>\
</body>\
</html>
"""
)
}
}

/// Tests the behavior when localization is not properly configured
///
/// Localization is considered improperly configured when one or both of the essential factors are missing.
/// In such case the renderer is expected to skip the localization and directly return the fallback string literal.
func testLocalizationFallback() throws {

let app = Application(.testing)

defer { app.shutdown() }

app.get("test") { request async throws -> Vapor.View in

return try await request.htmlkit.render(TestPage.ChildView())
}

try app.test(.GET, "test") { response in
XCTAssertEqual(response.status, .ok)
XCTAssertEqual(response.body.string,
"""
<!DOCTYPE html>\
<html>\
<head>\
<title>TestPage</title>\
</head>\
<body>\
<p>hello.world</p>\
</body>\
</html>
"""
)
}
}

/// Tests the error reporting to Vapor for issues that may occur during localization
///
/// The error is expected to be classified as an internal server error and includes a error message.
func testLocalizationErrorReporting() throws {

struct UnknownTableView: HTMLKit.View {

var body: HTMLKit.Content {
Paragraph("hello.world", tableName: "unknown.table")
}
}

struct UnknownTagView: HTMLKit.View {

var body: HTMLKit.Content {
Division {
Heading1("greeting.world")
.environment(key: \.locale)
}
.environment(key: \.locale, value: Locale(tag: "unknown.tag"))
}
}

let currentDirectory = currentFile.appendingPathComponent("Localization")
guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
return
}

let app = Application(.testing)

defer { app.shutdown() }

app.htmlkit.localization.set(source: currentDirectory)
app.htmlkit.localization.set(source: source)
app.htmlkit.localization.set(locale: "fr")

app.get("unknowntable") { request async throws -> Vapor.View in

return try await request.htmlkit.render(UnknownTableView())
}

app.get("unknowntag") { request async throws -> Vapor.View in

return try await request.htmlkit.render(UnknownTagView())
}

try app.test(.GET, "unknowntable") { response in

XCTAssertEqual(response.status, .internalServerError)

let abort = try response.content.decode(AbortResponse.self)

XCTAssertEqual(abort.reason, "Unable to find translation table 'unknown.table' for the locale 'fr'.")
}

try app.test(.GET, "unknowntag") { response in

XCTAssertEqual(response.status, .internalServerError)

let abort = try response.content.decode(AbortResponse.self)

XCTAssertEqual(abort.reason, "Unable to find a translation table for the locale 'unknown.tag'.")
}
}

/// Tests the localization behavior based on the accept language of the client
///
/// The environment locale is expected to be changed according to the language given by the provider.
/// The renderer is expected to localize correctly the content based on the updated environment locale.
func testLocalizationByAcceptingHeaders() throws {

guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
return
}

let app = Application(.testing)

defer { app.shutdown() }

app.htmlkit.localization.set(source: source)
app.htmlkit.localization.set(locale: "en-GB")

app.get("test") { request async throws -> Vapor.View in

// Overwrite the accept language header to simulate a different language
request.headers.replaceOrAdd(name: "accept-language", value: "fr")

return try await request.htmlkit.render(TestPage.ChildView())
}

Expand Down
6 changes: 6 additions & 0 deletions Tests/HTMLKitVaporTests/Utilities/AbortResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Vapor

struct AbortResponse: Vapor.Content {

var reason: String
}

0 comments on commit e0a2c3e

Please sign in to comment.