Skip to content

Commit e0a2c3e

Browse files
authored
Fix localization fallback behaviour (#163)
* Check if localization is properly configured * Test the error reporting for localization through the provider * Prevent the default locale from being overwritten
1 parent bbf9093 commit e0a2c3e

File tree

5 files changed

+167
-6
lines changed

5 files changed

+167
-6
lines changed

Sources/HTMLKit/Framework/Localization/Localization.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ public class Localization {
5656
}
5757
}
5858

59+
/// Indicates whether the localization is properly configured
60+
internal var isConfigured: Bool {
61+
62+
if self.tables != nil && self.locale != nil {
63+
return true
64+
}
65+
66+
return false
67+
}
68+
5969
/// The translations tables
6070
internal var tables: [Locale: [TranslationTable]]?
6171

Sources/HTMLKit/Framework/Rendering/Renderer.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,12 @@ public final class Renderer {
354354
private func render(localized string: LocalizedString) throws -> String {
355355

356356
guard let localization = self.localization else {
357-
// Bail early with the fallback since the localization isn't set up
357+
// Bail early with the fallback since the localization is not in use
358+
return string.key.literal
359+
}
360+
361+
if !localization.isConfigured {
362+
// Bail early, since the localization is not properly configured
358363
return string.key.literal
359364
}
360365

Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ extension Request {
112112
public var htmlkit: ViewRenderer {
113113

114114
if let acceptLanguage = self.acceptLanguage {
115-
self.application.htmlkit.localization.set(locale: acceptLanguage)
115+
self.application.htmlkit.environment.locale = HTMLKit.Locale(tag: acceptLanguage)
116116
}
117117

118118
return .init(eventLoop: self.eventLoop, configuration: self.application.htmlkit.configuration, logger: self.logger)

Tests/HTMLKitVaporTests/ProviderTests.swift

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,162 @@ final class ProviderTests: XCTestCase {
155155
)
156156
}
157157
}
158-
158+
159+
/// Tests the setup of localization through Vapor
159160
func testLocalizationIntegration() throws {
160161

161-
let currentFile = URL(fileURLWithPath: #file).deletingLastPathComponent()
162+
guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
163+
return
164+
}
165+
166+
let app = Application(.testing)
167+
168+
defer { app.shutdown() }
169+
170+
app.htmlkit.localization.set(source: source)
171+
app.htmlkit.localization.set(locale: "fr")
172+
173+
app.get("test") { request async throws -> Vapor.View in
174+
175+
return try await request.htmlkit.render(TestPage.ChildView())
176+
}
177+
178+
try app.test(.GET, "test") { response in
179+
XCTAssertEqual(response.status, .ok)
180+
XCTAssertEqual(response.body.string,
181+
"""
182+
<!DOCTYPE html>\
183+
<html>\
184+
<head>\
185+
<title>TestPage</title>\
186+
</head>\
187+
<body>\
188+
<p>Bonjour le monde</p>\
189+
</body>\
190+
</html>
191+
"""
192+
)
193+
}
194+
}
195+
196+
/// Tests the behavior when localization is not properly configured
197+
///
198+
/// Localization is considered improperly configured when one or both of the essential factors are missing.
199+
/// In such case the renderer is expected to skip the localization and directly return the fallback string literal.
200+
func testLocalizationFallback() throws {
201+
202+
let app = Application(.testing)
203+
204+
defer { app.shutdown() }
205+
206+
app.get("test") { request async throws -> Vapor.View in
207+
208+
return try await request.htmlkit.render(TestPage.ChildView())
209+
}
210+
211+
try app.test(.GET, "test") { response in
212+
XCTAssertEqual(response.status, .ok)
213+
XCTAssertEqual(response.body.string,
214+
"""
215+
<!DOCTYPE html>\
216+
<html>\
217+
<head>\
218+
<title>TestPage</title>\
219+
</head>\
220+
<body>\
221+
<p>hello.world</p>\
222+
</body>\
223+
</html>
224+
"""
225+
)
226+
}
227+
}
228+
229+
/// Tests the error reporting to Vapor for issues that may occur during localization
230+
///
231+
/// The error is expected to be classified as an internal server error and includes a error message.
232+
func testLocalizationErrorReporting() throws {
233+
234+
struct UnknownTableView: HTMLKit.View {
235+
236+
var body: HTMLKit.Content {
237+
Paragraph("hello.world", tableName: "unknown.table")
238+
}
239+
}
240+
241+
struct UnknownTagView: HTMLKit.View {
242+
243+
var body: HTMLKit.Content {
244+
Division {
245+
Heading1("greeting.world")
246+
.environment(key: \.locale)
247+
}
248+
.environment(key: \.locale, value: Locale(tag: "unknown.tag"))
249+
}
250+
}
162251

163-
let currentDirectory = currentFile.appendingPathComponent("Localization")
252+
guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
253+
return
254+
}
164255

165256
let app = Application(.testing)
166257

167258
defer { app.shutdown() }
168259

169-
app.htmlkit.localization.set(source: currentDirectory)
260+
app.htmlkit.localization.set(source: source)
170261
app.htmlkit.localization.set(locale: "fr")
171262

263+
app.get("unknowntable") { request async throws -> Vapor.View in
264+
265+
return try await request.htmlkit.render(UnknownTableView())
266+
}
267+
268+
app.get("unknowntag") { request async throws -> Vapor.View in
269+
270+
return try await request.htmlkit.render(UnknownTagView())
271+
}
272+
273+
try app.test(.GET, "unknowntable") { response in
274+
275+
XCTAssertEqual(response.status, .internalServerError)
276+
277+
let abort = try response.content.decode(AbortResponse.self)
278+
279+
XCTAssertEqual(abort.reason, "Unable to find translation table 'unknown.table' for the locale 'fr'.")
280+
}
281+
282+
try app.test(.GET, "unknowntag") { response in
283+
284+
XCTAssertEqual(response.status, .internalServerError)
285+
286+
let abort = try response.content.decode(AbortResponse.self)
287+
288+
XCTAssertEqual(abort.reason, "Unable to find a translation table for the locale 'unknown.tag'.")
289+
}
290+
}
291+
292+
/// Tests the localization behavior based on the accept language of the client
293+
///
294+
/// The environment locale is expected to be changed according to the language given by the provider.
295+
/// The renderer is expected to localize correctly the content based on the updated environment locale.
296+
func testLocalizationByAcceptingHeaders() throws {
297+
298+
guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else {
299+
return
300+
}
301+
302+
let app = Application(.testing)
303+
304+
defer { app.shutdown() }
305+
306+
app.htmlkit.localization.set(source: source)
307+
app.htmlkit.localization.set(locale: "en-GB")
308+
172309
app.get("test") { request async throws -> Vapor.View in
173310

311+
// Overwrite the accept language header to simulate a different language
312+
request.headers.replaceOrAdd(name: "accept-language", value: "fr")
313+
174314
return try await request.htmlkit.render(TestPage.ChildView())
175315
}
176316

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Vapor
2+
3+
struct AbortResponse: Vapor.Content {
4+
5+
var reason: String
6+
}

0 commit comments

Comments
 (0)