mac-cain13 / R.swift

Strong typed, autocompleted resources like images, fonts and segues in Swift projects
MIT License
9.49k stars 760 forks source link

Support localized strings like R.strings.myString #41

Closed Ewg777 closed 8 years ago

Ewg777 commented 9 years ago

Add new feature parsing .strings file

mac-cain13 commented 9 years ago

Thanks for this interesting suggestion! Could you explain a little bit more about your vision on how this would work?

I think lot of developers type something like NSLocalizedString("Please login when you're ready!", comment: "Login screen header") and then extract that to a .strings file. That's not a case where R.swift would be really handy I think.

When using keys in localized strings maybe it's handier? A concrete example of how you would like it to work would be nice!

Ewg777 commented 9 years ago

For example like this http://developer.android.com/guide/topics/resources/string-resource.html

mac-cain13 commented 9 years ago

Yeah, I get you're inspired by the Android way. I believe we should think a little more about how this works out and integrates into the daily workflow of developers, but I definitly like the idea!

Ewg777 commented 9 years ago

Done here https://github.com/AliSoftware/SwiftGen#localizablestrings

mac-cain13 commented 9 years ago

Ah nice example! Thanks for posting it here! :)

Definitely something to look into and implement in R.swift.

mac-cain13 commented 9 years ago

After talking with @tomlokhorst about this; I think that it will be hard to get a good solution that fits within the workflow. For example the SwiftGen style of working with string doesn't really show how they add new strings.

zxy198717 commented 8 years ago

@mac-cain13 Could you generate string code like this:

struct string {
    static var bar_title: String? { return CustomLocalizedString("bar_title") } 
}

Then, create one file to implement function CustomLocalizedString, and developer can modify this file.

public func CustomLocalizedString(key: String) -> String {
    // DEV coding ....
    return NSLocalizedString(key, comment: "");
}
Ewg777 commented 8 years ago

JiriTrecak/Laurine also does this trick

ntnmrndn commented 8 years ago

Let's not forget strings dictionaries if we do that :)

dlbuckley commented 8 years ago

I'm all for localised string generation, but I think there should be a way to access the string key separately so we can still use NSLocalizedString(). It's very useful to keep the macro/method as Xcode has tools that generate files based on it and it allows you to add more context to what you are using the text for.

I haven't tested it yet, but Xcode 7.3 also has string localisation testing in the static analyser, so thats another reason to keep the string keys available for reference.

tomlokhorst commented 8 years ago

In my opinion there are two basic approaches to doing localized strings in Xcode:

  1. Using NSLocalizedStrings() in source, and using genstring to create a Localizable.strings file. This is where you have code like this: NSLocalizedString("Please login when you're ready!", comment: "Login screen header")
  2. Using keys manually written in Localizable.strings. This is where you have code like this: NSLocalizedString("login_screen.header_notice", comment: "")

I don't think R.swift can do much to support the first approach. However, it can definitely support the second approach.

The generated struct in R.strings.* can include the hierarchy in keys, like login_screen.header_notice. Comments in the Localizable.strings file can be included as comments in the generated structs.

Also see "Programming Resources" at https://developer.apple.com/internationalization/

swiftcrossing commented 8 years ago

I currently work on a team that uses R.swift for every resource but colors and localized strings. For those two, we are using SwiftGen, like mentioned above. I feel that R.swift has the opportunity to offer a more convenient and well-integrated solution for teams because it is offered through CocoaPods, making it trivial to integrate with an Xcode project without the need to have a tool installed on each team members' local machine (which is why I love R.swift to begin with!!!).

@mac-cain13 I was wondering if you could elaborate more on what you mean by finding it hard to get a good solution that fits within the workflow. I feel like the workflow for developers would be the same as with the other aspects of R.swift. For example, a developer creates a storyboard file using Xcode's tools,, then uses R.swift with access parts of that storyboard in code. I envision the same workflow with strings, where the developer creates / adds strings to their Localizable.string file, then uses R.swift to access those strings in code. Is this not the workflow you were referring to?

mac-cain13 commented 8 years ago

I agree with @tomlokhorst latest comment and also with your approach @renrawnalon. I was overcomplicating and overthinking the problem a bit earlier.

It's clear that we should read strings files, probably combine those if there are multiple?! (Is that possible, how does that exactly work?) And then output them to R.strings.* as @tomlokhorst proposes. As a developer you update the *.strings file and R.swift will pick up the new key.

And as a reminder to self:

Let's not forget strings dictionaries if we do that :)

swiftcrossing commented 8 years ago

Yeah, that's exactly what I had in mind.

Regarding multiple Localizable.strings files, ideally, there would be some sort of validation to ensure that all of the language files contain all of the same keys.

For example, this would be a valid set of .strings files:

