diff --git a/App/Controllers/SettingsSceneDelegate.swift b/App/Controllers/SettingsSceneDelegate.swift index 3d9974a..25d70b4 100644 --- a/App/Controllers/SettingsSceneDelegate.swift +++ b/App/Controllers/SettingsSceneDelegate.swift @@ -70,12 +70,13 @@ private extension NSToolbarItem.Identifier { static let general = NSToolbarItem.Identifier("general") static let interface = NSToolbarItem.Identifier("interface") static let performance = NSToolbarItem.Identifier("performance") + static let advanced = NSToolbarItem.Identifier("advanced") } extension SettingsSceneDelegate: NSToolbarDelegate { func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [ .general, .interface, .performance ] + [ .general, .interface, .performance, .advanced ] } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { @@ -107,6 +108,12 @@ extension SettingsSceneDelegate: NSToolbarDelegate { icon: "hare", action: #selector(selectPerformanceTab)) + case .advanced: + return makeToolbarItem(itemIdentifier: itemIdentifier, + label: "Advanced", + icon: "sparkles", + action: #selector(selectAdvancedTab)) + default: return nil } @@ -152,5 +159,9 @@ extension SettingsSceneDelegate: NSToolbarDelegate { switchTab(rootView: SettingsPerformanceView(), size: CGSize(width: 600, height: 500)) } + @objc private func selectAdvancedTab() { + switchTab(rootView: SettingsAdvancedView(), size: CGSize(width: 600, height: 500)) + } + } #endif diff --git a/App/UI/Settings/Mac/SettingsGeneralView.swift b/App/UI/Settings/Mac/SettingsGeneralView.swift index 568d803..a504abb 100644 --- a/App/UI/Settings/Mac/SettingsGeneralView.swift +++ b/App/UI/Settings/Mac/SettingsGeneralView.swift @@ -42,7 +42,7 @@ struct SettingsGeneralView: View { header: Text("Settings Sync"), footer: Text("Keep your NewTerm settings in sync between your Mac, iPhone, and iPad by selecting iCloud sync. If you just want to keep a backup with a service such as Dropbox, select custom folder sync.") ) { - Picker("Sync app settings:", selection: preferences.$preferencesSyncService) { + PreferencesPicker(selection: preferences.$preferencesSyncService, label: "Sync app settings:") { Text("Don’t sync") .tag(PreferencesSyncService.none) Text("via iCloud") diff --git a/App/UI/Settings/Mac/SettingsInterfaceView.swift b/App/UI/Settings/Mac/SettingsInterfaceView.swift index 4c09ed0..947be8c 100644 --- a/App/UI/Settings/Mac/SettingsInterfaceView.swift +++ b/App/UI/Settings/Mac/SettingsInterfaceView.swift @@ -67,19 +67,14 @@ struct SettingsInterfaceView: View { ) .padding([.top, .bottom, .leading], 20) - let themes = Picker("Theme", selection: preferences.$themeName) { - ForEach(sortedThemes, id: \.key) { key, value in - Button( - action: { - preferences.themeName = key - }, - label: { Text(key) } - ) + let themes = PreferencesPicker(selection: preferences.$themeName, label: "Theme") { + ForEach(sortedThemes, id: \.key) { key, _ in + Text(key) } } .pickerStyle(MenuPickerStyle()) - let fonts = Picker("Font", selection: preferences.$fontName) { + let fonts = PreferencesPicker(selection: preferences.$fontName, label: "Font") { Text("Default (SF Mono)") .id("SF Mono") HStack { diff --git a/App/UI/Settings/PreferencesGroup.swift b/App/UI/Settings/PreferencesGroup.swift index 1efc9ff..c061a3c 100644 --- a/App/UI/Settings/PreferencesGroup.swift +++ b/App/UI/Settings/PreferencesGroup.swift @@ -43,7 +43,6 @@ struct PreferencesGroup: View { ) { content } - .pickerStyle(InlinePickerStyle()) #endif } diff --git a/App/UI/Settings/PreferencesList.swift b/App/UI/Settings/PreferencesList.swift index d321c52..e7b1f8c 100644 --- a/App/UI/Settings/PreferencesList.swift +++ b/App/UI/Settings/PreferencesList.swift @@ -26,7 +26,7 @@ struct PreferencesList: View { } .navigationBarTitleDisplayMode(.inline) #else - List { + Form { content } .listStyle(InsetGroupedListStyle()) diff --git a/App/UI/Settings/PreferencesPicker.swift b/App/UI/Settings/PreferencesPicker.swift new file mode 100644 index 0000000..1f75243 --- /dev/null +++ b/App/UI/Settings/PreferencesPicker.swift @@ -0,0 +1,51 @@ +// +// PreferencesPicker.swift +// NewTerm (iOS) +// +// Created by Adam Demasi on 5/12/21. +// + +import SwiftUI + +struct PreferencesPicker: View { + + var label: Label + var selectionBinding: Binding + var content: Content + + init(selection: Binding, label: Label, @ViewBuilder content: () -> Content) { + self.label = label + self.selectionBinding = selection + self.content = content() + } + + var body: some View { +#if targetEnvironment(macCatalyst) + Picker( + selection: selectionBinding, + content: { content }, + label: { label } + ) +#else + Section( + header: label + ) { + Picker( + selection: selectionBinding, + content: { content }, + label: { label } + ) + } + .pickerStyle(InlinePickerStyle()) +#endif + } + +} + +extension PreferencesPicker where Label == Text { + init(selection: Binding, label: String, @ViewBuilder content: () -> Content) { + self.label = Text(label) + self.selectionBinding = selection + self.content = content() + } +} diff --git a/App/UI/Settings/SettingsAdvancedView.swift b/App/UI/Settings/SettingsAdvancedView.swift new file mode 100644 index 0000000..a53c90d --- /dev/null +++ b/App/UI/Settings/SettingsAdvancedView.swift @@ -0,0 +1,96 @@ +// +// SettingsAdvancedView.swift +// NewTerm (iOS) +// +// Created by Adam Demasi on 16/9/21. +// + +import SwiftUI + +struct SettingsAdvancedView: View { + + private struct LocaleItem: Identifiable, Comparable { + let locale: Locale + let name: String + var id: String { locale == .autoupdatingCurrent ? "" : locale.identifier } + + static func < (lhs: Self, rhs: Self) -> Bool { + lhs.name < rhs.name + } + } + + private var systemLocale: LocaleItem { + let mainLocale = Locale.autoupdatingCurrent + let name = String(format: .localize("LOCALE_SYSTEM"), + mainLocale.localizedString(forIdentifier: mainLocale.identifier) ?? mainLocale.identifier) + return LocaleItem(locale: mainLocale, name: name) + } + + private let locales: [LocaleItem] = { + let mainLocale = Locale.autoupdatingCurrent + let items = try! FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: "/usr/share/locale"), + includingPropertiesForKeys: [], + options: .skipsSubdirectoryDescendants) + return items + .compactMap { item in + guard let code = item.pathComponents.last, + code.hasSuffix(".UTF-8") else { + return nil + } + let locale = Locale(identifier: code) + return LocaleItem(locale: locale, + name: mainLocale.localizedString(forIdentifier: locale.identifier) ?? locale.identifier) + } + .sorted() + }() + + @ObservedObject var preferences = Preferences.shared + + var body: some View { + let systemLocale = self.systemLocale + let list = ForEach(locales) { item in + Text(item.name) + } + + return PreferencesList { + #if !targetEnvironment(macCatalyst) + PreferencesGroup { + NavigationLink( + destination: SettingsPerformanceView(), + label: { Text("Performance") } + ) + } + #endif + + PreferencesGroup( + header: Text("Locale"), + footer: Text("NewTerm will ask terminal programs to use this locale. Not all programs support this. This will not apply to currently open tabs, and may be overridden by shell startup scripts.") + ) { + PreferencesPicker( + selection: preferences.$preferredLocale, + label: Text("Language") + ) { + Text(systemLocale.name) + .tag(systemLocale.id) + Divider() + list + } + #if targetEnvironment(macCatalyst) + .pickerStyle(MenuPickerStyle()) + #else + .pickerStyle(RadioGroupPickerStyle()) + #endif + } + } + .navigationBarTitle("Advanced") + } + +} + +struct SettingsAdvancedView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + SettingsAdvancedView() + } + } +} diff --git a/App/UI/Settings/SettingsPerformanceView.swift b/App/UI/Settings/SettingsPerformanceView.swift index ba45a98..7fa1e7d 100644 --- a/App/UI/Settings/SettingsPerformanceView.swift +++ b/App/UI/Settings/SettingsPerformanceView.swift @@ -68,7 +68,7 @@ struct SettingsPerformanceView: View { ? AnyView(EmptyView()) : AnyView(Text("The Performance setting is recommended.")) ) { - Picker( + PreferencesPicker( selection: preferences.$refreshRateOnAC, label: EmptyView() ) { @@ -88,7 +88,7 @@ struct SettingsPerformanceView: View { footer: Text("A lower refresh rate improves \(UIDevice.current.deviceModel) battery life, but may cause the terminal display to feel sluggish.\nThe Performance setting is recommended.") .fixedSize(horizontal: false, vertical: true) ) { - Picker( + PreferencesPicker( selection: preferences.$refreshRateOnBattery, label: EmptyView() ) { diff --git a/App/UI/Settings/iOS/SettingsFontView.swift b/App/UI/Settings/iOS/SettingsFontView.swift index fe22af7..26a3cf1 100644 --- a/App/UI/Settings/iOS/SettingsFontView.swift +++ b/App/UI/Settings/iOS/SettingsFontView.swift @@ -25,7 +25,7 @@ struct SettingsFontView: View { PreferencesList { PreferencesGroup(header: Text("Font")) { - Picker(selection: preferences.$fontName, label: EmptyView()) { + PreferencesPicker(selection: preferences.$fontName, label: EmptyView()) { ForEach(sortedFonts, id: \.key) { key, value in HStack(alignment: .center) { if value.previewFont == nil { diff --git a/App/UI/Settings/iOS/SettingsThemeView.swift b/App/UI/Settings/iOS/SettingsThemeView.swift index 88d0bbc..b421f04 100644 --- a/App/UI/Settings/iOS/SettingsThemeView.swift +++ b/App/UI/Settings/iOS/SettingsThemeView.swift @@ -24,7 +24,7 @@ struct SettingsThemeView: View { PreferencesList { PreferencesGroup(header: Text("Built in Themes")) { - Picker(selection: preferences.$themeName, label: EmptyView()) { + PreferencesPicker(selection: preferences.$themeName, label: EmptyView()) { ForEach(sortedThemes, id: \.key) { item in Text(item.key) } } } diff --git a/App/UI/Settings/iOS/SettingsView.swift b/App/UI/Settings/iOS/SettingsView.swift index 0e4151e..c982ccf 100644 --- a/App/UI/Settings/iOS/SettingsView.swift +++ b/App/UI/Settings/iOS/SettingsView.swift @@ -42,8 +42,8 @@ struct SettingsView: View { #if !targetEnvironment(macCatalyst) Section { NavigationLink( - destination: SettingsPerformanceView(), - label: { Text("Performance") } + destination: SettingsAdvancedView(), + label: { Text("Advanced") } ) } diff --git a/Common/Controllers/Preferences.swift b/Common/Controllers/Preferences.swift index 43167dc..ccf1b5d 100644 --- a/Common/Controllers/Preferences.swift +++ b/Common/Controllers/Preferences.swift @@ -159,6 +159,11 @@ public class Preferences: NSObject, ObservableObject { willSet { objectWillChange.send() } } + @AppStorage("preferredLocale") + public var preferredLocale: String = "" { + willSet { objectWillChange.send() } + } + @AppStorage("lastVersion") public var lastVersion: Int = 0 { willSet { objectWillChange.send() } diff --git a/Common/Controllers/SubProcess.swift b/Common/Controllers/SubProcess.swift index 1ae0fdb..2af9423 100644 --- a/Common/Controllers/SubProcess.swift +++ b/Common/Controllers/SubProcess.swift @@ -149,9 +149,17 @@ class SubProcess: NSObject { // instance, a phone set to Simplified Chinese but a region of Australia will only have the // language zh_AU… which isn’t a thing. But gettext only has languages in country pairs, no // safe generic fallbacks exist, like zh-Hans in this case. - for language in Locale.preferredLanguages { + var languages = Locale.preferredLanguages + let preferredLocale = Preferences.shared.preferredLocale + if preferredLocale != "", + Locale(identifier: preferredLocale).languageCode != nil { + languages.insert(preferredLocale, at: 0) + } + + for language in languages { let locale = Locale(identifier: language) - if let languageCode = locale.languageCode, let regionCode = locale.regionCode { + if let languageCode = locale.languageCode, + let regionCode = locale.regionCode { let identifier = "\(languageCode)_\(regionCode).UTF-8" let url = URL(fileURLWithPath: "/usr/share/locale").appendingPathComponent(identifier) if (try? url.checkResourceIsReachable()) == true { diff --git a/NewTerm.xcodeproj/project.pbxproj b/NewTerm.xcodeproj/project.pbxproj index f3e7626..2ec67fc 100644 --- a/NewTerm.xcodeproj/project.pbxproj +++ b/NewTerm.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ @@ -11,6 +11,8 @@ 4E006D8C26F4AB71008D0BD2 /* SettingsGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E006D8B26F4AB70008D0BD2 /* SettingsGeneralView.swift */; }; 4E0A97A02685CDE30022F569 /* SettingsAcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0A979F2685CDE30022F569 /* SettingsAcknowledgementsView.swift */; }; 4E0A97A42685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0A97A32685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift */; }; + 4E1028D1275C4BD9001A34AB /* SettingsAdvancedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1028D0275C4BD9001A34AB /* SettingsAdvancedView.swift */; }; + 4E1028D3275CD933001A34AB /* PreferencesPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1028D2275CD933001A34AB /* PreferencesPicker.swift */; }; 4E2492D026230E59002C47CB /* TerminalController+ITermExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */; }; 4E32378F26FB2D4400AEDA06 /* UIApplication+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E32378E26FB2D4400AEDA06 /* UIApplication+Additions.swift */; }; 4E32379026FB4B3300AEDA06 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98081926184A2500E41883 /* String+Localization.swift */; }; @@ -123,6 +125,8 @@ 4E006D8B26F4AB70008D0BD2 /* SettingsGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGeneralView.swift; sourceTree = ""; }; 4E0A979F2685CDE30022F569 /* SettingsAcknowledgementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgementsView.swift; sourceTree = ""; }; 4E0A97A32685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgementsTextView.swift; sourceTree = ""; }; + 4E1028D0275C4BD9001A34AB /* SettingsAdvancedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAdvancedView.swift; sourceTree = ""; }; + 4E1028D2275CD933001A34AB /* PreferencesPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesPicker.swift; sourceTree = ""; }; 4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TerminalController+ITermExtensions.swift"; sourceTree = ""; }; 4E32378E26FB2D4400AEDA06 /* UIApplication+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Additions.swift"; sourceTree = ""; }; 4E3237A326FB530C00AEDA06 /* UIColorAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorAdditions.swift; sourceTree = ""; }; @@ -375,6 +379,7 @@ 4EDC998F26F440650030C1F9 /* iOS */, 4EDC998C26F440090030C1F9 /* Mac */, 4E5E817426F2EBE000C4DB6D /* SettingsPerformanceView.swift */, + 4E1028D0275C4BD9001A34AB /* SettingsAdvancedView.swift */, 4E980827261879D200E41883 /* SettingsAboutView.swift */, 4E0A979F2685CDE30022F569 /* SettingsAcknowledgementsView.swift */, 4E0A97A32685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift */, @@ -384,6 +389,7 @@ 4EA7D40A26F35A1D0031F078 /* GroupedButtonStyle.swift */, 4E57499926FEED9800D94DF5 /* PreferencesList.swift */, 4E57499726FEE90700D94DF5 /* PreferencesGroup.swift */, + 4E1028D2275CD933001A34AB /* PreferencesPicker.swift */, ); path = Settings; sourceTree = ""; @@ -621,8 +627,9 @@ CF22F9A91FFB97F7003175DE /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1100; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1320; TargetAttributes = { CF22F9C01FFB98A5003175DE = { CreatedOnToolsVersion = 9.1; @@ -747,6 +754,7 @@ CFBB96C922B5481B00585BE6 /* TerminalSampleView.swift in Sources */, CFBB96DA22B5481B00585BE6 /* TerminalSessionViewController.swift in Sources */, 4EB057672621A91400766B8A /* TerminalSplitViewController.swift in Sources */, + 4E1028D3275CD933001A34AB /* PreferencesPicker.swift in Sources */, 4ECFC24625FCA67D007B0F51 /* RootViewController.swift in Sources */, 4E9808222618601F00E41883 /* SettingsThemeView.swift in Sources */, 4E98081E2618551D00E41883 /* SettingsFontView.swift in Sources */, @@ -782,6 +790,7 @@ CFBB96CC22B5481B00585BE6 /* TabToolbarViewController.swift in Sources */, 4EA7D40B26F35A1D0031F078 /* GroupedButtonStyle.swift in Sources */, 4E006D8C26F4AB71008D0BD2 /* SettingsGeneralView.swift in Sources */, + 4E1028D1275C4BD9001A34AB /* SettingsAdvancedView.swift in Sources */, CFBB96C822B5481B00585BE6 /* HUDView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1084,17 +1093,14 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 11; - DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = N2LN9ZT493; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(THEOS_VENDOR_LIBRARY_PATH)", "$(THEOS_LIBRARY_PATH)", ); GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1180,7 +1186,6 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 11; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = N2LN9ZT493; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1191,6 +1196,7 @@ ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = fast; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index 08d46dd..c07620c 100644 --- a/Resources/en.lproj/Localizable.strings +++ b/Resources/en.lproj/Localizable.strings @@ -78,5 +78,9 @@ You may need to review startup scripts or reinstall core packages to fix this is SPLIT_VERTICALLY Split Vertically + + + LOCALE_SYSTEM + System Language: %@