Comments (4)
Hi @kkiermasz 👋 Good catch, thanks for raising this! I haven't actually used %s
and %p
format specifiers myself so that's why this was missed.
I have however been looking into some improvements around this part of how we generate the defaultValue
(String.LocalizationValue
type) because of a separate issue with parameter ordering and I think there might be some limitations with the new String.LocalizationValue
type compared to using NSLocalizedString(..)
.
(I should mention that I only added parsing support for %s
and %p
as I was mirroring what other tools such as R.swift and SwiftGen supported)
The ###
hashes are a quick workaround to escape double quotes that may exist inside the value that I construct. In the example from your screenshot ###"Test \###(arg1)"###
is the same as "Test \(arg1)"
.
With that in mind, I think that we can work out the exact behaviour that we want by testing the String.LocalizationValue
directly... I'm guessing that the following code doesn't work?
let arg: UnsafeRawPointer = ...
let value: String.LocalizationValue = "Test \(arg)"
dump(value)
I think that this is because the String.LocalizationValue
type is much more restrictive compared to the NSLocalizedString(...)
API. You can look at String.LocalizationValue.StringInterpolation
to get an idea of which custom types it accepts for interpolation:
https://developer.apple.com/documentation/swift/string/localizationvalue/stringinterpolation
From what I can see, there are three overloads of interest to us that accept different values:
NSObject
String
_FormatSpecifiable
It seems that UnsafeRawPointer
doesn't fit into any of these overloads, which is why I think that we are seeing this compiler error 😕
Note
A note about the private _FormatSpecifiable
type.. It's underscored which means that it isn't documented, but you can find more about it in the .swiftinterface file.
/Applications/Xcode-15.2.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.2.sdk/System/Library/Frameworks/Foundation.framework/Modules/Foundation.swiftmodule/arm64-apple-ios.swiftinterface
Here is the important stuff
@_alwaysEmitIntoClient @_semantics("constant_evaluable") private func formatSpecifier<T>(_ type: T.Type) -> Swift.String {
switch type {
case is Int.Type:
fallthrough
case is Int64.Type:
return "%lld"
case is Int8.Type:
fallthrough
case is Int16.Type:
fallthrough
case is Int32.Type:
return "%d"
case is UInt.Type:
fallthrough
case is UInt64.Type:
return "%llu"
case is UInt8.Type:
fallthrough
case is UInt16.Type:
fallthrough
case is UInt32.Type:
return "%u"
case is Float.Type:
return "%f"
case is CGFloat.Type:
fallthrough
case is Double.Type:
return "%lf"
default:
return "%@"
}
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public protocol _FormatSpecifiable : Swift.Equatable {
associatedtype _Arg : Swift.CVarArg
var _arg: Self._Arg { get }
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Int : Foundation._FormatSpecifiable {
public var _arg: Swift.Int64 {
get
}
public typealias _Arg = Swift.Int64
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Int8 : Foundation._FormatSpecifiable {
public var _arg: Swift.Int32 {
get
}
public typealias _Arg = Swift.Int32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Int16 : Foundation._FormatSpecifiable {
public var _arg: Swift.Int32 {
get
}
public typealias _Arg = Swift.Int32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Int32 : Foundation._FormatSpecifiable {
public var _arg: Swift.Int32 {
get
}
public typealias _Arg = Swift.Int32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Int64 : Foundation._FormatSpecifiable {
public var _arg: Swift.Int64 {
get
}
public typealias _Arg = Swift.Int64
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.UInt : Foundation._FormatSpecifiable {
public var _arg: Swift.UInt64 {
get
}
public typealias _Arg = Swift.UInt64
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.UInt8 : Foundation._FormatSpecifiable {
public var _arg: Swift.UInt32 {
get
}
public typealias _Arg = Swift.UInt32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.UInt16 : Foundation._FormatSpecifiable {
public var _arg: Swift.UInt32 {
get
}
public typealias _Arg = Swift.UInt32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.UInt32 : Foundation._FormatSpecifiable {
public var _arg: Swift.UInt32 {
get
}
public typealias _Arg = Swift.UInt32
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.UInt64 : Foundation._FormatSpecifiable {
public var _arg: Swift.UInt64 {
get
}
public typealias _Arg = Swift.UInt64
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Float : Foundation._FormatSpecifiable {
public var _arg: Swift.Float {
get
}
public typealias _Arg = Swift.Float
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension Swift.Double : Foundation._FormatSpecifiable {
public var _arg: Swift.Double {
get
}
public typealias _Arg = Swift.Double
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension CoreFoundation.CGFloat : Foundation._FormatSpecifiable {
public var _arg: CoreFoundation.CGFloat {
get
}
public typealias _Arg = CoreFoundation.CGFloat
}
So, with this in mind, I am not sure how to proceed 😄 I have a few ideas though:
- Try adding
_FormatSpecifiable
conformance toUnsafeRawPointer
to the target where the code is generated (XCStringsTool can't do this). - If you are working with
NSObject
types, maybe the codegen could accept theNSObject
type rather thanUnsafeRawPointer
? - Remove (the broken) support for
%s
and%p
as it isn't supported byString.LocalizationValue
(I think this would also help with any attempt that I make to solve #40) - In your use case, use
%@
in your string and instead format the pointer value prior to passing it into the generated codelet _arg: UnsafeRawPointer = ... let arg = String(format: "%p", _arg) let localized = String(localized: .localizable.p(arg))
I hope that this brain-dump helps 😄 As I don't really use this feature myself, I am not sure what the best option is so it would be great to hear your thoughts! Thanks 🙏
from xcstrings-tool.
Hey @liamnichols 👋🏻 ,
wow, thanks for the response 😄
I'm not using them either. I was checking the library condition before adding it to the project 😛 The screenshot is from the test snapshot.
Thanks for the reference, now I see it's not support out-of-the-box :)
Since I'm not a String expert (which is a complex topic), I'm not voting for any of the 4 solutions you mentioned 🫠 I just thought it might be worth mentioning this in the documentation in some "limitations" section, since someone will probably encounter this problem later.
Btw it's a pity that there's no way (or I'm the reason in this case :D) to use one shared catalog across multiple feature modules, as Apple invented (View -> Catalog -> .strings), I'd give it a try.
from xcstrings-tool.
Btw did you notice any performance issues? One SwiftUI view (re)render causes ~2.4k LocalizedStringResource resolve attempts, each taking ~0.00016s. I wonder if that's because the tool (catalogs itself) was not designed to expose resources publicly :/
(you know, one Resources
module imported by several feature modules).
When using the old .strings in the same shared module with localizedString(forKey: key, value: value, table: table)
the resolve attempt time is pretty much the same, however, it's not called every time swiftui's view rerender.
It's unusable (LocalizedStringResource) with this performance tbh
I know it's not on your side, as I would assume SwiftUI is caching resources data or smth similar, but passing resources to the views was triggering the above issue. As I have some complex views it impacts performance too much. I made a fork and changed the file generation to something similar to SwiftGen. https://github.com/kkiermasz/xcstrings-tool/blob/main/Tests/XCStringsToolTests/__Snapshots__/GenerateTests/testGenerateWithPackageAccessLevel.Localizable.swift
Who's brain-dumping now? Haha
from xcstrings-tool.
I'm not using them either. I was checking the library condition before adding it to the project 😛
Ahh right I see, that's a good strategy 🙂
The screenshot is from the test snapshot.
Ooooh, good spot. I opened #56 to keep on top of that in the future.
Since I'm not a String expert (which is a complex topic), I'm not voting for any of the 4 solutions you mentioned 🫠 I just thought it might be worth mentioning this in the documentation in some "limitations" section, since someone will probably encounter this problem later.
Well now that I've had a think about it, I think that I am going to peruse option 3 and remove direct support for %s
and %p
to help with the @Sendable
work.
I believe that option 4 would then be the viable workaround for anybody that did need to use this kind of formatting... I'll keep this issue open to track that 👍
Btw it's a pity that there's no way (or I'm the reason in this case :D) to use one shared catalog across multiple feature modules, as Apple invented (View -> Catalog -> .strings), I'd give it a try.
I'm not quite sure if I understood what you are trying to achieve here, but does using the public
or package
access level do what you need here?
Btw did you notice any performance issues?
As for this - not yet. I haven't personally used XCStrings Tool in any complex SwiftUI projects where it has become a concern.
I'm not quite sure of the solution to this though because my understanding is that the purpose of LocalizedStringResource
is to defer the resolution of the localized string so that SwiftUI can handle it instead. I think this was primarily to support out-of-process rendering for things like App Intents, but also it lets you pass a custom locale
in via the SwiftUI environment and then have the correct language resolved dynamically.
Maybe it isn't common usage though, and in that case, caching the resolved strings might be a better option, but I don't think it's a good default.
I think that it probably needs some more investigation though, so thanks for flagging it.. Perhaps you can open a separate issue to track this? There might be some ways that we can optimise the generated code more, or maybe at least add some best practice documentation 🙏
from xcstrings-tool.
Related Issues (20)
- Integrating XCStrings Tool into a Swift Package Target has wrong package git url HOT 2
- ExtractionState config HOT 3
- Demonstrate use of language overrides in documentation HOT 3
- Feature Suggestion: Support to other build systems HOT 1
- Ensure that snapshots compile
- Multiple commands produce generated source when working with embedded watchOS app HOT 5
- Generate.swift in the Release files look different HOT 3
- Support SwiftSyntax 5.10
- Improve SwiftUI support on iOS 15 HOT 16
- String containing a single `%` is parsed incorrectly HOT 1
- Duplicates in Localizable.xcstrings in 0.2.0 HOT 9
- Set default value to be fallback documenting comment HOT 2
- Suggestion/Question init LocalizedStringKey with .localizable.key HOT 8
- How to use with Github Actions? HOT 3
- Duplicate accessors generated in extensions on both `String` and `LocalizedStringResource`
- Support deferred formatting
- Generated @available has trailing whitespace HOT 5
- Build errors after updating from 0.4.1 to higher version HOT 3
- Add note to documentation about unwanted target dependencies
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from xcstrings-tool.