mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-01 23:26:42 +01:00
Implement Localization (#80)
* Implement localization * Fix some localization keys * Adapt to recent changes
This commit is contained in:
parent
e519e9cdff
commit
980b9a5dd6
@ -64,6 +64,7 @@
|
||||
9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; };
|
||||
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; };
|
||||
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
|
||||
E9B576C329743F4C00BCE646 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -152,6 +153,7 @@
|
||||
9FD542E52962D2CE0045321A /* Lists */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Lists; path = Packages/Lists; sourceTree = "<group>"; };
|
||||
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
|
||||
9FE3DB55296FEF5800628CB0 /* AppAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AppAccount; path = Packages/AppAccount; sourceTree = "<group>"; };
|
||||
E9B576C429743F4C00BCE646 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -236,6 +238,7 @@
|
||||
9F2A542D296B1CC0009B2D7C /* glass.wav */,
|
||||
9F2A542B296B1177009B2D7C /* glass.caf */,
|
||||
9F24EEB729360C330042359D /* Preview Assets.xcassets */,
|
||||
E9B576C029743F2A00BCE646 /* Localization */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
@ -345,6 +348,14 @@
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E9B576C029743F2A00BCE646 /* Localization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E9B576C529743F4C00BCE646 /* Localizable.strings */,
|
||||
);
|
||||
path = Localization;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -498,6 +509,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9F2A542C296B1177009B2D7C /* glass.caf in Resources */,
|
||||
E9B576C329743F4C00BCE646 /* Localizable.strings in Resources */,
|
||||
9FD34823293D06E800DB0EE9 /* Assets.xcassets in Resources */,
|
||||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */,
|
||||
@ -569,12 +581,12 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
9FAD858C29743F7400496AB1 /* MainInterface.storyboard */ = {
|
||||
E9B576C529743F4C00BCE646 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
9FAD858D29743F7400496AB1 /* Base */,
|
||||
E9B576C429743F4C00BCE646 /* en */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
@ -24,7 +24,7 @@ struct AddAccountView: View {
|
||||
@State private var isSigninIn = false
|
||||
@State private var signInClient: Client?
|
||||
@State private var instances: [InstanceSocial] = []
|
||||
@State private var instanceFetchError: String?
|
||||
@State private var instanceFetchError: LocalizedStringKey?
|
||||
@State private var oauthURL: URL?
|
||||
|
||||
private let instanceNamePublisher = PassthroughSubject<String, Never>()
|
||||
@ -34,7 +34,7 @@ struct AddAccountView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
TextField("Instance URL", text: $instanceName)
|
||||
TextField("instance.url", text: $instanceName)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.keyboardType(.URL)
|
||||
.textContentType(.URL)
|
||||
@ -52,7 +52,7 @@ struct AddAccountView: View {
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Add account")
|
||||
.navigationTitle("account.add.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
@ -60,7 +60,7 @@ struct AddAccountView: View {
|
||||
.toolbar {
|
||||
if !appAccountsManager.availableAccounts.isEmpty {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { dismiss() })
|
||||
Button("action.cancel", action: { dismiss() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,7 @@ struct AddAccountView: View {
|
||||
self.instanceFetchError = nil
|
||||
} catch _ as DecodingError {
|
||||
self.instance = nil
|
||||
self.instanceFetchError = "This instance is not currently supported."
|
||||
self.instanceFetchError = "account.add.error.instance-not-supported"
|
||||
} catch {
|
||||
self.instance = nil
|
||||
}
|
||||
@ -122,7 +122,7 @@ struct AddAccountView: View {
|
||||
ProgressView()
|
||||
.tint(theme.labelColor)
|
||||
} else {
|
||||
Text("Sign in")
|
||||
Text("account.add.sign-in")
|
||||
.font(.scaledHeadline)
|
||||
}
|
||||
Spacer()
|
||||
@ -134,7 +134,7 @@ struct AddAccountView: View {
|
||||
}
|
||||
|
||||
private var instancesListView: some View {
|
||||
Section("Suggestions") {
|
||||
Section("instance.suggestions") {
|
||||
if instances.isEmpty {
|
||||
placeholderRow
|
||||
} else {
|
||||
@ -149,9 +149,11 @@ struct AddAccountView: View {
|
||||
Text(instance.info?.shortDescription ?? "")
|
||||
.font(.scaledBody)
|
||||
.foregroundColor(.gray)
|
||||
Text("\(instance.users) users ⸱ \(instance.statuses) posts")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
(Text("instance.list.users-\(instance.users)")
|
||||
+ Text(" ⸱ ")
|
||||
+ Text("instance.list.posts-\(instance.statuses)"))
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -162,13 +164,13 @@ struct AddAccountView: View {
|
||||
|
||||
private var placeholderRow: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Loading...")
|
||||
Text("placeholder.loading.short")
|
||||
.font(.scaledHeadline)
|
||||
.foregroundColor(.primary)
|
||||
Text("Loading, loading, loading ....")
|
||||
Text("placeholder.loading.long")
|
||||
.font(.scaledBody)
|
||||
.foregroundColor(.gray)
|
||||
Text("Loading ...")
|
||||
Text("placeholder.loading.short")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
@ -12,32 +12,32 @@ struct DisplaySettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Theme") {
|
||||
Section("settings.display.section.theme") {
|
||||
themeSelectorButton
|
||||
ColorPicker("Tint color", selection: $theme.tintColor)
|
||||
ColorPicker("Background color", selection: $theme.primaryBackgroundColor)
|
||||
ColorPicker("Secondary Background color", selection: $theme.secondaryBackgroundColor)
|
||||
ColorPicker("settings.display.theme.tint", selection: $theme.tintColor)
|
||||
ColorPicker("settings.display.theme.background", selection: $theme.primaryBackgroundColor)
|
||||
ColorPicker("settings.display.theme.secondary-background", selection: $theme.secondaryBackgroundColor)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
||||
Section("Display") {
|
||||
Picker("Avatar position", selection: $theme.avatarPosition) {
|
||||
Section("settings.display.section.display") {
|
||||
Picker("settings.display.avatar.position", selection: $theme.avatarPosition) {
|
||||
ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in
|
||||
Text(position.description).tag(position)
|
||||
}
|
||||
}
|
||||
Picker("Avatar shape", selection: $theme.avatarShape) {
|
||||
Picker("settings.display.avatar.shape", selection: $theme.avatarShape) {
|
||||
ForEach(Theme.AvatarShape.allCases, id: \.rawValue) { shape in
|
||||
Text(shape.description).tag(shape)
|
||||
}
|
||||
}
|
||||
Picker("Status actions buttons", selection: $theme.statusActionsDisplay) {
|
||||
Picker("settings.display.status.action-buttons", selection: $theme.statusActionsDisplay) {
|
||||
ForEach(Theme.StatusActionsDisplay.allCases, id: \.rawValue) { buttonStyle in
|
||||
Text(buttonStyle.description).tag(buttonStyle)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Status media style", selection: $theme.statusDisplayStyle) {
|
||||
Picker("settings.display.status.media-style", selection: $theme.statusDisplayStyle) {
|
||||
ForEach(Theme.StatusDisplayStyle.allCases, id: \.rawValue) { buttonStyle in
|
||||
Text(buttonStyle.description).tag(buttonStyle)
|
||||
}
|
||||
@ -59,12 +59,12 @@ struct DisplaySettingsView: View {
|
||||
theme.avatarPosition = .top
|
||||
theme.statusActionsDisplay = .full
|
||||
} label: {
|
||||
Text("Restore default")
|
||||
Text("settings.display.restore")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
.navigationTitle("Display Settings")
|
||||
.navigationTitle("settings.display.navigation-title")
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
}
|
||||
@ -72,7 +72,7 @@ struct DisplaySettingsView: View {
|
||||
private var themeSelectorButton: some View {
|
||||
NavigationLink(destination: ThemePreviewView()) {
|
||||
HStack {
|
||||
Text("Theme")
|
||||
Text("settings.display.section.theme")
|
||||
Spacer()
|
||||
Text(theme.selectedSet.rawValue)
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ struct IconSelectorView: View {
|
||||
}
|
||||
}
|
||||
.padding(6)
|
||||
.navigationTitle("Icons")
|
||||
.navigationTitle("settings.app.icon.navigation-title")
|
||||
}
|
||||
.background(theme.primaryBackgroundColor)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ struct InstanceInfoView: View {
|
||||
Form {
|
||||
InstanceInfoSection(instance: instance)
|
||||
}
|
||||
.navigationTitle("Instance Info")
|
||||
.navigationTitle("instance.info.navigation-title")
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
}
|
||||
@ -24,19 +24,19 @@ public struct InstanceInfoSection: View {
|
||||
let instance: Instance
|
||||
|
||||
public var body: some View {
|
||||
Section("Instance info") {
|
||||
LabeledContent("Name", value: instance.title)
|
||||
Section("instance.info.section.info") {
|
||||
LabeledContent("instance.info.name", value: instance.title)
|
||||
Text(instance.shortDescription)
|
||||
LabeledContent("Email", value: instance.email)
|
||||
LabeledContent("Version", value: instance.version)
|
||||
LabeledContent("Users", value: "\(instance.stats.userCount)")
|
||||
LabeledContent("Posts", value: "\(instance.stats.statusCount)")
|
||||
LabeledContent("Domains", value: "\(instance.stats.domainCount)")
|
||||
LabeledContent("instance.info.email", value: instance.email)
|
||||
LabeledContent("instance.info.version", value: instance.version)
|
||||
LabeledContent("instance.info.users", value: "\(instance.stats.userCount)")
|
||||
LabeledContent("instance.info.posts", value: "\(instance.stats.statusCount)")
|
||||
LabeledContent("instance.info.domains", value: "\(instance.stats.domainCount)")
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
||||
if let rules = instance.rules {
|
||||
Section("Instance rules") {
|
||||
Section("instance.info.section.rules") {
|
||||
ForEach(rules) { rule in
|
||||
Text(rule.text)
|
||||
}
|
||||
|
@ -18,39 +18,39 @@ struct PushNotificationsView: View {
|
||||
Form {
|
||||
Section {
|
||||
Toggle(isOn: $pushNotifications.isPushEnabled) {
|
||||
Text("Push notifications")
|
||||
Text("settings.push.main-toggle")
|
||||
}
|
||||
} footer: {
|
||||
Text("Receive push notifications on new activities")
|
||||
Text("settings.push.main-toggle.description")
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
||||
if pushNotifications.isPushEnabled {
|
||||
Section {
|
||||
Toggle(isOn: $pushNotifications.isMentionNotificationEnabled) {
|
||||
Label("Mentions", systemImage: "at")
|
||||
Label("settings.push.mentions", systemImage: "at")
|
||||
}
|
||||
Toggle(isOn: $pushNotifications.isFollowNotificationEnabled) {
|
||||
Label("Follows", systemImage: "person.badge.plus")
|
||||
Label("settings.push.follows", systemImage: "person.badge.plus")
|
||||
}
|
||||
Toggle(isOn: $pushNotifications.isFavoriteNotificationEnabled) {
|
||||
Label("Favorites", systemImage: "star")
|
||||
Label("settings.push.favorites", systemImage: "star")
|
||||
}
|
||||
Toggle(isOn: $pushNotifications.isReblogNotificationEnabled) {
|
||||
Label("Boosts", systemImage: "arrow.left.arrow.right.circle")
|
||||
Label("settings.push.boosts", systemImage: "arrow.left.arrow.right.circle")
|
||||
}
|
||||
Toggle(isOn: $pushNotifications.isPollNotificationEnabled) {
|
||||
Label("Polls Results", systemImage: "chart.bar")
|
||||
Label("settings.push.polls", systemImage: "chart.bar")
|
||||
}
|
||||
Toggle(isOn: $pushNotifications.isNewPostsNotificationEnabled) {
|
||||
Label("New Posts", systemImage: "bubble.right")
|
||||
Label("settings.push.new-posts", systemImage: "bubble.right")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
}
|
||||
.navigationTitle("Push Notifications")
|
||||
.navigationTitle("settings.push.navigation-title")
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.onAppear {
|
||||
|
@ -30,7 +30,7 @@ struct SettingsTabs: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle(Text("Settings"))
|
||||
.navigationTitle(Text("settings.title"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarBackground(theme.primaryBackgroundColor, for: .navigationBar)
|
||||
.withAppRouter()
|
||||
@ -54,7 +54,7 @@ struct SettingsTabs: View {
|
||||
}
|
||||
|
||||
private var accountsSection: some View {
|
||||
Section("Accounts") {
|
||||
Section("settings.section.accounts") {
|
||||
ForEach(appAccountsManager.availableAccounts) { account in
|
||||
AppAccountView(viewModel: .init(appAccount: account))
|
||||
}
|
||||
@ -76,33 +76,33 @@ struct SettingsTabs: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var generalSection: some View {
|
||||
Section("General") {
|
||||
Section("settings.section.general") {
|
||||
NavigationLink(destination: PushNotificationsView()) {
|
||||
Label("Push notifications", systemImage: "bell.and.waves.left.and.right")
|
||||
Label("settings.general.push-notifications", systemImage: "bell.and.waves.left.and.right")
|
||||
}
|
||||
if let instanceData = currentInstance.instance {
|
||||
NavigationLink(destination: InstanceInfoView(instance: instanceData)) {
|
||||
Label("Instance Information", systemImage: "server.rack")
|
||||
Label("settings.general.instance", systemImage: "server.rack")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: DisplaySettingsView()) {
|
||||
Label("Display Settings", systemImage: "paintpalette")
|
||||
Label("settings.general.display", systemImage: "paintpalette")
|
||||
}
|
||||
NavigationLink(destination: remoteLocalTimelinesView) {
|
||||
Label("Remote Local Timelines", systemImage: "dot.radiowaves.right")
|
||||
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||
Picker(selection: $preferences.preferredBrowser) {
|
||||
ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in
|
||||
switch browser {
|
||||
case .inAppSafari:
|
||||
Text("In-App Safari").tag(browser)
|
||||
Text("settings.general.browser.in-app").tag(browser)
|
||||
case .safari:
|
||||
Text("System Safari").tag(browser)
|
||||
Text("settings.general.browser.system").tag(browser)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Browser", systemImage: "network")
|
||||
Label("settings.general.browser", systemImage: "network")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,11 +110,11 @@ struct SettingsTabs: View {
|
||||
}
|
||||
|
||||
private var appSection: some View {
|
||||
Section("App") {
|
||||
Section("settings.section.app") {
|
||||
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||
NavigationLink(destination: IconSelectorView()) {
|
||||
Label {
|
||||
Text("App Icon")
|
||||
Text("settings.app.icon")
|
||||
} icon: {
|
||||
if let icon = IconSelectorView.Icon(string: UIApplication.shared.alternateIconName ?? "AppIcon") {
|
||||
Image(uiImage: .init(named: icon.iconName)!)
|
||||
@ -127,12 +127,12 @@ struct SettingsTabs: View {
|
||||
}
|
||||
|
||||
Link(destination: URL(string: "https://github.com/Dimillian/IceCubesApp")!) {
|
||||
Label("Source (GitHub link)", systemImage: "link")
|
||||
Label("settings.app.source", systemImage: "link")
|
||||
}
|
||||
.tint(theme.labelColor)
|
||||
|
||||
NavigationLink(destination: SupportAppView()) {
|
||||
Label("Support the app", systemImage: "wand.and.stars")
|
||||
Label("settings.app.support", systemImage: "wand.and.stars")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -142,7 +142,7 @@ struct SettingsTabs: View {
|
||||
Button {
|
||||
addAccountSheetPresented.toggle()
|
||||
} label: {
|
||||
Text("Add account")
|
||||
Text("settings.account.add")
|
||||
}
|
||||
.sheet(isPresented: $addAccountSheetPresented) {
|
||||
AddAccountView()
|
||||
@ -162,11 +162,11 @@ struct SettingsTabs: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .addRemoteLocalTimeline
|
||||
} label: {
|
||||
Label("Add a local timeline", systemImage: "badge.plus.radiowaves.right")
|
||||
Label("settings.timeline.add", systemImage: "badge.plus.radiowaves.right")
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
.navigationTitle("Remote Local Timelines")
|
||||
.navigationTitle("settings.general.remote-timelines")
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
}
|
||||
|
@ -15,26 +15,26 @@ struct SupportAppView: View {
|
||||
var productId: String {
|
||||
"icecubes.tipjar.\(rawValue)"
|
||||
}
|
||||
|
||||
var title: String {
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .one:
|
||||
return "🍬 Small Tip"
|
||||
return "settings.support.one.title"
|
||||
case .two:
|
||||
return "☕️ Nice Tip"
|
||||
return "settings.support.two.title"
|
||||
case .three:
|
||||
return "🤯 Generous Tip"
|
||||
return "settings.support.three.title"
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
|
||||
var subtitle: LocalizedStringKey {
|
||||
switch self {
|
||||
case .one:
|
||||
return "Small, but cute, and it taste good!"
|
||||
return "settings.support.one.subtitle"
|
||||
case .two:
|
||||
return "I love the taste of a fancy coffee ❤️"
|
||||
return "settings.support.two.subtitle"
|
||||
case .three:
|
||||
return "You're insane, thank you so much!"
|
||||
return "settings.support.three.subtitle"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,7 +61,7 @@ struct SupportAppView: View {
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(4)
|
||||
}
|
||||
Text("Hi there! My name is Thomas and I absolutely love creating open source apps. Ice Cubes is definitely one of my proudest projects to date - and let's be real, it's also the one that requires the most maintenance due to the ever-changing world of Mastodon and social media. If you're having a blast using Ice Cubes, consider tossing a little tip my way. It'll make my day (and help keep the app running smoothly for you). 🚀")
|
||||
Text("settings.support.message-from-dev")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -70,9 +70,9 @@ struct SupportAppView: View {
|
||||
if loadingProducts {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Loading ...")
|
||||
Text("placeholder.loading.short.")
|
||||
.font(.scaledSubheadline)
|
||||
Text("Loading subtitle...")
|
||||
Text("settings.support.placeholder.loading-subtitle")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
@ -118,18 +118,18 @@ struct SupportAppView: View {
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
.navigationTitle("Support Ice Cubes")
|
||||
.navigationTitle("settings.support.navigation-title")
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.alert("Thanks!", isPresented: $purchaseSuccessDisplayed, actions: {
|
||||
Button { purchaseSuccessDisplayed = false } label: { Text("Ok") }
|
||||
.alert("settings.support.alert.title", isPresented: $purchaseSuccessDisplayed, actions: {
|
||||
Button { purchaseSuccessDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("Thanks you so much for your tip! It's greatly appreciated!")
|
||||
Text("settings.support.alert.message")
|
||||
})
|
||||
.alert("Error!", isPresented: $purchaseErrorDisplayed, actions: {
|
||||
Button { purchaseErrorDisplayed = false } label: { Text("Ok") }
|
||||
.alert("alert.error", isPresented: $purchaseErrorDisplayed, actions: {
|
||||
Button { purchaseErrorDisplayed = false } label: { Text("alert.button.ok") }
|
||||
}, message: {
|
||||
Text("Error processing your in app purchase, please try again.")
|
||||
Text("settings.support.alert.error.message")
|
||||
})
|
||||
.onAppear {
|
||||
loadingProducts = true
|
||||
|
@ -55,23 +55,23 @@ enum Tab: Int, Identifiable, Hashable {
|
||||
var label: some View {
|
||||
switch self {
|
||||
case .timeline:
|
||||
Label("Timeline", systemImage: iconName)
|
||||
Label("tab.timeline", systemImage: iconName)
|
||||
case .trending:
|
||||
Label("Trending", systemImage: iconName)
|
||||
Label("tab.trending", systemImage: iconName)
|
||||
case .local:
|
||||
Label("Local", systemImage: iconName)
|
||||
Label("tab.local", systemImage: iconName)
|
||||
case .federated:
|
||||
Label("Federated", systemImage: iconName)
|
||||
Label("tab.federated", systemImage: iconName)
|
||||
case .notifications:
|
||||
Label("Notifications", systemImage: iconName)
|
||||
Label("tab.notifications", systemImage: iconName)
|
||||
case .mentions:
|
||||
Label("Notifications", systemImage: iconName)
|
||||
Label("tab.notifications", systemImage: iconName)
|
||||
case .explore:
|
||||
Label("Explore", systemImage: iconName)
|
||||
Label("tab.explore", systemImage: iconName)
|
||||
case .messages:
|
||||
Label("Messages", systemImage: iconName)
|
||||
Label("tab.messages", systemImage: iconName)
|
||||
case .settings:
|
||||
Label("Settings", systemImage: iconName)
|
||||
Label("tab.settings", systemImage: iconName)
|
||||
case .other, .profile:
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ struct AddRemoteTimelineView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
TextField("Instance URL", text: $instanceName)
|
||||
TextField("timeline.add.url", text: $instanceName)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.keyboardType(.URL)
|
||||
.textContentType(.URL)
|
||||
@ -32,7 +32,7 @@ struct AddRemoteTimelineView: View {
|
||||
.autocorrectionDisabled()
|
||||
.focused($isInstanceURLFieldFocused)
|
||||
if let instance {
|
||||
Label("\(instance.title) is a valid instance", systemImage: "checkmark.seal.fill")
|
||||
Label("timeline.\(instance.title)-is-valid", systemImage: "checkmark.seal.fill")
|
||||
.foregroundColor(.green)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
@ -41,21 +41,21 @@ struct AddRemoteTimelineView: View {
|
||||
preferences.remoteLocalTimelines.append(instanceName)
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Add")
|
||||
Text("timeline.add.action.add")
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
||||
instancesListView
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Add remote local timeline")
|
||||
.navigationTitle("timeline.add-remote.title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { dismiss() })
|
||||
Button("action.cancel", action: { dismiss() })
|
||||
}
|
||||
}
|
||||
.onChange(of: instanceName) { newValue in
|
||||
@ -78,7 +78,7 @@ struct AddRemoteTimelineView: View {
|
||||
}
|
||||
|
||||
private var instancesListView: some View {
|
||||
Section("Suggestions") {
|
||||
Section("instance.suggestions") {
|
||||
if instances.isEmpty {
|
||||
ProgressView()
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -94,9 +94,12 @@ struct AddRemoteTimelineView: View {
|
||||
Text(instance.info?.shortDescription ?? "")
|
||||
.font(.scaledBody)
|
||||
.foregroundColor(.gray)
|
||||
Text("\(instance.users) users ⸱ \(instance.statuses) posts")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
(Text("instance.list.users-\(instance.users)")
|
||||
+ Text(" ⸱ ")
|
||||
+ Text("instance.list.posts-\(instance.statuses)"))
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
@ -78,11 +78,11 @@ struct TimelineTab: View {
|
||||
Button {
|
||||
self.timeline = timeline
|
||||
} label: {
|
||||
Label(timeline.title(), systemImage: timeline.iconName() ?? "")
|
||||
Label(timeline.localizedTitle(), systemImage: timeline.iconName() ?? "")
|
||||
}
|
||||
}
|
||||
if !currentAccount.lists.isEmpty {
|
||||
Menu("Lists") {
|
||||
Menu("timeline.filter.lists") {
|
||||
ForEach(currentAccount.lists) { list in
|
||||
Button {
|
||||
timeline = .list(list: list)
|
||||
@ -94,7 +94,7 @@ struct TimelineTab: View {
|
||||
}
|
||||
|
||||
if !currentAccount.tags.isEmpty {
|
||||
Menu("Followed Tags") {
|
||||
Menu("timeline.filter.tags") {
|
||||
ForEach(currentAccount.tags) { tag in
|
||||
Button {
|
||||
timeline = .hashtag(tag: tag.name, accountId: nil)
|
||||
@ -104,8 +104,8 @@ struct TimelineTab: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu("Local Timelines") {
|
||||
|
||||
Menu("timeline.filter.local") {
|
||||
ForEach(preferences.remoteLocalTimelines, id: \.self) { server in
|
||||
Button {
|
||||
timeline = .remoteLocal(server: server)
|
||||
@ -116,7 +116,7 @@ struct TimelineTab: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .addRemoteLocalTimeline
|
||||
} label: {
|
||||
Label("Add a local timeline", systemImage: "badge.plus.radiowaves.right")
|
||||
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
303
IceCubesApp/Resources/Localization/en.lproj/Localizable.strings
Normal file
303
IceCubesApp/Resources/Localization/en.lproj/Localizable.strings
Normal file
@ -0,0 +1,303 @@
|
||||
// MARK: Common strings
|
||||
"action.cancel" = "Cancel";
|
||||
"action.delete" = "Delete";
|
||||
"action.save" = "Save";
|
||||
"action.done" = "Done";
|
||||
"action.retry" = "Retry";
|
||||
|
||||
"alert.button.ok" = "Ok";
|
||||
"alert.error" = "Error!";
|
||||
|
||||
"placeholder.loading.long" = "Loading, loading, loading ....";
|
||||
"placeholder.loading.short" = "Loading ...";
|
||||
|
||||
"see-more" = "See more";
|
||||
|
||||
// MARK: Add Account
|
||||
"account.add.error.instance-not-supported" = "This instance is not currently supported.";
|
||||
"account.add.navigation-title" = "Add account";
|
||||
"account.add.sign-in" = "Sign in";
|
||||
|
||||
// MARK: Enums
|
||||
"enum.avatar-position.leading" = "Leading";
|
||||
"enum.avatar-position.top" = "Top";
|
||||
"enum.avatar-shape.circle" = "Circle";
|
||||
"enum.avatar-shape.rounded" = "Rounded";
|
||||
"enum.status-actions-display.all" = "All";
|
||||
"enum.status-actions-display.no-buttons" = "No buttons";
|
||||
"enum.status-actions-display.only-buttons" = "Only buttons";
|
||||
"enum.status-display-style.compact" = "Compact";
|
||||
"enum.status-display-style.large" = "Large";
|
||||
|
||||
// MARK: Instances
|
||||
"instance.info.domains" = "Domains";
|
||||
"instance.info.email" = "Email";
|
||||
"instance.info.name" = "Name";
|
||||
"instance.info.navigation-title" = "Instance Info";
|
||||
"instance.info.posts" = "Posts";
|
||||
"instance.info.section.info" = "Instance info";
|
||||
"instance.info.section.rules" = "Instance rules";
|
||||
"instance.info.users" = "Users";
|
||||
"instance.info.version" = "Version";
|
||||
"instance.list.posts-%@" = "%@ posts";
|
||||
"instance.list.users-%@" = "%@ users";
|
||||
"instance.suggestions" = "Suggestions";
|
||||
"instance.url" = "Instance URL";
|
||||
|
||||
// MARK: Settings
|
||||
"settings.account.add" = "Add account";
|
||||
"settings.app.icon" = "App Icon";
|
||||
"settings.app.icon.navigation-title" = "Icons";
|
||||
"settings.app.source" = "Source (GitHub link)";
|
||||
"settings.app.support" = "Support the app";
|
||||
"settings.display.avatar.position" = "Avatar position";
|
||||
"settings.display.avatar.shape" = "Avatar shape";
|
||||
"settings.display.navigation-title" = "Display Settings";
|
||||
"settings.display.restore" = "Restore defaults";
|
||||
"settings.display.section.display" = "Display";
|
||||
"settings.display.section.theme" = "Theme";
|
||||
"settings.display.status.action-buttons" = "Status action buttons";
|
||||
"settings.display.status.media-style" = "Status media style";
|
||||
"settings.display.theme.background" = "Background color";
|
||||
"settings.display.theme.secondary-background" = "Secondary Background color";
|
||||
"settings.display.theme.tint" = "Tint color";
|
||||
"settings.general.browser" = "Browser";
|
||||
"settings.general.browser.in-app" = "In-App Safari";
|
||||
"settings.general.browser.system" = "System Safari";
|
||||
"settings.general.display" = "Display Settings";
|
||||
"settings.general.instance" = "Instance Information";
|
||||
"settings.general.push-notifications" = "Push notification";
|
||||
"settings.general.remote-timelines" = "Remote Local Timelines";
|
||||
"settings.push.boosts" = "Boosts";
|
||||
"settings.push.favorites" = "Favorites";
|
||||
"settings.push.follows" = "Follows";
|
||||
"settings.push.main-toggle" = "Push notifications";
|
||||
"settings.push.main-toggle.description" = "Receive push notifications on new activities";
|
||||
"settings.push.mentions" = "Mentions";
|
||||
"settings.push.navigation-title" = "Push Notifications";
|
||||
"settings.push.new-posts" = "New Posts";
|
||||
"settings.push.polls" = "Polls Results";
|
||||
"settings.section.accounts" = "Accounts";
|
||||
"settings.section.app" = "App";
|
||||
"settings.section.general" = "General";
|
||||
"settings.support.alert.error.message" = "Error processing your in app purchase, please try again.";
|
||||
"settings.support.alert.message" = "Thanks you so much for your tip! It's greatly appreciated!";
|
||||
"settings.support.alert.title" = "Thanks!";
|
||||
"settings.support.message-from-dev" = "Hi there! My name is Thomas and I absolutely love creating open source apps. Ice Cubes is definitely one of my proudest projects to date - and let's be real, it's also the one that requires the most maintenance due to the ever-changing world of Mastodon and social media. If you're having a blast using Ice Cubes, consider tossing a little tip my way. It'll make my day (and help keep the app running smoothly for you). 🚀";
|
||||
"settings.support.navigation-title" = "Support Ice Cubes";
|
||||
"settings.support.one.subtitle" = "Small, but cute, and it tastes good!";
|
||||
"settings.support.one.title" = "🍬 Small Tip";
|
||||
"settings.support.placeholder.loading-subtitle" = "Loading subtitle ...";
|
||||
"settings.support.three.subtitle" = "You're insane, thank you so much!";
|
||||
"settings.support.three.title" = "🤯 Generous Tip";
|
||||
"settings.support.two.subtitle" = "I love the taste of a fancy coffee ❤️";
|
||||
"settings.support.two.title" = "☕️ Nice Tip";
|
||||
"settings.timeline.add" = "Add a local timeline";
|
||||
"settings.title" = "Settings";
|
||||
|
||||
// MARK: Tabs
|
||||
"tab.explore" = "Explore";
|
||||
"tab.federated" = "Federated";
|
||||
"tab.local" = "Local";
|
||||
"tab.messages" = "Messages";
|
||||
"tab.notifications" = "Notifications";
|
||||
"tab.settings" = "Settings";
|
||||
"tab.timeline" = "Timeline";
|
||||
"tab.trending" = "Trending";
|
||||
|
||||
// MARK: Timeline
|
||||
"timeline.%@-is-valid" = "%@ is a valid instance";
|
||||
"timeline.add-remote.title" = "Add remote local timeline";
|
||||
"timeline.add.action.add" = "Add";
|
||||
"timeline.filter.add-local" = "Add a local timeline";
|
||||
"timeline.filter.lists" = "Lists";
|
||||
"timeline.filter.local" = "Local Timelines";
|
||||
"timeline.filter.tags" = "Followed Tags";
|
||||
|
||||
// MARK: Package: AppAccount
|
||||
"app-account.button.add" = "Add Account";
|
||||
|
||||
// MARK: Package: Account
|
||||
"account.action.add-remove-list" = "Add/Remove from lists";
|
||||
"account.action.edit-info" = "Edit Info";
|
||||
"account.action.mention" = "Mention";
|
||||
"account.action.message" = "Message";
|
||||
"account.boosted-by" = "Boosted by";
|
||||
"account.detail.about" = "About";
|
||||
"account.detail.familiar-followers" = "Also followed by";
|
||||
"account.detail.n-fields %lld" = "%lld fields";
|
||||
"account.detail.featured-tags-n-posts %lld" = "%lld posts";
|
||||
"account.edit.about" = "About";
|
||||
"account.edit.account-settings.bot" = "Bot account";
|
||||
"account.edit.account-settings.discoverable" = "Discoverable";
|
||||
"account.edit.account-settings.private" = "Private";
|
||||
"account.edit.account-settings.section-title" = "Account settings";
|
||||
"account.edit.display-name" = "Display Name";
|
||||
"account.edit.error.save.message" = "Error while saving your profile, please try again.";
|
||||
"account.edit.error.save.title" = "Error while saving your profile";
|
||||
"account.edit.navigation-title" = "Edit Profile";
|
||||
"account.edit.post-settings.privacy" = "Default privacy";
|
||||
"account.edit.post-settings.section-title" = "Post settings";
|
||||
"account.edit.post-settings.sensitive" = "Sensitive content";
|
||||
"account.favorited-by" = "Favorited by";
|
||||
"account.follow.follow" = "Follow";
|
||||
"account.follow.following" = "Following";
|
||||
"account.follow.requested" = "Requested";
|
||||
"account.followers" = "Followers";
|
||||
"account.following" = "Following";
|
||||
"account.list.create" = "Create a new list";
|
||||
"account.list.create.confirm" = "Create list";
|
||||
"account.list.create.description" = "Enter the name for your list";
|
||||
"account.list.delete" = "Delete list";
|
||||
"account.list.name" = "List name";
|
||||
"account.post.pinned" = "Pinned post";
|
||||
"account.posts" = "Posts";
|
||||
"account.relation.follows-you" = "Follows You";
|
||||
|
||||
// MARK: Package: Conversations
|
||||
"conversations.action.delete" = "Delete";
|
||||
"conversations.action.mark-read" = "Mark as read";
|
||||
"conversations.empty.message" = "Looking for some social media love? You'll find all your direct messages and private mentions right here. Happy messaging! 📱❤️";
|
||||
"conversations.empty.title" = "Inbox Zero";
|
||||
"conversations.error.button" = "Retry";
|
||||
"conversations.error.message" = "Error while loading your messages";
|
||||
"conversations.error.title" = "An error occurred";
|
||||
"conversations.navigation-title" = "Direct Messages";
|
||||
|
||||
// MARK: Package: DesignSystem
|
||||
"design.tag.n-posts-from-n-participants %lld %lld" = "%lld posts from %lld participants";
|
||||
"design.theme.navigation-title" = "Theme Selector";
|
||||
"design.theme.toots-preview" = "Toots preview";
|
||||
|
||||
// MARK: Package: Explore
|
||||
"explore.navigation-title" = "Explore";
|
||||
"explore.search.message-%@" = "From this screen you can search anything on %@";
|
||||
"explore.search.prompt" = "Search users, posts and tags";
|
||||
"explore.search.title" = "Search your instance";
|
||||
"explore.section.posts" = "Posts";
|
||||
"explore.section.suggested-users" = "Suggested Users";
|
||||
"explore.section.tags" = "Tags";
|
||||
"explore.section.trending.links" = "Trending Links";
|
||||
"explore.section.trending.posts" = "Trending Posts";
|
||||
"explore.section.trending.tags" = "Trending Tags";
|
||||
"explore.section.users" = "Users";
|
||||
|
||||
// MARK: Package: Env
|
||||
"env.poll-duration.5m" = "5 minutes";
|
||||
"env.poll-duration.30m" = "30 minutes";
|
||||
"env.poll-duration.1h" = "1 hour";
|
||||
"env.poll-duration.6h" = "6 hours";
|
||||
"env.poll-duration.1d" = "1 day";
|
||||
"env.poll-duration.3d" = "3 days";
|
||||
"env.poll-duration.7d" = "7 days";
|
||||
"env.poll-vote-frequency.one" = "One Vote";
|
||||
"env.poll-vote-frequency.multiple" = "Multiple Votes";
|
||||
|
||||
// MARK: Package: Lists
|
||||
"lists.add-remove-%@" = "Add/Remove %@";
|
||||
"lists.create" = "Create a new list";
|
||||
"lists.create.confirm" = "Create list";
|
||||
"lists.edit.users-in-list" = "Users in this list";
|
||||
"lists.name" = "List name";
|
||||
"lists.name.message" = "Enter the name for your list";
|
||||
|
||||
// MARK: Package: Notifications
|
||||
"notifications.empty.message" = "Notifications? What notifications? Your notification inbox is looking so empty. Keep on being awesome! 📱😎";
|
||||
"notifications.empty.title" = "No notifications";
|
||||
"notifications.error.message" = "An error occured while loading your notifications, please retry.";
|
||||
"notifications.error.title" = "An error occured";
|
||||
"notifications.label.favorite" = "starred";
|
||||
"notifications.label.follow" = "followed you";
|
||||
"notifications.label.follow-request" = "request to follow you";
|
||||
"notifications.label.mention" = "mentioned you";
|
||||
"notifications.label.poll" = "poll ended";
|
||||
"notifications.label.reblog" = "boosted";
|
||||
"notifications.label.status" = "posted a status";
|
||||
"notifications.label.update" = "edited a post";
|
||||
"notifications.menu-title.favorite" = "Favorite";
|
||||
"notifications.menu-title.follow" = "Follow";
|
||||
"notifications.menu-title.follow-request" = "Follow Request";
|
||||
"notifications.menu-title.mention" = "Mention";
|
||||
"notifications.menu-title.poll" = "Poll";
|
||||
"notifications.menu-title.reblog" = "Boost";
|
||||
"notifications.menu-title.status" = "Post";
|
||||
"notifications.menu-title.update" = "Post Edited";
|
||||
"notifications.navigation-title" = "All Notifications";
|
||||
"notifications.tab.all" = "All";
|
||||
"notifications.tab.mentions" = "Mentions";
|
||||
|
||||
// MARK: Package: Timeline
|
||||
"timeline.n-new-posts %lld" = "%lld new posts";
|
||||
"timeline.federated" = "Federated";
|
||||
"timeline.home" = "Home";
|
||||
"timeline.local" = "Local";
|
||||
"timeline.n-recent-from-n-participants %lld %lld" = "%lld recent posts from %lld participants";
|
||||
"timeline.trending" = "Trending";
|
||||
|
||||
// MARK: Package: Status
|
||||
"status.action.bookmark" = "Bookmark";
|
||||
"status.action.boost" = "Boost";
|
||||
"status.action.copy-text" = "Copy Text";
|
||||
"status.action.delete" = "Delete";
|
||||
"status.action.edit" = "Edit";
|
||||
"status.action.favorite" = "Favorite";
|
||||
"status.action.mention" = "Mention";
|
||||
"status.action.message" = "Message";
|
||||
"status.action.pin" = "Pin";
|
||||
"status.action.post" = "Post";
|
||||
"status.action.quote" = "Quote this post";
|
||||
"status.action.reply" = "Reply";
|
||||
"status.action.section.your-post" = "Your post";
|
||||
"status.action.share" = "Share this post";
|
||||
"status.action.unbookmark" = "Unbookmark";
|
||||
"status.action.unboost" = "Unboost";
|
||||
"status.action.unfavorite" = "Unfavorite";
|
||||
"status.action.unpin" = "Unpin";
|
||||
"status.action.view-in-browser" = "View in Browser";
|
||||
"status.draft.delete" = "Delete Draft";
|
||||
"status.draft.save" = "Save Draft";
|
||||
"status.editor.ai-prompt.correct" = "Correct text";
|
||||
"status.editor.ai-prompt.emphasize" = "Emphasize text";
|
||||
"status.editor.ai-prompt.fit" = "Shorten text";
|
||||
"status.editor.description.add" = "Add description";
|
||||
"status.editor.description.edit" = "Edit description";
|
||||
"status.editor.drafts.navigation-title" = "Drafts";
|
||||
"status.editor.error.upload" = "Error uploading";
|
||||
"status.editor.language-select.navigation-title" = "Select Language";
|
||||
"status.editor.media.edit-image" = "Edit Image";
|
||||
"status.editor.media.image-description" = "Image description";
|
||||
"status.editor.mode.edit" = "Editing your post";
|
||||
"status.editor.mode.new" = "New Post";
|
||||
"status.editor.mode.quote-%@" = "Quote of %@";
|
||||
"status.editor.mode.reply-%@" = "Replying to %@";
|
||||
"status.editor.restore-previous" = "Restore previous text";
|
||||
"status.editor.spoiler" = "Spoiler Text";
|
||||
"status.editor.text.placeholder" = "What's on your mind?";
|
||||
"status.editor.visibility" = "Post visibility";
|
||||
"status.error.loading.message" = "An error occured while loading posts, please try again.";
|
||||
"status.error.message" = "An error occured while this post context, please try again.";
|
||||
"status.error.title" = "An error occured";
|
||||
"status.filter.filtered-by-%@" = "Filtered by: %@";
|
||||
"status.filter.show-anyway" = "Show anyway";
|
||||
"status.image.alt-text.abbreviation" = "ALT";
|
||||
"status.media.content.show" = "Show content";
|
||||
"status.media.sensitive.show" = "Show sensitive content";
|
||||
"status.poll.n-votes %lld" = "%lld votes";
|
||||
"status.poll.closed" = "Closed";
|
||||
"status.poll.closes-in" = "Closes in ";
|
||||
"status.poll.duration" = "Poll Duration";
|
||||
"status.poll.frequency" = "Polling Frequency";
|
||||
"status.poll.option-n %lld" = "Option %lld";
|
||||
"status.post-from-%@" = "Post from %@";
|
||||
"status.row.was-boosted" = "boosted";
|
||||
"status.row.was-reply" = "Replied to";
|
||||
"status.row.you-boosted" = "You boosted";
|
||||
"status.show-less" = "Show less";
|
||||
"status.show-more" = "Show more";
|
||||
"status.summary.at-time" = " at ";
|
||||
"status.summary.n-boosts %lld" = "%lld boosts";
|
||||
"status.summary.n-favorites %lld" = "%lld favorites";
|
||||
"status.visibility.direct" = "Private";
|
||||
"status.visibility.follower" = "Followers";
|
||||
"status.visibility.public" = "Everyone";
|
||||
"status.visibility.unlisted" = "Unlisted";
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Account",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -54,7 +54,7 @@ struct AccountDetailHeaderView: View {
|
||||
}
|
||||
|
||||
if viewModel.relationship?.followedBy == true {
|
||||
Text("Follows You")
|
||||
Text("account.relation.follows-you")
|
||||
.font(.scaledFootnote)
|
||||
.fontWeight(.semibold)
|
||||
.padding(4)
|
||||
@ -89,13 +89,13 @@ struct AccountDetailHeaderView: View {
|
||||
scrollViewProxy?.scrollTo("status", anchor: .top)
|
||||
}
|
||||
} label: {
|
||||
makeCustomInfoLabel(title: "Posts", count: account.statusesCount)
|
||||
makeCustomInfoLabel(title: "account.posts", count: account.statusesCount)
|
||||
}
|
||||
NavigationLink(value: RouterDestinations.following(id: account.id)) {
|
||||
makeCustomInfoLabel(title: "Following", count: account.followingCount)
|
||||
makeCustomInfoLabel(title: "account.following", count: account.followingCount)
|
||||
}
|
||||
NavigationLink(value: RouterDestinations.followers(id: account.id)) {
|
||||
makeCustomInfoLabel(title: "Followers", count: account.followersCount)
|
||||
makeCustomInfoLabel(title: "account.followers", count: account.followersCount)
|
||||
}
|
||||
}.offset(y: 20)
|
||||
}
|
||||
@ -132,7 +132,7 @@ struct AccountDetailHeaderView: View {
|
||||
.offset(y: -40)
|
||||
}
|
||||
|
||||
private func makeCustomInfoLabel(title: String, count: Int) -> some View {
|
||||
private func makeCustomInfoLabel(title: LocalizedStringKey, count: Int) -> some View {
|
||||
VStack {
|
||||
Text("\(count)")
|
||||
.font(.scaledHeadline)
|
||||
|
@ -156,9 +156,9 @@ public struct AccountDetailView: View {
|
||||
isFieldsSheetDisplayed.toggle()
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("About")
|
||||
Text("account.detail.about")
|
||||
.font(.scaledCallout)
|
||||
Text("\(viewModel.fields.count) fields")
|
||||
Text("account.detail.n-fields \(viewModel.fields.count)")
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
@ -175,7 +175,7 @@ public struct AccountDetailView: View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("#\(tag.name)")
|
||||
.font(.scaledCallout)
|
||||
Text("\(tag.statusesCount) posts")
|
||||
Text("account.detail.featured-tags-n-posts \(tag.statusesCountInt)")
|
||||
.font(.caption2)
|
||||
}
|
||||
}.buttonStyle(.bordered)
|
||||
@ -191,7 +191,7 @@ public struct AccountDetailView: View {
|
||||
private var familiarFollowers: some View {
|
||||
if !viewModel.familiarFollowers.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Also followed by")
|
||||
Text("account.detail.familiar-followers")
|
||||
.font(.scaledHeadline)
|
||||
.padding(.leading, .layoutPadding)
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
@ -234,7 +234,7 @@ public struct AccountDetailView: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("About")
|
||||
.navigationTitle("account.detail.about")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button {
|
||||
@ -284,14 +284,14 @@ public struct AccountDetailView: View {
|
||||
.foregroundColor(theme.labelColor)
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Delete list", role: .destructive) {
|
||||
Button("account.list.delete", role: .destructive) {
|
||||
Task {
|
||||
await currentAccount.deleteList(list: list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("Create a new list") {
|
||||
Button("account.list.create") {
|
||||
isCreateListAlertPresented = true
|
||||
}
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
@ -299,13 +299,13 @@ public struct AccountDetailView: View {
|
||||
.task {
|
||||
await currentAccount.fetchLists()
|
||||
}
|
||||
.alert("Create a new list", isPresented: $isCreateListAlertPresented) {
|
||||
TextField("List name", text: $createListTitle)
|
||||
Button("Cancel") {
|
||||
.alert("account.list.create", isPresented: $isCreateListAlertPresented) {
|
||||
TextField("account.list.name", text: $createListTitle)
|
||||
Button("action.cancel") {
|
||||
isCreateListAlertPresented = false
|
||||
createListTitle = ""
|
||||
}
|
||||
Button("Create List") {
|
||||
Button("account.list.create.confirm") {
|
||||
guard !createListTitle.isEmpty else { return }
|
||||
isCreateListAlertPresented = false
|
||||
Task {
|
||||
@ -314,7 +314,7 @@ public struct AccountDetailView: View {
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("Enter the name for your list")
|
||||
Text("account.list.create.description")
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,7 +323,7 @@ public struct AccountDetailView: View {
|
||||
if !viewModel.pinned.isEmpty {
|
||||
ForEach(viewModel.pinned) { status in
|
||||
VStack(alignment: .leading) {
|
||||
Label("Pinned post", systemImage: "pin.fill")
|
||||
Label("account.post.pinned", systemImage: "pin.fill")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
.fontWeight(.semibold)
|
||||
@ -359,12 +359,12 @@ public struct AccountDetailView: View {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: account,
|
||||
visibility: preferences.serverPreferences?.postVisibility ?? .pub)
|
||||
} label: {
|
||||
Label("Mention", systemImage: "at")
|
||||
Label("account.action.mention", systemImage: "at")
|
||||
}
|
||||
Button {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: account, visibility: .direct)
|
||||
} label: {
|
||||
Label("Message", systemImage: "tray.full")
|
||||
Label("account.action.message", systemImage: "tray.full")
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
@ -373,7 +373,7 @@ public struct AccountDetailView: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .listAddAccount(account: account)
|
||||
} label: {
|
||||
Label("Add/Remove from lists", systemImage: "list.bullet")
|
||||
Label("account.action.add-remove-list", systemImage: "list.bullet")
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,7 +387,7 @@ public struct AccountDetailView: View {
|
||||
Button {
|
||||
isEditingAccount = true
|
||||
} label: {
|
||||
Label("Edit Info", systemImage: "pencil")
|
||||
Label("account.action.edit-info", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,17 @@ import SwiftUI
|
||||
public enum AccountsListMode {
|
||||
case following(accountId: String), followers(accountId: String)
|
||||
case favouritedBy(statusId: String), rebloggedBy(statusId: String)
|
||||
|
||||
var title: String {
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .following:
|
||||
return "Following"
|
||||
return "account.following"
|
||||
case .followers:
|
||||
return "Followers"
|
||||
return "account.followers"
|
||||
case .favouritedBy:
|
||||
return "Favourited by"
|
||||
return "account.favorited-by"
|
||||
case .rebloggedBy:
|
||||
return "Boosted by"
|
||||
return "account.boosted-by"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,16 +23,16 @@ struct EditAccountView: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("Edit Profile")
|
||||
.navigationTitle("account.edit.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
toolbarContent
|
||||
}
|
||||
.alert("Error while saving your profile",
|
||||
.alert("account.edit.error.save.title",
|
||||
isPresented: $viewModel.saveError,
|
||||
actions: {
|
||||
Button("Ok", action: {})
|
||||
}, message: { Text("Error while saving your profile, please try again.") })
|
||||
Button("alert.button.ok", action: {})
|
||||
}, message: { Text("account.edit.error.save.message") })
|
||||
.task {
|
||||
viewModel.client = client
|
||||
await viewModel.fetchAccount()
|
||||
@ -53,44 +53,44 @@ struct EditAccountView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var aboutSections: some View {
|
||||
Section("Display Name") {
|
||||
TextField("Display Name", text: $viewModel.displayName)
|
||||
Section("account.edit.display-name") {
|
||||
TextField("account.edit.display-name", text: $viewModel.displayName)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
Section("About") {
|
||||
TextField("About", text: $viewModel.note, axis: .vertical)
|
||||
Section("account.edit.about") {
|
||||
TextField("account.edit.about", text: $viewModel.note, axis: .vertical)
|
||||
.frame(maxHeight: 150)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
|
||||
private var postSettingsSection: some View {
|
||||
Section("Post settings") {
|
||||
Section("account.edit.post-settings.section-title") {
|
||||
Picker(selection: $viewModel.postPrivacy) {
|
||||
ForEach(Models.Visibility.supportDefault, id: \.rawValue) { privacy in
|
||||
Text(privacy.title).tag(privacy)
|
||||
}
|
||||
} label: {
|
||||
Label("Default privacy", systemImage: "lock")
|
||||
Label("account.edit.post-settings.privacy", systemImage: "lock")
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
Toggle(isOn: $viewModel.isSensitive) {
|
||||
Label("Sensitive content", systemImage: "eye")
|
||||
Label("account.edit.post-settings.sensitive", systemImage: "eye")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
|
||||
private var accountSection: some View {
|
||||
Section("Account settings") {
|
||||
Section("account.edit.account-settings.section-title") {
|
||||
Toggle(isOn: $viewModel.isLocked) {
|
||||
Label("Private", systemImage: "lock")
|
||||
Label("account.edit.account-settings.private", systemImage: "lock")
|
||||
}
|
||||
Toggle(isOn: $viewModel.isBot) {
|
||||
Label("Bot account", systemImage: "laptopcomputer.trianglebadge.exclamationmark")
|
||||
Label("account.edit.account-settings.bot", systemImage: "laptopcomputer.trianglebadge.exclamationmark")
|
||||
}
|
||||
Toggle(isOn: $viewModel.isDiscoverable) {
|
||||
Label("Discoverable", systemImage: "magnifyingglass")
|
||||
Label("account.edit.account-settings.discoverable", systemImage: "magnifyingglass")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -99,7 +99,7 @@ struct EditAccountView: View {
|
||||
@ToolbarContentBuilder
|
||||
private var toolbarContent: some ToolbarContent {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
Button("action.cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
@ -114,7 +114,7 @@ struct EditAccountView: View {
|
||||
if viewModel.isSaving {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("Save")
|
||||
Text("action.save")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,9 +70,9 @@ public struct FollowButton: View {
|
||||
}
|
||||
} label: {
|
||||
if viewModel.relationship.requested == true {
|
||||
Text("Requested")
|
||||
Text("account.follow.requested")
|
||||
} else {
|
||||
Text(viewModel.relationship.following ? "Following" : "Follow")
|
||||
Text(viewModel.relationship.following ? "account.follow.following" : "account.follow.follow")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "AppAccount",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -81,7 +81,7 @@ public struct AppAccountsSelectorView: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .addAccount
|
||||
} label: {
|
||||
Label("Add Account", systemImage: "person.badge.plus")
|
||||
Label("app-account.button.add", systemImage: "person.badge.plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Conversations",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -81,7 +81,7 @@ struct ConversationsListRow: View {
|
||||
await viewModel.markAsRead(conversation: conversation)
|
||||
}
|
||||
} label: {
|
||||
Label("Mark as read", systemImage: "eye")
|
||||
Label("conversations.action.mark-read", systemImage: "eye")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
@ -89,7 +89,7 @@ struct ConversationsListRow: View {
|
||||
await viewModel.delete(conversation: conversation)
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
Label("conversations.action.delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ public struct ConversationsListView: View {
|
||||
}
|
||||
} else if conversations.isEmpty && !viewModel.isLoadingFirstPage && !viewModel.isError {
|
||||
EmptyView(iconName: "tray",
|
||||
title: "Inbox Zero",
|
||||
message: "Looking for some social media love? You'll find all your direct messages and private mentions right here. Happy messaging! 📱❤️")
|
||||
title: "conversations.empty.title",
|
||||
message: "conversations.empty.message")
|
||||
} else if viewModel.isError {
|
||||
ErrorView(title: "An error occurred",
|
||||
message: "Error while loading your messages",
|
||||
buttonTitle: "Retry") {
|
||||
ErrorView(title: "conversations.error.title",
|
||||
message: "conversations.error.message",
|
||||
buttonTitle: "conversations.error.button") {
|
||||
Task {
|
||||
await viewModel.fetchConversations()
|
||||
}
|
||||
@ -56,7 +56,7 @@ public struct ConversationsListView: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.navigationTitle("Direct Messages")
|
||||
.navigationTitle("conversations.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
StatusEditorToolbarItem(visibility: .direct)
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "DesignSystem",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -14,9 +14,9 @@ public class Theme: ObservableObject {
|
||||
public var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .leading:
|
||||
return "Leading"
|
||||
return "enum.avatar-position.leading"
|
||||
case .top:
|
||||
return "Top"
|
||||
return "enum.avatar-position.top"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,9 +27,9 @@ public class Theme: ObservableObject {
|
||||
public var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .circle:
|
||||
return "Circle"
|
||||
return "enum.avatar-shape.circle"
|
||||
case .rounded:
|
||||
return "Rounded"
|
||||
return "enum.avatar-shape.rounded"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,11 +40,11 @@ public class Theme: ObservableObject {
|
||||
public var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .full:
|
||||
return "All"
|
||||
return "enum.status-actions-display.all"
|
||||
case .discret:
|
||||
return "Only buttons"
|
||||
return "enum.status-actions-display.only-buttons"
|
||||
case .none:
|
||||
return "No buttons"
|
||||
return "enum.status-actions-display.no-buttons"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,9 +55,9 @@ public class Theme: ObservableObject {
|
||||
public var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .large:
|
||||
return "Large"
|
||||
return "enum.status-display-style.large"
|
||||
case .compact:
|
||||
return "Compact"
|
||||
return "enum.status-display-style.compact"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import SwiftUI
|
||||
|
||||
public struct EmptyView: View {
|
||||
public let iconName: String
|
||||
public let title: String
|
||||
public let message: String
|
||||
|
||||
public init(iconName: String, title: String, message: String) {
|
||||
public let title: LocalizedStringKey
|
||||
public let message: LocalizedStringKey
|
||||
|
||||
public init(iconName: String, title: LocalizedStringKey, message: LocalizedStringKey) {
|
||||
self.iconName = iconName
|
||||
self.title = title
|
||||
self.message = message
|
||||
|
@ -1,12 +1,12 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct ErrorView: View {
|
||||
public let title: String
|
||||
public let message: String
|
||||
public let buttonTitle: String
|
||||
public let title: LocalizedStringKey
|
||||
public let message: LocalizedStringKey
|
||||
public let buttonTitle: LocalizedStringKey
|
||||
public let onButtonPress: () -> Void
|
||||
|
||||
public init(title: String, message: String, buttonTitle: String, onButtonPress: @escaping (() -> Void)) {
|
||||
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() -> Void)) {
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.buttonTitle = buttonTitle
|
||||
|
@ -16,7 +16,7 @@ public struct TagRowView: View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("#\(tag.name)")
|
||||
.font(.scaledHeadline)
|
||||
Text("\(tag.totalUses) posts from \(tag.totalAccounts) participants")
|
||||
Text("design.tag.n-posts-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public struct ThemePreviewView: View {
|
||||
.padding(4)
|
||||
.frame(maxHeight: .infinity)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.navigationTitle("Theme Selector")
|
||||
.navigationTitle("design.theme.navigation-title")
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@ struct ThemeBoxView: View {
|
||||
.foregroundColor(color.tintColor)
|
||||
.font(.system(size: 20))
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Toots preview")
|
||||
|
||||
Text("design.theme.toots-preview")
|
||||
.foregroundColor(color.labelColor)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Env",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public enum PollDuration: Int, CaseIterable {
|
||||
// rawValue == time in seconds; used for sending to the API
|
||||
@ -10,22 +11,22 @@ public enum PollDuration: Int, CaseIterable {
|
||||
case threeDays = 259_200
|
||||
case sevenDays = 604_800
|
||||
|
||||
public var displayString: String {
|
||||
public var displayString: LocalizedStringKey {
|
||||
switch self {
|
||||
case .fiveMinutes: return "5 minutes"
|
||||
case .halfAnHour: return "30 minutes"
|
||||
case .oneHour: return "1 hour"
|
||||
case .sixHours: return "6 hours"
|
||||
case .oneDay: return "1 day"
|
||||
case .threeDays: return "3 days"
|
||||
case .sevenDays: return "7 days"
|
||||
case .fiveMinutes: return "env.poll-duration.5m"
|
||||
case .halfAnHour: return "env.poll-duration.30m"
|
||||
case .oneHour: return "env.poll-duration.1h"
|
||||
case .sixHours: return "env.poll-duration.6h"
|
||||
case .oneDay: return "env.poll-duration.1d"
|
||||
case .threeDays: return "env.poll-duration.3d"
|
||||
case .sevenDays: return "env.poll-duration.7d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PollVotingFrequency: String, CaseIterable {
|
||||
case oneVote = "One Vote"
|
||||
case multipleVotes = "Multiple Votes"
|
||||
case oneVote = "one-vote"
|
||||
case multipleVotes = "multiple-votes"
|
||||
|
||||
public var canVoteMultipleTimes: Bool {
|
||||
switch self {
|
||||
@ -33,4 +34,11 @@ public enum PollVotingFrequency: String, CaseIterable {
|
||||
case .oneVote: return false
|
||||
}
|
||||
}
|
||||
|
||||
public var displayString: LocalizedStringKey {
|
||||
switch self {
|
||||
case .oneVote: return "env.poll-vote-frequency.one"
|
||||
case .multipleVotes: return "env.poll-vote-frequency.multiple"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Explore",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -28,8 +28,8 @@ public struct ExploreView: View {
|
||||
loadingView
|
||||
} else if viewModel.allSectionsEmpty {
|
||||
EmptyView(iconName: "magnifyingglass",
|
||||
title: "Search your instance",
|
||||
message: "From this screen you can search anything on \(client.server)")
|
||||
title: "explore.search.title",
|
||||
message: "explore.search.message-\(client.server)")
|
||||
.listRowBackground(theme.secondaryBackgroundColor)
|
||||
} else {
|
||||
if !viewModel.trendingTags.isEmpty {
|
||||
@ -58,11 +58,11 @@ public struct ExploreView: View {
|
||||
.listStyle(.grouped)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("Explore")
|
||||
.navigationTitle("explore.navigation-title")
|
||||
.searchable(text: $viewModel.searchQuery,
|
||||
tokens: $viewModel.tokens,
|
||||
suggestedTokens: $viewModel.suggestedToken,
|
||||
prompt: Text("Search users, posts and tags"),
|
||||
prompt: Text("explore.search.prompt"),
|
||||
token: { token in
|
||||
Text(token.rawValue)
|
||||
})
|
||||
@ -81,7 +81,7 @@ public struct ExploreView: View {
|
||||
@ViewBuilder
|
||||
private func makeSearchResultsView(results: SearchResults) -> some View {
|
||||
if !results.accounts.isEmpty {
|
||||
Section("Users") {
|
||||
Section("explore.section.users") {
|
||||
ForEach(results.accounts) { account in
|
||||
if let relationship = results.relationships.first(where: { $0.id == account.id }) {
|
||||
AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
|
||||
@ -91,7 +91,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
}
|
||||
if !results.hashtags.isEmpty {
|
||||
Section("Tags") {
|
||||
Section("explore.section.tags") {
|
||||
ForEach(results.hashtags) { tag in
|
||||
TagRowView(tag: tag)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -100,7 +100,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
}
|
||||
if !results.statuses.isEmpty {
|
||||
Section("Posts") {
|
||||
Section("explore.section.posts") {
|
||||
ForEach(results.statuses) { status in
|
||||
StatusRowView(viewModel: .init(status: status))
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -111,7 +111,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
|
||||
private var suggestedAccountsSection: some View {
|
||||
Section("Suggested Users") {
|
||||
Section("explore.section.suggested-users") {
|
||||
ForEach(viewModel.suggestedAccounts
|
||||
.prefix(upTo: viewModel.suggestedAccounts.count > 3 ? 3 : viewModel.suggestedAccounts.count)) { account in
|
||||
if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) {
|
||||
@ -131,10 +131,10 @@ public struct ExploreView: View {
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Suggested Users")
|
||||
.navigationTitle("explore.section.suggested-users")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("See more")
|
||||
Text("see-more")
|
||||
.foregroundColor(theme.tintColor)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -142,7 +142,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
|
||||
private var trendingTagsSection: some View {
|
||||
Section("Trending Tags") {
|
||||
Section("explore.section.trending.tags") {
|
||||
ForEach(viewModel.trendingTags
|
||||
.prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) { tag in
|
||||
TagRowView(tag: tag)
|
||||
@ -160,10 +160,10 @@ public struct ExploreView: View {
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Trending Tags")
|
||||
.navigationTitle("explore.section.trending.tags")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("See more")
|
||||
Text("see-more")
|
||||
.foregroundColor(theme.tintColor)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -171,7 +171,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
|
||||
private var trendingPostsSection: some View {
|
||||
Section("Trending Posts") {
|
||||
Section("explore.section.trending.posts") {
|
||||
ForEach(viewModel.trendingStatuses
|
||||
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
@ -190,10 +190,10 @@ public struct ExploreView: View {
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Trending Posts")
|
||||
.navigationTitle("explore.section.trending.posts")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("See more")
|
||||
Text("see-more")
|
||||
.foregroundColor(theme.tintColor)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
@ -201,7 +201,7 @@ public struct ExploreView: View {
|
||||
}
|
||||
|
||||
private var trendingLinksSection: some View {
|
||||
Section("Trending Links") {
|
||||
Section("explore.section.trending.links") {
|
||||
ForEach(viewModel.trendingLinks
|
||||
.prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) { card in
|
||||
StatusCardView(card: card)
|
||||
@ -219,10 +219,10 @@ public struct ExploreView: View {
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Trending Links")
|
||||
.navigationTitle("explore.section.trending.links")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text("See more")
|
||||
Text("see-more")
|
||||
.foregroundColor(theme.tintColor)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Lists",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -39,29 +39,29 @@ public struct ListAddAccountView: View {
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
Button("Create a new list") {
|
||||
Button("lists.create") {
|
||||
isCreateListAlertPresented = true
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("Add/Remove \(viewModel.account.displayName)")
|
||||
.navigationTitle("lists.add-remove-\(viewModel.account.displayName)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button("Done") {
|
||||
Button("action.done") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Create a new list", isPresented: $isCreateListAlertPresented) {
|
||||
TextField("List name", text: $createListTitle)
|
||||
Button("Cancel") {
|
||||
.alert("lists.create", isPresented: $isCreateListAlertPresented) {
|
||||
TextField("lists.name", text: $createListTitle)
|
||||
Button("action.cancel") {
|
||||
isCreateListAlertPresented = false
|
||||
createListTitle = ""
|
||||
}
|
||||
Button("Create List") {
|
||||
Button("lists.create.confirm") {
|
||||
guard !createListTitle.isEmpty else { return }
|
||||
isCreateListAlertPresented = false
|
||||
Task {
|
||||
@ -70,7 +70,7 @@ public struct ListAddAccountView: View {
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("Enter the name for your list")
|
||||
Text("lists.name.message")
|
||||
}
|
||||
}
|
||||
.task {
|
||||
|
@ -18,7 +18,7 @@ public struct ListEditView: View {
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section("Users in this list") {
|
||||
Section("lists.edit.users-in-list") {
|
||||
if viewModel.isLoadingAccounts {
|
||||
HStack {
|
||||
Spacer()
|
||||
@ -54,7 +54,7 @@ public struct ListEditView: View {
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button("Done") {
|
||||
Button("action.done") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Models",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Network",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Notifications",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -1,24 +1,25 @@
|
||||
import Models
|
||||
import SwiftUI
|
||||
|
||||
extension Models.Notification.NotificationType {
|
||||
func label() -> String {
|
||||
func label() -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .status:
|
||||
return "posted a status"
|
||||
return "notifications.label.status"
|
||||
case .mention:
|
||||
return "mentioned you"
|
||||
return "notifications.label.mention"
|
||||
case .reblog:
|
||||
return "boosted"
|
||||
return "notifications.label.reblog"
|
||||
case .follow:
|
||||
return "followed you"
|
||||
return "notifications.label.follow"
|
||||
case .follow_request:
|
||||
return "request to follow you"
|
||||
return "notifications.label.follow-request"
|
||||
case .favourite:
|
||||
return "starred"
|
||||
return "notifications.label.favorite"
|
||||
case .poll:
|
||||
return "poll ended"
|
||||
return "notifications.label.poll"
|
||||
case .update:
|
||||
return "edited a post"
|
||||
return "notifications.label.update"
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,25 +41,25 @@ extension Models.Notification.NotificationType {
|
||||
return "pencil.line"
|
||||
}
|
||||
}
|
||||
|
||||
func menuTitle() -> String {
|
||||
|
||||
func menuTitle() -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .status:
|
||||
return "Post"
|
||||
return "notifications.menu-title.status"
|
||||
case .mention:
|
||||
return "Mentions"
|
||||
return "notifications.menu-title.mention"
|
||||
case .reblog:
|
||||
return "Boost"
|
||||
return "notifications.menu-title.reblog"
|
||||
case .follow:
|
||||
return "Follow"
|
||||
return "notifications.menu-title.follow"
|
||||
case .follow_request:
|
||||
return "Follow Request"
|
||||
return "notifications.menu-title.follow-request"
|
||||
case .favourite:
|
||||
return "Favorite"
|
||||
return "notifications.menu-title.favorite"
|
||||
case .poll:
|
||||
return "Poll"
|
||||
return "notifications.menu-title.poll"
|
||||
case .update:
|
||||
return "Post Edited"
|
||||
return "notifications.menu-title.update"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public struct NotificationsListView: View {
|
||||
.padding(.top, .layoutPadding + 16)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
}
|
||||
.navigationTitle(lockedType?.menuTitle() ?? viewModel.selectedType?.menuTitle() ?? "All Notifications")
|
||||
.navigationTitle(lockedType?.menuTitle() ?? viewModel.selectedType?.menuTitle() ?? "notifications.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
if lockedType == nil {
|
||||
@ -35,7 +35,7 @@ public struct NotificationsListView: View {
|
||||
Button {
|
||||
viewModel.selectedType = nil
|
||||
} label: {
|
||||
Label("All Notifications", systemImage: "bell.fill")
|
||||
Label("notifications.navigation-title", systemImage: "bell.fill")
|
||||
}
|
||||
Divider()
|
||||
ForEach(Notification.NotificationType.allCases, id: \.self) { type in
|
||||
@ -96,8 +96,8 @@ public struct NotificationsListView: View {
|
||||
case let .display(notifications, nextPageState):
|
||||
if notifications.isEmpty {
|
||||
EmptyView(iconName: "bell.slash",
|
||||
title: "No notifications",
|
||||
message: "Notifications? What notifications? Your notification inbox is looking so empty. Keep on being awesome! 📱😎")
|
||||
title: "notifications.empty.title",
|
||||
message: "notifications.empty.message")
|
||||
} else {
|
||||
ForEach(notifications) { notification in
|
||||
if notification.supportedType != nil {
|
||||
@ -127,9 +127,9 @@ public struct NotificationsListView: View {
|
||||
}
|
||||
|
||||
case .error:
|
||||
ErrorView(title: "An error occurred",
|
||||
message: "An error occurred while loading your notifications, please retry.",
|
||||
buttonTitle: "Retry") {
|
||||
ErrorView(title: "notifications.error.title",
|
||||
message: "notifications.error.message",
|
||||
buttonTitle: "action.retry") {
|
||||
Task {
|
||||
await viewModel.fetchNotifications()
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ class NotificationsViewModel: ObservableObject {
|
||||
case error(error: Error)
|
||||
}
|
||||
|
||||
public enum Tab: String, CaseIterable {
|
||||
case all = "All"
|
||||
case mentions = "Mentions"
|
||||
public enum Tab: LocalizedStringKey, CaseIterable {
|
||||
case all = "notifications.tab.all"
|
||||
case mentions = "notifications.tab.mentions"
|
||||
}
|
||||
|
||||
var client: Client? {
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Status",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -63,9 +63,9 @@ public struct StatusDetailView: View {
|
||||
}
|
||||
|
||||
case .error:
|
||||
ErrorView(title: "An error occurred",
|
||||
message: "An error occurred while this post context, please try again.",
|
||||
buttonTitle: "Retry") {
|
||||
ErrorView(title: "status.error.title",
|
||||
message: "status.error.message",
|
||||
buttonTitle: "action.retry") {
|
||||
Task {
|
||||
await viewModel.fetch()
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ class StatusDetailViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
@Published var state: State = .loading
|
||||
@Published var title: String = ""
|
||||
|
||||
@Published var title: LocalizedStringKey = ""
|
||||
|
||||
init(statusId: String) {
|
||||
state = .loading
|
||||
self.statusId = statusId
|
||||
@ -65,7 +65,7 @@ class StatusDetailViewModel: ObservableObject {
|
||||
do {
|
||||
let data = try await fetchContextData(client: client, statusId: statusId)
|
||||
state = .display(status: data.status, context: data.context)
|
||||
title = "Post from \(data.status.account.displayNameWithoutEmojis)"
|
||||
title = "status.post-from-\(data.status.account.displayNameWithoutEmojis)"
|
||||
} catch {
|
||||
state = .error(error: error)
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ enum StatusEditorAIPrompts: CaseIterable {
|
||||
var label: some View {
|
||||
switch self {
|
||||
case .correct:
|
||||
Label("Correct text", systemImage: "text.badge.checkmark")
|
||||
Label("status.editor.ai-prompt.correct", systemImage: "text.badge.checkmark")
|
||||
case .fit:
|
||||
Label("Shorten text", systemImage: "text.badge.minus")
|
||||
Label("status.editor.ai-prompt.fit", systemImage: "text.badge.minus")
|
||||
case .emphasize:
|
||||
Label("Emphasize text", systemImage: "text.badge.star")
|
||||
Label("status.editor.ai-prompt.emphasize", systemImage: "text.badge.star")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,10 +127,10 @@ struct StatusEditorAccessoryView: View {
|
||||
.searchable(text: $languageSearch)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { isLanguageSheetDisplayed = false })
|
||||
Button("action.cancel", action: { isLanguageSheetDisplayed = false })
|
||||
}
|
||||
}
|
||||
.navigationTitle("Select Languages")
|
||||
.navigationTitle("status.editor.language-select.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
@ -157,12 +157,12 @@ struct StatusEditorAccessoryView: View {
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { isDraftsSheetDisplayed = false })
|
||||
Button("action.cancel", action: { isDraftsSheetDisplayed = false })
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.navigationTitle("Drafts")
|
||||
.navigationTitle("status.editor.drafts.navigation-title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.presentationDetents([.medium])
|
||||
|
@ -15,7 +15,7 @@ struct StatusEditorMediaEditView: View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Image description", text: $imageDescription, axis: .horizontal)
|
||||
TextField("status.editor.media.image-description", text: $imageDescription, axis: .horizontal)
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
Section {
|
||||
@ -45,11 +45,11 @@ struct StatusEditorMediaEditView: View {
|
||||
.onAppear {
|
||||
imageDescription = container.mediaAttachment?.description ?? ""
|
||||
}
|
||||
.navigationTitle("Edit Image")
|
||||
.navigationTitle("status.editor.media.edit-image")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
Button("action.done") {
|
||||
if !imageDescription.isEmpty {
|
||||
Task {
|
||||
await viewModel.addDescription(container: container, description: imageDescription)
|
||||
@ -60,7 +60,7 @@ struct StatusEditorMediaEditView: View {
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
Button("action.cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +47,14 @@ struct StatusEditorMediaView: View {
|
||||
.cornerRadius(8)
|
||||
if container.error != nil {
|
||||
VStack {
|
||||
Text("Error uploading")
|
||||
Text("status.editor.error.upload")
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
|
||||
}
|
||||
} label: {
|
||||
VStack {
|
||||
Text("Delete")
|
||||
Text("action.delete")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
@ -64,7 +64,7 @@ struct StatusEditorMediaView: View {
|
||||
}
|
||||
} label: {
|
||||
VStack {
|
||||
Text("Retry")
|
||||
Text("action.retry")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
@ -97,7 +97,7 @@ struct StatusEditorMediaView: View {
|
||||
editingContainer = container
|
||||
} label: {
|
||||
Label(container.mediaAttachment?.description?.isEmpty == false ?
|
||||
"Edit description" : "Add description",
|
||||
"status.editor.description.edit" : "status.editor.description.add",
|
||||
systemImage: "pencil.line")
|
||||
}
|
||||
}
|
||||
@ -106,13 +106,13 @@ struct StatusEditorMediaView: View {
|
||||
viewModel.mediasImages.removeAll(where: { $0.id == container.id })
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
Label("action.delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
private var altMarker: some View {
|
||||
Button {} label: {
|
||||
Text("ALT")
|
||||
Text("status.image.alt-text.abbreviation")
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(4)
|
||||
|
@ -25,7 +25,7 @@ struct StatusEditorPollView: View {
|
||||
ForEach(0 ..< count, id: \.self) { index in
|
||||
VStack {
|
||||
HStack(spacing: 16) {
|
||||
TextField("Option \(index + 1)", text: $viewModel.pollOptions[index])
|
||||
TextField("status.poll.option-n \(index + 1)", text: $viewModel.pollOptions[index])
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.focused($focused, equals: .option(index))
|
||||
.onTapGesture {
|
||||
@ -66,9 +66,9 @@ struct StatusEditorPollView: View {
|
||||
}
|
||||
|
||||
HStack {
|
||||
Picker("Polling Frequency", selection: $viewModel.pollVotingFrequency) {
|
||||
Picker("status.poll.frequency", selection: $viewModel.pollVotingFrequency) {
|
||||
ForEach(PollVotingFrequency.allCases, id: \.rawValue) {
|
||||
Text($0.rawValue)
|
||||
Text($0.displayString)
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
@ -76,7 +76,7 @@ struct StatusEditorPollView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Picker("Poll Duration", selection: $viewModel.pollDuration) {
|
||||
Picker("status.poll.duration", selection: $viewModel.pollDuration) {
|
||||
ForEach(PollDuration.allCases, id: \.rawValue) {
|
||||
Text($0.displayString)
|
||||
.tag($0)
|
||||
|
@ -38,7 +38,7 @@ public struct StatusEditorView: View {
|
||||
accountHeaderView
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
TextView($viewModel.statusText, $viewModel.selectedRange)
|
||||
.placeholder("What's on your mind")
|
||||
.placeholder(String(localized: "status.editor.text.placeholder"))
|
||||
.font(Font.scaledBodyUIFont)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
StatusEditorMediaView(viewModel: viewModel)
|
||||
@ -110,7 +110,7 @@ public struct StatusEditorView: View {
|
||||
if viewModel.isPosting {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("Post")
|
||||
Text("status.action.post")
|
||||
}
|
||||
}
|
||||
.disabled(!viewModel.canPost)
|
||||
@ -126,24 +126,24 @@ public struct StatusEditorView: View {
|
||||
object: nil)
|
||||
}
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
Text("action.cancel")
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
.confirmationDialog("",
|
||||
isPresented: $isDismissAlertPresented,
|
||||
actions: {
|
||||
Button("Delete Draft", role: .destructive) {
|
||||
Button("status.draft.delete", role: .destructive) {
|
||||
dismiss()
|
||||
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
|
||||
object: nil)
|
||||
}
|
||||
Button("Save Draft") {
|
||||
Button("status.draft.save") {
|
||||
preferences.draftsPosts.insert(viewModel.statusText.string, at: 0)
|
||||
dismiss()
|
||||
NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
|
||||
object: nil)
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
Button("action.cancel", role: .cancel) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -155,7 +155,7 @@ public struct StatusEditorView: View {
|
||||
private var spoilerTextView: some View {
|
||||
if viewModel.spoilerOn {
|
||||
VStack {
|
||||
TextField("Spoiler Text", text: $viewModel.spoilerText)
|
||||
TextField("status.editor.spoiler", text: $viewModel.spoilerText)
|
||||
.focused($isSpoilerTextFocused)
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
}
|
||||
@ -185,7 +185,7 @@ public struct StatusEditorView: View {
|
||||
|
||||
private var privacyMenu: some View {
|
||||
Menu {
|
||||
Section("Post visibility") {
|
||||
Section("status.editor.visibility") {
|
||||
ForEach(Models.Visibility.allCases, id: \.self) { visibility in
|
||||
Button {
|
||||
viewModel.visibility = visibility
|
||||
@ -226,7 +226,7 @@ public struct StatusEditorView: View {
|
||||
viewModel.replaceTextWith(text: backup.string)
|
||||
viewModel.backupStatusText = nil
|
||||
} label: {
|
||||
Label("Restore previous text", systemImage: "arrow.uturn.right")
|
||||
Label("status.editor.restore-previous", systemImage: "arrow.uturn.right")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
@ -1,13 +1,14 @@
|
||||
import Models
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
public extension StatusEditorViewModel {
|
||||
enum Mode {
|
||||
case replyTo(status: Status)
|
||||
case new(visibility: Visibility)
|
||||
case new(visibility: Models.Visibility)
|
||||
case edit(status: Status)
|
||||
case quote(status: Status)
|
||||
case mention(account: Account, visibility: Visibility)
|
||||
case mention(account: Account, visibility: Models.Visibility)
|
||||
case shareExtension(items: [NSItemProvider])
|
||||
|
||||
var isInShareExtension: Bool {
|
||||
@ -36,17 +37,17 @@ public extension StatusEditorViewModel {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .new, .mention, .shareExtension:
|
||||
return "New Post"
|
||||
return "status.editor.mode.new"
|
||||
case .edit:
|
||||
return "Editing your post"
|
||||
return "status.editor.mode.edit"
|
||||
case let .replyTo(status):
|
||||
return "Replying to \(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
|
||||
return "status.editor.mode.reply-\(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
|
||||
case let .quote(status):
|
||||
return "Quote of \(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
|
||||
return "status.editor.mode.quote-\(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Models
|
||||
import SwiftUI
|
||||
|
||||
public extension Visibility {
|
||||
static var supportDefault: [Visibility] {
|
||||
public extension Models.Visibility {
|
||||
static var supportDefault: [Self] {
|
||||
[.pub, .priv, .unlisted]
|
||||
}
|
||||
|
||||
@ -18,16 +19,16 @@ public extension Visibility {
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .pub:
|
||||
return "Everyone"
|
||||
return "status.visibility.public"
|
||||
case .unlisted:
|
||||
return "Unlisted"
|
||||
return "status.visibility.unlisted"
|
||||
case .priv:
|
||||
return "Followers"
|
||||
return "status.visibility.follower"
|
||||
case .direct:
|
||||
return "Private"
|
||||
return "status.visibility.direct"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
||||
.padding(.vertical, .dividerPadding)
|
||||
}
|
||||
case .error:
|
||||
ErrorView(title: "An error occurred",
|
||||
message: "An error occurred while loading posts, please try again.",
|
||||
buttonTitle: "Retry") {
|
||||
ErrorView(title: "status.error.title",
|
||||
message: "status.error.loading.message",
|
||||
buttonTitle: "action.retry") {
|
||||
Task {
|
||||
await fetcher.fetchStatuses()
|
||||
}
|
||||
|
@ -65,12 +65,12 @@ public struct StatusPollView: View {
|
||||
|
||||
private var footerView: some View {
|
||||
HStack(spacing: 0) {
|
||||
Text("\(viewModel.poll.votesCount) votes")
|
||||
Text("status.poll.n-votes \(viewModel.poll.votesCount)")
|
||||
Text(" ⸱ ")
|
||||
if viewModel.poll.expired {
|
||||
Text("Closed")
|
||||
Text("status.poll.closed")
|
||||
} else {
|
||||
Text("Close in ")
|
||||
Text("status.poll.closes-in")
|
||||
Text(viewModel.poll.expiresAt.asDate, style: .timer)
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ struct StatusActionsView: View {
|
||||
Divider()
|
||||
HStack {
|
||||
Text(viewModel.status.createdAt.asDate, style: .date) +
|
||||
Text(" at ") +
|
||||
Text("status.summary.at-time") +
|
||||
Text(viewModel.status.createdAt.asDate, style: .time) +
|
||||
Text(" ·")
|
||||
Image(systemName: viewModel.status.visibility.iconName)
|
||||
@ -120,7 +120,7 @@ struct StatusActionsView: View {
|
||||
if viewModel.favouritesCount > 0 {
|
||||
Divider()
|
||||
NavigationLink(value: RouterDestinations.favouritedBy(id: viewModel.status.id)) {
|
||||
Text("\(viewModel.favouritesCount) favorites")
|
||||
Text("status.summary.n-favorites \(viewModel.favouritesCount)")
|
||||
.font(.scaledCallout)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
@ -129,7 +129,7 @@ struct StatusActionsView: View {
|
||||
if viewModel.reblogsCount > 0 {
|
||||
Divider()
|
||||
NavigationLink(value: RouterDestinations.rebloggedBy(id: viewModel.status.id)) {
|
||||
Text("\(viewModel.reblogsCount) boosts")
|
||||
Text("status.summary.n-boosts \(viewModel.reblogsCount)")
|
||||
.font(.scaledCallout)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
|
@ -108,9 +108,9 @@ public struct StatusMediaPreviewView: View {
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.alert("Image description",
|
||||
.alert("status.editor.media.image-description",
|
||||
isPresented: $isAltAlertDisplayed) {
|
||||
Button("Ok", action: {})
|
||||
Button("alert.button.ok", action: {})
|
||||
} message: {
|
||||
Text(altTextDisplayed ?? "")
|
||||
}
|
||||
@ -167,7 +167,7 @@ public struct StatusMediaPreviewView: View {
|
||||
altTextDisplayed = alt
|
||||
isAltAlertDisplayed = true
|
||||
} label: {
|
||||
Text("ALT")
|
||||
Text("status.image.alt-text.abbreviation")
|
||||
}
|
||||
.padding(8)
|
||||
.background(.thinMaterial)
|
||||
@ -233,7 +233,7 @@ public struct StatusMediaPreviewView: View {
|
||||
altTextDisplayed = alt
|
||||
isAltAlertDisplayed = true
|
||||
} label: {
|
||||
Text("ALT")
|
||||
Text("status.image.alt-text.abbreviation")
|
||||
.font(.scaledFootnote)
|
||||
}
|
||||
.padding(4)
|
||||
@ -289,9 +289,9 @@ public struct StatusMediaPreviewView: View {
|
||||
}
|
||||
} label: {
|
||||
if sensitive {
|
||||
Label("Show sensitive content", systemImage: "eye")
|
||||
Label("status.media.sensitive.show", systemImage: "eye")
|
||||
} else {
|
||||
Label("Show content", systemImage: "eye")
|
||||
Label("status.media.content.show", systemImage: "eye")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
@ -19,7 +19,7 @@ struct StatusRowContextMenu: View {
|
||||
await viewModel.favourite()
|
||||
}
|
||||
} } label: {
|
||||
Label(viewModel.isFavourited ? "Unfavorite" : "Favorite", systemImage: "star")
|
||||
Label(viewModel.isFavourited ? "status.action.unfavorite" : "status.action.favorite", systemImage: "star")
|
||||
}
|
||||
Button { Task {
|
||||
if viewModel.isReblogged {
|
||||
@ -28,7 +28,7 @@ struct StatusRowContextMenu: View {
|
||||
await viewModel.reblog()
|
||||
}
|
||||
} } label: {
|
||||
Label(viewModel.isReblogged ? "Unboost" : "Boost", systemImage: "arrow.left.arrow.right.circle")
|
||||
Label(viewModel.isReblogged ? "status.action.unboost" : "status.action.boost", systemImage: "arrow.left.arrow.right.circle")
|
||||
}
|
||||
Button { Task {
|
||||
if viewModel.isBookmarked {
|
||||
@ -37,13 +37,13 @@ struct StatusRowContextMenu: View {
|
||||
await viewModel.bookmark()
|
||||
}
|
||||
} } label: {
|
||||
Label(viewModel.isReblogged ? "Unbookmark" : "Bookmark",
|
||||
Label(viewModel.isReblogged ? "status.action.unbookmark" : "status.action.bookmark",
|
||||
systemImage: "bookmark")
|
||||
}
|
||||
Button {
|
||||
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||
} label: {
|
||||
Label("Reply", systemImage: "arrowshape.turn.up.left")
|
||||
Label("status.action.reply", systemImage: "arrowshape.turn.up.left")
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ struct StatusRowContextMenu: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
|
||||
} label: {
|
||||
Label("Quote this post", systemImage: "quote.bubble")
|
||||
Label("status.action.quote", systemImage: "quote.bubble")
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,24 +59,24 @@ struct StatusRowContextMenu: View {
|
||||
|
||||
if let url = viewModel.status.reblog?.url ?? viewModel.status.url {
|
||||
ShareLink(item: url) {
|
||||
Label("Share this post", systemImage: "square.and.arrow.up")
|
||||
Label("status.action.share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
|
||||
if let url = URL(string: viewModel.status.reblog?.url ?? viewModel.status.url ?? "") {
|
||||
Button { openURL(url) } label: {
|
||||
Label("View in Browser", systemImage: "safari")
|
||||
Label("status.action.view-in-browser", systemImage: "safari")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
UIPasteboard.general.string = viewModel.status.content.asRawText
|
||||
} label: {
|
||||
Label("Copy Text", systemImage: "doc.on.doc")
|
||||
Label("status.action.copy-text", systemImage: "doc.on.doc")
|
||||
}
|
||||
|
||||
if account.account?.id == viewModel.status.account.id {
|
||||
Section("Your post") {
|
||||
Section("status.action.section.your-post") {
|
||||
Button {
|
||||
Task {
|
||||
if viewModel.isPinned {
|
||||
@ -86,15 +86,15 @@ struct StatusRowContextMenu: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(viewModel.isPinned ? "Unpin" : "Pin", systemImage: viewModel.isPinned ? "pin.fill" : "pin")
|
||||
Label(viewModel.isPinned ? "status.action.unpin" : "status.action.pin", systemImage: viewModel.isPinned ? "pin.fill" : "pin")
|
||||
}
|
||||
Button {
|
||||
routerPath.presentedSheet = .editStatusEditor(status: viewModel.status)
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
Label("status.action.edit", systemImage: "pencil")
|
||||
}
|
||||
Button(role: .destructive) { Task { await viewModel.delete() } } label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
Label("status.action.delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
} else if !viewModel.isRemote {
|
||||
@ -102,12 +102,12 @@ struct StatusRowContextMenu: View {
|
||||
Button {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .pub)
|
||||
} label: {
|
||||
Label("Mention", systemImage: "at")
|
||||
Label("status.action.mention", systemImage: "at")
|
||||
}
|
||||
Button {
|
||||
routerPath.presentedSheet = .mentionStatusEditor(account: viewModel.status.account, visibility: .direct)
|
||||
} label: {
|
||||
Label("Message", systemImage: "tray.full")
|
||||
Label("status.action.message", systemImage: "tray.full")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,13 +82,13 @@ public struct StatusRowView: View {
|
||||
|
||||
private func makeFilterView(filter: Filter) -> some View {
|
||||
HStack {
|
||||
Text("Filtered by: \(filter.title)")
|
||||
Text("status.filter.filtered-by-\(filter.title)")
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.isFiltered = false
|
||||
}
|
||||
} label: {
|
||||
Text("Show anyway")
|
||||
Text("status.filter.show-anyway")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,9 +101,9 @@ public struct StatusRowView: View {
|
||||
AvatarView(url: viewModel.status.account.avatar, size: .boost)
|
||||
if viewModel.status.account.username != account.account?.username {
|
||||
EmojiTextApp(viewModel.status.account.safeDisplayName.asMarkdown, emojis: viewModel.status.account.emojis)
|
||||
Text("boosted")
|
||||
Text("status.row.was-boosted")
|
||||
} else {
|
||||
Text("You boosted")
|
||||
Text("status.row.you-boosted")
|
||||
}
|
||||
}
|
||||
.font(.scaledFootnote)
|
||||
@ -128,7 +128,7 @@ public struct StatusRowView: View {
|
||||
{
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "arrowshape.turn.up.left.fill")
|
||||
Text("Replied to")
|
||||
Text("status.row.was-reply")
|
||||
Text(mention.username)
|
||||
}
|
||||
.font(.scaledFootnote)
|
||||
@ -185,7 +185,7 @@ public struct StatusRowView: View {
|
||||
viewModel.displaySpoiler.toggle()
|
||||
}
|
||||
} label: {
|
||||
Text(viewModel.displaySpoiler ? "Show more" : "Show less")
|
||||
Text(viewModel.displaySpoiler ? "status.show-more" : "status.show-less")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Timeline",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Foundation
|
||||
import Models
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
public enum TimelineFilter: Hashable, Equatable {
|
||||
case home, local, federated, trending
|
||||
case hashtag(tag: String, accountId: String?)
|
||||
case list(list: List)
|
||||
case list(list: Models.List)
|
||||
case remoteLocal(server: String)
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
@ -37,7 +38,26 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||
return server
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func localizedTitle() -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .federated:
|
||||
return "timeline.federated"
|
||||
case .local:
|
||||
return "timeline.local"
|
||||
case .trending:
|
||||
return "timeline.trending"
|
||||
case .home:
|
||||
return "timeline.home"
|
||||
case let .hashtag(tag, _):
|
||||
return "#\(tag)"
|
||||
case let .list(list):
|
||||
return LocalizedStringKey(list.title)
|
||||
case let .remoteLocal(server):
|
||||
return LocalizedStringKey(server)
|
||||
}
|
||||
}
|
||||
|
||||
public func iconName() -> String? {
|
||||
switch self {
|
||||
case .federated:
|
||||
|
@ -59,7 +59,7 @@ public struct TimelineView: View {
|
||||
scrollProxy = proxy
|
||||
}
|
||||
}
|
||||
.navigationTitle(timeline.title())
|
||||
.navigationTitle(timeline.localizedTitle())
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onAppear {
|
||||
if viewModel.client == nil {
|
||||
@ -143,7 +143,7 @@ public struct TimelineView: View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("#\(tag.name)")
|
||||
.font(.scaledHeadline)
|
||||
Text("\(tag.totalUses) recent posts from \(tag.totalAccounts) participants")
|
||||
Text("timeline.n-recent-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
|
||||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
@ -157,7 +157,7 @@ public struct TimelineView: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(tag.following ? "Following" : "Follow")
|
||||
Text(tag.following ? "account.follow.following" : "account.follow.follow")
|
||||
}.buttonStyle(.bordered)
|
||||
}
|
||||
.padding(.horizontal, .layoutPadding)
|
||||
|
@ -46,10 +46,10 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||
@Published var pendingStatuses: [Status] = []
|
||||
@Published var pendingStatusesState: PendingStatusesState = .stream
|
||||
|
||||
var pendingStatusesButtonTitle: String {
|
||||
var pendingStatusesButtonTitle: LocalizedStringKey {
|
||||
switch pendingStatusesState {
|
||||
case .stream, .refresh:
|
||||
return "\(pendingStatuses.count) new posts"
|
||||
return "timeline.n-new-posts \(pendingStatuses.count)"
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user