|
@@ -1,3 +1,4 @@
|
|
|
|
|
+import AppKit
|
|
|
import Foundation
|
|
import Foundation
|
|
|
|
|
|
|
|
extension Notification.Name {
|
|
extension Notification.Name {
|
|
@@ -130,11 +131,78 @@ enum AppLanguageManager {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// Locale identifier used for layout direction (selected language or system default).
|
|
|
|
|
+ static var activeLanguageIdentifier: String {
|
|
|
|
|
+ if let code = selectedLanguageCode {
|
|
|
|
|
+ return code
|
|
|
|
|
+ }
|
|
|
|
|
+ return Locale.preferredLanguages.first ?? Locale.current.identifier
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static var isRightToLeft: Bool {
|
|
|
|
|
+ let identifier = activeLanguageIdentifier
|
|
|
|
|
+ let locale = Locale(identifier: identifier)
|
|
|
|
|
+ let languageCode = locale.languageCode
|
|
|
|
|
+ ?? identifier.split(whereSeparator: { $0 == "-" || $0 == "_" }).first.map(String.init)
|
|
|
|
|
+ ?? identifier
|
|
|
|
|
+ if languageCode == "ar" || languageCode == "he" {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static var userInterfaceLayoutDirection: NSUserInterfaceLayoutDirection {
|
|
|
|
|
+ isRightToLeft ? .rightToLeft : .leftToRight
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static var baseWritingDirection: NSWritingDirection {
|
|
|
|
|
+ isRightToLeft ? .rightToLeft : .leftToRight
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static var textAlignment: NSTextAlignment {
|
|
|
|
|
+ isRightToLeft ? .right : .left
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Alignment for labels pinned to the trailing edge (e.g. numeric counters).
|
|
|
|
|
+ static var trailingEdgeTextAlignment: NSTextAlignment {
|
|
|
|
|
+ isRightToLeft ? .left : .right
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static var disclosureChevron: String {
|
|
|
|
|
+ isRightToLeft ? "‹" : "›"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static func applyGlobalLayoutDirection() {
|
|
|
|
|
+ for window in NSApp.windows {
|
|
|
|
|
+ if let contentView = window.contentView {
|
|
|
|
|
+ applyLayoutDirection(to: contentView)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static func configureTextFieldForLayout(_ field: NSTextField, alignment: NSTextAlignment? = nil) {
|
|
|
|
|
+ field.alignment = alignment ?? textAlignment
|
|
|
|
|
+ field.baseWritingDirection = baseWritingDirection
|
|
|
|
|
+ field.userInterfaceLayoutDirection = userInterfaceLayoutDirection
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static func configureContainerForLayout(_ view: NSView) {
|
|
|
|
|
+ view.userInterfaceLayoutDirection = userInterfaceLayoutDirection
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static func applyLayoutDirection(to view: NSView) {
|
|
|
|
|
+ view.userInterfaceLayoutDirection = userInterfaceLayoutDirection
|
|
|
|
|
+ for subview in view.subviews {
|
|
|
|
|
+ applyLayoutDirection(to: subview)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
static func setLanguage(code: String?) {
|
|
static func setLanguage(code: String?) {
|
|
|
let normalized = code?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let normalized = code?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
let cleaned = (normalized?.isEmpty == false) ? normalized : nil
|
|
let cleaned = (normalized?.isEmpty == false) ? normalized : nil
|
|
|
guard cleaned != selectedLanguageCode else { return }
|
|
guard cleaned != selectedLanguageCode else { return }
|
|
|
selectedLanguageCode = cleaned
|
|
selectedLanguageCode = cleaned
|
|
|
|
|
+ applyGlobalLayoutDirection()
|
|
|
NotificationCenter.default.post(name: .appLanguageDidChange, object: nil)
|
|
NotificationCenter.default.post(name: .appLanguageDidChange, object: nil)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|