From 6d9f25c7cf774d121924aaff225c375615fcb456 Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Mon, 3 Nov 2025 14:04:56 -0500 Subject: [PATCH 1/3] Create localized strings file --- ios/HackerNews.xcodeproj/project.pbxproj | 3 + ios/HackerNews/Localizable.xcstrings | 168 +++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 ios/HackerNews/Localizable.xcstrings diff --git a/ios/HackerNews.xcodeproj/project.pbxproj b/ios/HackerNews.xcodeproj/project.pbxproj index 6ea399b8..533566c6 100644 --- a/ios/HackerNews.xcodeproj/project.pbxproj +++ b/ios/HackerNews.xcodeproj/project.pbxproj @@ -689,6 +689,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -744,6 +745,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; @@ -1027,6 +1029,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; diff --git a/ios/HackerNews/Localizable.xcstrings b/ios/HackerNews/Localizable.xcstrings new file mode 100644 index 00000000..c6c3fbd6 --- /dev/null +++ b/ios/HackerNews/Localizable.xcstrings @@ -0,0 +1,168 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + "@%@" : { + + }, + "@humdinger" : { + + }, + "%lld" : { + + }, + "2h ago" : { + + }, + "45" : { + + }, + "99" : { + + }, + "About" : { + + }, + "Add a comment" : { + + }, + "Appearance" : { + + }, + "Ask" : { + + }, + "Best" : { + + }, + "Bookmark" : { + + }, + "Bookmarks" : { + + }, + "By signing in, you agree to the " : { + + }, + "Check for Updates" : { + + }, + "Collapse" : { + + }, + "Comment Font Size (%@pt)" : { + + }, + "Email" : { + + }, + "Follow Emerge" : { + + }, + "Follow Supergooey" : { + + }, + "Font Family" : { + + }, + "Font Style" : { + + }, + "Hacker News Guidelines" : { + + }, + "IBM Plex" : { + + }, + "Invalid username or password" : { + + }, + "Login" : { + + }, + "Logout" : { + + }, + "Long-press a story to bookmark it." : { + + }, + "Name" : { + + }, + "New" : { + + }, + "Open in browser" : { + + }, + "Password" : { + + }, + "Privacy Policy" : { + + }, + "Profile" : { + + }, + "Remove Bookmark" : { + + }, + "Report Comment" : { + + }, + "Report Post" : { + + }, + "Sans" : { + + }, + "Sans + Mono" : { + + }, + "Send Feedback" : { + + }, + "Settings" : { + + }, + "Share" : { + + }, + "Show" : { + + }, + "Some Short Title" : { + + }, + "Submit" : { + + }, + "System" : { + + }, + "Thank you for your feedback!" : { + + }, + "Title Font Size (%@pt)" : { + + }, + "Top" : { + + }, + "Username" : { + + }, + "Words of wisdom" : { + + }, + "Your Feedback" : { + + }, + "Your Info (optional)" : { + + } + }, + "version" : "1.1" +} \ No newline at end of file From c15a8ac27efdd3541baf0959b06f4330e111cf3e Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Mon, 3 Nov 2025 14:17:08 -0500 Subject: [PATCH 2/3] Add localized strings file --- ios/HackerNews/Auth/LoginRow.swift | 2 +- ios/HackerNews/Auth/LoginScreen.swift | 12 +- .../Bookmarks/BookmarksScreen.swift | 4 +- ios/HackerNews/Comments/CommentComposer.swift | 4 +- ios/HackerNews/Comments/CommentRow.swift | 4 +- ios/HackerNews/Comments/CommentsHeader.swift | 2 +- ios/HackerNews/Feed/FeedScreen.swift | 2 +- ios/HackerNews/Localizable.xcstrings | 673 ++++++++++++++---- .../Settings/SendFeedbackScreen.swift | 14 +- ios/HackerNews/Settings/SettingsScreen.swift | 36 +- ios/HackerNews/Web/WebView.swift | 4 +- .../Common/Sources/Common/Feed/FeedType.swift | 10 +- .../Common/Sources/Common/Utils/Theme.swift | 8 +- 13 files changed, 587 insertions(+), 188 deletions(-) diff --git a/ios/HackerNews/Auth/LoginRow.swift b/ios/HackerNews/Auth/LoginRow.swift index 645c7956..4d03ffa3 100644 --- a/ios/HackerNews/Auth/LoginRow.swift +++ b/ios/HackerNews/Auth/LoginRow.swift @@ -41,7 +41,7 @@ struct LoginRow: View { } func loginText() -> String { - return loggedIn ? "Logout" : "Login" + return loggedIn ? String(localized: "auth.button.logout") : String(localized: "auth.button.login") } func glowColor() -> Color { diff --git a/ios/HackerNews/Auth/LoginScreen.swift b/ios/HackerNews/Auth/LoginScreen.swift index e9e21c2a..4c4fd205 100644 --- a/ios/HackerNews/Auth/LoginScreen.swift +++ b/ios/HackerNews/Auth/LoginScreen.swift @@ -39,7 +39,7 @@ struct LoginScreen: View { Spacer() .frame(maxHeight: 16) - TextField("Username", text: $loginState.username) + TextField(String(localized: "auth.field.username"), text: $loginState.username) .textInputAutocapitalization(.none) .padding() .background(Color.background) @@ -49,7 +49,7 @@ struct LoginScreen: View { .stroke(Color.background.opacity(0.5), lineWidth: 1) ) - SecureField("Password", text: $loginState.password) + SecureField(String(localized: "auth.field.password"), text: $loginState.password) .padding() .background(Color.background) .clipShape(RoundedRectangle(cornerRadius: 12)) @@ -59,16 +59,16 @@ struct LoginScreen: View { ) if loginState.showError { - Text("Invalid username or password") + Text("auth.error.invalidCredentials") .foregroundColor(.red) .font(.ibmPlexMono(.regular, size: 14)) } HStack(spacing: 0) { - Text("By signing in, you agree to the ") + Text("auth.agreement.prefix") .font(.ibmPlexSans(.regular, size: 12)) - Text("Hacker News Guidelines") + Text("auth.agreement.guidelines") .font(.ibmPlexSans(.regular, size: 12)) .foregroundColor(.blue) .underline() @@ -97,7 +97,7 @@ struct LoginScreen: View { } }, label: { - Text("Submit") + Text("auth.button.submit") .font(.ibmPlexMono(.bold, size: 16)) .frame(maxWidth: .infinity) .frame(height: 40) diff --git a/ios/HackerNews/Bookmarks/BookmarksScreen.swift b/ios/HackerNews/Bookmarks/BookmarksScreen.swift index 858b0b05..8c334cdb 100644 --- a/ios/HackerNews/Bookmarks/BookmarksScreen.swift +++ b/ios/HackerNews/Bookmarks/BookmarksScreen.swift @@ -15,7 +15,7 @@ struct BookmarksScreen: View { Group { if model.bookmarks.isEmpty { ZStack { - Text("Long-press a story to bookmark it.") + Text("bookmarks.emptyState") .font(.ibmPlexSans(.medium, size: 18)) } } else { @@ -46,7 +46,7 @@ struct BookmarksScreen: View { .background(.ultraThinMaterial) .containerShape(.rect(cornerRadius: 24, style: .continuous)) - Text("Bookmarks") + Text("bookmarks.title") .font(.ibmPlexMono(.bold, size: 24)) .padding(.horizontal, 16) } diff --git a/ios/HackerNews/Comments/CommentComposer.swift b/ios/HackerNews/Comments/CommentComposer.swift index 88ecfca0..b7ee569e 100644 --- a/ios/HackerNews/Comments/CommentComposer.swift +++ b/ios/HackerNews/Comments/CommentComposer.swift @@ -16,11 +16,11 @@ struct CommentComposer: View { HStack(alignment: .center) { Image(systemName: "message.fill") .font(.system(size: 12)) - Text("Add a comment") + Text("comments.composer.title") .font(.ibmPlexSans(.medium, size: 12)) } TextField( - "Words of wisdom", + String(localized: "comments.composer.placeholder"), text: $state.text ) .textFieldStyle(.roundedBorder) diff --git a/ios/HackerNews/Comments/CommentRow.swift b/ios/HackerNews/Comments/CommentRow.swift index 50c04f96..5af47a9d 100644 --- a/ios/HackerNews/Comments/CommentRow.swift +++ b/ios/HackerNews/Comments/CommentRow.swift @@ -57,10 +57,10 @@ struct CommentRow: View { .clipShape(Capsule()) Menu { - Button("Report Comment") { + Button(String(localized: "comments.action.report")) { flagComment(state) } - Button(state.hidden ? "Show" : "Collapse") { + Button(state.hidden ? String(localized: "comments.action.show") : String(localized: "comments.action.collapse")) { toggleComment() } } label: { diff --git a/ios/HackerNews/Comments/CommentsHeader.swift b/ios/HackerNews/Comments/CommentsHeader.swift index 683e0b4d..c0909164 100644 --- a/ios/HackerNews/Comments/CommentsHeader.swift +++ b/ios/HackerNews/Comments/CommentsHeader.swift @@ -64,7 +64,7 @@ struct CommentsHeader: View { .clipShape(Capsule()) Menu { - Button("Report Post", action: flagPost) + Button(String(localized: "post.action.report"), action: flagPost) } label: { Image(systemName: "ellipsis") .font(.system(size: 12)) diff --git a/ios/HackerNews/Feed/FeedScreen.swift b/ios/HackerNews/Feed/FeedScreen.swift index 46a78222..b8bb0a47 100644 --- a/ios/HackerNews/Feed/FeedScreen.swift +++ b/ios/HackerNews/Feed/FeedScreen.swift @@ -91,7 +91,7 @@ private struct FeedListView: View { model.toggleBookmark(content) } label: { Label( - content.bookmarked ? "Remove Bookmark" : "Bookmark", + content.bookmarked ? String(localized: "bookmarks.action.remove") : String(localized: "bookmarks.action.bookmark"), systemImage: content.bookmarked ? "book.fill" : "book" ) } diff --git a/ios/HackerNews/Localizable.xcstrings b/ios/HackerNews/Localizable.xcstrings index c6c3fbd6..8b137df1 100644 --- a/ios/HackerNews/Localizable.xcstrings +++ b/ios/HackerNews/Localizable.xcstrings @@ -22,146 +22,547 @@ "99" : { }, - "About" : { - - }, - "Add a comment" : { - - }, - "Appearance" : { - - }, - "Ask" : { - - }, - "Best" : { - - }, - "Bookmark" : { - - }, - "Bookmarks" : { - - }, - "By signing in, you agree to the " : { - - }, - "Check for Updates" : { - - }, - "Collapse" : { - - }, - "Comment Font Size (%@pt)" : { - - }, - "Email" : { - - }, - "Follow Emerge" : { - - }, - "Follow Supergooey" : { - - }, - "Font Family" : { - - }, - "Font Style" : { - - }, - "Hacker News Guidelines" : { - - }, - "IBM Plex" : { - - }, - "Invalid username or password" : { - - }, - "Login" : { - - }, - "Logout" : { - - }, - "Long-press a story to bookmark it." : { - - }, - "Name" : { - - }, - "New" : { - - }, - "Open in browser" : { - - }, - "Password" : { - - }, - "Privacy Policy" : { - - }, - "Profile" : { - - }, - "Remove Bookmark" : { - - }, - "Report Comment" : { - - }, - "Report Post" : { - - }, - "Sans" : { - - }, - "Sans + Mono" : { - - }, - "Send Feedback" : { - - }, - "Settings" : { - - }, - "Share" : { - - }, - "Show" : { - + "auth.agreement.guidelines" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hacker News Guidelines" + } + } + } + }, + "auth.agreement.prefix" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "By signing in, you agree to the " + } + } + } + }, + "auth.button.login" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Login" + } + } + } + }, + "auth.button.logout" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logout" + } + } + } + }, + "auth.button.submit" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Submit" + } + } + } + }, + "auth.error.invalidCredentials" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid username or password" + } + } + } + }, + "auth.field.password" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + } + } + }, + "auth.field.username" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Username" + } + } + } + }, + "bookmarks.action.bookmark" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bookmark" + } + } + } + }, + "bookmarks.action.remove" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove Bookmark" + } + } + } + }, + "bookmarks.emptyState" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Long-press a story to bookmark it." + } + } + } + }, + "bookmarks.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bookmarks" + } + } + } + }, + "comments.action.collapse" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Collapse" + } + } + } + }, + "comments.action.report" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Report Comment" + } + } + } + }, + "comments.action.show" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Show" + } + } + } + }, + "comments.composer.placeholder" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Words of wisdom" + } + } + } + }, + "comments.composer.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add a comment" + } + } + } + }, + "feed.type.ask" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ask" + } + } + } + }, + "feed.type.best" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Best" + } + } + } + }, + "feed.type.new" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New" + } + } + } + }, + "feed.type.show" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Show" + } + } + } + }, + "feed.type.top" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Top" + } + } + } + }, + "feedback.button.submit" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Submit" + } + } + } + }, + "feedback.field.email" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Email" + } + } + } + }, + "feedback.field.name" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name" + } + } + } + }, + "feedback.section.yourFeedback" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your Feedback" + } + } + } + }, + "feedback.section.yourInfo" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your Info (optional)" + } + } + } + }, + "feedback.success" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thank you for your feedback!" + } + } + } + }, + "feedback.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Send Feedback" + } + } + } + }, + "post.action.report" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Report Post" + } + } + } + }, + "settings.commentFontSize" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comment Font Size (%@pt)" + } + } + } + }, + "settings.row.checkForUpdates" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Check for Updates" + } + } + } + }, + "settings.row.followEmerge" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Follow Emerge" + } + } + } + }, + "settings.row.followSupergooey" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Follow Supergooey" + } + } + } + }, + "settings.row.fontFamily" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Font Family" + } + } + } + }, + "settings.row.fontStyle" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Font Style" + } + } + } + }, + "settings.row.privacyPolicy" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + } + } + }, + "settings.row.sendFeedback" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Send Feedback" + } + } + } + }, + "settings.section.about" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "About" + } + } + } + }, + "settings.section.appearance" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appearance" + } + } + } + }, + "settings.section.profile" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profile" + } + } + } + }, + "settings.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + } + } + }, + "settings.titleFontSize" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Title Font Size (%@pt)" + } + } + } }, "Some Short Title" : { }, - "Submit" : { - - }, - "System" : { - - }, - "Thank you for your feedback!" : { - - }, - "Title Font Size (%@pt)" : { - - }, - "Top" : { - - }, - "Username" : { - - }, - "Words of wisdom" : { - - }, - "Your Feedback" : { - - }, - "Your Info (optional)" : { - + "theme.fontFamily.ibmPlex" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IBM Plex" + } + } + } + }, + "theme.fontFamily.system" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "System" + } + } + } + }, + "theme.fontStyle.sans" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sans" + } + } + } + }, + "theme.fontStyle.sansMono" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sans + Mono" + } + } + } + }, + "web.action.openInBrowser" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open in browser" + } + } + } + }, + "web.action.share" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share" + } + } + } } }, "version" : "1.1" diff --git a/ios/HackerNews/Settings/SendFeedbackScreen.swift b/ios/HackerNews/Settings/SendFeedbackScreen.swift index f67f6acc..ac25eac8 100644 --- a/ios/HackerNews/Settings/SendFeedbackScreen.swift +++ b/ios/HackerNews/Settings/SendFeedbackScreen.swift @@ -19,15 +19,15 @@ struct SendFeedbackScreen: View { var body: some View { NavigationStack { Form { - Section("Your Info (optional)") { - TextField("Name", text: $name) - TextField("Email", text: $email) + Section(String(localized: "feedback.section.yourInfo")) { + TextField(String(localized: "feedback.field.name"), text: $name) + TextField(String(localized: "feedback.field.email"), text: $email) .keyboardType(.emailAddress) .textContentType(.emailAddress) .autocapitalization(.none) } - Section("Your Feedback") { + Section(String(localized: "feedback.section.yourFeedback")) { TextEditor(text: $message) .frame(minHeight: 150) } @@ -35,7 +35,7 @@ struct SendFeedbackScreen: View { if isSubmitted { Section { Label( - "Thank you for your feedback!", + String(localized: "feedback.success"), systemImage: "checkmark.seal.fill" ) .foregroundColor(.green) @@ -44,7 +44,7 @@ struct SendFeedbackScreen: View { } } } - .navigationTitle("Send Feedback") + .navigationTitle(String(localized: "feedback.title")) .navigationBarTitleDisplayMode(.inline) .animation(.spring(), value: isSubmitted) .toolbar { @@ -62,7 +62,7 @@ struct SendFeedbackScreen: View { } .safeAreaInset(edge: .bottom) { Button(action: { sendFeedback() }) { - Text("Submit") + Text("feedback.button.submit") .font(.ibmPlexMono(.bold, size: 16)) .frame(maxWidth: .infinity) .frame(height: 40) diff --git a/ios/HackerNews/Settings/SettingsScreen.swift b/ios/HackerNews/Settings/SettingsScreen.swift index 4e6f7193..9960ba28 100644 --- a/ios/HackerNews/Settings/SettingsScreen.swift +++ b/ios/HackerNews/Settings/SettingsScreen.swift @@ -18,7 +18,7 @@ struct SettingsScreen: View { ScrollView { LazyVStack(spacing: 8) { VStack(alignment: .leading, spacing: 4) { - Text("Profile") + Text("settings.section.profile") .font(theme.themedFont(size: 12, style: .sans, weight: .medium)) LoginRow(loggedIn: model.authState == AuthState.loggedIn) { model.gotoLogin() @@ -26,10 +26,10 @@ struct SettingsScreen: View { } VStack(alignment: .leading, spacing: 4) { - Text("About") + Text("settings.section.about") .font(theme.themedFont(size: 12, style: .sans, weight: .medium)) SettingsRow( - text: "Follow Emerge", + text: String(localized: "settings.row.followEmerge"), leadingIcon: { Image(systemName: "bird.fill") .font(.system(size: 12)) @@ -49,7 +49,7 @@ struct SettingsScreen: View { ) SettingsRow( - text: "Follow Supergooey", + text: String(localized: "settings.row.followSupergooey"), leadingIcon: { Image(systemName: "bird.fill") .font(.system(size: 12)) @@ -69,7 +69,7 @@ struct SettingsScreen: View { #if ADHOC SettingsRow( - text: "Check for Updates", + text: String(localized: "settings.row.checkForUpdates"), leadingIcon: { Image(systemName: "icloud.and.arrow.down") .font(.system(size: 12)) @@ -88,7 +88,7 @@ struct SettingsScreen: View { #endif SettingsRow( - text: "Send Feedback", + text: String(localized: "settings.row.sendFeedback"), leadingIcon: { Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 12)) @@ -106,7 +106,7 @@ struct SettingsScreen: View { ) SettingsRow( - text: "Privacy Policy", + text: String(localized: "settings.row.privacyPolicy"), leadingIcon: { Image(systemName: "lock.fill") .font(.system(size: 12)) @@ -130,11 +130,11 @@ struct SettingsScreen: View { VStack(alignment: .leading, spacing: 4) { @Bindable var theme = theme - Text("Appearance") + Text("settings.section.appearance") .font(.ibmPlexSans(.medium, size: 12)) SettingsRow( - text: "Font Family", + text: String(localized: "settings.row.fontFamily"), leadingIcon: { Image(systemName: "textformat") .font(.system(size: 12)) @@ -142,10 +142,10 @@ struct SettingsScreen: View { }, trailingIcon: { Menu { - Button("System") { + Button(String(localized: "theme.fontFamily.system")) { theme.fontFamilyPreference = .system } - Button("IBM Plex") { + Button(String(localized: "theme.fontFamily.ibmPlex")) { theme.fontFamilyPreference = .ibmPlex } } label: { @@ -164,7 +164,7 @@ struct SettingsScreen: View { ) SettingsRow( - text: "Font Style", + text: String(localized: "settings.row.fontStyle"), leadingIcon: { Image(systemName: "textformat") .font(.system(size: 12)) @@ -172,10 +172,10 @@ struct SettingsScreen: View { }, trailingIcon: { Menu { - Button("Sans") { + Button(String(localized: "theme.fontStyle.sans")) { theme.fontStylePreference = .sans } - Button("Sans + Mono") { + Button(String(localized: "theme.fontStyle.sansMono")) { theme.fontStylePreference = .sansAndMono } } label: { @@ -194,8 +194,7 @@ struct SettingsScreen: View { ) SettingsRow( - text: - "Comment Font Size (\(String(format: "%.1f", theme.commentFontSize))pt)", + text: String(localized: "settings.commentFontSize", defaultValue: "Comment Font Size (\(String(format: "%.1f", theme.commentFontSize))pt)"), leadingIcon: { Image(systemName: "text.bubble") .font(.system(size: 12)) @@ -215,8 +214,7 @@ struct SettingsScreen: View { .animation(.smooth, value: theme.commentFontSize) SettingsRow( - text: - "Title Font Size (\(String(format: "%.1f", theme.titleFontSize))pt)", + text: String(localized: "settings.titleFontSize", defaultValue: "Title Font Size (\(String(format: "%.1f", theme.titleFontSize))pt)"), leadingIcon: { Image(systemName: "text.alignleft") .font(.system(size: 12)) @@ -244,7 +242,7 @@ struct SettingsScreen: View { .background(.ultraThinMaterial) .containerShape(.rect(cornerRadius: 24, style: .continuous)) - Text("Settings") + Text("settings.title") .font(.ibmPlexMono(.bold, size: 24)) .padding(.horizontal, 16) } diff --git a/ios/HackerNews/Web/WebView.swift b/ios/HackerNews/Web/WebView.swift index f58cdeab..1527c7d4 100644 --- a/ios/HackerNews/Web/WebView.swift +++ b/ios/HackerNews/Web/WebView.swift @@ -59,10 +59,10 @@ struct WebViewContainer: View { Button { openURL(self.model.url) } label: { - Label("Open in browser", systemImage: "safari") + Label(String(localized: "web.action.openInBrowser"), systemImage: "safari") } ShareLink(item: self.model.url) { - Label("Share", systemImage: "square.and.arrow.up") + Label(String(localized: "web.action.share"), systemImage: "square.and.arrow.up") } } label: { Image(systemName: "ellipsis") diff --git a/ios/Packages/Common/Sources/Common/Feed/FeedType.swift b/ios/Packages/Common/Sources/Common/Feed/FeedType.swift index 79ae3bc9..81869389 100644 --- a/ios/Packages/Common/Sources/Common/Feed/FeedType.swift +++ b/ios/Packages/Common/Sources/Common/Feed/FeedType.swift @@ -15,15 +15,15 @@ public enum FeedType: CaseIterable { public var title: String { switch self { case .top: - return "Top" + return String(localized: "feed.type.top", bundle: .main) case .new: - return "New" + return String(localized: "feed.type.new", bundle: .main) case .best: - return "Best" + return String(localized: "feed.type.best", bundle: .main) case .ask: - return "Ask" + return String(localized: "feed.type.ask", bundle: .main) case .show: - return "Show" + return String(localized: "feed.type.show", bundle: .main) } } } diff --git a/ios/Packages/Common/Sources/Common/Utils/Theme.swift b/ios/Packages/Common/Sources/Common/Utils/Theme.swift index 7cbe0dcf..4352516a 100644 --- a/ios/Packages/Common/Sources/Common/Utils/Theme.swift +++ b/ios/Packages/Common/Sources/Common/Utils/Theme.swift @@ -18,9 +18,9 @@ public enum FontStylePreference: String { public var displayName: String { switch self { case .sans: - "Sans" + String(localized: "theme.fontStyle.sans", bundle: .main) case .sansAndMono: - "Sans + Mono" + String(localized: "theme.fontStyle.sansMono", bundle: .main) } } } @@ -32,9 +32,9 @@ public enum FontFamilyPreference: String { public var displayName: String { switch self { case .system: - "System" + String(localized: "theme.fontFamily.system", bundle: .main) case .ibmPlex: - "IBM Plex" + String(localized: "theme.fontFamily.ibmPlex", bundle: .main) } } From bdc07017f7e5b430b5685a4e212248bc2a2b97fc Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Mon, 3 Nov 2025 14:40:51 -0500 Subject: [PATCH 3/3] prefer catalog --- ios/HackerNews.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/HackerNews.xcodeproj/project.pbxproj b/ios/HackerNews.xcodeproj/project.pbxproj index a5074692..db07f8d6 100644 --- a/ios/HackerNews.xcodeproj/project.pbxproj +++ b/ios/HackerNews.xcodeproj/project.pbxproj @@ -784,6 +784,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "\"$(PROJECT_DIR)/Frameworks/Reaper.xcframework\""; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 3.10; OTHER_LDFLAGS = "-fprofile-instr-generate"; OTHER_SWIFT_FLAGS = "-profile-generate -profile-coverage-mapping"; @@ -828,6 +829,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "\"$(PROJECT_DIR)/Frameworks/Reaper.xcframework\""; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 3.10; PRODUCT_BUNDLE_IDENTIFIER = com.emergetools.hackernews; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1067,6 +1069,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "\"$(PROJECT_DIR)/Frameworks/Reaper.xcframework\""; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 3.10; PRODUCT_BUNDLE_IDENTIFIER = com.emergetools.hackernews; PRODUCT_NAME = "$(TARGET_NAME)";