Skip to content

Only generating constants/definitions and not localization helpers #104

@liamnichols

Description

@liamnichols

The code generated by xcstrings-tool can be broken down into two categories:

  1. Definitions
  2. Helpers

Definitions

internal struct Localizable: Sendable {
enum BundleDescription: Sendable {
case main
case atURL(URL)
case forClass(AnyClass)
#if !SWIFT_PACKAGE
private class BundleLocator {
}
#endif
static var current: BundleDescription {
#if SWIFT_PACKAGE
.atURL(Bundle.module.bundleURL)
#else
.forClass(BundleLocator.self)
#endif
}
}
enum Argument: Sendable {
case int(Int)
case uint(UInt)
case float(Float)
case double(Double)
case object(String)
var value: any CVarArg {
switch self {
case .int(let value):
value
case .uint(let value):
value
case .float(let value):
value
case .double(let value):
value
case .object(let value):
value
}
}
}
let key: StaticString
let arguments: [Argument]
let table: String?
let bundle: BundleDescription
fileprivate init(
key: StaticString,
arguments: [Argument],
table: String?,
bundle: BundleDescription
) {
self.key = key
self.arguments = arguments
self.table = table
self.bundle = bundle
}
/// A key that conflicts with a keyword in swift that isn't suitable for a variable/method and should be backticked.
///
/// ### Source Localization
///
/// ```
/// Continue
/// ```
internal static var `continue`: Localizable {
Localizable(
key: "continue",
arguments: [],
table: "Localizable",
bundle: .current
)
}

Helpers

internal init(localizable: Localizable, locale: Locale? = nil) {
let bundle: Bundle = .from(description: localizable.bundle) ?? .main
let key = String(describing: localizable.key)
self.init(
format: bundle.localizedString(forKey: key, value: nil, table: localizable.table),
locale: locale,
arguments: localizable.arguments.map(\.value)
)
}

extension Bundle {
static func from(description: String.Localizable.BundleDescription) -> Bundle? {
switch description {
case .main:
Bundle.main
case .atURL(let url):
Bundle(url: url)
case .forClass(let anyClass):
Bundle(for: anyClass)
}
}
}

#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.5, iOS 13, tvOS 13, watchOS 6, *)
extension Text {
/// Creates a text view that displays a localized string defined in the ‘Localizable‘ strings table.
internal init(localizable: String.Localizable) {
if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) {
self.init(LocalizedStringResource(localizable: localizable))
return
}
var stringInterpolation = LocalizedStringKey.StringInterpolation(literalCapacity: 0, interpolationCount: localizable.arguments.count)
for argument in localizable.arguments {
switch argument {
case .int(let value):
stringInterpolation.appendInterpolation(value)
case .uint(let value):
stringInterpolation.appendInterpolation(value)
case .float(let value):
stringInterpolation.appendInterpolation(value)
case .double(let value):
stringInterpolation.appendInterpolation(value)
case .object(let value):
stringInterpolation.appendInterpolation(value)
}
}
let makeKey = LocalizedStringKey.init(stringInterpolation:)
var key = makeKey(stringInterpolation)
key.overrideKeyForLookup(using: localizable.key)
self.init(key, tableName: localizable.table, bundle: .from(description: localizable.bundle))
}
}
@available(macOS 10.5, iOS 13, tvOS 13, watchOS 6, *)
extension LocalizedStringKey {
/// Creates a localized string key that represents a localized value in the ‘Localizable‘ strings table.
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
internal init(localizable: String.Localizable) {
var stringInterpolation = LocalizedStringKey.StringInterpolation(literalCapacity: 0, interpolationCount: 1)
if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) {
stringInterpolation.appendInterpolation(LocalizedStringResource(localizable: localizable))
} else {
stringInterpolation.appendInterpolation(Text(localizable: localizable))
}
let makeKey = LocalizedStringKey.init(stringInterpolation:)
self = makeKey(stringInterpolation)
}
/// Creates a `LocalizedStringKey` that represents a localized value in the ‘Localizable‘ strings table.
@available(macOS 11, iOS 14, tvOS 14, watchOS 7, *)
internal static func localizable(_ localizable: String.Localizable) -> LocalizedStringKey {
LocalizedStringKey(localizable: localizable)
}
/// Updates the underlying `key` used when performing localization lookups.
///
/// By default, an instance of `LocalizedStringKey` can only be created
/// using string interpolation, so if arguments are included, the format
/// specifiers make up part of the key.
///
/// This method allows you to change the key after initialization in order
/// to match the value that might be defined in the strings table.
fileprivate mutating func overrideKeyForLookup(using key: StaticString) {
withUnsafeMutablePointer(to: &self) { pointer in
let raw = UnsafeMutableRawPointer(pointer)
let bound = raw.assumingMemoryBound(to: String.self)
bound.pointee = String(describing: key)
}
}
}
#endif


While the helpers are helpful in getting you up and running in 95% of use cases, they are not always exactly what everybody wants. In my use case, I still have a UIKit application that does custom localizations, so the generated String.init(localizable:locale:) method is not actually useful to me because I need to resolve the language in a different way.

It would be good in my project if I could just generate the definitions, and then write my own String.init(localizable:) method that works exactly how I need it instead of using the generated one.


While I'm aware that I could still do this by just creating a method with a different name, the concern that I have with leaving the default String.init(localizable:) method is that it makes things confusing and risks potentially using the wrong method. It would be better to eliminate it entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions