New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support explicit symbol localization #90
Conversation
There is still a problem regarding |
@knothed Thanks for the PR. I took a quick look and have a few questions: Documentation
As explained in #82, Localizations are available for old names, just not explicitly. I suggest mentioning for every localization whether it's explicit or implicit: /// Localizations:
/// - Chinese (Implicit) (iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0)
/// - Hindi (Explicit) (iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0) Localization Protocols
SFSymbolSetCan you please explain more the reasoning behind this new type? is it for testing? tbh I think it's a bit overkill. |
Sounds good, will do.
Also good. It really doesn’t matter if they’re there or not.
Hmm… If there is a symbol with 9 localiztions, this will produce quite a long type. I think hi is clear enough as it is only used in conjunction with SFSymbol.
Yes, it’s impossible – as
Well, if we support Also, yes, it’s quite handy for testing. |
You can constraint the extension: extension hi where Self: SFSymbol {
var hi: SFSymbol { .init(rawValue: "\(rawValue).hi") }
} or even better, constraint the protocol itself. This will also solve the naming issue because the constraint itself will be the explanation (we don't need to include "symbol" in the name): public protocol hi where Self: SFSymbol {
var hi: SFSymbol { get }
}
extension hi {
var hi: SFSymbol { .init(rawValue: "\(rawValue).hi") }
} and I think its enough to only make the extension for the default localization protocols (I'm not sure)
Hmm… I'm not sure. if you look at the SFSymbols.app, the "All" tab does not have the localized versions. I think |
Good work @knothed, good suggestions @Stevenmagdy! 👍 Regarding the Before this PR gets merged, we also still need some README-documentation on the availability of explicit localizations (and maybe on the |
I forgot to mention that I'm rather against specifying whether the localization is available explicitly or implicitly. Even users with advanced knowledge won't really understand what this means at first sight... |
@fredpi I agree, it's confusing. but I think it's more important to mention the implicit localizations because that is what most users will care about. How about keeping the localizations docs as it is (before the PR) and maybe explaining the explicit localized properties in the README? |
Nice, haven't thought of that!
Hmm, I think it's most confusing if the documentation says "Localizable in Arabic" but then there is no
Yes, writing |
I propose to add a public So, if the user had some if symbol.has(.ar) {
let localized = (symbol as! ar).ar
// use localized
} else {
// use symbol
} The new struct Localization {
case ar
...
case zhTraditional
}
I would use this inside |
Proposal: if let localized = symbol.localized(to: .ar) {
// use localized
} else {
// use symbol
} |
I like it @j-f1. This would make the SFSymbolSet compltely redundant as users can just write: symbols.map {
$0.localized(to: .ar) ?? $0
} We could still make an |
If @fredpi and @Stevenmagdy have no objection, I would implement this as suggested by @j-f1. This would get rid of SFSymbolSet and allow the user, in addition to static localization via |
I agree, we should provide them for all protocols for consistency and to make it easier for conformance.
Yes, we didn't think of this limitation/bug. I think we should rethink the protocol approach because of this.
Are those proposals a replacement for the protocol approach? That is very similar to my option 2 in #87 (first post) which was not chosen because of the optionality. I actually like it, I'm just curious. |
@knothed sorry I didn't see your last comment.
I'm against providing both approaches, this is too complicated. I think we should only provide one intuitive approach. |
I like the protocol approach, it’s pretty simple to use, but we cannot avoid this limitation. Even if we would introduce a new class per localization availability combination, we still couldn’t do So to see whether an arbitraty
No, I do not like the optionality. It would just be a supplement to the static localization, which is pretty nice as is.
Internally inside SFSymbol. extension SFSymbol {
static let arSymbol: SFSymbol & ar & hi_v30 = LocalizedSymbol(„symbol“, localizations: [.ar(.v1), .hi(.v30)])
} The dynamic check ( |
Should I move forward implementing this? |
@knothed Sorry for the delayed response.
I'm still strongly against providing two APIs for localization, even if each one addresses a different use case. IMO this is an indication that the protocol approach was not a good fit in the first place. I think we should release a new version ASAP (SFSymbols 3.2 is out), so I suggest postponing this feature until the next major version (we don't have to wait for SFSymbols 4.0). Until then, we can think of an API that can serve all use cases. Other options to discuss:
|
Happy holidays! I don’t really like the two options you stated. Optionality is kind of a deal-breaker as it provides no benefits over using raw strings. We should think about a better approach before moving forward. |
@Stevenmagdy @knothed Sorry for the period of inactivity!
I would suggest to try to quickly finish the implementation of the localized symbol supported before releasing version 3, because if there are breaking changes, the 3.0 release would be the best fit for them (there are already breaking changes in it).
I agree with this; I'm not very happy with these options. I revisited #87 and found this suggestion. What about using it instead of the protocol approach? Then the user can't write I would also implement dynamic localization as suggested in this comment by @knothed.
I don't think this would be an indication that the protocol approach is bad. Both use cases (dynamic and static) are valid. A dynamic approach is needed e. g. for operations on a BTW: With #91 merged, this PR also needs a rebase. |
The post ends up using Here is another suggestion: @dynamicMemberLookup
public class Localizable1Symbol<L1: SymbolLocalization>: SFSymbol {
subscript(dynamicMember keyPath: KeyPath<L1, SFSymbol>) -> SFSymbol {
L1(source: self)[keyPath: keyPath]
}
}
@dynamicMemberLookup
public class Localizable2Symbol<L1: SymbolLocalization, L2: SymbolLocalization>: SFSymbol {
subscript(dynamicMember keyPath: KeyPath<L1, SFSymbol>) -> SFSymbol {
L1(source: self)[keyPath: keyPath]
}
subscript(dynamicMember keyPath: KeyPath<L2, SFSymbol>) -> SFSymbol {
L2(source: self)[keyPath: keyPath]
}
}
// Localizable3Symbol
// Localizable4Symbol
// Localizable5Symbol
// Localizable6Symbol
// Localizable7Symbol
// Localizable8Symbol
public protocol SymbolLocalization {
init(source: SFSymbol)
}
public struct Ar: SymbolLocalization {
let source: SFSymbol
public init(source: SFSymbol) { self.source = source }
public var ar: SFSymbol { .init(rawValue: "\(source.rawValue).ar") }
}
public struct Zh3_0: SymbolLocalization {
let source: SFSymbol
public init(source: SFSymbol) { self.source = source }
@available(iOS 15.0, *)
public var zh: SFSymbol { .init(rawValue: "\(source.rawValue).zh") }
}
extension SFSymbol {
static let symbolWithAr = Localizable1Symbol<Ar>(rawValue: "symbolWithAr")
static let symbolWithArAndZh = Localizable2Symbol<Ar, Zh3_0>(rawValue: "symbolWithArAndZh")
}
let exampleImage = UIImage(systemSymbol: . symbolWithArAndZh.ar) Swift does not have Variadic Generics yet. Which will let us write Variadic Generics is being discussed right now. But until its released, we can do what Apple does and duplicate the class/struct with a different number. There is another workaround that I don't like, which is providing only public struct None: SymbolLocalization {
let source: SFSymbol
public init(source: SFSymbol) { self.source = source }
}
extension SFSymbol {
static let symbolWithAr = Localizable8Symbol<Ar, None, None, None, None, None, None, None>(rawValue: "symbolWithAr")
static let symbolWithArAndZh = Localizable8Symbol<Ar, Zh3_0, None, None, None, None, None, None>(rawValue: "symbolWithArAndZh")
} |
I think that's reasonable! And if variadic genetics are added a year or two down the line (and assuming the compiler supports backdeploying them) it should be reasonable to switch to using them. |
I'm not quite sure how I should feel about this, sounds like a very sensitive issue to me. Especially if a compiler is involved. With variadic generics I'd be fine on the other hand...
I really like this approach @Stevenmagdy. How does it help us in performing a localisation check at runtime however? Wouldn't we have to basically switch over the type of the |
@knothed This was only a suggestion for a replacement to the protocol approach to solve the extension SFSymbol {
static let arSymbol: SFSymbol & ar & hi_v30 = LocalizedSymbol(„symbol“, localizations: [.ar(.v1), .hi(.v30)])
} If you want to derive the runtime checking from the static information, we can add additional information to the class/protocol: protocol LocalizableSymbol where Self: SFSymbol {
var localizations: [SymbolLocalization.Type] { get }
}
@dynamicMemberLookup
public class Localizable2Symbol<L1: SymbolLocalization, L2: SymbolLocalization>: SFSymbol, LocalizableSymbol {
var localizations: [SymbolLocalization.Type] { [L1.self, L2.self] }
subscript(dynamicMember keyPath: KeyPath<L1, SFSymbol>) -> SFSymbol {
L1(source: self)[keyPath: keyPath]
}
subscript(dynamicMember keyPath: KeyPath<L2, SFSymbol>) -> SFSymbol {
L2(source: self)[keyPath: keyPath]
}
}
extension SFSymbol {
public static let symbolWithAr = Localizable1Symbol<Ar>(rawValue: "symbolWithAr")
public static let symbolWithArAndZh = Localizable2Symbol<Ar, Zh>(rawValue: "symbolWithArAndZh")
}
let allSymbols: [LocalizableSymbol] = [SFSymbol.symbolWithAr, SFSymbol.symbolWithArAndZh]
let arSymbol = allSymbols.filter { $0.localizations.contains { $0 == Ar.self } }
let zhSymbol = allSymbols.filter { $0.localizations.contains { $0 == Zh.self } } But I think that should be used only for |
Sorry, no time atm. |
@Stevenmagdy I‘d be ready to tackle this in the near future. Are there updates on the proposed implementation? |
@knothed No, that is the best implementation that I can come up with, even though I don't like it very much. |
@Stevenmagdy I do quite like it, assuming variadic generics will make their way into Swift in the future. |
Deprecated via #98 |
Implements #87.
Following this suggestion by @Stevenmagdy, one protocol per localization per availability is created.
The decision for the protocol names fell on
hi
andhi_v30
as these are shorter, easier to read and look better thanHiLocalizable
andHiLocalizableV30
. What do you think?Additionally,
allSymbols
was made into anSFSymbolSet
which allows to localize all symbols (viaallSymbols.hi
) and which exposesallSymbolVariants
containing every localization variant of every symbol.The symbol documentations were adapted to match the actual exposed localizations: when deprecated symbols do not provide some localizations, but only their new versions do, these localizations are not in the documentation of the deprecated symbol to match its actual type. (example in the image)