Skip to content
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

R.swift not work well with automatic grammar agreement (^[..](inflect: true)) #858

Open
susi021 opened this issue Oct 6, 2023 · 3 comments

Comments

@susi021
Copy link

susi021 commented Oct 6, 2023

For example, when I define the string "%lld person" = "^[%lld person](inflect: true)"; in localizable.string, and call it with the R.string.localizable.lldPerson(2), it will return ^[2 person](inflect: true), not the 2 people as I expected.

To leverage the automatic grammar agreement, I need to define the following:

"person" = "person";
"%lld %@" = "^[%lld %@](inflect: true)";

Use the R.swift to get the translation:

let person = R.string.localizable.person()
let attributedString = AttributedString(localized: "\(num) \(person)")
return String(attributedString.characters)

So that it will return "2 people", which is translated and pluralized.

Is there any better way to do that?

@tomlokhorst
Copy link
Collaborator

tomlokhorst commented Oct 6, 2023

Rswift works with .stringsdict files from Xcode, see: https://developer.apple.com/documentation/xcode/localizing-strings-that-contain-plurals

Alternatively, as of Xcode 15, you can use a strings catalog: https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog

I suggest you first try to make your required behaviour work without Rswift, using the features from the platform, before adding Rswift code generation.

@susi021
Copy link
Author

susi021 commented Oct 10, 2023

Thanks for the advice @tomlokhorst!

To make use of automatic grammar agreement, I will retrieve the localizable string without relying on R.swift for handling plurals.

Additionally, I'm curious to know if R.swift has any plans to support String Catalog, or even implement automatic grammar agreement?

@fthdgn
Copy link

fthdgn commented Mar 8, 2024

"%lld car" = "^[%lld car](inflect: true)";

To generate a R.swift method for this key, we should be able to
1- access it by the key
2- pass arguments
3- generate an AttributedString from it. (inflect system is a a feature of AttributedString)

The localization system of Swift, use ExpressibleByStringInterpolation everywhere.

LocalizedStringResource
String.LocalizationValue
LocalizedStringKey
all of these use ExpressibleByStringInterpolation to create necessary information to access and format localized string.

The problem is none of them have any other init to pass key and argument types.

I achieved this by utilizing LocalizedStringResource.init with defaultValue which requires iOS 16.

let carKey: LocalizedStringResource = .init("%lld car", defaultValue: "\(2)")
print(String(AttributedString(localized: carKey).characters))
// prints: 2 cars

I tried other examples.

On this case there are multiple plural and the order of objects are different on different localizations.

// English
"pen_and_book" = "^[%1$lld pen](inflect: true) and ^[%2$lld book](inflect: true) ";
// French
"pen_and_book" = "^[%2$lld livre](inflect: true) and ^[%1$lld stylo](inflect: true) ";
let enKey: LocalizedStringResource = .init("pen_and_book", defaultValue: "\(1)\(2)", locale: .init(identifier: "en"))
print(String(AttributedString(localized: enKey).characters))
// prints: 1 pen and 2 books

let frKey: LocalizedStringResource = .init("pen_and_book", defaultValue: "\(1)\(2)", locale: .init(identifier: "fr"))
print(String(AttributedString(localized: frKey).characters))
// prints: 2 livres et 1 stylo

This method can also replace NSLocalizedString completly (after iOS 16)

"hello_with_name" = "Hello %@!";
let key: LocalizedStringResource = .init("hello_with_name", defaultValue: "\("World")")
print(String(localized: key))
// prints: Hello World!

This is a prelimenery extensions to convert Rswift String resources to String and Attributed String.

@available(iOS 16, *)
extension RswiftResources.StringResource1 {
    // defaultValue does not support CVarArg protocol. I could not find a suitable protocol.
    
    func localizedStringResource(_ arg1: Arg1) -> LocalizedStringResource where Arg1 == String {
        switch source {
        // Cannot pass bundle inside of source. Its type is different.
        case let .selected(_, locale):
            return .init(self.key, defaultValue: "\(arg1)", table: self.tableName, locale: locale, comment: self.comment)
        default:
            return .init(self.key, defaultValue: "\(arg1)", table: self.tableName, comment: self.comment)
        }
    }
    
    func localizedStringResource(_ arg1: Arg1) -> LocalizedStringResource where Arg1 == Int {
        switch source {
        case let .selected(_, locale):
            return .init(self.key, defaultValue: "\(arg1)", table: self.tableName, locale: locale, comment: self.comment)
        default:
            return .init(self.key, defaultValue: "\(arg1)", table: self.tableName, comment: self.comment)
        }
    }
}

@available(iOS 16, *)
extension LocalizedStringResource {
    var asString: String {
        .init(localized: self)
    }
    
    var asAttributedString: AttributedString {
        .init(localized: self)
    }
}

extension AttributedString {
    var asString: String {
        .init(self.characters)
    }
}

How to use:

print(R.string.localizable.lldCar.localizedStringResource(5).asAttributedString.asString)
// prints: 5 cars

print(R.string.localizable(preferredLanguages: ["fr"]).lldCar.localizedStringResource(5).asAttributedString.asString)
// prints: 5 voitures

Currently there is SwiftUI extension of Rswift, however, they lose inflect information.

This implementation fixes that problem.

@available(iOS 16, *)
extension Text {
    public init<Arg1: CVarArg>(_ resource: StringResource1<Arg1>, _ arg1: Arg1) where Arg1 == String {
        self.init(resource.localizedStringResource(arg1))
    }
    
    public init<Arg1: CVarArg>(_ resource: StringResource1<Arg1>, _ arg1: Arg1) where Arg1 == Int {
        self.init(resource.localizedStringResource(arg1))
    }
}
Text(R.string.localizable.lldCar, 5)
// displays: 5 cars

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants