Skip to content

Commit 3ab59ba

Browse files
Improve a few complex strings now that we have shifted to .xcstrings
This also introduces a new manually-maintained lookup for localizable strings to replace swiftgen. This manual system allows us to organize the strings as much as we want and is much less work than maintaining the swiftgen system used to be. Contributes to IOS-582
1 parent f46fc7e commit 3ab59ba

File tree

6 files changed

+2670
-10016
lines changed

6 files changed

+2670
-10016
lines changed

Mastodon/In Progress New Layout and Datamodel/Common Components/Views/TimelineRowViews/MastodonPostViewModel.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,17 @@ extension MastodonPostViewModel {
237237
let boostLabel: String? = {
238238
guard metrics.boostCount > 0 else { return nil }
239239
if myActions.boosted {
240-
return L10n.Plural.Count.youAndOthersBoosted(metrics.boostCount - 1)
240+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.youAndOthersBoosted(othersCount: metrics.boostCount - 1)
241241
} else {
242-
return L10n.Plural.Count.reblogA11y(metrics.boostCount)
242+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.peopleBoosted(boostCount: metrics.boostCount)
243243
}
244244
}()
245245
let favoriteLabel: String? = {
246246
guard metrics.favoriteCount > 0 else { return nil }
247247
if myActions.favorited {
248-
return L10n.Plural.Count.youAndOthersFavorited(metrics.favoriteCount - 1)
248+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.youAndOthersFavorited(othersCount: metrics.favoriteCount - 1)
249249
} else {
250-
return L10n.Plural.Count.favorite(metrics.favoriteCount)
250+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.peopleFavourited(favouriteCount: metrics.favoriteCount)
251251
}
252252
}()
253253
let bookmarkLabel: String? = {

Mastodon/In Progress New Layout and Datamodel/Common Components/Views/TimelineRowViews/Molecules/EmbeddedPostView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ extension GenericMastodonPost.PostAttachment {
365365
return L10n.Plural.Count.attachment(array.count)
366366
}
367367
case .poll:
368-
return L10n.Plural.Count.poll(1)
368+
return L10nLookup.pluralCountPoll(1)
369369
case .linkPreviewCard:
370370
return nil
371371
}

Mastodon/In Progress New Layout and Datamodel/Common Components/Views/TimelineRowViews/NotificationRowView.swift

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ extension GroupedNotificationType {
127127
plainString = firstAuthorName
128128
case .poll(let status):
129129
let votersCount = status?.poll?.votersCount ?? 0
130-
let pollDescription = L10n.Plural.Count.pollThatYouAndOthersVotedIn(votersCount - 1)
131-
plainString = L10n.Scene.Notification.GroupedNotificationDescription.singleNameRanPoll(firstAuthorName, pollDescription)
130+
plainString = L10nLookup.Scene.Notification.GroupedNotificationDescription.pollHasEnded(pollAuthor: firstAuthorName, otherVotersCount: votersCount - 1)
132131
case .status:
133132
plainString = firstAuthorName
134133
case .adminSignUp:
@@ -143,7 +142,7 @@ extension GroupedNotificationType {
143142
} else {
144143
switch self {
145144
case .favourite:
146-
plainString = L10n.Plural.Count.peopleFavourited(totalAuthorCount)
145+
plainString = L10nLookup.Scene.Notification.GroupedNotificationDescription.peopleFavourited(favouriteCount: totalAuthorCount)
147146
case .follow:
148147
plainString = L10n.Plural.Count.peopleFollowedYou(totalAuthorCount)
149148
case .reblog:
@@ -167,31 +166,25 @@ extension Mastodon.Entity.Report {
167166
var summaryHtml: String {
168167
if let targetedAccount = targetAccount {
169168
let targetedAccountName = targetedAccount.displayNameWithFallback
170-
let postCountString: String? = {
171-
if let postCount = flaggedStatusIDs?.count {
172-
return L10n.Plural.Count.post(postCount)
173-
} else {
174-
return nil
175-
}
176-
}()
169+
let postCount: Int? = flaggedStatusIDs?.count
177170

178171
let summaryPlainstring: String = {
179172
switch category {
180173
case .spam:
181-
if let postCountString {
182-
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedPostsFromAccountForSpam(postCountString, targetedAccountName)
174+
if let postCount {
175+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.someoneReportedPostsForSpam(postCount: postCount, violatingAccountName: targetedAccountName)
183176
} else {
184177
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedAccountForSpam(targetedAccountName)
185178
}
186179
case .violation:
187-
if let postCountString {
188-
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedPostsFromAccountForRuleViolation(postCountString, targetedAccountName)
180+
if let postCount {
181+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.someoneReportedPostsForRuleViolation(postCount: postCount, violatingAccountName: targetedAccountName)
189182
} else {
190183
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedAccountForRuleViolation(targetedAccountName)
191184
}
192185
case ._other, nil:
193-
if let postCountString {
194-
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedPostsFromAccount(postCountString, targetedAccountName)
186+
if let postCount {
187+
return L10nLookup.Scene.Notification.GroupedNotificationDescription.someoneReportedPosts(postCount: postCount, violatingAccountName: targetedAccountName)
195188
} else {
196189
return L10n.Scene.Notification.GroupedNotificationDescription.someoneReportedAccount(targetedAccountName)
197190
}
@@ -732,7 +725,7 @@ extension Mastodon.Entity.Status {
732725
case .generic(let count):
733726
return L10n.Plural.Count.attachment(count)
734727
case .poll:
735-
return L10n.Plural.Count.poll(1)
728+
return L10nLookup.pluralCountPoll(1)
736729
}
737730
}
738731

MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,18 +1102,6 @@ public enum L10n {
11021102
public static let rejected = L10n.tr("Localizable", "Scene.Notification.FollowRequest.Rejected", fallback: "Rejected")
11031103
}
11041104
public enum GroupedNotificationDescription {
1105-
/// %@ boosted:
1106-
public static func multiplePeopleBoosted(_ p1: Any) -> String {
1107-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.MultiplePeopleBoosted", String(describing: p1), fallback: "%@ boosted:")
1108-
}
1109-
/// %@ favorited:
1110-
public static func multiplePeopleFavourited(_ p1: Any) -> String {
1111-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.MultiplePeopleFavourited", String(describing: p1), fallback: "%@ favorited:")
1112-
}
1113-
/// %@ followed you
1114-
public static func multiplePeopleFollowedYou(_ p1: Any) -> String {
1115-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.MultiplePeopleFollowedYou", String(describing: p1), fallback: "%@ followed you")
1116-
}
11171105
/// %@ boosted:
11181106
public static func singleNameBoosted(_ p1: Any) -> String {
11191107
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SingleNameBoosted", String(describing: p1), fallback: "%@ boosted:")
@@ -1146,10 +1134,6 @@ public enum L10n {
11461134
public static func singleNameQuoted(_ p1: Any) -> String {
11471135
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SingleNameQuoted", String(describing: p1), fallback: "%@ quoted you")
11481136
}
1149-
/// %@ ran %@
1150-
public static func singleNameRanPoll(_ p1: Any, _ p2: Any) -> String {
1151-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SingleNameRanPoll", String(describing: p1), String(describing: p2), fallback: "%@ ran %@")
1152-
}
11531137
/// %@ requested to follow you
11541138
public static func singleNameRequestedToFollowYou(_ p1: Any) -> String {
11551139
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SingleNameRequestedToFollowYou", String(describing: p1), fallback: "%@ requested to follow you")
@@ -1170,18 +1154,6 @@ public enum L10n {
11701154
public static func someoneReportedAccountForSpam(_ p1: Any) -> String {
11711155
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeoneReportedAccountForSpam", String(describing: p1), fallback: "Someone reported %@ for spam.")
11721156
}
1173-
/// Someone reported %@ from %@.
1174-
public static func someoneReportedPostsFromAccount(_ p1: Any, _ p2: Any) -> String {
1175-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeoneReportedPostsFromAccount", String(describing: p1), String(describing: p2), fallback: "Someone reported %@ from %@.")
1176-
}
1177-
/// Someone reported %@ from %@ for rule violation.
1178-
public static func someoneReportedPostsFromAccountForRuleViolation(_ p1: Any, _ p2: Any) -> String {
1179-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeoneReportedPostsFromAccountForRuleViolation", String(describing: p1), String(describing: p2), fallback: "Someone reported %@ from %@ for rule violation.")
1180-
}
1181-
/// Someone reported %@ from %@ for spam.
1182-
public static func someoneReportedPostsFromAccountForSpam(_ p1: Any, _ p2: Any) -> String {
1183-
return L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeoneReportedPostsFromAccountForSpam", String(describing: p1), String(describing: p2), fallback: "Someone reported %@ from %@ for spam.")
1184-
}
11851157
/// Your poll has ended
11861158
public static let yourPollHasEnded = L10n.tr("Localizable", "Scene.Notification.GroupedNotificationDescription.YourPollHasEnded", fallback: "Your poll has ended")
11871159
}
@@ -1216,16 +1188,6 @@ public enum L10n {
12161188
public static let showMentions = L10n.tr("Localizable", "Scene.Notification.Keyobard.ShowMentions", fallback: "Show Mentions")
12171189
}
12181190
public enum NotificationDescription {
1219-
/// favorited your post
1220-
public static let favoritedYourPost = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.FavoritedYourPost", fallback: "favorited your post")
1221-
/// followed you
1222-
public static let followedYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.FollowedYou", fallback: "followed you")
1223-
/// mentioned you
1224-
public static let mentionedYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.MentionedYou", fallback: "mentioned you")
1225-
/// poll has ended
1226-
public static let pollHasEnded = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.PollHasEnded", fallback: "poll has ended")
1227-
/// boosted your post
1228-
public static let rebloggedYourPost = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RebloggedYourPost", fallback: "boosted your post")
12291191
/// An admin from %@ has blocked %@, including %@.
12301192
public static func relationshipSeverance(_ p1: Any, _ p2: Any, _ p3: Any) -> String {
12311193
return L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RelationshipSeverance", String(describing: p1), String(describing: p2), String(describing: p3), fallback: "An admin from %@ has blocked %@, including %@.")
@@ -2356,21 +2318,9 @@ public enum L10n {
23562318
return L10n.tr("Localizable", "plural.count.people_boosted", p1, fallback: "Plural format key: \"%#@count_people@\"")
23572319
}
23582320
/// Plural format key: "%#@count_people@"
2359-
public static func peopleFavourited(_ p1: Int) -> String {
2360-
return L10n.tr("Localizable", "plural.count.people_favourited", p1, fallback: "Plural format key: \"%#@count_people@\"")
2361-
}
2362-
/// Plural format key: "%#@count_people@"
23632321
public static func peopleFollowedYou(_ p1: Int) -> String {
23642322
return L10n.tr("Localizable", "plural.count.people_followed_you", p1, fallback: "Plural format key: \"%#@count_people@\"")
23652323
}
2366-
/// Plural format key: "%#@poll_count@"
2367-
public static func poll(_ p1: Int) -> String {
2368-
return L10n.tr("Localizable", "plural.count.poll", p1, fallback: "Plural format key: \"%#@poll_count@\"")
2369-
}
2370-
/// Plural format key: "%#@count_others@"
2371-
public static func pollThatYouAndOthersVotedIn(_ p1: Int) -> String {
2372-
return L10n.tr("Localizable", "plural.count.poll_that_you_and_others_voted_in", p1, fallback: "Plural format key: \"%#@count_others@\"")
2373-
}
23742324
/// Plural format key: "%#@post_count@"
23752325
public static func post(_ p1: Int) -> String {
23762326
return L10n.tr("Localizable", "plural.count.post", p1, fallback: "Plural format key: \"%#@post_count@\"")
@@ -2399,14 +2349,6 @@ public enum L10n {
23992349
public static func voter(_ p1: Int) -> String {
24002350
return L10n.tr("Localizable", "plural.count.voter", p1, fallback: "Plural format key: \"%#@voter_count@\"")
24012351
}
2402-
/// Plural format key: "%#@count_others@"
2403-
public static func youAndOthersBoosted(_ p1: Int) -> String {
2404-
return L10n.tr("Localizable", "plural.count.you_and_others_boosted", p1, fallback: "Plural format key: \"%#@count_others@\"")
2405-
}
2406-
/// Plural format key: "%#@count_others@"
2407-
public static func youAndOthersFavorited(_ p1: Int) -> String {
2408-
return L10n.tr("Localizable", "plural.count.you_and_others_favorited", p1, fallback: "Plural format key: \"%#@count_others@\"")
2409-
}
24102352
}
24112353
public enum FilteredNotificationBanner {
24122354
/// Plural format key: "%#@number_of_requests@"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// L10nLookup.swift
3+
// MastodonSDK
4+
//
5+
// Created by Shannon Hughes on 11/19/25.
6+
//
7+
8+
import Foundation
9+
10+
/// This bridge seems to be necessary for now because our localizations are contained within a Swift package. Also, nesting inside meaningful structs is helpful for organization and the automatic symbol generation is limited in that regard.
11+
///
12+
/// To add new strings:
13+
/// 1. Add the entry in .xcstrings with the English string and any pluralization variants
14+
/// 2. Use the "Convert Strings to Symbols" option in .xcstrings to create the function name and set the argument labels
15+
/// 3. Add a function here (in the expected nested struct) that matches the new function signature and looks up the new key.
16+
///
17+
/// NOTE: Take care not to modify existing keys without checking to maintain agreement here and on CrowdIn.
18+
19+
public struct L10nLookup {
20+
21+
public struct Scene {
22+
public struct Notification {
23+
public struct GroupedNotificationDescription {
24+
public static func youAndOthersFavorited(othersCount: Int) -> String {
25+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.YouAndOthersFavorited", othersCount, fallback: "sceneNotificationGroupedNotificationDescriptionYouAndOthersFavorited")
26+
return result
27+
}
28+
29+
public static func peopleFavourited(favouriteCount: Int) -> String {
30+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.PeopleFavourited", favouriteCount, fallback: "Scene.Notification.GroupedNotificationDescription.PeopleFavourited")
31+
return result
32+
}
33+
34+
public static func youAndOthersBoosted(othersCount: Int) -> String {
35+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.YouAndOthersBoosted", othersCount, fallback: "Scene.Notification.GroupedNotificationDescription.YouAndOthersBoosted")
36+
return result
37+
}
38+
39+
public static func peopleBoosted(boostCount: Int) -> String {
40+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.PeopleBoosted", boostCount, fallback: "Scene.Notification.GroupedNotificationDescription.PeopleBoosted")
41+
return result
42+
}
43+
44+
public static func peopleFollowedYou(newFollowerCount: Int) -> String {
45+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.PeopleFollowedYou", newFollowerCount, fallback: "sceneNotificationGroupedNotificationDescriptionPeopleFollowedYou")
46+
return result
47+
}
48+
49+
public static func pollHasEnded(pollAuthor: String, otherVotersCount: Int) -> String {
50+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.PollHasEnded", pollAuthor, otherVotersCount, fallback: "sceneNotificationGroupedNotificationDescriptionPollHasEnded")
51+
return result
52+
}
53+
54+
public static func someoneReportedPosts(postCount: Int, violatingAccountName: String) -> String {
55+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeonReportedPosts", postCount, violatingAccountName, fallback: "sceneNotificationGroupedNotificationDescriptionSomeoneReportedPosts")
56+
return result
57+
}
58+
59+
public static func someoneReportedPostsForRuleViolation(postCount: Int, violatingAccountName: String) -> String {
60+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeonReportedPostsForRuleViolation", postCount, violatingAccountName, fallback: "sceneNotificationGroupedNotificationDescriptionSomeoneReportedPostsForRuleViolation")
61+
return result
62+
}
63+
64+
public static func someoneReportedPostsForSpam(postCount: Int, violatingAccountName: String) -> String {
65+
let result = tr("Localizable", "Scene.Notification.GroupedNotificationDescription.SomeonReportedPostsForSpam", postCount, violatingAccountName, fallback: "sceneNotificationGroupedNotificationDescriptionSomeoneReportedPostsForSpam")
66+
return result
67+
}
68+
}
69+
}
70+
}
71+
72+
public static func pluralCountPoll(_ count: Int) -> String {
73+
let result = tr("Localizable", "plural.count.poll", count, fallback: "pluralCountPoll")
74+
return result
75+
}
76+
77+
private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
78+
let missingKey = "_MISSING_"
79+
let format = {
80+
let localized = Bundle.module.localizedString(forKey: key, value: missingKey, table: table)
81+
if localized != missingKey {
82+
return localized
83+
} else {
84+
return englishBundle?.localizedString(forKey: key, value: value, table: table) ?? value
85+
}
86+
}()
87+
return String(format: format, locale: Locale.current, arguments: args)
88+
}
89+
90+
private static var englishBundle: Bundle? = {
91+
guard let enBundlePath = Bundle.module.path(forResource: "en", ofType: "lproj") else { return nil }
92+
return Bundle(path: enBundlePath)
93+
}()
94+
}

0 commit comments

Comments
 (0)