/* en.lproj/Localizable.strings */
"myString.one" = "One";
"myString.two" = "Two: %@";

/* es.lproj/Localizable.strings */
"myString.one" = "Uno";
"myString.two" = "Dos: %@";

This is valid because when you call NSLocalizedString("myString.one", comment: "") there is a valid mapping for each language.

However,

/* en.lproj/Localizable.strings */
"myString.one" = "One";

/* es.lproj/Localizable.strings */
"myString.two" = "Dos: %@";

This would fail validation, either throwing a warning, but more likely an error? because there are missing mappings.

With this strict compile-time and/or run-time validation, the developer could be sure that all keys have valid mappings, removing the possible error of accidentally forgetting to add a translation in a certain language. This is surprisingly easy to forget when you are working with lots of localization files because Xcode doesn't warn you about it.

The resulting output from the above input would ideally look something like this (implementation details aside):

struct R: Rswift.Validatable {
  ...
  struct string {
    static let myStringOne = LocalizedString("myString.one")
    static let myStringTwo = { (value1: String) in LocalizedString("myString.one", value1) }
  }
  ...
}
tomlokhorst commented 8 years ago

This is only slightly related to this issue, but maybe also interesting to consider:

@renrawnalon Could you elaborate on how you use colors from SwiftGen?

I don't understand the benefit of writing a colors.txt file:

Cyan         : 0xff66ccff
ArticleTitle : #33fe66

Couldn't you just as easily write Colors.swift?

struct Colors {
  static let Cyan = UIColor(hex: 0xFF66CCFF)
  static let ArticleTitle = UIColor(hex: 0x33FE66)
}

For localization, using Localizable.strings seems obvious because it's already part of the ecosystem. But I don't see this for colors?

swiftcrossing commented 8 years ago

@tomlokhorst I actually use swiftgen colors with color palette files (.clr) which support was recently added for, not with .txt files. Using a .clr file in combination with SwiftGen, my team is able to manage colors in Interface Builder with a custom color palette, then use those color definitions in code as well. Another nice thing about .clr files is that they can be shared across mac applications, including Photoshop, Illustrator, and likely Sketch, so the design team can keep the .clr file for a project up to date, and the development team, can use that file to implement design in IB or in code for dynamic UIs.

Here is a quick read about how custom color palettes can be created and shared with your team, if you are not familiar with them.

As for using swiftgen colors with a text file, I don't think there is much benefit, other than that you might be able to share that text file with (or have it populated by) other members of your project that are not familiar with Swift, like your design team. I personally haven't tried this, and in practice, it might not prove to be useful.

If you don't use Storyboards to create your UI or you don't work on a team, and you arethe sole designer and developer for your apps, then I don't think it would make sense to use SwiftGen for colors, but it's quick convenient when working on a team.

tomlokhorst commented 8 years ago

@renrawnalon Right, I see. That makes sense. Thanks!

I've created an issue for discussing R.colors further: https://github.com/mac-cain13/R.swift/issues/169

alexanderjrobinson commented 8 years ago

+1 for adding localized string support similar to SwiftGen

swiftcrossing commented 8 years ago

I've created a pull request that is the first step in solving this issue. So far, I have only implemented parsing with Localization.strings keys without printf style formatting support or .stringsdict support. I figured the PR would have been to large if I included all of these at once, but I am already working on figuring out how to do the format parsing. I'm thinking a regex should be sufficient, but if anyone has any suggestions, I'm all ears. However, I believe that this PR is plenty useful as is, and I think releasing this version, then later adding support for printf formatting and .stringsdict file would work out nicely.

One potential problem with this approach would be that when printf formatting support is added, it would break existing code by change this:

R.string.myFormattedKey

to this:

R.string.myFormattedKey(value)

This might be a welcome breaking change, but this is definitely something that needs to be taken into consideration.

@mac-cain13 I know you've been busy recently, but I would appreciate it if you could take a look at this when you have time and let me know what you think about both the PR and the problem that I presented above.

Thanks!

mac-cain13 commented 8 years ago

Thanks to @tomlokhorst and @renrawnalon localized strings have landed in R.swift! 🛬

It's a big feature and I think a lot of people will be very happy with this. Enjoy! 🎉

fabb commented 6 years ago

Is there a reason why R.swift contrary to Laurine did not use nested structs for accessing parts of localized strings which are separated by a .? E.g. Laurine allows to write things like Localizations.Profile.NavigationBar.Items.Done which looks very nice.

mac-cain13 commented 6 years ago

No there is no specific reason for this other then that it's in line with all other name conversions we have in the codebase. I agree it would be nice to have this behaviour, we also use this dot-pattern a lot in our own strings files and the conversion R.swift applies now makes it less convenient to read.

I think it would be a change to consider. I'll create a separate issue for this improvement so we can discuss/implement it!