Skip to content

Commit

Permalink
Change localization interpolation (#161)
Browse files Browse the repository at this point in the history
* Change the string interpolation for the localization

* Avoid type casting by using a type erasure

* Return the fallback literal instead of throwing an error

* Try to solve the test errors

* Replace the argument placeholders with the format specifiers used in swift
  • Loading branch information
mattesmohr authored Dec 7, 2024
1 parent 18061c6 commit 275404a
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 164 deletions.
56 changes: 28 additions & 28 deletions Sources/HTMLKit/Abstraction/Elements/BodyElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/HTMLKit/Abstraction/Elements/FormElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
}
}

Expand Down Expand Up @@ -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)]
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/HTMLKit/Abstraction/Elements/TableElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
}
}
45 changes: 45 additions & 0 deletions Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift
Original file line number Diff line number Diff line change
@@ -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 "%@"
}
}
}
3 changes: 1 addition & 2 deletions Sources/HTMLKit/Framework/Localization/Localizable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
}
73 changes: 39 additions & 34 deletions Sources/HTMLKit/Framework/Localization/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand All @@ -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
Expand All @@ -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)
}

Expand All @@ -224,17 +230,16 @@ 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)
}

return translation
}
}

throw Errors.missingKey(key.key, currentLocale.tag)
throw Errors.missingKey(string.key.value, currentLocale.tag)
}
}

23 changes: 23 additions & 0 deletions Sources/HTMLKit/Framework/Localization/LocalizedString.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 275404a

Please sign in to comment.