Browse Source

Fix Arabic CV templates breaking RTL layout mirroring.

Stop setting AppleLanguages for app locale so CV columns and sidebars stay LTR while Arabic copy still loads from ar.lproj.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 4 days ago
parent
commit
e413b324bc

+ 3 - 1
App for Indeed/Services/AppLocalization.swift

@@ -77,6 +77,8 @@ final class AppLanguageManager {
77
     }
77
     }
78
 
78
 
79
     func applyStoredPreferenceOnLaunch() {
79
     func applyStoredPreferenceOnLaunch() {
80
+        // UI copy uses `appLocalized`; do not drive layout from `AppleLanguages` (Arabic forces RTL and breaks CV templates).
81
+        UserDefaults.standard.removeObject(forKey: "AppleLanguages")
80
         if UserDefaults.standard.string(forKey: UserDefaultsKey.preferredLanguage) == nil {
82
         if UserDefaults.standard.string(forKey: UserDefaultsKey.preferredLanguage) == nil {
81
             setLanguage(AppLanguage.systemLanguage, notify: false)
83
             setLanguage(AppLanguage.systemLanguage, notify: false)
82
         }
84
         }
@@ -85,7 +87,7 @@ final class AppLanguageManager {
85
     func setLanguage(_ language: AppLanguage, notify: Bool = true) {
87
     func setLanguage(_ language: AppLanguage, notify: Bool = true) {
86
         let code = language.localeIdentifier
88
         let code = language.localeIdentifier
87
         UserDefaults.standard.set(code, forKey: UserDefaultsKey.preferredLanguage)
89
         UserDefaults.standard.set(code, forKey: UserDefaultsKey.preferredLanguage)
88
-        UserDefaults.standard.set([code], forKey: "AppleLanguages")
90
+        UserDefaults.standard.removeObject(forKey: "AppleLanguages")
89
         if notify {
91
         if notify {
90
             NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
92
             NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
91
         }
93
         }

+ 25 - 5
App for Indeed/Views/CVProfileDocumentView.swift

@@ -227,6 +227,7 @@ final class CVProfileDocumentView: NSView {
227
 
227
 
228
         let root = buildRoot()
228
         let root = buildRoot()
229
         root.translatesAutoresizingMaskIntoConstraints = false
229
         root.translatesAutoresizingMaskIntoConstraints = false
230
+        enforceLeftToRightLayout(on: root)
230
         card.addSubview(root)
231
         card.addSubview(root)
231
 
232
 
232
         addSubview(card)
233
         addSubview(card)
@@ -1562,11 +1563,15 @@ final class CVProfileDocumentView: NSView {
1562
         case .slashed: s = "// \(upper)"
1563
         case .slashed: s = "// \(upper)"
1563
         case .bracketed: s = "[ \(upper) ]"
1564
         case .bracketed: s = "[ \(upper) ]"
1564
         }
1565
         }
1565
-        let t = NSTextField(labelWithString: s)
1566
-        t.font = style.sectionFont
1567
-        t.textColor = style.sectionInk
1568
-        t.alignment = .left
1569
-        return t
1566
+        return label(s, font: style.sectionFont, color: style.sectionInk, maxLines: 1)
1567
+    }
1568
+
1569
+    /// Résumé templates are designed LTR (columns, rails, sidebars). Keep layout LTR even when UI copy is Arabic.
1570
+    private func enforceLeftToRightLayout(on view: NSView) {
1571
+        view.userInterfaceLayoutDirection = .leftToRight
1572
+        for child in view.subviews {
1573
+            enforceLeftToRightLayout(on: child)
1574
+        }
1570
     }
1575
     }
1571
 
1576
 
1572
     private func label(_ string: String, font: NSFont, color: NSColor, maxLines: Int) -> NSTextField {
1577
     private func label(_ string: String, font: NSFont, color: NSColor, maxLines: Int) -> NSTextField {
@@ -1579,12 +1584,27 @@ final class CVProfileDocumentView: NSView {
1579
             t = NSTextField(labelWithString: string)
1584
             t = NSTextField(labelWithString: string)
1580
             t.maximumNumberOfLines = maxLines
1585
             t.maximumNumberOfLines = maxLines
1581
         }
1586
         }
1587
+        t.translatesAutoresizingMaskIntoConstraints = false
1582
         t.font = font
1588
         t.font = font
1583
         t.textColor = color
1589
         t.textColor = color
1584
         t.alignment = .left
1590
         t.alignment = .left
1591
+        applyLeftToRightTextLayout(to: t)
1585
         return t
1592
         return t
1586
     }
1593
     }
1587
 
1594
 
1595
+    /// Mixed Arabic/English body copy still measures and wraps in column width when base direction is LTR.
1596
+    private func applyLeftToRightTextLayout(to field: NSTextField) {
1597
+        let paragraph = NSMutableParagraphStyle()
1598
+        paragraph.alignment = field.alignment
1599
+        paragraph.baseWritingDirection = .leftToRight
1600
+        paragraph.lineBreakMode = field.maximumNumberOfLines == 0 ? .byWordWrapping : .byTruncatingTail
1601
+        field.attributedStringValue = NSAttributedString(string: field.stringValue, attributes: [
1602
+            .font: field.font ?? .systemFont(ofSize: 12),
1603
+            .foregroundColor: field.textColor ?? .labelColor,
1604
+            .paragraphStyle: paragraph
1605
+        ])
1606
+    }
1607
+
1588
     private func displayable(_ value: String, placeholder: String) -> String {
1608
     private func displayable(_ value: String, placeholder: String) -> String {
1589
         let t = value.trimmingCharacters(in: .whitespacesAndNewlines)
1609
         let t = value.trimmingCharacters(in: .whitespacesAndNewlines)
1590
         return t.isEmpty ? placeholder : t
1610
         return t.isEmpty ? placeholder : t

+ 20 - 1
App for Indeed/Views/CVTemplateMiniPreview.swift

@@ -71,6 +71,7 @@ final class CVTemplatePreviewView: NSView {
71
         super.init(frame: .zero)
71
         super.init(frame: .zero)
72
         wantsLayer = true
72
         wantsLayer = true
73
         translatesAutoresizingMaskIntoConstraints = false
73
         translatesAutoresizingMaskIntoConstraints = false
74
+        userInterfaceLayoutDirection = .leftToRight
74
         configurePaper()
75
         configurePaper()
75
     }
76
     }
76
 
77
 
@@ -100,6 +101,7 @@ final class CVTemplatePreviewView: NSView {
100
 
101
 
101
         let root = buildResumeRoot()
102
         let root = buildResumeRoot()
102
         root.translatesAutoresizingMaskIntoConstraints = false
103
         root.translatesAutoresizingMaskIntoConstraints = false
104
+        enforceLeftToRightLayout(on: root)
103
         paper.addSubview(root)
105
         paper.addSubview(root)
104
         NSLayoutConstraint.activate([
106
         NSLayoutConstraint.activate([
105
             root.leadingAnchor.constraint(equalTo: paper.leadingAnchor, constant: 7),
107
             root.leadingAnchor.constraint(equalTo: paper.leadingAnchor, constant: 7),
@@ -894,7 +896,7 @@ final class CVTemplatePreviewView: NSView {
894
         row.orientation = .horizontal
896
         row.orientation = .horizontal
895
         row.spacing = 5
897
         row.spacing = 5
896
         row.translatesAutoresizingMaskIntoConstraints = false
898
         row.translatesAutoresizingMaskIntoConstraints = false
897
-        let lab = makeLabel("PORTFOLIO SNAPSHOT", font: .systemFont(ofSize: 6.5, weight: .black), color: palette.previewInk, alignment: .left, maxLines: 1)
899
+        let lab = makeLabel(L("PORTFOLIO SNAPSHOT"), font: .systemFont(ofSize: 6.5, weight: .black), color: palette.previewInk, alignment: .left, maxLines: 1)
898
         row.addArrangedSubview(stripe)
900
         row.addArrangedSubview(stripe)
899
         row.addArrangedSubview(lab)
901
         row.addArrangedSubview(lab)
900
         v.addSubview(row)
902
         v.addSubview(row)
@@ -1002,6 +1004,13 @@ final class CVTemplatePreviewView: NSView {
1002
         return row
1004
         return row
1003
     }
1005
     }
1004
 
1006
 
1007
+    private func enforceLeftToRightLayout(on view: NSView) {
1008
+        view.userInterfaceLayoutDirection = .leftToRight
1009
+        for child in view.subviews {
1010
+            enforceLeftToRightLayout(on: child)
1011
+        }
1012
+    }
1013
+
1005
     private func makeLabel(
1014
     private func makeLabel(
1006
         _ text: String,
1015
         _ text: String,
1007
         font: NSFont,
1016
         font: NSFont,
@@ -1016,6 +1025,7 @@ final class CVTemplatePreviewView: NSView {
1016
             tf = NSTextField(wrappingLabelWithString: text)
1025
             tf = NSTextField(wrappingLabelWithString: text)
1017
             tf.maximumNumberOfLines = maxLines
1026
             tf.maximumNumberOfLines = maxLines
1018
         }
1027
         }
1028
+        tf.translatesAutoresizingMaskIntoConstraints = false
1019
         tf.font = font
1029
         tf.font = font
1020
         tf.textColor = color
1030
         tf.textColor = color
1021
         tf.alignment = alignment
1031
         tf.alignment = alignment
@@ -1024,6 +1034,15 @@ final class CVTemplatePreviewView: NSView {
1024
         tf.drawsBackground = false
1034
         tf.drawsBackground = false
1025
         tf.isBordered = false
1035
         tf.isBordered = false
1026
         tf.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
1036
         tf.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
1037
+        let paragraph = NSMutableParagraphStyle()
1038
+        paragraph.alignment = alignment
1039
+        paragraph.baseWritingDirection = .leftToRight
1040
+        paragraph.lineBreakMode = maxLines == 1 ? .byTruncatingTail : .byWordWrapping
1041
+        tf.attributedStringValue = NSAttributedString(string: text, attributes: [
1042
+            .font: font,
1043
+            .foregroundColor: color,
1044
+            .paragraphStyle: paragraph
1045
+        ])
1027
         return tf
1046
         return tf
1028
     }
1047
     }
1029
 }
1048
 }

+ 3 - 0
App for Indeed/ar.lproj/Localizable.strings

@@ -317,6 +317,9 @@
317
 "SUMMARY" = "الملخص";
317
 "SUMMARY" = "الملخص";
318
 "PROFESSIONAL SUMMARY" = "الملخص المهني";
318
 "PROFESSIONAL SUMMARY" = "الملخص المهني";
319
 "SELECTED EXPERIENCE" = "الخبرة المختارة";
319
 "SELECTED EXPERIENCE" = "الخبرة المختارة";
320
+"CORE COMPETENCIES" = "الكفاءات الأساسية";
321
+"TOOLS" = "الأدوات";
322
+"IMPACT" = "التأثير";
320
 
323
 
321
 // MARK: - متصفح الوظائف
324
 // MARK: - متصفح الوظائف
322
 "Return to the previous screen" = "العودة إلى الشاشة السابقة";
325
 "Return to the previous screen" = "العودة إلى الشاشة السابقة";

+ 3 - 0
App for Indeed/en.lproj/Localizable.strings

@@ -317,6 +317,9 @@
317
 "SUMMARY" = "SUMMARY";
317
 "SUMMARY" = "SUMMARY";
318
 "PROFESSIONAL SUMMARY" = "PROFESSIONAL SUMMARY";
318
 "PROFESSIONAL SUMMARY" = "PROFESSIONAL SUMMARY";
319
 "SELECTED EXPERIENCE" = "SELECTED EXPERIENCE";
319
 "SELECTED EXPERIENCE" = "SELECTED EXPERIENCE";
320
+"CORE COMPETENCIES" = "CORE COMPETENCIES";
321
+"TOOLS" = "TOOLS";
322
+"IMPACT" = "IMPACT";
320
 
323
 
321
 // MARK: - Job Browser
324
 // MARK: - Job Browser
322
 "Return to the previous screen" = "Return to the previous screen";
325
 "Return to the previous screen" = "Return to the previous screen";