-
Notifications
You must be signed in to change notification settings - Fork 14
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
Migrate to Apple-native string catalog and codegen LocalizedStringResources with xcstrings-tool-plugin #738
Conversation
@@ -251,9 +251,7 @@ fileprivate struct BottomOverlay: View { | |||
if replyCount == 0 { | |||
return nil | |||
} | |||
let replyCount = replyCount | |||
let localized = replyCount == 1 ? Localized.Reply.one : Localized.Reply.many |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic did not work for locales that support different pluralization rules than just one
and many
.
@@ -57,16 +57,16 @@ struct RepostButton: View { | |||
.padding(.vertical, 12) | |||
} | |||
.disabled(tapped) | |||
.confirmationDialog("Repost", isPresented: $shouldConfirmRepost) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded string that was not localized.
@@ -57,16 +57,16 @@ struct RepostButton: View { | |||
.padding(.vertical, 12) | |||
} | |||
.disabled(tapped) | |||
.confirmationDialog("Repost", isPresented: $shouldConfirmRepost) { | |||
Button("Repost") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded string that was not localized.
tapped = false | ||
} | ||
} | ||
.confirmationDialog("Delete repost", isPresented: $shouldConfirmDelete) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded string that was not localized.
tapped = false | ||
} | ||
} | ||
.confirmationDialog("Delete repost", isPresented: $shouldConfirmDelete) { | ||
Button("Delete repost", role: .destructive) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded string that was not localized.
@@ -42,9 +42,7 @@ struct NoteCard: View { | |||
if replyCount == 0 { | |||
return nil | |||
} | |||
let replyCount = replyCount | |||
let localized = replyCount == 1 ? Localized.Reply.one : Localized.Reply.many |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic did not work for locales that support different pluralization rules than just one
and many
.
"two": second.safeName, | ||
"count": "\(followers.count - 2)" | ||
])) | ||
Text(.localizable.followedByTwoAndMore(first.safeName, second.safeName, followers.count - 2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to figure out if named arguments could be supported. Leaving that as an open question.
translation: /Nos/Assets/Localization/%osx_code%/%original_file_name% | ||
- source: /Nos/Assets/Localization/ImagePicker.xcstrings | ||
translation: /Nos/Assets/Localization/ImagePicker.xcstrings | ||
multilingual: 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indicates that the file contains translations for multiple locales.
Rebased on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is a huge contribution and a big improvement over the previous system! Regarding the history here: I actually don't know why we were using a handmade ExportStrings.sh
to do localization. It was set up for the Planetary app before I started, and when we started Nos we copied a lot of the strings over and with it came this system.
@martindsq can you give this a proper review since you manage the Crowdin integration and have a lot more expertise than me in this area?
Crowdin has fixed the issue. I'm removing this blurb from the PR description. |
|
Yeah sorry about that. Just clicked merge on that PR. Also we have some tests failing on |
No worries. I just rebased and resolved the conflicts.. |
@@ -55,7 +55,7 @@ struct PreviewData { | |||
}() | |||
|
|||
lazy var bob: Author = { | |||
let author = Author(context: previewContext) | |||
let author = try! Author.findOrCreate(by: KeyFixture.bob.publicKeyHex, context: previewContext) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was a bug that bob
wasn't in Core Data. alice
and eve
were though, so I copied what they did.
@@ -157,13 +157,13 @@ struct ContentWarningMessage: View { | |||
// Assuming there's a property or method 'safeName' in 'Author' that safely returns the author's name. | |||
private var firstAuthorSafeName: String { | |||
// Getting the safe name of the first author. Adjust according to your actual data structure. | |||
reports.first?.author?.safeName ?? "Unknown Author" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulled out Unknown Author
to localize it.
} | ||
|
||
private var reason: String { | ||
// Extract content from reports and remove empty content | ||
let contents = reports.compactMap { (($0.content?.isEmpty) != nil) ? nil : $0.content } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bug. It would always return nil
.
…ources with xcstrings-tool-plugin
Rebased on the |
Closes: #718
Previously,
English strings were written in
Nos/Assets/Localization/Localized.swift
on theLocalized
enum, and thenGenerated.strings
were generated for each locale that Nos supports, which could be localized and read in code. Argument substitutions were done in a bespoke way using regex. Locales (such as Arabic) that did not follow the pluralization rules of English would not be able to localize properly. There were no comments to provide context to translators, which probably made their work more difficult.Now,
Nos/Assets/Localization/*.xcstrings
(string catalog) files are used instead. String catalogs were introduced in Xcode 15 and they are supported natively, including argument substitution and localized plural rules. Each xcstrings file contains the translations of all locales. Crowdin has support for xcstrings files, and will write translations into the same file. I opted to use an MIT-licensed open source plugin called xcstrings-tool that transforms xcstrings files to generated code, so that we can referenceLocalizedStringResources
in a type-safe way. There's also now a way to provide comments to strings so that translators have context when translating.I copied all the current translations from Crowdin to the string catalog files. There was a bit of manual work here so hopefully I got it all right.
Someone who has access to the Crowdin project will need to make sure the integration works fine with these changes. I did some testing on my local Crowdin project and it seemed to work. Keep in mind that the history of the past translation work and translator attribution will probably get wiped out.
There also seems to be a bug where Xcode can occasionally crash when entering strings with arguments in the string catalog editor for non-default locales. The workaround for now is to edit in a text editor. But typically translators won't be interacting with Xcode, just Crowdin.
Xcode String Catalog Editor
Generated code from xcstrings-tool