Prepend language list with recently used languages (#353)
* Add new preference entry for recently used languages Exposes a function to keep the language array clean: no more than 3 items, starting with the most recently used iso code * Add the preferences to the status editor ViewModel * Add language selector handling of most recent languages Only when the user has explicitly selected a language, when the posting was successful, add the selected language to the preferences array. - Makes Language a local private struct for clarity - Ensures all available languages are only fetched once - Separates recently used, other and search result section contents using specific vars/funcs * Copy new key in all localization files Co-authored-by: Pascal Batty <pascal@zen.ly>
This commit is contained in:
parent
5b3afc72de
commit
a1218e1488
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "Entwürfe";
|
||||
"status.editor.error.upload" = "Fehler beim Hochladen";
|
||||
"status.editor.language-select.navigation-title" = "Sprache auswählen";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "Bild bearbeiten";
|
||||
"status.editor.media.image-description" = "Bildbeschreibung";
|
||||
"status.editor.mode.edit" = "Deinen Post bearbeiten";
|
||||
|
|
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "Drafts";
|
||||
"status.editor.error.upload" = "Error uploading";
|
||||
"status.editor.language-select.navigation-title" = "Select Language";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "Edit Image";
|
||||
"status.editor.media.image-description" = "Image description";
|
||||
"status.editor.mode.edit" = "Editing your post";
|
||||
|
|
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "Borradores";
|
||||
"status.editor.error.upload" = "Error subiendo";
|
||||
"status.editor.language-select.navigation-title" = "Seleccionar idioma";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "Editar Imagen";
|
||||
"status.editor.media.image-description" = "Descripción de la imagen";
|
||||
"status.editor.mode.edit" = "Editando tu publicación";
|
||||
|
|
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "Bozze";
|
||||
"status.editor.error.upload" = "Errore durante il caricamento";
|
||||
"status.editor.language-select.navigation-title" = "Scegli la lingua";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "Modifica l'immagine";
|
||||
"status.editor.media.image-description" = "Descrizione dell'immagine";
|
||||
"status.editor.mode.edit" = "Messaggio in modifica";
|
||||
|
|
|
@ -277,6 +277,7 @@
|
|||
"status.editor.drafts.navigation-title" = "下書き";
|
||||
"status.editor.error.upload" = "アップロードエラー";
|
||||
"status.editor.language-select.navigation-title" = "言語設定";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "イメージの編集";
|
||||
"status.editor.media.image-description" = "イメージの説明文";
|
||||
"status.editor.mode.edit" = "投稿を編集する";
|
||||
|
|
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "Concepten";
|
||||
"status.editor.error.upload" = "Fout tijdens uploaden";
|
||||
"status.editor.language-select.navigation-title" = "Taal selecteren";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "Afbeelding bewerken";
|
||||
"status.editor.media.image-description" = "Omschrijving";
|
||||
"status.editor.mode.edit" = "Je post bewerken";
|
||||
|
|
|
@ -281,6 +281,7 @@
|
|||
"status.editor.drafts.navigation-title" = "草稿";
|
||||
"status.editor.error.upload" = "上传错误";
|
||||
"status.editor.language-select.navigation-title" = "选择语言";
|
||||
"status.editor.language-select.recently-used" = "Recently Used";
|
||||
"status.editor.media.edit-image" = "编辑图片";
|
||||
"status.editor.media.image-description" = "图片描述";
|
||||
"status.editor.mode.edit" = "正在编辑你的嘟文";
|
||||
|
|
|
@ -16,6 +16,7 @@ public class UserPreferences: ObservableObject {
|
|||
@AppStorage("font_size_scale") public var fontSizeScale: Double = 1
|
||||
@AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true
|
||||
@AppStorage("is_open_ai_enabled") public var isOpenAIEnabled: Bool = true
|
||||
@AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = []
|
||||
|
||||
public var pushNotificationsCount: Int {
|
||||
get {
|
||||
|
@ -41,4 +42,13 @@ public class UserPreferences: ObservableObject {
|
|||
guard let client, client.isAuth else { return }
|
||||
serverPreferences = try? await client.get(endpoint: Accounts.preferences)
|
||||
}
|
||||
|
||||
public func markLanguageAsSelected(isoCode: String) {
|
||||
var copy = recentlyUsedLanguages
|
||||
if let index = copy.firstIndex(of: isoCode) {
|
||||
copy.remove(at: index)
|
||||
}
|
||||
copy.insert(isoCode, at: 0)
|
||||
recentlyUsedLanguages = Array(copy.prefix(3))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,21 +107,17 @@ struct StatusEditorAccessoryView: View {
|
|||
private var languageSheetView: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(availableLanguages, id: \.0) { isoCode, nativeName, name in
|
||||
HStack {
|
||||
languageTextView(isoCode: isoCode, nativeName: nativeName, name: name)
|
||||
.tag(isoCode)
|
||||
Spacer()
|
||||
if isoCode == viewModel.selectedLanguage {
|
||||
Image(systemName: "checkmark")
|
||||
if languageSearch.isEmpty {
|
||||
if !recentlyUsedLanguages.isEmpty {
|
||||
Section("Recently Used") {
|
||||
languageSheetSection(languages: recentlyUsedLanguages)
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
viewModel.selectedLanguage = isoCode
|
||||
isLanguageSheetDisplayed = false
|
||||
Section {
|
||||
languageSheetSection(languages: otherLanguages)
|
||||
}
|
||||
} else {
|
||||
languageSheetSection(languages: languageSearchResult(query: languageSearch))
|
||||
}
|
||||
}
|
||||
.searchable(text: $languageSearch)
|
||||
|
@ -137,6 +133,29 @@ struct StatusEditorAccessoryView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func languageSheetSection(languages: [Language]) -> some View {
|
||||
ForEach(languages) { language in
|
||||
HStack {
|
||||
languageTextView(
|
||||
isoCode: language.isoCode,
|
||||
nativeName: language.nativeName,
|
||||
name: language.localizedName
|
||||
).tag(language.isoCode)
|
||||
Spacer()
|
||||
if language.isoCode == viewModel.selectedLanguage {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
viewModel.selectedLanguage = language.isoCode
|
||||
viewModel.hasExplicitlySelectedLanguage = true
|
||||
isLanguageSheetDisplayed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var draftsSheetView: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
|
@ -206,22 +225,43 @@ struct StatusEditorAccessoryView: View {
|
|||
.font(.scaledCallout)
|
||||
}
|
||||
|
||||
private var availableLanguages: [(String, String?, String?)] {
|
||||
private struct Language: Identifiable, Equatable {
|
||||
var id: String { isoCode }
|
||||
|
||||
let isoCode: String
|
||||
let nativeName: String?
|
||||
let localizedName: String?
|
||||
}
|
||||
|
||||
private let allAvailableLanguages: [Language] =
|
||||
Locale.LanguageCode.isoLanguageCodes
|
||||
.filter { $0.identifier.count == 2 } // Mastodon only supports ISO 639-1 (two-letter) codes
|
||||
.map { lang in
|
||||
let nativeLocale = Locale(languageComponents: Locale.Language.Components(languageCode: lang))
|
||||
return (
|
||||
lang.identifier,
|
||||
nativeLocale.localizedString(forLanguageCode: lang.identifier),
|
||||
Locale.current.localizedString(forLanguageCode: lang.identifier)
|
||||
return Language(
|
||||
isoCode: lang.identifier,
|
||||
nativeName: nativeLocale.localizedString(forLanguageCode: lang.identifier)?.capitalized,
|
||||
localizedName: Locale.current.localizedString(forLanguageCode: lang.identifier)?.localizedCapitalized
|
||||
)
|
||||
}
|
||||
.filter { _, nativeLocale, _ in
|
||||
|
||||
private var recentlyUsedLanguages: [Language] {
|
||||
preferences.recentlyUsedLanguages.compactMap { isoCode in
|
||||
allAvailableLanguages.first { $0.isoCode == isoCode }
|
||||
}
|
||||
}
|
||||
|
||||
private var otherLanguages: [Language] {
|
||||
allAvailableLanguages.filter { !preferences.recentlyUsedLanguages.contains($0.isoCode) }
|
||||
}
|
||||
|
||||
private func languageSearchResult(query: String) -> [Language] {
|
||||
allAvailableLanguages.filter { language in
|
||||
guard !languageSearch.isEmpty else {
|
||||
return true
|
||||
}
|
||||
return nativeLocale?.lowercased().hasPrefix(languageSearch.lowercased()) == true
|
||||
return language.nativeName?.lowercased().hasPrefix(query.lowercased()) == true
|
||||
|| language.localizedName?.lowercased().hasPrefix(query.lowercased()) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ public struct StatusEditorView: View {
|
|||
viewModel.client = client
|
||||
viewModel.currentAccount = currentAccount.account
|
||||
viewModel.theme = theme
|
||||
viewModel.preferences = preferences
|
||||
viewModel.prepareStatusText()
|
||||
if !client.isAuth {
|
||||
dismiss()
|
||||
|
|
|
@ -13,6 +13,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
var client: Client?
|
||||
var currentAccount: Account?
|
||||
var theme: Theme?
|
||||
var preferences: UserPreferences?
|
||||
|
||||
@Published var statusText = NSMutableAttributedString(string: "") {
|
||||
didSet {
|
||||
|
@ -87,6 +88,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
@Published var mentionsSuggestions: [Account] = []
|
||||
@Published var tagsSuggestions: [Tag] = []
|
||||
@Published var selectedLanguage: String?
|
||||
var hasExplicitlySelectedLanguage: Bool = false
|
||||
private var currentSuggestionRange: NSRange?
|
||||
|
||||
private var embeddedStatusURL: URL? {
|
||||
|
@ -136,6 +138,9 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
|
||||
}
|
||||
generator.notificationOccurred(.success)
|
||||
if hasExplicitlySelectedLanguage, let selectedLanguage {
|
||||
preferences?.markLanguageAsSelected(isoCode: selectedLanguage)
|
||||
}
|
||||
isPosting = false
|
||||
return postStatus
|
||||
} catch let error {
|
||||
|
|
Loading…
Reference in New Issue