Procházet zdrojové kódy

Add English and Arabic localization across the app.

Introduce Localizable.strings with AppLocalization helpers, wire UI copy through L(), and add Arabic translations including Pro, PDF, Mac, and dynamic CV template names.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 před 1 měsícem
rodič
revize
75f5cafd97

+ 9 - 8
App for Indeed.xcodeproj/project.pbxproj

@@ -39,20 +39,20 @@
 			);
 			);
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		27D852922FB1D369008DF557 /* app-connect */ = {
+		27D852812FB1D367008DF557 /* Products */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				27D852912FB1D369008DF557 /* AppConnect.xcconfig */,
+				27D852802FB1D367008DF557 /* App for Indeed.app */,
 			);
 			);
-			path = "app-connect";
+			name = Products;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		27D852812FB1D367008DF557 /* Products */ = {
+		27D852922FB1D369008DF557 /* app-connect */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				27D852802FB1D367008DF557 /* App for Indeed.app */,
+				27D852912FB1D369008DF557 /* AppConnect.xcconfig */,
 			);
 			);
-			name = Products;
+			path = "app-connect";
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
 /* End PBXGroup section */
 /* End PBXGroup section */
@@ -101,6 +101,7 @@
 			knownRegions = (
 			knownRegions = (
 				en,
 				en,
 				Base,
 				Base,
+				ar,
 			);
 			);
 			mainGroup = 27D852772FB1D367008DF557;
 			mainGroup = 27D852772FB1D367008DF557;
 			minimizedProjectReferenceProxies = 1;
 			minimizedProjectReferenceProxies = 1;
@@ -263,7 +264,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = NNC7V99779;
+				DEVELOPMENT_TEAM = "";
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
 				ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
 				ENABLE_USER_SELECTED_FILES = readonly;
 				ENABLE_USER_SELECTED_FILES = readonly;
@@ -303,7 +304,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = NNC7V99779;
+				DEVELOPMENT_TEAM = "";
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
 				ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
 				ENABLE_USER_SELECTED_FILES = readonly;
 				ENABLE_USER_SELECTED_FILES = readonly;

+ 1 - 0
App for Indeed/AppDelegate.swift

@@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
 
 
     func applicationWillFinishLaunching(_ notification: Notification) {
     func applicationWillFinishLaunching(_ notification: Notification) {
         AppAppearanceManager.shared.apply()
         AppAppearanceManager.shared.apply()
+        AppLanguageManager.shared.applyStoredPreferenceOnLaunch()
     }
     }
 
 
     func applicationDidFinishLaunching(_ aNotification: Notification) {
     func applicationDidFinishLaunching(_ aNotification: Notification) {

+ 23 - 7
App for Indeed/Controllers/IndeedJobBrowserWindowController.swift

@@ -31,9 +31,10 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     private let backButton = NSButton()
     private let backButton = NSButton()
     private let forwardButton = NSButton()
     private let forwardButton = NSButton()
     private let reloadButton = NSButton()
     private let reloadButton = NSButton()
-    private let dismissEmbeddedButton = NSButton(title: "Home", target: nil, action: nil)
+    private let dismissEmbeddedButton = NSButton(title: L("Home"), target: nil, action: nil)
     private let toolbarContainer = NSView()
     private let toolbarContainer = NSView()
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
 
     override func loadView() {
     override func loadView() {
         view = NSView(frame: NSRect(x: 0, y: 0, width: 920, height: 720))
         view = NSView(frame: NSRect(x: 0, y: 0, width: 920, height: 720))
@@ -55,7 +56,7 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         dismissEmbeddedButton.isBordered = true
         dismissEmbeddedButton.isBordered = true
         dismissEmbeddedButton.target = self
         dismissEmbeddedButton.target = self
         dismissEmbeddedButton.action = #selector(dismissEmbedded)
         dismissEmbeddedButton.action = #selector(dismissEmbedded)
-        dismissEmbeddedButton.toolTip = "Return to the previous screen"
+        dismissEmbeddedButton.toolTip = L("Return to the previous screen")
 
 
         toolbarContainer.translatesAutoresizingMaskIntoConstraints = false
         toolbarContainer.translatesAutoresizingMaskIntoConstraints = false
         toolbarContainer.wantsLayer = true
         toolbarContainer.wantsLayer = true
@@ -112,6 +113,13 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
 
 
         if let pendingURL {
         if let pendingURL {
             webView.load(URLRequest(url: pendingURL))
             webView.load(URLRequest(url: pendingURL))
@@ -123,6 +131,9 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     func loadPage(_ url: URL) {
     func loadPage(_ url: URL) {
@@ -166,6 +177,11 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         reloadButton.contentTintColor = accent
         reloadButton.contentTintColor = accent
     }
     }
 
 
+    private func applyLocalizedStrings() {
+        dismissEmbeddedButton.title = L("Home")
+        dismissEmbeddedButton.toolTip = L("Return to the previous screen")
+    }
+
     private func updateNavigationButtons() {
     private func updateNavigationButtons() {
         backButton.isEnabled = webView.canGoBack
         backButton.isEnabled = webView.canGoBack
         forwardButton.isEnabled = webView.canGoForward
         forwardButton.isEnabled = webView.canGoForward
@@ -272,7 +288,7 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
     ) {
         let alert = NSAlert()
         let alert = NSAlert()
         alert.messageText = message
         alert.messageText = message
-        alert.addButton(withTitle: NSLocalizedString("OK", comment: "Web alert dismiss"))
+        alert.addButton(withTitle: L("OK"))
         alert.runModal()
         alert.runModal()
         completionHandler()
         completionHandler()
     }
     }
@@ -285,8 +301,8 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
     ) {
         let alert = NSAlert()
         let alert = NSAlert()
         alert.messageText = message
         alert.messageText = message
-        alert.addButton(withTitle: NSLocalizedString("OK", comment: "Web confirm accept"))
-        alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Web confirm cancel"))
+        alert.addButton(withTitle: L("OK"))
+        alert.addButton(withTitle: L("Cancel"))
         completionHandler(alert.runModal() == .alertFirstButtonReturn)
         completionHandler(alert.runModal() == .alertFirstButtonReturn)
     }
     }
 
 
@@ -299,8 +315,8 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
     ) {
         let alert = NSAlert()
         let alert = NSAlert()
         alert.messageText = prompt
         alert.messageText = prompt
-        alert.addButton(withTitle: NSLocalizedString("OK", comment: "Web prompt accept"))
-        alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Web prompt cancel"))
+        alert.addButton(withTitle: L("OK"))
+        alert.addButton(withTitle: L("Cancel"))
         let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 280, height: 24))
         let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 280, height: 24))
         field.stringValue = defaultText ?? ""
         field.stringValue = defaultText ?? ""
         alert.accessoryView = field
         alert.accessoryView = field

+ 160 - 90
App for Indeed/Controllers/PremiumPlansWindowController.swift

@@ -8,7 +8,7 @@ final class PremiumPlansWindowController: NSWindowController {
     init() {
     init() {
         let viewController = PremiumPlansViewController()
         let viewController = PremiumPlansViewController()
         let window = NSWindow(contentViewController: viewController)
         let window = NSWindow(contentViewController: viewController)
-        window.title = "Premium Plans"
+        window.title = L("Premium Plans")
         // Borderless avoids titled-window chrome: its rounded titlebar frame often leaves dark wedges at
         // Borderless avoids titled-window chrome: its rounded titlebar frame often leaves dark wedges at
         // the corners when combined with a custom full-bleed paywall (this window is only shown as a sheet).
         // the corners when combined with a custom full-bleed paywall (this window is only shown as a sheet).
         window.styleMask = [.borderless, .closable, .resizable]
         window.styleMask = [.borderless, .closable, .resizable]
@@ -293,7 +293,7 @@ private final class PremiumPlansViewController: NSViewController {
             wantsLayer = true
             wantsLayer = true
             layer?.cornerRadius = 15
             layer?.cornerRadius = 15
             bezelStyle = .regularSquare
             bezelStyle = .regularSquare
-            image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Close")
+            image = NSImage(systemSymbolName: "xmark", accessibilityDescription: L("Close"))
             imageScaling = .scaleProportionallyDown
             imageScaling = .scaleProportionallyDown
             focusRingType = .none
             focusRingType = .none
             translatesAutoresizingMaskIntoConstraints = false
             translatesAutoresizingMaskIntoConstraints = false
@@ -480,6 +480,7 @@ private final class PremiumPlansViewController: NSViewController {
         let featureLabels: [NSTextField]
         let featureLabels: [NSTextField]
         let featureIcons: [NSImageView]
         let featureIcons: [NSImageView]
         let purchaseButton: PlanPurchaseHoverButton
         let purchaseButton: PlanPurchaseHoverButton
+        let billedPillLabel: NSTextField?
     }
     }
 
 
     private enum FeatureListMetrics {
     private enum FeatureListMetrics {
@@ -497,68 +498,73 @@ private final class PremiumPlansViewController: NSViewController {
     private var premiumCloseButton: PremiumCloseHoverButton?
     private var premiumCloseButton: PremiumCloseHoverButton?
     private var subscriptionStatusObservation: NSObjectProtocol?
     private var subscriptionStatusObservation: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
 
     /// Core Pro capabilities shown on every pricing card (replaces generic “All premium features”).
     /// Core Pro capabilities shown on every pricing card (replaces generic “All premium features”).
-    private static let proCapabilityFeatures = [
-        "Unlimited AI job search on Home",
-        "Save jobs & open listings in-app",
-        "CV Maker, profiles & PDF export",
-        "Role, company & skill shortcuts"
-    ]
-
-    private let plans: [Plan] = [
-        Plan(
-            id: "weekly",
-            title: "Weekly",
-            subtitle: "Flexible and commitment-free",
-            period: "/ week",
-            billedPill: "",
-            billedLine: "",
-            crossedPrice: nil,
-            savingsText: nil,
-            features: proCapabilityFeatures + [
-                "Perfect for short-term job hunts",
-                "Cancel anytime"
-            ],
-            iconName: "paperplane.fill",
-            iconTint: Theme.iconTint,
-            highlight: false
-        ),
-        Plan(
-            id: "monthly",
-            title: "Monthly",
-            subtitle: "Balanced for regular productivity",
-            period: "/ month",
-            billedPill: "",
-            billedLine: "",
-            crossedPrice: nil,
-            savingsText: nil,
-            features: proCapabilityFeatures + [
-                "Best for regular job seekers",
-                "Priority support"
-            ],
-            iconName: "bolt.fill",
-            iconTint: Theme.accent,
-            highlight: true
-        ),
-        Plan(
-            id: "yearly",
-            title: "Yearly",
-            subtitle: "Best value for long-term users",
-            period: "/ year",
-            billedPill: "3 days free trial",
-            billedLine: "",
-            crossedPrice: nil,
-            savingsText: nil,
-            features: proCapabilityFeatures + [
-                "Lowest effective monthly cost",
-                "Ideal for long-term use"
-            ],
-            iconName: "crown.fill",
-            iconTint: Theme.successText,
-            highlight: false
-        )
-    ]
+    private var proCapabilityFeatures: [String] {
+        [
+            L("Unlimited AI job search on Home"),
+            L("Save jobs & open listings in-app"),
+            L("CV Maker, profiles & PDF export"),
+            L("Role, company & skill shortcuts")
+        ]
+    }
+
+    private var plans: [Plan] {
+        [
+            Plan(
+                id: "weekly",
+                title: L("Weekly"),
+                subtitle: L("Flexible and commitment-free"),
+                period: L("/ week"),
+                billedPill: "",
+                billedLine: "",
+                crossedPrice: nil,
+                savingsText: nil,
+                features: proCapabilityFeatures + [
+                    L("Perfect for short-term job hunts"),
+                    L("Cancel anytime")
+                ],
+                iconName: "paperplane.fill",
+                iconTint: Theme.iconTint,
+                highlight: false
+            ),
+            Plan(
+                id: "monthly",
+                title: L("Monthly"),
+                subtitle: L("Balanced for regular productivity"),
+                period: L("/ month"),
+                billedPill: "",
+                billedLine: "",
+                crossedPrice: nil,
+                savingsText: nil,
+                features: proCapabilityFeatures + [
+                    L("Best for regular job seekers"),
+                    L("Priority support")
+                ],
+                iconName: "bolt.fill",
+                iconTint: Theme.accent,
+                highlight: true
+            ),
+            Plan(
+                id: "yearly",
+                title: L("Yearly"),
+                subtitle: L("Best value for long-term users"),
+                period: L("/ year"),
+                billedPill: L("3 days free trial"),
+                billedLine: "",
+                crossedPrice: nil,
+                savingsText: nil,
+                features: proCapabilityFeatures + [
+                    L("Lowest effective monthly cost"),
+                    L("Ideal for long-term use")
+                ],
+                iconName: "crown.fill",
+                iconTint: Theme.successText,
+                highlight: false
+            )
+        ]
+    }
 
 
     private let pageGradient = CAGradientLayer()
     private let pageGradient = CAGradientLayer()
     private var premiumTitleLabel: NSTextField?
     private var premiumTitleLabel: NSTextField?
@@ -574,6 +580,9 @@ private final class PremiumPlansViewController: NSViewController {
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     override func viewDidLoad() {
     override func viewDidLoad() {
@@ -585,6 +594,13 @@ private final class PremiumPlansViewController: NSViewController {
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         subscriptionStatusObservation = NotificationCenter.default.addObserver(
         subscriptionStatusObservation = NotificationCenter.default.addObserver(
             forName: .subscriptionStatusDidChange,
             forName: .subscriptionStatusDidChange,
             object: nil,
             object: nil,
@@ -627,13 +643,13 @@ private final class PremiumPlansViewController: NSViewController {
         crownIcon.image = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: nil)
         crownIcon.image = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: nil)
         crownIcon.contentTintColor = NSColor(srgbRed: 254 / 255, green: 214 / 255, blue: 92 / 255, alpha: 1)
         crownIcon.contentTintColor = NSColor(srgbRed: 254 / 255, green: 214 / 255, blue: 92 / 255, alpha: 1)
 
 
-        let title = NSTextField(labelWithString: "Upgrade to Pro")
+        let title = NSTextField(labelWithString: L("Upgrade to Pro"))
         title.font = .systemFont(ofSize: 40, weight: .semibold)
         title.font = .systemFont(ofSize: 40, weight: .semibold)
         title.textColor = Theme.primaryText
         title.textColor = Theme.primaryText
         title.alignment = .center
         title.alignment = .center
         premiumTitleLabel = title
         premiumTitleLabel = title
 
 
-        let subtitle = NSTextField(labelWithString: "Unlock unlimited access to premium tools and boost your productivity.")
+        let subtitle = NSTextField(labelWithString: L("Unlock unlimited access to premium tools and boost your productivity."))
         subtitle.font = .systemFont(ofSize: 14, weight: .regular)
         subtitle.font = .systemFont(ofSize: 14, weight: .regular)
         subtitle.textColor = Theme.secondaryText
         subtitle.textColor = Theme.secondaryText
         subtitle.alignment = .center
         subtitle.alignment = .center
@@ -800,7 +816,7 @@ private final class PremiumPlansViewController: NSViewController {
 
 
         let selectButton = PlanPurchaseHoverButton(
         let selectButton = PlanPurchaseHoverButton(
             planId: plan.id,
             planId: plan.id,
-            title: "Get \(plan.title)",
+            title: String(format: L("Get %@"), plan.title),
             isPrimaryStyle: plan.highlight,
             isPrimaryStyle: plan.highlight,
             target: self,
             target: self,
             action: #selector(didTapSelectPlan)
             action: #selector(didTapSelectPlan)
@@ -830,7 +846,8 @@ private final class PremiumPlansViewController: NSViewController {
                 divider: divider,
                 divider: divider,
                 featureLabels: featureLabels,
                 featureLabels: featureLabels,
                 featureIcons: featureIcons,
                 featureIcons: featureIcons,
-                purchaseButton: selectButton
+                purchaseButton: selectButton,
+                billedPillLabel: plan.billedPill.isEmpty ? nil : topRightTag
             )
             )
         )
         )
 
 
@@ -965,10 +982,10 @@ private final class PremiumPlansViewController: NSViewController {
 
 
     private func makeTrustRow() -> NSView {
     private func makeTrustRow() -> NSView {
         let badges = NSStackView(views: [
         let badges = NSStackView(views: [
-            trustBadge(icon: "shield.fill", title: "Secure Payments", subtitle: "Your payment is 100% secure."),
-            trustBadge(icon: "arrow.counterclockwise", title: "Cancel Anytime", subtitle: "No commitment, cancel anytime."),
-            trustBadge(icon: "headphones", title: "24/7 Support", subtitle: "We're here to help you anytime."),
-            trustBadge(icon: "lock.fill", title: "Privacy First", subtitle: "Your data is safe with us.")
+            trustBadge(icon: "shield.fill", title: L("Secure Payments"), subtitle: L("Your payment is 100% secure.")),
+            trustBadge(icon: "arrow.counterclockwise", title: L("Cancel Anytime"), subtitle: L("No commitment, cancel anytime.")),
+            trustBadge(icon: "headphones", title: L("24/7 Support"), subtitle: L("We're here to help you anytime.")),
+            trustBadge(icon: "lock.fill", title: L("Privacy First"), subtitle: L("Your data is safe with us."))
         ])
         ])
         badges.orientation = .horizontal
         badges.orientation = .horizontal
         badges.alignment = .centerY
         badges.alignment = .centerY
@@ -997,10 +1014,10 @@ private final class PremiumPlansViewController: NSViewController {
         subscriptionPrimaryFooterButton = primary.button
         subscriptionPrimaryFooterButton = primary.button
 
 
         let entries: [(text: String, action: Selector)] = [
         let entries: [(text: String, action: Selector)] = [
-            ("Restore Purchase", #selector(didTapRestorePurchases)),
-            ("Privacy Policy", #selector(didTapFooterPrivacyPolicy)),
-            ("Terms of Use", #selector(didTapFooterTermsOfServices)),
-            ("Support", #selector(didTapFooterSupport))
+            (L("Restore Purchase"), #selector(didTapRestorePurchases)),
+            (L("Privacy Policy"), #selector(didTapFooterPrivacyPolicy)),
+            (L("Terms of Use"), #selector(didTapFooterTermsOfServices)),
+            (L("Support"), #selector(didTapFooterSupport))
         ]
         ]
 
 
         let cells = [primary.container] + entries.enumerated().map { index, entry in
         let cells = [primary.container] + entries.enumerated().map { index, entry in
@@ -1049,8 +1066,8 @@ private final class PremiumPlansViewController: NSViewController {
     }
     }
 
 
     private enum PrimaryFooterSubscriptionTitle {
     private enum PrimaryFooterSubscriptionTitle {
-        static let manage = "Manage Subscription"
-        static let continueFree = "Continue with free plan"
+        static var manage: String { L("Manage Subscription") }
+        static var continueFree: String { L("Continue with free plan") }
     }
     }
 
 
     private func subscriptionPrimaryFooterTitle() -> String {
     private func subscriptionPrimaryFooterTitle() -> String {
@@ -1163,10 +1180,10 @@ private final class PremiumPlansViewController: NSViewController {
     private func periodSuffix(for period: Product.SubscriptionPeriod) -> String {
     private func periodSuffix(for period: Product.SubscriptionPeriod) -> String {
         let value = period.value
         let value = period.value
         switch period.unit {
         switch period.unit {
-        case .day: return value == 1 ? "/ day" : "/ \(value) days"
-        case .week: return value == 1 ? "/ week" : "/ \(value) weeks"
-        case .month: return value == 1 ? "/ month" : "/ \(value) months"
-        case .year: return value == 1 ? "/ year" : "/ \(value) years"
+        case .day: return value == 1 ? L("/ day") : String(format: L("/ %d days"), value)
+        case .week: return value == 1 ? L("/ week") : String(format: L("/ %d weeks"), value)
+        case .month: return value == 1 ? L("/ month") : String(format: L("/ %d months"), value)
+        case .year: return value == 1 ? L("/ year") : String(format: L("/ %d years"), value)
         @unknown default: return ""
         @unknown default: return ""
         }
         }
     }
     }
@@ -1185,10 +1202,10 @@ private final class PremiumPlansViewController: NSViewController {
             guard completed else { return }
             guard completed else { return }
             AppRatingCoordinator.shared.scheduleReviewAfterSubscriptionPurchase()
             AppRatingCoordinator.shared.scheduleReviewAfterSubscriptionPurchase()
             let alert = NSAlert()
             let alert = NSAlert()
-            alert.messageText = "You're subscribed"
-            alert.informativeText = "Thank you — Pro features are now available."
+            alert.messageText = L("You're subscribed")
+            alert.informativeText = L("Thank you — Pro features are now available.")
             alert.alertStyle = .informational
             alert.alertStyle = .informational
-            alert.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = view.window {
             if let window = view.window {
                 alert.beginSheetModal(for: window) { [weak self] _ in
                 alert.beginSheetModal(for: window) { [weak self] _ in
                     self?.dismissPremiumSheetFromParentIfNeeded()
                     self?.dismissPremiumSheetFromParentIfNeeded()
@@ -1212,14 +1229,14 @@ private final class PremiumPlansViewController: NSViewController {
             let active = subscriptionStore.isProActive
             let active = subscriptionStore.isProActive
             let alert = NSAlert()
             let alert = NSAlert()
             if active {
             if active {
-                alert.messageText = "Purchases restored"
-                alert.informativeText = "Your subscription is active."
+                alert.messageText = L("Purchases restored")
+                alert.informativeText = L("Your subscription is active.")
             } else {
             } else {
-                alert.messageText = "No subscription found"
-                alert.informativeText = "There was nothing to restore for this Apple ID."
+                alert.messageText = L("No subscription found")
+                alert.informativeText = L("There was nothing to restore for this Apple ID.")
             }
             }
             alert.alertStyle = .informational
             alert.alertStyle = .informational
-            alert.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = view.window {
             if let window = view.window {
                 alert.beginSheetModal(for: window) { [weak self] _ in
                 alert.beginSheetModal(for: window) { [weak self] _ in
                     if active {
                     if active {
@@ -1241,7 +1258,7 @@ private final class PremiumPlansViewController: NSViewController {
 
 
     private func presentPurchaseError(_ error: Error) {
     private func presentPurchaseError(_ error: Error) {
         let alert = NSAlert()
         let alert = NSAlert()
-        alert.messageText = "Something went wrong"
+        alert.messageText = L("Something went wrong")
         if let localized = error as? LocalizedError {
         if let localized = error as? LocalizedError {
             var parts: [String] = []
             var parts: [String] = []
             if let description = localized.errorDescription {
             if let description = localized.errorDescription {
@@ -1255,7 +1272,7 @@ private final class PremiumPlansViewController: NSViewController {
             alert.informativeText = error.localizedDescription
             alert.informativeText = error.localizedDescription
         }
         }
         alert.alertStyle = .warning
         alert.alertStyle = .warning
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window = view.window {
         if let window = view.window {
             alert.beginSheetModal(for: window)
             alert.beginSheetModal(for: window)
         } else {
         } else {
@@ -1263,6 +1280,59 @@ private final class PremiumPlansViewController: NSViewController {
         }
         }
     }
     }
 
 
+    private func applyLocalizedStrings() {
+        view.window?.title = L("Premium Plans")
+        premiumTitleLabel?.stringValue = L("Upgrade to Pro")
+        premiumSubtitleLabel?.stringValue = L("Unlock unlimited access to premium tools and boost your productivity.")
+
+        for target in pricingCardTargets {
+            guard let plan = plans.first(where: { $0.id == target.planId }) else { continue }
+            target.titleLabel.stringValue = plan.title
+            target.subtitleLabel.stringValue = plan.subtitle
+            if subscriptionStore.product(forPlanKey: plan.id) == nil {
+                target.periodLabel.stringValue = plan.period
+            }
+            target.billedPillLabel?.stringValue = plan.billedPill
+            target.billedPillLabel?.isHidden = plan.billedPill.isEmpty
+            for (index, label) in target.featureLabels.enumerated() where index < plan.features.count {
+                label.stringValue = plan.features[index]
+            }
+            target.purchaseButton.title = String(format: L("Get %@"), plan.title)
+        }
+
+        if let trustBadgesRow {
+            let trustData: [(String, String)] = [
+                (L("Secure Payments"), L("Your payment is 100% secure.")),
+                (L("Cancel Anytime"), L("No commitment, cancel anytime.")),
+                (L("24/7 Support"), L("We're here to help you anytime.")),
+                (L("Privacy First"), L("Your data is safe with us."))
+            ]
+            for (index, subview) in trustBadgesRow.arrangedSubviews.enumerated() {
+                guard let badge = subview as? NSStackView, index < trustData.count else { continue }
+                for case let textStack as NSStackView in badge.arrangedSubviews {
+                    let labels = textStack.arrangedSubviews.compactMap { $0 as? NSTextField }
+                    if labels.count >= 2 {
+                        labels[0].stringValue = trustData[index].0
+                        labels[1].stringValue = trustData[index].1
+                    }
+                }
+            }
+        }
+
+        let footerTitles = [
+            subscriptionPrimaryFooterTitle(),
+            L("Restore Purchase"),
+            L("Privacy Policy"),
+            L("Terms of Use"),
+            L("Support")
+        ]
+        for (index, button) in footerLinkButtons.enumerated() where index < footerTitles.count {
+            button.title = footerTitles[index]
+        }
+        updateSubscriptionPrimaryFooter()
+        applyStorePricing()
+    }
+
     private func dismissPremiumSheetFromParentIfNeeded() {
     private func dismissPremiumSheetFromParentIfNeeded() {
         guard let sheet = view.window, let parent = sheet.sheetParent else { return }
         guard let sheet = view.window, let parent = sheet.sheetParent else { return }
         parent.endSheet(sheet)
         parent.endSheet(sheet)

+ 6 - 6
App for Indeed/Models/DashboardModels.swift

@@ -30,13 +30,13 @@ protocol DashboardDataProviding {
 final class MockDashboardDataProvider: DashboardDataProviding {
 final class MockDashboardDataProvider: DashboardDataProviding {
     func loadDashboardData() -> DashboardData {
     func loadDashboardData() -> DashboardData {
         DashboardData(
         DashboardData(
-            subtitle: "Find your perfect job with the power of AI.",
+            subtitle: L("Find your perfect job with the power of AI."),
             sidebarItems: [
             sidebarItems: [
-                SidebarItem(title: "Home", systemImage: "house.fill", badge: nil),
-                SidebarItem(title: "Saved Jobs", systemImage: "heart", badge: nil),
-                SidebarItem(title: "CV Maker", systemImage: "doc.text", badge: nil),
-                SidebarItem(title: "Profile", systemImage: "person", badge: nil),
-                SidebarItem(title: "Settings", systemImage: "gearshape", badge: nil)
+                SidebarItem(title: L("Home"), systemImage: "house.fill", badge: nil),
+                SidebarItem(title: L("Saved Jobs"), systemImage: "heart", badge: nil),
+                SidebarItem(title: L("CV Maker"), systemImage: "doc.text", badge: nil),
+                SidebarItem(title: L("Profile"), systemImage: "person", badge: nil),
+                SidebarItem(title: L("Settings"), systemImage: "gearshape", badge: nil)
             ],
             ],
             jobListings: [
             jobListings: [
                 JobListing(
                 JobListing(

+ 5 - 5
App for Indeed/Services/AppLaunchCoordinator.swift

@@ -14,19 +14,19 @@ enum AppLaunchCoordinator {
 
 
     /// Subscription refresh and product catalog load before the dashboard appears.
     /// Subscription refresh and product catalog load before the dashboard appears.
     static func performStartup(update: StatusUpdate) async {
     static func performStartup(update: StatusUpdate) async {
-        update("Starting \(AppMarketingLinks.displayName)…", 0.12)
+        update(String(format: L("Starting %@…"), AppMarketingLinks.displayName), 0.12)
         try? await Task.sleep(nanoseconds: 180_000_000)
         try? await Task.sleep(nanoseconds: 180_000_000)
 
 
-        update("Checking your Pro subscription…", 0.38)
+        update(L("Checking your Pro subscription…"), 0.38)
         await SubscriptionStore.shared.refreshEntitlements(deep: true)
         await SubscriptionStore.shared.refreshEntitlements(deep: true)
 
 
-        update("Loading premium plans from the App Store…", 0.62)
+        update(L("Loading premium plans from the App Store…"), 0.62)
         await SubscriptionStore.shared.loadProducts()
         await SubscriptionStore.shared.loadProducts()
 
 
-        update("Preparing your job search workspace…", 0.86)
+        update(L("Preparing your job search workspace…"), 0.86)
         NotificationCenter.default.post(name: .subscriptionStatusDidChange, object: nil)
         NotificationCenter.default.post(name: .subscriptionStatusDidChange, object: nil)
         try? await Task.sleep(nanoseconds: 220_000_000)
         try? await Task.sleep(nanoseconds: 220_000_000)
 
 
-        update("Almost ready…", 1.0)
+        update(L("Almost ready…"), 1.0)
     }
     }
 }
 }

+ 98 - 0
App for Indeed/Services/AppLocalization.swift

@@ -0,0 +1,98 @@
+//
+//  AppLocalization.swift
+//  App for Indeed
+//
+//  English-first localization using Localizable.strings (same pattern as the LinkedIn app).
+//  Add more locales by creating `<locale>.lproj/Localizable.strings` and extending AppLanguage.
+//
+
+import Foundation
+
+enum AppLanguage: String, CaseIterable {
+    case english = "English"
+    case arabic = "Arabic"
+
+    var localeIdentifier: String {
+        switch self {
+        case .english:
+            return "en"
+        case .arabic:
+            return "ar"
+        }
+    }
+
+    static var systemLanguage: AppLanguage {
+        let preferred = Locale.preferredLanguages.first ?? "en"
+        for language in AppLanguage.allCases where preferred.hasPrefix(language.localeIdentifier) {
+            return language
+        }
+        return .english
+    }
+
+    var localizedDisplayName: String {
+        switch self {
+        case .english:
+            return "English"
+        case .arabic:
+            return "العربية"
+        }
+    }
+}
+
+func appLocalized(_ key: String, language: AppLanguage) -> String {
+    guard let path = Bundle.main.path(forResource: language.localeIdentifier, ofType: "lproj"),
+          let bundle = Bundle(path: path) else {
+        return key
+    }
+    return bundle.localizedString(forKey: key, value: key, table: nil)
+}
+
+func currentAppLanguage() -> AppLanguage {
+    let code = UserDefaults.standard.string(forKey: "com.appforindeed.preferredLanguage") ?? "en"
+    return AppLanguage.allCases.first(where: { $0.localeIdentifier == code }) ?? .english
+}
+
+/// Resolves copy for the user’s currently selected language.
+func L(_ key: String) -> String {
+    appLocalized(key, language: currentAppLanguage())
+}
+
+/// Localized CV template title; `name` is always the English localization key.
+func localizedTemplateName(_ nameKey: String) -> String {
+    L(nameKey)
+}
+
+@MainActor
+final class AppLanguageManager {
+    static let shared = AppLanguageManager()
+
+    static let didChangeNotification = Notification.Name("AppLanguageManager.didChange")
+
+    private enum UserDefaultsKey {
+        static let preferredLanguage = "com.appforindeed.preferredLanguage"
+    }
+
+    var current: AppLanguage {
+        currentAppLanguage()
+    }
+
+    func applyStoredPreferenceOnLaunch() {
+        if UserDefaults.standard.string(forKey: UserDefaultsKey.preferredLanguage) == nil {
+            setLanguage(AppLanguage.systemLanguage, notify: false)
+        }
+    }
+
+    func setLanguage(_ language: AppLanguage, notify: Bool = true) {
+        let code = language.localeIdentifier
+        UserDefaults.standard.set(code, forKey: UserDefaultsKey.preferredLanguage)
+        UserDefaults.standard.set([code], forKey: "AppleLanguages")
+        if notify {
+            NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
+        }
+    }
+
+    func setLanguage(code: String, notify: Bool = true) {
+        guard let language = AppLanguage.allCases.first(where: { $0.localeIdentifier == code }) else { return }
+        setLanguage(language, notify: notify)
+    }
+}

+ 3 - 3
App for Indeed/Services/UserFacingErrorMessage.swift

@@ -11,13 +11,13 @@ enum UserFacingErrorMessage {
                  .cannotFindHost,
                  .cannotFindHost,
                  .cannotConnectToHost,
                  .cannotConnectToHost,
                  .dnsLookupFailed:
                  .dnsLookupFailed:
-                return "We couldn't reach the server. Check your internet connection and try again."
+                return L("We couldn't reach the server. Check your internet connection and try again.")
             case .cancelled:
             case .cancelled:
-                return "The search was cancelled. Try again when you're ready."
+                return L("The search was cancelled. Try again when you're ready.")
             default:
             default:
                 break
                 break
             }
             }
         }
         }
-        return "Something went wrong while searching. Please try again in a moment."
+        return L("Something went wrong while searching. Please try again in a moment.")
     }
     }
 }
 }

+ 1 - 1
App for Indeed/Subscription/SubscriptionStore.swift

@@ -151,7 +151,7 @@ enum SubscriptionStoreError: LocalizedError {
     var errorDescription: String? {
     var errorDescription: String? {
         switch self {
         switch self {
         case .productUnavailable:
         case .productUnavailable:
-            return "That subscription isn’t available from the App Store right now."
+            return L("That subscription isn’t available from the App Store right now.")
         }
         }
     }
     }
 
 

+ 32 - 10
App for Indeed/Views/CVFilledPreviewPageView.swift

@@ -106,9 +106,9 @@ final class CVFilledPreviewPageView: NSView {
 
 
     var onDismiss: (() -> Void)?
     var onDismiss: (() -> Void)?
 
 
-    private let backButton = NSButton(title: "← Profiles", target: nil, action: nil)
-    private let titleLabel = NSTextField(labelWithString: "CV preview")
-    private let exportButton = CVPreviewPrimaryCTAButton(title: "Export PDF…", target: nil, action: nil)
+    private let backButton = NSButton(title: L("← Profiles"), target: nil, action: nil)
+    private let titleLabel = NSTextField(labelWithString: L("CV preview"))
+    private let exportButton = CVPreviewPrimaryCTAButton(title: L("Export PDF…"), target: nil, action: nil)
     private let scrollView = NSScrollView()
     private let scrollView = NSScrollView()
     private let documentView = CVPreviewFlippedDocumentView()
     private let documentView = CVPreviewFlippedDocumentView()
     private let contentStack = NSStackView()
     private let contentStack = NSStackView()
@@ -119,6 +119,7 @@ final class CVFilledPreviewPageView: NSView {
 
 
     private let subtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let subtitleLabel = NSTextField(wrappingLabelWithString: "")
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
 
     override init(frame frameRect: NSRect) {
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
         super.init(frame: frameRect)
@@ -138,7 +139,7 @@ final class CVFilledPreviewPageView: NSView {
         exportButton.target = self
         exportButton.target = self
         exportButton.action = #selector(didTapExportPDF)
         exportButton.action = #selector(didTapExportPDF)
 
 
-        subtitleLabel.stringValue = "Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules)."
+        subtitleLabel.stringValue = L("Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules).")
         subtitleLabel.font = .systemFont(ofSize: 12, weight: .regular)
         subtitleLabel.font = .systemFont(ofSize: 12, weight: .regular)
         subtitleLabel.maximumNumberOfLines = 0
         subtitleLabel.maximumNumberOfLines = 0
 
 
@@ -200,13 +201,24 @@ final class CVFilledPreviewPageView: NSView {
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
     }
 
 
     deinit {
     deinit {
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     @available(*, unavailable)
     @available(*, unavailable)
@@ -227,6 +239,16 @@ final class CVFilledPreviewPageView: NSView {
         exportButton.applyCurrentAppearance()
         exportButton.applyCurrentAppearance()
     }
     }
 
 
+    func applyLocalizedStrings() {
+        backButton.title = L("← Profiles")
+        titleLabel.stringValue = L("CV preview")
+        exportButton.title = L("Export PDF…")
+        subtitleLabel.stringValue = L("Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules).")
+        if let profile = lastProfile, let template = lastTemplate {
+            configure(profile: profile, template: template)
+        }
+    }
+
     func configure(profile: SavedProfile, template: CVTemplate) {
     func configure(profile: SavedProfile, template: CVTemplate) {
         lastProfile = profile
         lastProfile = profile
         lastTemplate = template
         lastTemplate = template
@@ -237,8 +259,8 @@ final class CVFilledPreviewPageView: NSView {
         let doc = CVProfileDocumentView(profile: profile, template: template)
         let doc = CVProfileDocumentView(profile: profile, template: template)
         profileDocumentView = doc
         profileDocumentView = doc
         contentStack.addArrangedSubview(doc)
         contentStack.addArrangedSubview(doc)
-        let profileTitle = profile.profileDisplayName.isEmpty ? "Untitled profile" : profile.profileDisplayName
-        titleLabel.stringValue = "\(template.name) · \(profileTitle)"
+        let profileTitle = profile.profileDisplayName.isEmpty ? L("Untitled profile") : profile.profileDisplayName
+        titleLabel.stringValue = "\(template.localizedName) · \(profileTitle)"
     }
     }
 
 
     @objc private func didTapBack() {
     @objc private func didTapBack() {
@@ -263,14 +285,14 @@ final class CVFilledPreviewPageView: NSView {
         // text. Rasterising what is actually drawn on screen preserves the full layout.
         // text. Rasterising what is actually drawn on screen preserves the full layout.
         let data = doc.pdfDataMatchingScreenAppearance() ?? doc.dataWithPDF(inside: bounds)
         let data = doc.pdfDataMatchingScreenAppearance() ?? doc.dataWithPDF(inside: bounds)
         guard !data.isEmpty else {
         guard !data.isEmpty else {
-            presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
+            presentExportError(L("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again."))
             return
             return
         }
         }
 
 
         let panel = NSSavePanel()
         let panel = NSSavePanel()
         panel.canCreateDirectories = true
         panel.canCreateDirectories = true
         panel.allowedContentTypes = [.pdf]
         panel.allowedContentTypes = [.pdf]
-        let base = lastTemplate?.name ?? "CV"
+        let base = lastTemplate?.name ?? L("CV")
         let safe = base.replacingOccurrences(of: "/", with: "-")
         let safe = base.replacingOccurrences(of: "/", with: "-")
         panel.nameFieldStringValue = "\(safe).pdf"
         panel.nameFieldStringValue = "\(safe).pdf"
 
 
@@ -300,10 +322,10 @@ final class CVFilledPreviewPageView: NSView {
 
 
     private func presentExportError(_ message: String) {
     private func presentExportError(_ message: String) {
         let alert = NSAlert()
         let alert = NSAlert()
-        alert.messageText = "Couldn’t save PDF"
+        alert.messageText = L("Couldn't save PDF")
         alert.informativeText = message
         alert.informativeText = message
         alert.alertStyle = .warning
         alert.alertStyle = .warning
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
         if let window {
             alert.beginSheetModal(for: window, completionHandler: nil)
             alert.beginSheetModal(for: window, completionHandler: nil)
         } else {
         } else {

+ 47 - 22
App for Indeed/Views/CVMakerPageView.swift

@@ -18,8 +18,8 @@ enum CVCategoryGroup: Hashable {
 
 
     var title: String {
     var title: String {
         switch self {
         switch self {
-        case .designBased: return "Design-Based"
-        case .professionBased: return "Profession-Based"
+        case .designBased: return L("Design-Based")
+        case .professionBased: return L("Profession-Based")
         }
         }
     }
     }
 }
 }
@@ -29,11 +29,11 @@ enum CVDesignFamily: String, CaseIterable, Hashable {
 
 
     var title: String {
     var title: String {
         switch self {
         switch self {
-        case .professional: return "Professional"
-        case .modern: return "Modern"
-        case .creative: return "Creative"
-        case .minimal: return "Minimal"
-        case .executive: return "Executive"
+        case .professional: return L("Professional")
+        case .modern: return L("Modern")
+        case .creative: return L("Creative")
+        case .minimal: return L("Minimal")
+        case .executive: return L("Executive")
         }
         }
     }
     }
 }
 }
@@ -46,9 +46,9 @@ enum CVTemplateLayoutType: String, Hashable {
 
 
     var gallerySubtitle: String {
     var gallerySubtitle: String {
         switch self {
         switch self {
-        case .atsSingleColumn: return "ATS layout"
-        case .twoColumnSidebarLeading: return "Sidebar left"
-        case .twoColumnSidebarTrailing: return "Sidebar right"
+        case .atsSingleColumn: return L("ATS layout")
+        case .twoColumnSidebarLeading: return L("Sidebar left")
+        case .twoColumnSidebarTrailing: return L("Sidebar right")
         }
         }
     }
     }
 }
 }
@@ -121,6 +121,9 @@ struct CVTemplate: Hashable {
         NSColor(srgbRed: themeRed, green: themeGreen, blue: themeBlue, alpha: 1)
         NSColor(srgbRed: themeRed, green: themeGreen, blue: themeBlue, alpha: 1)
     }
     }
 
 
+    /// User-facing template title for the active language (`name` is the English localization key).
+    var localizedName: String { localizedTemplateName(name) }
+
     /// Optional bundle image name; `nil` means render a live vector/text preview.
     /// Optional bundle image name; `nil` means render a live vector/text preview.
     var previewImageAssetName: String? { nil }
     var previewImageAssetName: String? { nil }
 
 
@@ -579,19 +582,20 @@ final class CVMakerPageView: NSView {
     }
     }
 
 
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
 
     private let pageGradientLayer = CAGradientLayer()
     private let pageGradientLayer = CAGradientLayer()
     private let filterChrome = NSVisualEffectView()
     private let filterChrome = NSVisualEffectView()
     private let filterStack = NSStackView()
     private let filterStack = NSStackView()
 
 
-    private let titleLabel = NSTextField(labelWithString: "Templates")
-    private let subtitleLabel = NSTextField(labelWithString: "Polished layouts with live previews — pick a style that fits your story.")
+    private let titleLabel = NSTextField(labelWithString: L("Templates"))
+    private let subtitleLabel = NSTextField(labelWithString: L("Polished layouts with live previews — pick a style that fits your story."))
     private let groupTabsRow = NSStackView()
     private let groupTabsRow = NSStackView()
     private let familyChipsRow = NSStackView()
     private let familyChipsRow = NSStackView()
     private let scrollView = NSScrollView()
     private let scrollView = NSScrollView()
     private let gridDocument = TopFlippedView()
     private let gridDocument = TopFlippedView()
     private let gridStack = NSStackView()
     private let gridStack = NSStackView()
-    private let ctaButton = CVHoverableButton(title: "Use Template & Select Profile  →", target: nil, action: nil)
+    private let ctaButton = CVHoverableButton(title: L("Use Template & Select Profile  →"), target: nil, action: nil)
 
 
     private var selectedGroup: CVCategoryGroup = .professionBased
     private var selectedGroup: CVCategoryGroup = .professionBased
     private var selectedFamily: CVDesignFamily? = nil // nil == "All"
     private var selectedFamily: CVDesignFamily? = nil // nil == "All"
@@ -648,13 +652,24 @@ final class CVMakerPageView: NSView {
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
     }
 
 
     deinit {
     deinit {
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     @available(*, unavailable)
     @available(*, unavailable)
@@ -685,6 +700,16 @@ final class CVMakerPageView: NSView {
         updateSelectedChipStates()
         updateSelectedChipStates()
     }
     }
 
 
+    func applyLocalizedStrings() {
+        titleLabel.stringValue = L("Templates")
+        subtitleLabel.stringValue = L("Polished layouts with live previews — pick a style that fits your story.")
+        styleCTAButton(ctaButton)
+        configureGroupTabs()
+        reloadFamilyChips()
+        reloadTemplateGrid()
+        updateSelectedChipStates()
+    }
+
     // MARK: Setup
     // MARK: Setup
 
 
     private func configureLayout() {
     private func configureLayout() {
@@ -850,7 +875,7 @@ final class CVMakerPageView: NSView {
         familyChipButtons.removeAll()
         familyChipButtons.removeAll()
 
 
         let allCount = templates(forGroup: selectedGroup, family: nil).count
         let allCount = templates(forGroup: selectedGroup, family: nil).count
-        let allChip = CVChipButton(title: "All", badgeText: "\(allCount)", leadingSymbol: nil, style: .pillSmall)
+        let allChip = CVChipButton(title: L("All"), badgeText: "\(allCount)", leadingSymbol: nil, style: .pillSmall)
         allChip.translatesAutoresizingMaskIntoConstraints = false
         allChip.translatesAutoresizingMaskIntoConstraints = false
         allChip.onSelect = { [weak self] in self?.didSelectFamily(nil) }
         allChip.onSelect = { [weak self] in self?.didSelectFamily(nil) }
         familyChipsRow.addArrangedSubview(allChip)
         familyChipsRow.addArrangedSubview(allChip)
@@ -904,7 +929,7 @@ final class CVMakerPageView: NSView {
 
 
         let templates = visibleTemplates
         let templates = visibleTemplates
         if templates.isEmpty {
         if templates.isEmpty {
-            let empty = NSTextField(labelWithString: "No templates yet for this category.")
+            let empty = NSTextField(labelWithString: L("No templates yet for this category."))
             empty.font = .systemFont(ofSize: 13)
             empty.font = .systemFont(ofSize: 13)
             empty.textColor = Palette.secondaryText
             empty.textColor = Palette.secondaryText
             gridStack.addArrangedSubview(empty)
             gridStack.addArrangedSubview(empty)
@@ -1044,7 +1069,7 @@ final class CVMakerPageView: NSView {
             chosen = nil
             chosen = nil
         }
         }
         guard let template = chosen else {
         guard let template = chosen else {
-            presentPlaceholderAlert(title: "Pick a template", message: "Select a template first, then choose a profile to continue.")
+            presentPlaceholderAlert(title: L("Pick a template"), message: L("Select a template first, then choose a profile to continue."))
             return
             return
         }
         }
         onContinueToProfileSelection?(template)
         onContinueToProfileSelection?(template)
@@ -1060,7 +1085,7 @@ final class CVMakerPageView: NSView {
     }
     }
 
 
     private func styleCTAButton(_ button: CVHoverableButton) {
     private func styleCTAButton(_ button: CVHoverableButton) {
-        button.title = "Use Template & Select Profile  →"
+        button.title = L("Use Template & Select Profile  →")
         button.font = .systemFont(ofSize: 14, weight: .semibold)
         button.font = .systemFont(ofSize: 14, weight: .semibold)
         button.isBordered = false
         button.isBordered = false
         button.bezelStyle = .rounded
         button.bezelStyle = .rounded
@@ -1082,8 +1107,8 @@ final class CVMakerPageView: NSView {
 
 
     private func beginLoadingAICatalogIfPossible() {
     private func beginLoadingAICatalogIfPossible() {
         guard OpenAIConfiguration.hasAPIKey else { return }
         guard OpenAIConfiguration.hasAPIKey else { return }
-        let defaultSubtitle = subtitleLabel.stringValue
-        subtitleLabel.stringValue = "Fetching AI-curated templates…"
+        let defaultSubtitle = L("Polished layouts with live previews — pick a style that fits your story.")
+        subtitleLabel.stringValue = L("Fetching AI-curated templates…")
         CVTemplateFetchService.shared.fetchTemplates { [weak self] result in
         CVTemplateFetchService.shared.fetchTemplates { [weak self] result in
             DispatchQueue.main.async {
             DispatchQueue.main.async {
                 guard let self else { return }
                 guard let self else { return }
@@ -1098,7 +1123,7 @@ final class CVMakerPageView: NSView {
                         self.updateSelectedChipStates()
                         self.updateSelectedChipStates()
                     }
                     }
                 case .failure:
                 case .failure:
-                    self.subtitleLabel.stringValue = "Couldn’t load AI templates — showing the built-in gallery."
+                    self.subtitleLabel.stringValue = L("Couldn’t load AI templates — showing the built-in gallery.")
                     DispatchQueue.main.asyncAfter(deadline: .now() + 5.5) { [weak self] in
                     DispatchQueue.main.asyncAfter(deadline: .now() + 5.5) { [weak self] in
                         self?.subtitleLabel.stringValue = defaultSubtitle
                         self?.subtitleLabel.stringValue = defaultSubtitle
                     }
                     }
@@ -1112,7 +1137,7 @@ final class CVMakerPageView: NSView {
         alert.messageText = title
         alert.messageText = title
         alert.informativeText = message
         alert.informativeText = message
         alert.alertStyle = .informational
         alert.alertStyle = .informational
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
         if let window {
             alert.beginSheetModal(for: window)
             alert.beginSheetModal(for: window)
         } else {
         } else {
@@ -1383,7 +1408,7 @@ private final class CVTemplateCard: NSView {
         preview.translatesAutoresizingMaskIntoConstraints = false
         preview.translatesAutoresizingMaskIntoConstraints = false
         previewSurface.addSubview(preview)
         previewSurface.addSubview(preview)
 
 
-        nameLabel.stringValue = template.name
+        nameLabel.stringValue = template.localizedName
         nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
         nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
         nameLabel.textColor = palette.primaryText
         nameLabel.textColor = palette.primaryText
         nameLabel.isBordered = false
         nameLabel.isBordered = false

+ 55 - 55
App for Indeed/Views/CVProfileDocumentView.swift

@@ -313,8 +313,8 @@ final class CVProfileDocumentView: NSView {
     private func modernClassicBandDocument() -> NSView {
     private func modernClassicBandDocument() -> NSView {
         let theme = template.themeColor
         let theme = template.themeColor
         let white = NSColor.white
         let white = NSColor.white
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
 
 
         let header = NSView()
         let header = NSView()
         header.translatesAutoresizingMaskIntoConstraints = false
         header.translatesAutoresizingMaskIntoConstraints = false
@@ -405,10 +405,10 @@ final class CVProfileDocumentView: NSView {
         inner.spacing = 10
         inner.spacing = 10
         inner.alignment = .leading
         inner.alignment = .leading
         inner.translatesAutoresizingMaskIntoConstraints = false
         inner.translatesAutoresizingMaskIntoConstraints = false
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         let contactParts = [profile.personal.email, profile.personal.phone].filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
         let contactParts = [profile.personal.email, profile.personal.phone].filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
-        let contactLine = contactParts.isEmpty ? "Add contact in your profile" : contactParts.joined(separator: " · ")
+        let contactLine = contactParts.isEmpty ? L("Add contact in your profile") : contactParts.joined(separator: " · ")
 
 
         inner.addArrangedSubview(label(nameText, font: .systemFont(ofSize: 21, weight: .bold), color: style.ink, maxLines: 2))
         inner.addArrangedSubview(label(nameText, font: .systemFont(ofSize: 21, weight: .bold), color: style.ink, maxLines: 2))
         inner.addArrangedSubview(label(roleText, font: .systemFont(ofSize: 14, weight: .semibold), color: theme, maxLines: 2))
         inner.addArrangedSubview(label(roleText, font: .systemFont(ofSize: 14, weight: .semibold), color: theme, maxLines: 2))
@@ -426,8 +426,8 @@ final class CVProfileDocumentView: NSView {
 
 
     private func modernSplitHeaderDocument() -> NSView {
     private func modernSplitHeaderDocument() -> NSView {
         let theme = template.themeColor
         let theme = template.themeColor
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         let loc = profile.personal.address.trimmingCharacters(in: .whitespacesAndNewlines)
         let loc = profile.personal.address.trimmingCharacters(in: .whitespacesAndNewlines)
 
 
         let left = NSStackView()
         let left = NSStackView()
@@ -505,13 +505,13 @@ final class CVProfileDocumentView: NSView {
         v.alignment = .leading
         v.alignment = .leading
 
 
         if includeSummaryInMain, let summary = nonEmpty(profile.careerSummary) {
         if includeSummaryInMain, let summary = nonEmpty(profile.careerSummary) {
-            v.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: "About", theme: theme))
+            v.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: L("About"), theme: theme))
             v.addArrangedSubview(paragraph(summary, compact: compact))
             v.addArrangedSubview(paragraph(summary, compact: compact))
         }
         }
 
 
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         if !jobs.isEmpty {
         if !jobs.isEmpty {
-            v.addArrangedSubview(modernSectionRow(symbol: "briefcase.fill", title: "Experience", theme: theme))
+            v.addArrangedSubview(modernSectionRow(symbol: "briefcase.fill", title: L("Experience"), theme: theme))
             for (index, job) in jobs.enumerated() {
             for (index, job) in jobs.enumerated() {
                 v.addArrangedSubview(experienceBlock(job: job, compact: compact))
                 v.addArrangedSubview(experienceBlock(job: job, compact: compact))
                 if index == 0 {
                 if index == 0 {
@@ -522,7 +522,7 @@ final class CVProfileDocumentView: NSView {
 
 
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         if !schools.isEmpty {
         if !schools.isEmpty {
-            v.addArrangedSubview(modernSectionRow(symbol: "graduationcap.fill", title: "Education", theme: theme))
+            v.addArrangedSubview(modernSectionRow(symbol: "graduationcap.fill", title: L("Education"), theme: theme))
             for edu in schools {
             for edu in schools {
                 v.addArrangedSubview(educationBlock(edu: edu, compact: compact))
                 v.addArrangedSubview(educationBlock(edu: edu, compact: compact))
             }
             }
@@ -546,16 +546,16 @@ final class CVProfileDocumentView: NSView {
         }
         }
 
 
         if let summary = nonEmpty(profile.careerSummary) {
         if let summary = nonEmpty(profile.careerSummary) {
-            box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: "About", theme: theme))
+            box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: L("About"), theme: theme))
             box.addArrangedSubview(paragraph(summary, compact: true))
             box.addArrangedSubview(paragraph(summary, compact: true))
         }
         }
         if let hi = highlightsBodyText() {
         if let hi = highlightsBodyText() {
-            box.addArrangedSubview(modernSectionRow(symbol: "star.fill", title: "Highlights", theme: theme))
+            box.addArrangedSubview(modernSectionRow(symbol: "star.fill", title: L("Highlights"), theme: theme))
             box.addArrangedSubview(paragraph(hi, compact: true))
             box.addArrangedSubview(paragraph(hi, compact: true))
         }
         }
         if box.arrangedSubviews.isEmpty {
         if box.arrangedSubviews.isEmpty {
-            box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: "About", theme: theme))
-            box.addArrangedSubview(paragraph("Add a career summary or interests in your profile to populate this column.", compact: true))
+            box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: L("About"), theme: theme))
+            box.addArrangedSubview(paragraph(L("Add a career summary or interests in your profile to populate this column."), compact: true))
         }
         }
         return box
         return box
     }
     }
@@ -628,8 +628,8 @@ final class CVProfileDocumentView: NSView {
 
 
     private func creativeSingleColumnDocument() -> NSView {
     private func creativeSingleColumnDocument() -> NSView {
         let theme = template.themeColor
         let theme = template.themeColor
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
 
 
         let banner = NSView()
         let banner = NSView()
         banner.translatesAutoresizingMaskIntoConstraints = false
         banner.translatesAutoresizingMaskIntoConstraints = false
@@ -669,8 +669,8 @@ final class CVProfileDocumentView: NSView {
         sidebarStack.layer?.cornerRadius = variant % 2 == 0 ? 10 : 8
         sidebarStack.layer?.cornerRadius = variant % 2 == 0 ? 10 : 8
         sidebarStack.edgeInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
         sidebarStack.edgeInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
 
 
-        let nm = displayable(profile.personal.fullName, placeholder: "Your name")
-        let role = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nm = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let role = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         sidebarStack.addArrangedSubview(label(nm, font: .systemFont(ofSize: 18, weight: .bold), color: onSidebar, maxLines: 2))
         sidebarStack.addArrangedSubview(label(nm, font: .systemFont(ofSize: 18, weight: .bold), color: onSidebar, maxLines: 2))
         sidebarStack.addArrangedSubview(label(role, font: .systemFont(ofSize: 13, weight: .medium), color: onSidebar.withAlphaComponent(0.85), maxLines: 2))
         sidebarStack.addArrangedSubview(label(role, font: .systemFont(ofSize: 13, weight: .medium), color: onSidebar.withAlphaComponent(0.85), maxLines: 2))
         if !profile.personal.email.isEmpty {
         if !profile.personal.email.isEmpty {
@@ -679,7 +679,7 @@ final class CVProfileDocumentView: NSView {
         if !profile.personal.phone.isEmpty {
         if !profile.personal.phone.isEmpty {
             sidebarStack.addArrangedSubview(label(profile.personal.phone, font: .systemFont(ofSize: 11.5), color: onSidebar.withAlphaComponent(0.82), maxLines: 1))
             sidebarStack.addArrangedSubview(label(profile.personal.phone, font: .systemFont(ofSize: 11.5), color: onSidebar.withAlphaComponent(0.82), maxLines: 1))
         }
         }
-        sidebarStack.addArrangedSubview(creativeSidebarHeading("STRENGTHS", onSidebar: onSidebar, accent: theme))
+        sidebarStack.addArrangedSubview(creativeSidebarHeading(L("STRENGTHS"), onSidebar: onSidebar, accent: theme))
         for token in skillTokensFromProfile(max: 8) {
         for token in skillTokensFromProfile(max: 8) {
             sidebarStack.addArrangedSubview(label("\(skillPrefix)\(token)", font: .systemFont(ofSize: 12, weight: .semibold), color: onSidebar.withAlphaComponent(0.92), maxLines: 2))
             sidebarStack.addArrangedSubview(label("\(skillPrefix)\(token)", font: .systemFont(ofSize: 12, weight: .semibold), color: onSidebar.withAlphaComponent(0.92), maxLines: 2))
         }
         }
@@ -730,7 +730,7 @@ final class CVProfileDocumentView: NSView {
         row.orientation = .horizontal
         row.orientation = .horizontal
         row.spacing = 8
         row.spacing = 8
         row.translatesAutoresizingMaskIntoConstraints = false
         row.translatesAutoresizingMaskIntoConstraints = false
-        let lab = label("PORTFOLIO SNAPSHOT", font: .systemFont(ofSize: 12, weight: .heavy), color: style.ink, maxLines: 1)
+        let lab = label(L("PORTFOLIO SNAPSHOT"), font: .systemFont(ofSize: 12, weight: .heavy), color: style.ink, maxLines: 1)
         row.addArrangedSubview(stripe)
         row.addArrangedSubview(stripe)
         row.addArrangedSubview(lab)
         row.addArrangedSubview(lab)
         v.addSubview(row)
         v.addSubview(row)
@@ -751,12 +751,12 @@ final class CVProfileDocumentView: NSView {
         stack.alignment = .leading
         stack.alignment = .leading
         stack.addArrangedSubview(creativeMainHeader(theme: theme))
         stack.addArrangedSubview(creativeMainHeader(theme: theme))
         if let summary = nonEmpty(profile.careerSummary) {
         if let summary = nonEmpty(profile.careerSummary) {
-            stack.addArrangedSubview(sectionHeading("Profile"))
+            stack.addArrangedSubview(sectionHeading(L("Profile")))
             stack.addArrangedSubview(paragraph(summary, compact: false))
             stack.addArrangedSubview(paragraph(summary, compact: false))
         }
         }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         if !jobs.isEmpty {
         if !jobs.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Impact"))
+            stack.addArrangedSubview(sectionHeading(L("Impact")))
             for job in jobs {
             for job in jobs {
                 let titleLine = [job.jobTitle, job.company].filter { !$0.isEmpty }.joined(separator: " — ")
                 let titleLine = [job.jobTitle, job.company].filter { !$0.isEmpty }.joined(separator: " — ")
                 if !titleLine.isEmpty {
                 if !titleLine.isEmpty {
@@ -770,7 +770,7 @@ final class CVProfileDocumentView: NSView {
         }
         }
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         if !schools.isEmpty {
         if !schools.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Education"))
+            stack.addArrangedSubview(sectionHeading(L("Education")))
             for edu in schools {
             for edu in schools {
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
             }
             }
@@ -783,12 +783,12 @@ final class CVProfileDocumentView: NSView {
 
 
     private func buildExecutiveDocument() -> NSView {
     private func buildExecutiveDocument() -> NSView {
         let centeredHead = (variant % 2) == 0
         let centeredHead = (variant % 2) == 0
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         let contactParts = [profile.personal.email, profile.personal.phone, profile.personal.address].filter {
         let contactParts = [profile.personal.email, profile.personal.phone, profile.personal.address].filter {
             !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
             !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
         }
         }
-        let contactText = contactParts.isEmpty ? "Add contact details in your profile" : contactParts.joined(separator: " · ")
+        let contactText = contactParts.isEmpty ? L("Add contact details in your profile") : contactParts.joined(separator: " · ")
 
 
         let name = label(nameText, font: style.nameFont, color: style.ink, maxLines: 2)
         let name = label(nameText, font: style.nameFont, color: style.ink, maxLines: 2)
         let role = label(roleText, font: style.roleFont, color: style.muted, maxLines: 2)
         let role = label(roleText, font: style.roleFont, color: style.muted, maxLines: 2)
@@ -852,7 +852,7 @@ final class CVProfileDocumentView: NSView {
         stack.spacing = (compact ? style.bodyBlockSpacing : style.bodyBlockSpacing + 2) - (tightLeading ? 2 : 0)
         stack.spacing = (compact ? style.bodyBlockSpacing : style.bodyBlockSpacing + 2) - (tightLeading ? 2 : 0)
         stack.alignment = .leading
         stack.alignment = .leading
 
 
-        let summaryTitle = (variant % 6 == 3) ? "Summary" : "Professional Summary"
+        let summaryTitle = (variant % 6 == 3) ? L("Summary") : L("Professional Summary")
         if let summary = nonEmpty(profile.careerSummary) {
         if let summary = nonEmpty(profile.careerSummary) {
             stack.addArrangedSubview(sectionHeading(summaryTitle))
             stack.addArrangedSubview(sectionHeading(summaryTitle))
             stack.addArrangedSubview(paragraph(summary, compact: compact))
             stack.addArrangedSubview(paragraph(summary, compact: compact))
@@ -860,7 +860,7 @@ final class CVProfileDocumentView: NSView {
 
 
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         if !jobs.isEmpty {
         if !jobs.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Selected Experience"))
+            stack.addArrangedSubview(sectionHeading(L("Selected Experience")))
             for job in jobs {
             for job in jobs {
                 stack.addArrangedSubview(executiveExperienceBlock(job: job, compact: compact))
                 stack.addArrangedSubview(executiveExperienceBlock(job: job, compact: compact))
             }
             }
@@ -868,7 +868,7 @@ final class CVProfileDocumentView: NSView {
 
 
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         if !schools.isEmpty {
         if !schools.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Education"))
+            stack.addArrangedSubview(sectionHeading(L("Education")))
             for edu in schools {
             for edu in schools {
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: compact))
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: compact))
             }
             }
@@ -913,24 +913,24 @@ final class CVProfileDocumentView: NSView {
 
 
         let skills = skillTokensFromProfile(max: 12)
         let skills = skillTokensFromProfile(max: 12)
         if !skills.isEmpty {
         if !skills.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Core Competencies"))
+            stack.addArrangedSubview(sectionHeading(L("Core Competencies")))
             for s in skills {
             for s in skills {
                 stack.addArrangedSubview(label("· \(s)", font: style.bodyFont, color: style.ink, maxLines: 0))
                 stack.addArrangedSubview(label("· \(s)", font: style.bodyFont, color: style.ink, maxLines: 0))
             }
             }
         }
         }
 
 
         if let cert = nonEmpty(profile.certificates) {
         if let cert = nonEmpty(profile.certificates) {
-            stack.addArrangedSubview(sectionHeading("Tools"))
+            stack.addArrangedSubview(sectionHeading(L("Tools")))
             stack.addArrangedSubview(paragraph(cert, compact: true))
             stack.addArrangedSubview(paragraph(cert, compact: true))
         }
         }
 
 
         if showMetrics, let hi = highlightsBodyText() {
         if showMetrics, let hi = highlightsBodyText() {
-            stack.addArrangedSubview(sectionHeading("Impact"))
+            stack.addArrangedSubview(sectionHeading(L("Impact")))
             stack.addArrangedSubview(paragraph(hi, compact: true))
             stack.addArrangedSubview(paragraph(hi, compact: true))
         }
         }
 
 
         if stack.arrangedSubviews.isEmpty {
         if stack.arrangedSubviews.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Contact"))
+            stack.addArrangedSubview(sectionHeading(L("Contact")))
             for line in contactLines() {
             for line in contactLines() {
                 stack.addArrangedSubview(label(line, font: style.contactFont, color: style.ink, maxLines: 0))
                 stack.addArrangedSubview(label(line, font: style.contactFont, color: style.ink, maxLines: 0))
             }
             }
@@ -941,12 +941,12 @@ final class CVProfileDocumentView: NSView {
     // MARK: - Minimal (matches gallery light typography)
     // MARK: - Minimal (matches gallery light typography)
 
 
     private func buildMinimalDocument() -> NSView {
     private func buildMinimalDocument() -> NSView {
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         let contactParts = [profile.personal.email, profile.personal.phone].filter {
         let contactParts = [profile.personal.email, profile.personal.phone].filter {
             !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
             !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
         }
         }
-        let contactText = contactParts.isEmpty ? "Add contact in your profile" : contactParts.joined(separator: "   ")
+        let contactText = contactParts.isEmpty ? L("Add contact in your profile") : contactParts.joined(separator: "   ")
 
 
         let nameWeight: NSFont.Weight = (variant % 3 == 0) ? .ultraLight : .light
         let nameWeight: NSFont.Weight = (variant % 3 == 0) ? .ultraLight : .light
         let nameSize: CGFloat = template.headline == .centered ? 22 + CGFloat(variant % 2) : 20 + CGFloat(variant % 3)
         let nameSize: CGFloat = template.headline == .centered ? 22 + CGFloat(variant % 2) : 20 + CGFloat(variant % 3)
@@ -1013,14 +1013,14 @@ final class CVProfileDocumentView: NSView {
 
 
         let appendSummary: () -> Void = { [self] in
         let appendSummary: () -> Void = { [self] in
             if let summary = nonEmpty(profile.careerSummary) {
             if let summary = nonEmpty(profile.careerSummary) {
-                stack.addArrangedSubview(sectionHeading("Profile"))
+                stack.addArrangedSubview(sectionHeading(L("Profile")))
                 stack.addArrangedSubview(paragraph(summary, compact: false))
                 stack.addArrangedSubview(paragraph(summary, compact: false))
             }
             }
         }
         }
         let appendExperience: () -> Void = { [self] in
         let appendExperience: () -> Void = { [self] in
             let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
             let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
             if !jobs.isEmpty {
             if !jobs.isEmpty {
-                stack.addArrangedSubview(sectionHeading("Experience"))
+                stack.addArrangedSubview(sectionHeading(L("Experience")))
                 for job in jobs {
                 for job in jobs {
                     stack.addArrangedSubview(experienceBlock(job: job, compact: false))
                     stack.addArrangedSubview(experienceBlock(job: job, compact: false))
                 }
                 }
@@ -1029,7 +1029,7 @@ final class CVProfileDocumentView: NSView {
         let appendEducation: () -> Void = { [self] in
         let appendEducation: () -> Void = { [self] in
             let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
             let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
             if !schools.isEmpty {
             if !schools.isEmpty {
-                stack.addArrangedSubview(sectionHeading("Education"))
+                stack.addArrangedSubview(sectionHeading(L("Education")))
                 for edu in schools {
                 for edu in schools {
                     stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
                     stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
                 }
                 }
@@ -1056,13 +1056,13 @@ final class CVProfileDocumentView: NSView {
         stack.alignment = .leading
         stack.alignment = .leading
         let skills = skillTokensFromProfile(max: 12)
         let skills = skillTokensFromProfile(max: 12)
         if skills.isEmpty {
         if skills.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Contact"))
+            stack.addArrangedSubview(sectionHeading(L("Contact")))
             for line in contactLines() {
             for line in contactLines() {
                 stack.addArrangedSubview(label(line, font: style.contactFont, color: style.muted, maxLines: 0))
                 stack.addArrangedSubview(label(line, font: style.contactFont, color: style.muted, maxLines: 0))
             }
             }
             return stack
             return stack
         }
         }
-        stack.addArrangedSubview(sectionHeading("Skills"))
+        stack.addArrangedSubview(sectionHeading(L("Skills")))
         for (i, s) in skills.enumerated() {
         for (i, s) in skills.enumerated() {
             let prefix = numbered ? "\(i + 1). " : "·  "
             let prefix = numbered ? "\(i + 1). " : "·  "
             stack.addArrangedSubview(label("\(prefix)\(s)", font: style.bodyCompactFont, color: style.muted, maxLines: 0))
             stack.addArrangedSubview(label("\(prefix)\(s)", font: style.bodyCompactFont, color: style.muted, maxLines: 0))
@@ -1168,10 +1168,10 @@ final class CVProfileDocumentView: NSView {
     }
     }
 
 
     private func headerBlock() -> NSView {
     private func headerBlock() -> NSView {
-        let nameText = displayable(profile.personal.fullName, placeholder: "Your name")
-        let roleText = displayable(profile.personal.jobTitle, placeholder: "Professional headline")
+        let nameText = displayable(profile.personal.fullName, placeholder: L("Your name"))
+        let roleText = displayable(profile.personal.jobTitle, placeholder: L("Professional headline"))
         let contactParts = [profile.personal.email, profile.personal.phone, profile.personal.address].filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
         let contactParts = [profile.personal.email, profile.personal.phone, profile.personal.address].filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
-        let contactText = contactParts.isEmpty ? "Add contact details in your profile" : contactParts.joined(separator: " · ")
+        let contactText = contactParts.isEmpty ? L("Add contact details in your profile") : contactParts.joined(separator: " · ")
 
 
         let roleColor = style.roleUsesThemeColor ? template.themeColor : style.muted
         let roleColor = style.roleUsesThemeColor ? template.themeColor : style.muted
         let name = label(nameText, font: style.nameFont, color: style.ink, maxLines: 2)
         let name = label(nameText, font: style.nameFont, color: style.ink, maxLines: 2)
@@ -1256,7 +1256,7 @@ final class CVProfileDocumentView: NSView {
             return "\(a)\(b)".uppercased()
             return "\(a)\(b)".uppercased()
         }
         }
         if let first = parts.first { return String(first.prefix(2)).uppercased() }
         if let first = parts.first { return String(first.prefix(2)).uppercased() }
-        return "CV"
+        return L("CV")
     }
     }
 
 
     private func headlineAccent() -> NSView {
     private func headlineAccent() -> NSView {
@@ -1315,14 +1315,14 @@ final class CVProfileDocumentView: NSView {
         }
         }
         box.edgeInsets = NSEdgeInsets(top: tinted ? 14 : 0, left: tinted ? 14 : 0, bottom: tinted ? 14 : 0, right: tinted ? 14 : 0)
         box.edgeInsets = NSEdgeInsets(top: tinted ? 14 : 0, left: tinted ? 14 : 0, bottom: tinted ? 14 : 0, right: tinted ? 14 : 0)
 
 
-        box.addArrangedSubview(sectionHeading("Contact"))
+        box.addArrangedSubview(sectionHeading(L("Contact")))
         for line in contactLines() {
         for line in contactLines() {
             box.addArrangedSubview(label(line, font: style.contactFont, color: style.ink, maxLines: 0))
             box.addArrangedSubview(label(line, font: style.contactFont, color: style.ink, maxLines: 0))
         }
         }
 
 
         let skills = skillTokensFromProfile(max: 8)
         let skills = skillTokensFromProfile(max: 8)
         if !skills.isEmpty {
         if !skills.isEmpty {
-            box.addArrangedSubview(sectionHeading("Skills"))
+            box.addArrangedSubview(sectionHeading(L("Skills")))
             if variant % 5 == 2 {
             if variant % 5 == 2 {
                 box.addArrangedSubview(skillTagRow(theme: template.themeColor, maxTags: 5))
                 box.addArrangedSubview(skillTagRow(theme: template.themeColor, maxTags: 5))
             } else {
             } else {
@@ -1333,10 +1333,10 @@ final class CVProfileDocumentView: NSView {
         }
         }
 
 
         if variant % 7 == 3, let tools = nonEmpty(profile.certificates) {
         if variant % 7 == 3, let tools = nonEmpty(profile.certificates) {
-            box.addArrangedSubview(sectionHeading("Tools"))
+            box.addArrangedSubview(sectionHeading(L("Tools")))
             box.addArrangedSubview(paragraph(tools, compact: true))
             box.addArrangedSubview(paragraph(tools, compact: true))
         } else if let ancillary = combinedAncillaryText(), !ancillary.isEmpty {
         } else if let ancillary = combinedAncillaryText(), !ancillary.isEmpty {
-            box.addArrangedSubview(sectionHeading("Languages & more"))
+            box.addArrangedSubview(sectionHeading(L("Languages & more")))
             box.addArrangedSubview(paragraph(ancillary, compact: true))
             box.addArrangedSubview(paragraph(ancillary, compact: true))
         }
         }
 
 
@@ -1353,7 +1353,7 @@ final class CVProfileDocumentView: NSView {
         let summaryBody: NSView? = nonEmpty(profile.careerSummary).map { paragraph($0, compact: compact) }
         let summaryBody: NSView? = nonEmpty(profile.careerSummary).map { paragraph($0, compact: compact) }
 
 
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
-        let experienceHeading = sectionHeading("Experience")
+        let experienceHeading = sectionHeading(L("Experience"))
         var experienceBlocks: [NSView] = []
         var experienceBlocks: [NSView] = []
         for job in jobs {
         for job in jobs {
             experienceBlocks.append(experienceBlock(job: job, compact: compact))
             experienceBlocks.append(experienceBlock(job: job, compact: compact))
@@ -1379,7 +1379,7 @@ final class CVProfileDocumentView: NSView {
         }
         }
         let appendEducation: () -> Void = { [self] in
         let appendEducation: () -> Void = { [self] in
             if !schools.isEmpty {
             if !schools.isEmpty {
-                v.addArrangedSubview(sectionHeading("Education"))
+                v.addArrangedSubview(sectionHeading(L("Education")))
                 educationBlocks.forEach { v.addArrangedSubview($0) }
                 educationBlocks.forEach { v.addArrangedSubview($0) }
             }
             }
         }
         }
@@ -1400,15 +1400,15 @@ final class CVProfileDocumentView: NSView {
 
 
     private func appendCertificatesInterestsReferrals(to v: NSStackView, compact: Bool) {
     private func appendCertificatesInterestsReferrals(to v: NSStackView, compact: Bool) {
         if let cert = nonEmpty(profile.certificates) {
         if let cert = nonEmpty(profile.certificates) {
-            v.addArrangedSubview(sectionHeading("Certificates"))
+            v.addArrangedSubview(sectionHeading(L("Certificates")))
             v.addArrangedSubview(paragraph(cert, compact: compact))
             v.addArrangedSubview(paragraph(cert, compact: compact))
         }
         }
         if let interests = nonEmpty(profile.interests) {
         if let interests = nonEmpty(profile.interests) {
-            v.addArrangedSubview(sectionHeading("Interests"))
+            v.addArrangedSubview(sectionHeading(L("Interests")))
             v.addArrangedSubview(paragraph(interests, compact: compact))
             v.addArrangedSubview(paragraph(interests, compact: compact))
         }
         }
         if let ref = nonEmpty(profile.referral) {
         if let ref = nonEmpty(profile.referral) {
-            v.addArrangedSubview(sectionHeading("Referrals"))
+            v.addArrangedSubview(sectionHeading(L("Referrals")))
             v.addArrangedSubview(paragraph(ref, compact: compact))
             v.addArrangedSubview(paragraph(ref, compact: compact))
         }
         }
     }
     }
@@ -1551,7 +1551,7 @@ final class CVProfileDocumentView: NSView {
 
 
     /// Gallery + ATS “Clear Path” style use “Profile”; other families keep the neutral résumé label.
     /// Gallery + ATS “Clear Path” style use “Profile”; other families keep the neutral résumé label.
     private var summarySectionTitle: String {
     private var summarySectionTitle: String {
-        template.family == .professional ? "Profile" : "Summary"
+        template.family == .professional ? L("Profile") : L("Summary")
     }
     }
 
 
     private func sectionHeading(_ raw: String) -> NSTextField {
     private func sectionHeading(_ raw: String) -> NSTextField {

+ 43 - 43
App for Indeed/Views/CVTemplateMiniPreview.swift

@@ -31,28 +31,28 @@ struct CVTemplateCardPalette {
 // MARK: - Demo résumé content
 // MARK: - Demo résumé content
 
 
 fileprivate enum CVPreviewDemoContent {
 fileprivate enum CVPreviewDemoContent {
-    static let fullName = "Sarah Johnson"
+    static var fullName: String { L("Sarah Johnson") }
     /// Shown in the header / contact band (broad role).
     /// Shown in the header / contact band (broad role).
-    static let title = "Senior Product Manager"
+    static var title: String { L("Senior Product Manager") }
     /// Scoped title under Experience so it is not a verbatim repeat of the header line.
     /// Scoped title under Experience so it is not a verbatim repeat of the header line.
-    static let experienceRole = "Group PM, Consumer Growth & Activation"
-    static let company = "Google"
-    static let companyLine = "Google · Mountain View, CA · 2019 – Present"
-    static let university = "Stanford University"
-    static let degree = "M.S. Management Science & Engineering"
-    static let educationYears = "2014 – 2016"
-    static let email = "sarah.johnson@email.com"
-    static let phone = "(415) 555-0198"
-    static let location = "Mountain View, CA"
-    static let summary = "Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences."
-    static let bullet1 = "Defined multi-year platform strategy with exec stakeholders and quarterly OKRs."
-    static let bullet2 = "Partnered with engineering and design to launch experiments improving activation by 12%."
-    static let bullet3 = "Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics."
+    static var experienceRole: String { L("Group PM, Consumer Growth & Activation") }
+    static var company: String { L("Google") }
+    static var companyLine: String { L("Google · Mountain View, CA · 2019 – Present") }
+    static var university: String { L("Stanford University") }
+    static var degree: String { L("M.S. Management Science & Engineering") }
+    static var educationYears: String { L("2014 – 2016") }
+    static var email: String { L("sarah.johnson@email.com") }
+    static var phone: String { L("(415) 555-0198") }
+    static var location: String { L("Mountain View, CA") }
+    static var summary: String { L("Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences.") }
+    static var bullet1: String { L("Defined multi-year platform strategy with exec stakeholders and quarterly OKRs.") }
+    static var bullet2: String { L("Partnered with engineering and design to launch experiments improving activation by 12%.") }
+    static var bullet3: String { L("Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics.") }
     /// Sidebar “highlights” blurb kept distinct from the experience bullets.
     /// Sidebar “highlights” blurb kept distinct from the experience bullets.
-    static let careerHighlights = "Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks."
+    static var careerHighlights: String { L("Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks.") }
     /// Single tools line reused wherever the résumé lists a stack (avoids scattered near-duplicate strings).
     /// Single tools line reused wherever the résumé lists a stack (avoids scattered near-duplicate strings).
-    static let toolsLine = "Figma · SQL · Amplitude · Jira · BigQuery"
-    static let skillsList = ["Product Strategy", "SQL", "Figma", "A/B Testing", "Roadmapping"]
+    static var toolsLine: String { L("Figma · SQL · Amplitude · Jira · BigQuery") }
+    static var skillsList: [String] { [L("Product Strategy"), L("SQL"), L("Figma"), L("A/B Testing"), L("Roadmapping") ] }
 }
 }
 
 
 // MARK: - Mini preview
 // MARK: - Mini preview
@@ -273,11 +273,11 @@ final class CVTemplatePreviewView: NSView {
         inner.orientation = .vertical
         inner.orientation = .vertical
         inner.spacing = 5
         inner.spacing = 5
         inner.alignment = .leading
         inner.alignment = .leading
-        inner.addArrangedSubview(sectionHeading("CONTACT"))
+        inner.addArrangedSubview(sectionHeading(L("CONTACT")))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.location, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
         inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.location, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
-        inner.addArrangedSubview(sectionHeading("SKILLS"))
+        inner.addArrangedSubview(sectionHeading(L("SKILLS")))
         if variant % 5 == 2 {
         if variant % 5 == 2 {
             inner.addArrangedSubview(tagRow(theme: template.themeColor))
             inner.addArrangedSubview(tagRow(theme: template.themeColor))
         } else {
         } else {
@@ -286,7 +286,7 @@ final class CVTemplatePreviewView: NSView {
             }
             }
         }
         }
         if variant % 7 == 3 {
         if variant % 7 == 3 {
-            inner.addArrangedSubview(sectionHeading("TOOLS"))
+            inner.addArrangedSubview(sectionHeading(L("TOOLS")))
             inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.toolsLine, font: .systemFont(ofSize: 5.9), color: palette.previewMuted, alignment: .left, maxLines: 1))
             inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.toolsLine, font: .systemFont(ofSize: 5.9), color: palette.previewMuted, alignment: .left, maxLines: 1))
         }
         }
         box.addArrangedSubview(inner)
         box.addArrangedSubview(inner)
@@ -301,11 +301,11 @@ final class CVTemplatePreviewView: NSView {
         let sp: CGFloat = compact ? 6.2 : 6.5
         let sp: CGFloat = compact ? 6.2 : 6.5
 
 
         let profileBlock: () -> Void = {
         let profileBlock: () -> Void = {
-            stack.addArrangedSubview(self.sectionHeading("PROFILE"))
+            stack.addArrangedSubview(self.sectionHeading(L("PROFILE")))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: sp), color: self.palette.previewInk, alignment: .left, maxLines: 3))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: sp), color: self.palette.previewInk, alignment: .left, maxLines: 3))
         }
         }
         let experienceBlock: () -> Void = {
         let experienceBlock: () -> Void = {
-            stack.addArrangedSubview(self.sectionHeading("EXPERIENCE"))
+            stack.addArrangedSubview(self.sectionHeading(L("EXPERIENCE")))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.experienceRole, font: .systemFont(ofSize: 6.8, weight: .semibold), color: self.palette.previewInk, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.experienceRole, font: .systemFont(ofSize: 6.8, weight: .semibold), color: self.palette.previewInk, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.companyLine, font: .systemFont(ofSize: 6.2, weight: .medium), color: self.template.themeColor, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.companyLine, font: .systemFont(ofSize: 6.2, weight: .medium), color: self.template.themeColor, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet1, size: sp))
             stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet1, size: sp))
@@ -320,7 +320,7 @@ final class CVTemplatePreviewView: NSView {
             profileBlock()
             profileBlock()
             experienceBlock()
             experienceBlock()
         }
         }
-        stack.addArrangedSubview(sectionHeading("EDUCATION"))
+        stack.addArrangedSubview(sectionHeading(L("EDUCATION")))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.university, font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 1))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.university, font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 1))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 2))
         return stack
         return stack
@@ -467,7 +467,7 @@ final class CVTemplatePreviewView: NSView {
         let onW = NSColor.white
         let onW = NSColor.white
         right.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.95), alignment: .left, maxLines: 2))
         right.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.95), alignment: .left, maxLines: 2))
         right.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.9), alignment: .left, maxLines: 1))
         right.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.9), alignment: .left, maxLines: 1))
-        right.addArrangedSubview(makeLabel("Open to relocation", font: .systemFont(ofSize: 5.6, weight: .regular), color: onW.withAlphaComponent(0.75), alignment: .left, maxLines: 1))
+        right.addArrangedSubview(makeLabel(L("Open to relocation"), font: .systemFont(ofSize: 5.6, weight: .regular), color: onW.withAlphaComponent(0.75), alignment: .left, maxLines: 1))
 
 
         let top = NSStackView(views: [left, right])
         let top = NSStackView(views: [left, right])
         top.orientation = .horizontal
         top.orientation = .horizontal
@@ -493,9 +493,9 @@ final class CVTemplatePreviewView: NSView {
             box.layer?.cornerRadius = 4
             box.layer?.cornerRadius = 4
         }
         }
         box.edgeInsets = NSEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
         box.edgeInsets = NSEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
-        box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: "About", theme: theme))
+        box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: L("About"), theme: theme))
         box.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6), color: palette.previewInk, alignment: .left, maxLines: 4))
         box.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6), color: palette.previewInk, alignment: .left, maxLines: 4))
-        box.addArrangedSubview(modernSectionRow(symbol: "star.fill", title: "Highlights", theme: theme))
+        box.addArrangedSubview(modernSectionRow(symbol: "star.fill", title: L("Highlights"), theme: theme))
         box.addArrangedSubview(makeLabel(CVPreviewDemoContent.careerHighlights, font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 3))
         box.addArrangedSubview(makeLabel(CVPreviewDemoContent.careerHighlights, font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 3))
         return box
         return box
     }
     }
@@ -520,11 +520,11 @@ final class CVTemplatePreviewView: NSView {
         stack.orientation = .vertical
         stack.orientation = .vertical
         stack.spacing = 5
         stack.spacing = 5
         stack.alignment = .leading
         stack.alignment = .leading
-        stack.addArrangedSubview(modernSectionRow(symbol: "briefcase.fill", title: "Experience", theme: theme))
+        stack.addArrangedSubview(modernSectionRow(symbol: "briefcase.fill", title: L("Experience"), theme: theme))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.experienceRole) — \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.experienceRole) — \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("2019 – Present · Led cross-functional pods from discovery through launch and post-ship learning.", font: .systemFont(ofSize: 6.1), color: palette.previewMuted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("2019 – Present · Led cross-functional pods from discovery through launch and post-ship learning.", font: .systemFont(ofSize: 6.1), color: palette.previewMuted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(tagRow(theme: theme))
         stack.addArrangedSubview(tagRow(theme: theme))
-        stack.addArrangedSubview(modernSectionRow(symbol: "graduationcap.fill", title: "Education", theme: theme))
+        stack.addArrangedSubview(modernSectionRow(symbol: "graduationcap.fill", title: L("Education"), theme: theme))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) (\(CVPreviewDemoContent.educationYears))", font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) (\(CVPreviewDemoContent.educationYears))", font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
         return stack
         return stack
     }
     }
@@ -623,15 +623,15 @@ final class CVTemplatePreviewView: NSView {
         stack.alignment = .leading
         stack.alignment = .leading
 
 
         let edu: () -> Void = {
         let edu: () -> Void = {
-            stack.addArrangedSubview(self.sectionHeading("EDUCATION"))
+            stack.addArrangedSubview(self.sectionHeading(L("EDUCATION")))
             stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.university) — \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.1, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.university) — \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.1, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 2))
         }
         }
         let prof: () -> Void = {
         let prof: () -> Void = {
-            stack.addArrangedSubview(self.sectionHeading("PROFILE"))
+            stack.addArrangedSubview(self.sectionHeading(L("PROFILE")))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.3, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 3))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.3, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 3))
         }
         }
         let exp: () -> Void = {
         let exp: () -> Void = {
-            stack.addArrangedSubview(self.sectionHeading("EXPERIENCE"))
+            stack.addArrangedSubview(self.sectionHeading(L("EXPERIENCE")))
             stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.experienceRole) · \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.5, weight: .regular), color: self.palette.previewInk, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.experienceRole) · \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.5, weight: .regular), color: self.palette.previewInk, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet1, font: .systemFont(ofSize: 6, weight: .light), color: self.palette.previewMuted, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet1, font: .systemFont(ofSize: 6, weight: .light), color: self.palette.previewMuted, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet2, font: .systemFont(ofSize: 6, weight: .light), color: self.palette.previewMuted, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet2, font: .systemFont(ofSize: 6, weight: .light), color: self.palette.previewMuted, alignment: .left, maxLines: 2))
@@ -654,7 +654,7 @@ final class CVTemplatePreviewView: NSView {
         stack.orientation = .vertical
         stack.orientation = .vertical
         stack.spacing = 6
         stack.spacing = 6
         stack.alignment = .leading
         stack.alignment = .leading
-        stack.addArrangedSubview(sectionHeading("SKILLS"))
+        stack.addArrangedSubview(sectionHeading(L("SKILLS")))
         for (i, s) in CVPreviewDemoContent.skillsList.enumerated() {
         for (i, s) in CVPreviewDemoContent.skillsList.enumerated() {
             let prefix = numbered ? "\(i + 1). " : "·  "
             let prefix = numbered ? "\(i + 1). " : "·  "
             stack.addArrangedSubview(makeLabel("\(prefix)\(s)", font: .systemFont(ofSize: 6, weight: .light), color: palette.previewMuted, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(makeLabel("\(prefix)\(s)", font: .systemFont(ofSize: 6, weight: .light), color: palette.previewMuted, alignment: .left, maxLines: 1))
@@ -728,16 +728,16 @@ final class CVTemplatePreviewView: NSView {
         stack.orientation = .vertical
         stack.orientation = .vertical
         stack.spacing = (compact ? 4 : 5) - (tightLeading ? 1 : 0)
         stack.spacing = (compact ? 4 : 5) - (tightLeading ? 1 : 0)
         stack.alignment = .leading
         stack.alignment = .leading
-        let sumTitle = (layoutVariant % 6 == 3) ? "SUMMARY" : "PROFESSIONAL SUMMARY"
+        let sumTitle = (layoutVariant % 6 == 3) ? L("SUMMARY") : L("PROFESSIONAL SUMMARY")
         stack.addArrangedSubview(sectionHeading(sumTitle))
         stack.addArrangedSubview(sectionHeading(sumTitle))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: serif, color: ink, alignment: .left, maxLines: 3))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: serif, color: ink, alignment: .left, maxLines: 3))
-        stack.addArrangedSubview(sectionHeading("SELECTED EXPERIENCE"))
+        stack.addArrangedSubview(sectionHeading(L("SELECTED EXPERIENCE")))
         let jobFont = NSFontManager.shared.convert(serif, toHaveTrait: .boldFontMask)
         let jobFont = NSFontManager.shared.convert(serif, toHaveTrait: .boldFontMask)
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.experienceRole), \(CVPreviewDemoContent.company)", font: jobFont, color: ink, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.experienceRole), \(CVPreviewDemoContent.company)", font: jobFont, color: ink, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("2019 – Present", font: serif, color: theme, alignment: .left, maxLines: 1))
         stack.addArrangedSubview(makeLabel("2019 – Present", font: serif, color: theme, alignment: .left, maxLines: 1))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.bullet1, font: serif, color: muted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.bullet1, font: serif, color: muted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.bullet2, font: serif, color: muted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.bullet2, font: serif, color: muted, alignment: .left, maxLines: 2))
-        stack.addArrangedSubview(sectionHeading("EDUCATION"))
+        stack.addArrangedSubview(sectionHeading(L("EDUCATION")))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: serif, color: ink, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: serif, color: ink, alignment: .left, maxLines: 2))
         return stack
         return stack
     }
     }
@@ -756,14 +756,14 @@ final class CVTemplatePreviewView: NSView {
             stack.layer?.cornerRadius = layoutVariant % 4 == 2 ? 5 : 3
             stack.layer?.cornerRadius = layoutVariant % 4 == 2 ? 5 : 3
             stack.edgeInsets = NSEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
             stack.edgeInsets = NSEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
         }
         }
-        stack.addArrangedSubview(sectionHeading("CORE COMPETENCIES"))
+        stack.addArrangedSubview(sectionHeading(L("CORE COMPETENCIES")))
         for s in CVPreviewDemoContent.skillsList {
         for s in CVPreviewDemoContent.skillsList {
             stack.addArrangedSubview(makeLabel("· \(s)", font: serif, color: palette.previewInk, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(makeLabel("· \(s)", font: serif, color: palette.previewInk, alignment: .left, maxLines: 1))
         }
         }
-        stack.addArrangedSubview(sectionHeading("TOOLS"))
+        stack.addArrangedSubview(sectionHeading(L("TOOLS")))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.toolsLine, font: serif, color: palette.previewMuted, alignment: .left, maxLines: 2))
         stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.toolsLine, font: serif, color: palette.previewMuted, alignment: .left, maxLines: 2))
         if showMetrics {
         if showMetrics {
-            stack.addArrangedSubview(sectionHeading("IMPACT"))
+            stack.addArrangedSubview(sectionHeading(L("IMPACT")))
             stack.addArrangedSubview(makeLabel("+12% activation · $4.2M ARR influenced", font: serif, color: palette.previewInk, alignment: .left, maxLines: 2))
             stack.addArrangedSubview(makeLabel("+12% activation · $4.2M ARR influenced", font: serif, color: palette.previewInk, alignment: .left, maxLines: 2))
         }
         }
         return stack
         return stack
@@ -791,7 +791,7 @@ final class CVTemplatePreviewView: NSView {
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 6.5, weight: .medium), color: onSidebar.withAlphaComponent(0.85), alignment: .left, maxLines: 2))
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 6.5, weight: .medium), color: onSidebar.withAlphaComponent(0.85), alignment: .left, maxLines: 2))
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
         sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
-        sidebar.addArrangedSubview(creativeSidebarHeading("STRENGTHS", onSidebar: onSidebar, accent: theme))
+        sidebar.addArrangedSubview(creativeSidebarHeading(L("STRENGTHS"), onSidebar: onSidebar, accent: theme))
         for s in CVPreviewDemoContent.skillsList.prefix(5) {
         for s in CVPreviewDemoContent.skillsList.prefix(5) {
             sidebar.addArrangedSubview(makeLabel("\(skillPrefix)\(s)", font: .systemFont(ofSize: 6, weight: .semibold), color: onSidebar.withAlphaComponent(0.92), alignment: .left, maxLines: 1))
             sidebar.addArrangedSubview(makeLabel("\(skillPrefix)\(s)", font: .systemFont(ofSize: 6, weight: .semibold), color: onSidebar.withAlphaComponent(0.92), alignment: .left, maxLines: 1))
         }
         }
@@ -801,14 +801,14 @@ final class CVTemplatePreviewView: NSView {
         main.spacing = 4 + CGFloat(layoutVariant % 3)
         main.spacing = 4 + CGFloat(layoutVariant % 3)
         main.alignment = .leading
         main.alignment = .leading
         main.addArrangedSubview(creativeMainHeader(theme: theme))
         main.addArrangedSubview(creativeMainHeader(theme: theme))
-        main.addArrangedSubview(sectionHeading("PROFILE"))
+        main.addArrangedSubview(sectionHeading(L("PROFILE")))
         main.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 3))
         main.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 3))
-        main.addArrangedSubview(sectionHeading("IMPACT"))
+        main.addArrangedSubview(sectionHeading(L("IMPACT")))
         main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.company) — \(CVPreviewDemoContent.experienceRole)", font: .systemFont(ofSize: 6.6, weight: .heavy), color: palette.previewInk, alignment: .left, maxLines: 2))
         main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.company) — \(CVPreviewDemoContent.experienceRole)", font: .systemFont(ofSize: 6.6, weight: .heavy), color: palette.previewInk, alignment: .left, maxLines: 2))
         let bMark = (layoutVariant % 2 == 0) ? "—  " : "▸  "
         let bMark = (layoutVariant % 2 == 0) ? "—  " : "▸  "
         main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet1)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
         main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet1)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
         main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet2)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
         main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet2)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
-        main.addArrangedSubview(sectionHeading("EDUCATION"))
+        main.addArrangedSubview(sectionHeading(L("EDUCATION")))
         main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university) · \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.1), color: palette.previewInk, alignment: .left, maxLines: 2))
         main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university) · \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.1), color: palette.previewInk, alignment: .left, maxLines: 2))
 
 
         let sidebarMult = 0.32 + CGFloat(layoutVariant % 3) * 0.02
         let sidebarMult = 0.32 + CGFloat(layoutVariant % 3) * 0.02

+ 207 - 109
App for Indeed/Views/DashboardView.swift

@@ -120,7 +120,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private let findJobsButton = HoverableButton()
     private let findJobsButton = HoverableButton()
     private let findJobsCTAPill = HoverableView()
     private let findJobsCTAPill = HoverableView()
     private let sendIconView = NSImageView()
     private let sendIconView = NSImageView()
-    private let sendLabel = NSTextField(labelWithString: "Send")
+    private let sendLabel = NSTextField(labelWithString: L("Send"))
     private let sendContentStack = NSStackView()
     private let sendContentStack = NSStackView()
     private let findJobsCTAHost = NSView()
     private let findJobsCTAHost = NSView()
     private let welcomeHeroHost = NSView()
     private let welcomeHeroHost = NSView()
@@ -129,7 +129,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private let welcomeLogoView = IndeedLogoView(displayHeight: 40, variant: .compact)
     private let welcomeLogoView = IndeedLogoView(displayHeight: 40, variant: .compact)
     private let featureCardsRow = NSStackView()
     private let featureCardsRow = NSStackView()
     private enum FeatureShortcut: Int { case role = 0, company = 1, skill = 2 }
     private enum FeatureShortcut: Int { case role = 0, company = 1, skill = 2 }
-    private let clearChatButton = NSButton(title: "Clear chat", target: nil, action: nil)
+    private let clearChatButton = NSButton(title: L("Clear chat"), target: nil, action: nil)
     private let chatScrollView = NSScrollView()
     private let chatScrollView = NSScrollView()
     private let chatDocumentView = JobListingsDocumentView()
     private let chatDocumentView = JobListingsDocumentView()
     private let chatStack = NSStackView()
     private let chatStack = NSStackView()
@@ -141,7 +141,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private let nonHomeTitleLabel = NSTextField(labelWithString: "")
     private let nonHomeTitleLabel = NSTextField(labelWithString: "")
     private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let savedJobsPageContainer = NSView()
     private let savedJobsPageContainer = NSView()
-    private let savedJobsPageTitleLabel = NSTextField(labelWithString: "Saved Jobs")
+    private let savedJobsPageTitleLabel = NSTextField(labelWithString: L("Saved Jobs"))
     private let savedJobsPageSubtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let savedJobsPageSubtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let savedJobsScrollView = NSScrollView()
     private let savedJobsScrollView = NSScrollView()
     private let savedJobsDocumentView = JobListingsDocumentView()
     private let savedJobsDocumentView = JobListingsDocumentView()
@@ -193,6 +193,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private weak var sidebarUpgradeButton: HoverableButton?
     private weak var sidebarUpgradeButton: HoverableButton?
     private var subscriptionObserver: NSObjectProtocol?
     private var subscriptionObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
     /// Retains the system share picker until the user picks a destination or dismisses the menu.
     /// Retains the system share picker until the user picks a destination or dismisses the menu.
     private var appSharePicker: NSSharingServicePicker?
     private var appSharePicker: NSSharingServicePicker?
 
 
@@ -226,6 +227,9 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     override func viewDidChangeEffectiveAppearance() {
     override func viewDidChangeEffectiveAppearance() {
@@ -241,6 +245,103 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.refreshLocalizedStrings()
+        }
+    }
+
+    private func refreshLocalizedStrings() {
+        greetingLabel.stringValue = L("Welcome")
+        subtitleLabel.stringValue = L("Find your perfect job with the power of AI.")
+        sendLabel.stringValue = L("Send")
+        clearChatButton.title = L("Clear chat")
+        clearChatButton.toolTip = L("Remove all messages and start a new conversation")
+        savedJobsPageTitleLabel.stringValue = L("Saved Jobs")
+        nonHomeSubtitleLabel.stringValue = L("This area is not available in the preview build. Use Home to search jobs.")
+
+        jobKeywordsField.placeholderAttributedString = NSAttributedString(
+            string: L("Ask for roles, skills, salary, or job descriptions..."),
+            attributes: [
+                .foregroundColor: Theme.secondaryText,
+                .font: NSFont.systemFont(ofSize: 14, weight: .regular)
+            ]
+        )
+        jobSearchIcon.setAccessibilityLabel(L("Ask AI"))
+        findJobsButton.setAccessibilityLabel(L("Send"))
+
+        appearanceModeSegment?.setLabel(L("System"), forSegment: 0)
+        appearanceModeSegment?.setLabel(L("Light"), forSegment: 1)
+        appearanceModeSegment?.setLabel(L("Dark"), forSegment: 2)
+
+        refreshLanguagePopUp()
+        refreshSettingsLocalizedLabels()
+        refreshSidebarItemTitles()
+        updateFreeJobSearchQuotaLabel()
+        applyProSubscriptionToSidebar()
+        configureSidebar()
+        reloadSavedJobsListings()
+        rebuildFeatureShortcutCards()
+        trailingLoadMoreJobsButton?.title = L("Show more jobs")
+    }
+
+    private func refreshSidebarItemTitles() {
+        currentSidebarItems = currentSidebarItems.map { item in
+            SidebarItem(
+                title: localizedSidebarTitle(forSystemImage: item.systemImage),
+                systemImage: item.systemImage,
+                badge: item.badge
+            )
+        }
+    }
+
+    private func localizedSidebarTitle(forSystemImage systemImage: String) -> String {
+        switch systemImage {
+        case "house.fill":
+            return L("Home")
+        case "heart":
+            return L("Saved Jobs")
+        case "doc.text":
+            return L("CV Maker")
+        case "person":
+            return L("Profile")
+        case "gearshape":
+            return L("Settings")
+        default:
+            return L("Home")
+        }
+    }
+
+    private func refreshLanguagePopUp() {
+        guard let popup = languagePopUp else { return }
+        let selectedCode = AppLanguageManager.shared.current.localeIdentifier
+        popup.removeAllItems()
+        for language in AppLanguage.allCases {
+            popup.addItem(withTitle: language.localizedDisplayName)
+            popup.lastItem?.representedObject = language.localeIdentifier
+        }
+        if let index = AppLanguage.allCases.firstIndex(where: { $0.localeIdentifier == selectedCode }) {
+            popup.selectItem(at: index)
+        }
+        popup.isEnabled = !AppLanguage.allCases.isEmpty
+    }
+
+    private func refreshSettingsLocalizedLabels() {
+        for view in settingsPageContainer.subviewsRecursive() {
+            guard let rawID = view.identifier?.rawValue else { continue }
+            let sectionPrefix = SettingsAppearanceID.sectionHeader + "."
+            let rowPrefix = SettingsAppearanceID.rowTitle + "."
+            if rawID.hasPrefix(sectionPrefix) {
+                let key = String(rawID.dropFirst(sectionPrefix.count))
+                (view as? NSTextField)?.stringValue = L(key)
+            } else if rawID.hasPrefix(rowPrefix) {
+                let key = String(rawID.dropFirst(rowPrefix.count))
+                (view as? NSTextField)?.stringValue = L(key)
+            }
+        }
     }
     }
 
 
     override func viewDidMoveToWindow() {
     override func viewDidMoveToWindow() {
@@ -262,7 +363,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     func render(_ data: DashboardData) {
     func render(_ data: DashboardData) {
         dismissIndeedJobBrowserEmbedded()
         dismissIndeedJobBrowserEmbedded()
-        greetingLabel.stringValue = "Welcome"
+        greetingLabel.stringValue = L("Welcome")
         subtitleLabel.stringValue = data.subtitle
         subtitleLabel.stringValue = data.subtitle
         currentSidebarItems = data.sidebarItems
         currentSidebarItems = data.sidebarItems
         if selectedSidebarIndex >= currentSidebarItems.count {
         if selectedSidebarIndex >= currentSidebarItems.count {
@@ -303,7 +404,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
         searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
         jobKeywordsField.textColor = Theme.primaryText
         jobKeywordsField.textColor = Theme.primaryText
         jobKeywordsField.placeholderAttributedString = NSAttributedString(
         jobKeywordsField.placeholderAttributedString = NSAttributedString(
-            string: "Ask for roles, skills, salary, or job descriptions...",
+            string: L("Ask for roles, skills, salary, or job descriptions..."),
             attributes: [
             attributes: [
                 .foregroundColor: Theme.secondaryText,
                 .foregroundColor: Theme.secondaryText,
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular)
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular)
@@ -318,11 +419,11 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
         appearanceModeSegment?.selectedSegment = AppAppearanceManager.shared.mode.segmentIndex
         appearanceModeSegment?.selectedSegment = AppAppearanceManager.shared.mode.segmentIndex
         if let langPopUp = languagePopUp {
         if let langPopUp = languagePopUp {
-            let saved = UserDefaults.standard.string(forKey: Self.languageUserDefaultsKey) ?? "en"
-            if let index = Self.supportedLanguages.firstIndex(where: { $0.code == saved }) {
+            let saved = AppLanguageManager.shared.current.localeIdentifier
+            if let index = AppLanguage.allCases.firstIndex(where: { $0.localeIdentifier == saved }) {
                 langPopUp.selectItem(at: index)
                 langPopUp.selectItem(at: index)
             }
             }
-            langPopUp.isEnabled = !Self.supportedLanguages.isEmpty
+            langPopUp.isEnabled = !AppLanguage.allCases.isEmpty
         }
         }
         cvMakerPageView.applyCurrentAppearance()
         cvMakerPageView.applyCurrentAppearance()
         profilesListPageView.applyCurrentAppearance()
         profilesListPageView.applyCurrentAppearance()
@@ -340,7 +441,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func refreshSettingsPageAppearance(in root: NSView) {
     private func refreshSettingsPageAppearance(in root: NSView) {
         for view in root.subviewsRecursive() {
         for view in root.subviewsRecursive() {
-            switch view.identifier?.rawValue {
+            guard let rawID = view.identifier?.rawValue else { continue }
+            switch rawID {
             case SettingsAppearanceID.section:
             case SettingsAppearanceID.section:
                 view.layer?.backgroundColor = Theme.settingsGroupBackground.cgColor
                 view.layer?.backgroundColor = Theme.settingsGroupBackground.cgColor
                 view.layer?.borderColor = Theme.border.cgColor
                 view.layer?.borderColor = Theme.border.cgColor
@@ -351,12 +453,12 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
                 for case let icon as NSImageView in view.subviews {
                 for case let icon as NSImageView in view.subviews {
                     icon.contentTintColor = Theme.brandBlue
                     icon.contentTintColor = Theme.brandBlue
                 }
                 }
-            case SettingsAppearanceID.sectionHeader:
-                (view as? NSTextField)?.textColor = Theme.secondaryText
-            case SettingsAppearanceID.rowTitle:
-                (view as? NSTextField)?.textColor = Theme.primaryText
             default:
             default:
-                break
+                if rawID.hasPrefix(SettingsAppearanceID.sectionHeader + ".") {
+                    (view as? NSTextField)?.textColor = Theme.secondaryText
+                } else if rawID.hasPrefix(SettingsAppearanceID.rowTitle + ".") {
+                    (view as? NSTextField)?.textColor = Theme.primaryText
+                }
             }
             }
         }
         }
     }
     }
@@ -524,7 +626,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         clearChatButton.font = .systemFont(ofSize: 12, weight: .medium)
         clearChatButton.font = .systemFont(ofSize: 12, weight: .medium)
         clearChatButton.target = self
         clearChatButton.target = self
         clearChatButton.action = #selector(didTapClearChat)
         clearChatButton.action = #selector(didTapClearChat)
-        clearChatButton.toolTip = "Remove all messages and start a new conversation"
+        clearChatButton.toolTip = L("Remove all messages and start a new conversation")
         clearChatButton.setContentHuggingPriority(.required, for: .horizontal)
         clearChatButton.setContentHuggingPriority(.required, for: .horizontal)
         chatHeaderRow.addArrangedSubview(chatHeaderLeadingSpacer)
         chatHeaderRow.addArrangedSubview(chatHeaderLeadingSpacer)
         chatHeaderRow.addArrangedSubview(clearChatButton)
         chatHeaderRow.addArrangedSubview(clearChatButton)
@@ -589,6 +691,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             welcomeHeroContent.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, constant: -32)
             welcomeHeroContent.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, constant: -32)
         ])
         ])
         registerSubscriptionObserverOnce()
         registerSubscriptionObserverOnce()
+        refreshLocalizedStrings()
     }
     }
 
 
     private func registerSubscriptionObserverOnce() {
     private func registerSubscriptionObserverOnce() {
@@ -612,15 +715,15 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
         let descriptionWidth: CGFloat = 158
         let descriptionWidth: CGFloat = 158
         if active {
         if active {
-            headline.stringValue = "You're on Pro"
-            upgradeDescription.stringValue = "Manage billing, renewals, and plans in Premium."
+            headline.stringValue = L("You're on Pro")
+            upgradeDescription.stringValue = L("Manage billing, renewals, and plans in Premium.")
             upgradeDescription.preferredMaxLayoutWidth = descriptionWidth
             upgradeDescription.preferredMaxLayoutWidth = descriptionWidth
-            upgradeButton.title = "Manage Subscription"
+            upgradeButton.title = L("Manage Subscription")
         } else {
         } else {
-            headline.stringValue = "Upgrade to Pro"
-            upgradeDescription.stringValue = "Unlimited AI matches, smart alerts, and interview prep—all in one place."
+            headline.stringValue = L("Upgrade to Pro")
+            upgradeDescription.stringValue = L("Unlimited AI matches, smart alerts, and interview prep—all in one place.")
             upgradeDescription.preferredMaxLayoutWidth = descriptionWidth
             upgradeDescription.preferredMaxLayoutWidth = descriptionWidth
-            upgradeButton.title = "Try Pro"
+            upgradeButton.title = L("Try Pro")
         }
         }
         updateFreeJobSearchQuotaLabel()
         updateFreeJobSearchQuotaLabel()
     }
     }
@@ -635,8 +738,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let remaining = FreeTierJobSearchQuota.remainingUserMessages(isProActive: false)
         let remaining = FreeTierJobSearchQuota.remainingUserMessages(isProActive: false)
         freeJobSearchQuotaLabel.isHidden = false
         freeJobSearchQuotaLabel.isHidden = false
         freeJobSearchQuotaLabel.stringValue = remaining == 1
         freeJobSearchQuotaLabel.stringValue = remaining == 1
-            ? "1 reply left"
-            : "\(remaining) replies left"
+            ? L("1 reply left")
+            : String(format: L("%d replies left"), remaining)
     }
     }
 
 
     /// Returns `false` and presents the paywall when the user does not have an active Pro subscription.
     /// Returns `false` and presents the paywall when the user does not have an active Pro subscription.
@@ -696,7 +799,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         featureCardsRow.alignment = .top
         featureCardsRow.alignment = .top
         featureCardsRow.translatesAutoresizingMaskIntoConstraints = false
         featureCardsRow.translatesAutoresizingMaskIntoConstraints = false
 
 
-        let specs: [(symbol: String, title: String, subtitle: String, action: Selector)] = [
+        let specs: [(symbol: String, titleKey: String, subtitleKey: String, action: Selector)] = [
             ("briefcase", "Role", "Explore similar or better job roles", #selector(didTapFeatureRole)),
             ("briefcase", "Role", "Explore similar or better job roles", #selector(didTapFeatureRole)),
             ("building.2", "Company", "Find opportunities at other companies", #selector(didTapFeatureCompany)),
             ("building.2", "Company", "Find opportunities at other companies", #selector(didTapFeatureCompany)),
             ("chevron.left.forwardslash.chevron.right", "Skill", "Match jobs that fit your skills", #selector(didTapFeatureSkill))
             ("chevron.left.forwardslash.chevron.right", "Skill", "Match jobs that fit your skills", #selector(didTapFeatureSkill))
@@ -704,8 +807,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         for spec in specs {
         for spec in specs {
             let card = FeatureShortcutCardView(
             let card = FeatureShortcutCardView(
                 symbolName: spec.symbol,
                 symbolName: spec.symbol,
-                title: spec.title,
-                subtitle: spec.subtitle,
+                title: L(spec.titleKey),
+                subtitle: L(spec.subtitleKey),
                 target: self,
                 target: self,
                 action: spec.action
                 action: spec.action
             )
             )
@@ -841,7 +944,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func jobListingHostSubtitle(_ job: JobListing) -> String {
     private func jobListingHostSubtitle(_ job: JobListing) -> String {
         guard let raw = job.url, let url = URL(string: raw), let host = url.host?.lowercased() else {
         guard let raw = job.url, let url = URL(string: raw), let host = url.host?.lowercased() else {
-            return "Indeed"
+            return L("Indeed")
         }
         }
         if host.hasPrefix("www.") {
         if host.hasPrefix("www.") {
             return String(host.dropFirst(4))
             return String(host.dropFirst(4))
@@ -943,7 +1046,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         descriptionField.tag = 502
         descriptionField.tag = 502
         descriptionField.translatesAutoresizingMaskIntoConstraints = false
         descriptionField.translatesAutoresizingMaskIntoConstraints = false
 
 
-        let applyButton = JobPayloadButton(title: "Apply", target: self, action: #selector(didTapJobApply(_:)))
+        let applyButton = JobPayloadButton(title: L("Apply"), target: self, action: #selector(didTapJobApply(_:)))
         applyButton.jobPayload = job
         applyButton.jobPayload = job
         applyButton.cardContext = context
         applyButton.cardContext = context
         applyButton.isBordered = false
         applyButton.isBordered = false
@@ -962,7 +1065,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         applyButton.setContentCompressionResistancePriority(.required, for: .horizontal)
         applyButton.setContentCompressionResistancePriority(.required, for: .horizontal)
 
 
         let savedOn = isJobSaved(job)
         let savedOn = isJobSaved(job)
-        let savedButton = SaveJobPayloadButton(title: savedOn ? "Saved" : "Save", target: self, action: #selector(didTapJobSaved(_:)))
+        let savedButton = SaveJobPayloadButton(title: savedOn ? L("Saved") : L("Save"), target: self, action: #selector(didTapJobSaved(_:)))
         savedButton.jobPayload = job
         savedButton.jobPayload = job
         savedButton.cardContext = context
         savedButton.cardContext = context
         savedButton.setButtonType(.toggle)
         savedButton.setButtonType(.toggle)
@@ -986,7 +1089,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let dismissButton = JobPayloadButton()
         let dismissButton = JobPayloadButton()
         dismissButton.jobPayload = job
         dismissButton.jobPayload = job
         dismissButton.cardContext = context
         dismissButton.cardContext = context
-        dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Dismiss")
+        dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: L("Dismiss"))
         dismissButton.imagePosition = .imageOnly
         dismissButton.imagePosition = .imageOnly
         dismissButton.imageScaling = .scaleProportionallyDown
         dismissButton.imageScaling = .scaleProportionallyDown
         dismissButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
         dismissButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
@@ -995,7 +1098,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         dismissButton.contentTintColor = Theme.secondaryText
         dismissButton.contentTintColor = Theme.secondaryText
         dismissButton.target = self
         dismissButton.target = self
         dismissButton.action = #selector(didTapJobDismiss(_:))
         dismissButton.action = #selector(didTapJobDismiss(_:))
-        dismissButton.toolTip = context == .savedJobsPage ? "Remove from saved" : "Dismiss"
+        dismissButton.toolTip = context == .savedJobsPage ? L("Remove from saved") : L("Dismiss")
         dismissButton.focusRingType = .none
         dismissButton.focusRingType = .none
         dismissButton.wantsLayer = true
         dismissButton.wantsLayer = true
         dismissButton.layer?.cornerRadius = 8
         dismissButton.layer?.cornerRadius = 8
@@ -1199,7 +1302,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let willSave = !isJobSaved(job)
         let willSave = !isJobSaved(job)
         applySavedState(willSave, for: job)
         applySavedState(willSave, for: job)
         sender.state = willSave ? .on : .off
         sender.state = willSave ? .on : .off
-        sender.title = willSave ? "Saved" : "Save"
+        sender.title = willSave ? L("Saved") : L("Save")
         sender.image = NSImage(systemSymbolName: willSave ? "heart.fill" : "heart", accessibilityDescription: nil)
         sender.image = NSImage(systemSymbolName: willSave ? "heart.fill" : "heart", accessibilityDescription: nil)
         styleJobSavedButton(sender)
         styleJobSavedButton(sender)
         if isSavedJobsSidebarIndex(selectedSidebarIndex) {
         if isSavedJobsSidebarIndex(selectedSidebarIndex) {
@@ -1313,10 +1416,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
         jobSearchIcon.translatesAutoresizingMaskIntoConstraints = false
         jobSearchIcon.translatesAutoresizingMaskIntoConstraints = false
         jobSearchIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
         jobSearchIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
-        jobSearchIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: "Ask AI")
+        jobSearchIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: L("Ask AI"))
         jobSearchIcon.contentTintColor = Theme.brandBlue
         jobSearchIcon.contentTintColor = Theme.brandBlue
 
 
-        configureField(jobKeywordsField, placeholder: "Ask for roles, skills, salary, or job descriptions...")
+        configureField(jobKeywordsField, placeholder: L("Ask for roles, skills, salary, or job descriptions..."))
 
 
         let ctaHeight: CGFloat = 42
         let ctaHeight: CGFloat = 42
         let ctaCorner = ctaHeight / 2
         let ctaCorner = ctaHeight / 2
@@ -1366,7 +1469,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         findJobsButton.pointerCursor = true
         findJobsButton.pointerCursor = true
         findJobsButton.target = self
         findJobsButton.target = self
         findJobsButton.action = #selector(didSubmitSearch)
         findJobsButton.action = #selector(didSubmitSearch)
-        findJobsButton.setAccessibilityLabel("Send")
+        findJobsButton.setAccessibilityLabel(L("Send"))
         findJobsButton.hoverHandler = { [weak self] hovering in
         findJobsButton.hoverHandler = { [weak self] hovering in
             self?.findJobsCTAPill.layer?.backgroundColor = (hovering ? Theme.brandBlueHover : Theme.brandBlue).cgColor
             self?.findJobsCTAPill.layer?.backgroundColor = (hovering ? Theme.brandBlueHover : Theme.brandBlue).cgColor
         }
         }
@@ -1503,7 +1606,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         nonHomeSubtitleLabel.textColor = Theme.secondaryText
         nonHomeSubtitleLabel.textColor = Theme.secondaryText
         nonHomeSubtitleLabel.alignment = .center
         nonHomeSubtitleLabel.alignment = .center
         nonHomeSubtitleLabel.maximumNumberOfLines = 0
         nonHomeSubtitleLabel.maximumNumberOfLines = 0
-        nonHomeSubtitleLabel.stringValue = "This area is not available in the preview build. Use Home to search jobs."
+        nonHomeSubtitleLabel.stringValue = L("This area is not available in the preview build. Use Home to search jobs.")
 
 
         let genericStack = NSStackView(views: [nonHomeTitleLabel, nonHomeSubtitleLabel])
         let genericStack = NSStackView(views: [nonHomeTitleLabel, nonHomeSubtitleLabel])
         genericStack.orientation = .vertical
         genericStack.orientation = .vertical
@@ -1613,7 +1716,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     /// Switches the main panel to **Profile** so the user can pick a saved CV profile after choosing a template in CV Maker.
     /// Switches the main panel to **Profile** so the user can pick a saved CV profile after choosing a template in CV Maker.
     private func selectProfileSidebarForCVMakerFlow() {
     private func selectProfileSidebarForCVMakerFlow() {
-        guard let index = currentSidebarItems.firstIndex(where: { $0.title == "Profile" }) else { return }
+        guard let index = currentSidebarItems.firstIndex(where: { $0.title == L("Profile") }) else { return }
         selectSidebarItem(at: index)
         selectSidebarItem(at: index)
     }
     }
 
 
@@ -1719,13 +1822,13 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private func confirmDeleteProfile(id: UUID) {
     private func confirmDeleteProfile(id: UUID) {
         let displayName = SavedProfilesStore.profile(id: id)?.profileDisplayName ?? ""
         let displayName = SavedProfilesStore.profile(id: id)?.profileDisplayName ?? ""
         let alert = NSAlert()
         let alert = NSAlert()
-        alert.messageText = "Delete this profile?"
+        alert.messageText = L("Delete this profile?")
         alert.informativeText = displayName.isEmpty
         alert.informativeText = displayName.isEmpty
-            ? "This profile will be removed from this Mac."
-            : "“\(displayName)” will be removed from this Mac."
+            ? L("This profile will be removed from this Mac.")
+            : String(format: L("“%@” will be removed from this Mac."), displayName)
         alert.alertStyle = .warning
         alert.alertStyle = .warning
-        alert.addButton(withTitle: "Cancel")
-        alert.addButton(withTitle: "Delete")
+        alert.addButton(withTitle: L("Cancel"))
+        alert.addButton(withTitle: L("Delete"))
         guard let window = window else {
         guard let window = window else {
             let response = alert.runModal()
             let response = alert.runModal()
             if response == .alertSecondButtonReturn {
             if response == .alertSecondButtonReturn {
@@ -1758,19 +1861,19 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         contentStack.alignment = .leading
         contentStack.alignment = .leading
         contentStack.translatesAutoresizingMaskIntoConstraints = false
         contentStack.translatesAutoresizingMaskIntoConstraints = false
 
 
-        let appearanceTitle = NSTextField(labelWithString: "Appearance")
+        let appearanceTitle = NSTextField(labelWithString: L("Appearance"))
         appearanceTitle.font = .systemFont(ofSize: 12, weight: .semibold)
         appearanceTitle.font = .systemFont(ofSize: 12, weight: .semibold)
         appearanceTitle.textColor = Theme.secondaryText
         appearanceTitle.textColor = Theme.secondaryText
         appearanceTitle.alignment = .left
         appearanceTitle.alignment = .left
-        appearanceTitle.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.sectionHeader)
+        appearanceTitle.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.sectionHeader).Appearance")
 
 
         let themeSegment = makeAppearanceModeSegment()
         let themeSegment = makeAppearanceModeSegment()
         appearanceModeSegment = themeSegment
         appearanceModeSegment = themeSegment
         let langPopUp = makeLanguagePopUp()
         let langPopUp = makeLanguagePopUp()
         languagePopUp = langPopUp
         languagePopUp = langPopUp
         let appearanceSection = makeSettingsSection(rows: [
         let appearanceSection = makeSettingsSection(rows: [
-            makeSettingsRow(title: "Theme", systemImage: "circle.lefthalf.filled", accessory: themeSegment, tapAction: nil),
-            makeSettingsRow(title: "Language", systemImage: "character.bubble", accessory: langPopUp, tapAction: nil)
+            makeSettingsRow(localizationKey: "Theme", systemImage: "circle.lefthalf.filled", accessory: themeSegment, tapAction: nil),
+            makeSettingsRow(localizationKey: "Language", systemImage: "character.bubble", accessory: langPopUp, tapAction: nil)
         ])
         ])
 
 
         let appearanceStack = NSStackView(views: [appearanceTitle, appearanceSection])
         let appearanceStack = NSStackView(views: [appearanceTitle, appearanceSection])
@@ -1780,21 +1883,21 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         appearanceStack.translatesAutoresizingMaskIntoConstraints = false
         appearanceStack.translatesAutoresizingMaskIntoConstraints = false
 
 
         let settingsSection = makeSettingsSection(rows: [
         let settingsSection = makeSettingsSection(rows: [
-            makeSettingsRow(title: "Share App", systemImage: "square.and.arrow.up", accessory: nil, tapAction: #selector(didTapShareApp)),
-            makeSettingsRow(title: "More Apps", systemImage: "square.grid.2x2", accessory: nil, tapAction: #selector(didTapMoreApps))
+            makeSettingsRow(localizationKey: "Share App", systemImage: "square.and.arrow.up", accessory: nil, tapAction: #selector(didTapShareApp)),
+            makeSettingsRow(localizationKey: "More Apps", systemImage: "square.grid.2x2", accessory: nil, tapAction: #selector(didTapMoreApps))
         ])
         ])
 
 
-        let aboutTitle = NSTextField(labelWithString: "About")
+        let aboutTitle = NSTextField(labelWithString: L("About"))
         aboutTitle.font = .systemFont(ofSize: 12, weight: .semibold)
         aboutTitle.font = .systemFont(ofSize: 12, weight: .semibold)
         aboutTitle.textColor = Theme.secondaryText
         aboutTitle.textColor = Theme.secondaryText
         aboutTitle.alignment = .left
         aboutTitle.alignment = .left
-        aboutTitle.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.sectionHeader)
+        aboutTitle.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.sectionHeader).About")
 
 
         let aboutSection = makeSettingsSection(rows: [
         let aboutSection = makeSettingsSection(rows: [
-            makeSettingsRow(title: "Website", systemImage: "globe", accessory: nil, tapAction: #selector(didTapWebsite)),
-            makeSettingsRow(title: "Support", systemImage: "questionmark.circle", accessory: nil, tapAction: #selector(didTapSupport)),
-            makeSettingsRow(title: "Terms of Use", systemImage: "doc.text", accessory: nil, tapAction: #selector(didTapTermsOfUse)),
-            makeSettingsRow(title: "Privacy Policy", systemImage: "shield", accessory: nil, tapAction: #selector(didTapPrivacyPolicy))
+            makeSettingsRow(localizationKey: "Website", systemImage: "globe", accessory: nil, tapAction: #selector(didTapWebsite)),
+            makeSettingsRow(localizationKey: "Support", systemImage: "questionmark.circle", accessory: nil, tapAction: #selector(didTapSupport)),
+            makeSettingsRow(localizationKey: "Terms of Use", systemImage: "doc.text", accessory: nil, tapAction: #selector(didTapTermsOfUse)),
+            makeSettingsRow(localizationKey: "Privacy Policy", systemImage: "shield", accessory: nil, tapAction: #selector(didTapPrivacyPolicy))
         ])
         ])
 
 
         let aboutStack = NSStackView(views: [aboutTitle, aboutSection])
         let aboutStack = NSStackView(views: [aboutTitle, aboutSection])
@@ -1823,7 +1926,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func makeAppearanceModeSegment() -> NSSegmentedControl {
     private func makeAppearanceModeSegment() -> NSSegmentedControl {
         let segment = NSSegmentedControl(
         let segment = NSSegmentedControl(
-            labels: ["System", "Light", "Dark"],
+            labels: [L("System"), L("Light"), L("Dark")],
             trackingMode: .selectOne,
             trackingMode: .selectOne,
             target: self,
             target: self,
             action: #selector(appearanceModeChanged(_:))
             action: #selector(appearanceModeChanged(_:))
@@ -1841,30 +1944,24 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         AppAppearanceManager.shared.mode = mode
         AppAppearanceManager.shared.mode = mode
     }
     }
 
 
-    private static let languageUserDefaultsKey = "com.appforindeed.preferredLanguage"
-
-    private static let supportedLanguages: [(code: String, title: String)] = [
-        ("en", "English")
-    ]
-
     private func makeLanguagePopUp() -> NSPopUpButton {
     private func makeLanguagePopUp() -> NSPopUpButton {
         let popup = NSPopUpButton(frame: .zero, pullsDown: false)
         let popup = NSPopUpButton(frame: .zero, pullsDown: false)
         popup.translatesAutoresizingMaskIntoConstraints = false
         popup.translatesAutoresizingMaskIntoConstraints = false
         popup.removeAllItems()
         popup.removeAllItems()
 
 
-        for lang in Self.supportedLanguages {
-            popup.addItem(withTitle: lang.title)
-            popup.lastItem?.representedObject = lang.code
+        for language in AppLanguage.allCases {
+            popup.addItem(withTitle: language.localizedDisplayName)
+            popup.lastItem?.representedObject = language.localeIdentifier
         }
         }
 
 
-        let saved = UserDefaults.standard.string(forKey: Self.languageUserDefaultsKey) ?? "en"
-        if let index = Self.supportedLanguages.firstIndex(where: { $0.code == saved }) {
+        let currentCode = AppLanguageManager.shared.current.localeIdentifier
+        if let index = AppLanguage.allCases.firstIndex(where: { $0.localeIdentifier == currentCode }) {
             popup.selectItem(at: index)
             popup.selectItem(at: index)
         }
         }
 
 
         popup.target = self
         popup.target = self
         popup.action = #selector(languageChanged(_:))
         popup.action = #selector(languageChanged(_:))
-        popup.isEnabled = !Self.supportedLanguages.isEmpty
+        popup.isEnabled = !AppLanguage.allCases.isEmpty
         popup.setContentHuggingPriority(.required, for: .horizontal)
         popup.setContentHuggingPriority(.required, for: .horizontal)
         popup.setContentCompressionResistancePriority(.required, for: .horizontal)
         popup.setContentCompressionResistancePriority(.required, for: .horizontal)
         return popup
         return popup
@@ -1872,8 +1969,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     @objc private func languageChanged(_ sender: NSPopUpButton) {
     @objc private func languageChanged(_ sender: NSPopUpButton) {
         guard let code = sender.selectedItem?.representedObject as? String else { return }
         guard let code = sender.selectedItem?.representedObject as? String else { return }
-        UserDefaults.standard.set(code, forKey: Self.languageUserDefaultsKey)
-        UserDefaults.standard.set([code], forKey: "AppleLanguages")
+        AppLanguageManager.shared.setLanguage(code: code)
     }
     }
 
 
     private func makeSettingsSection(rows: [NSView]) -> NSView {
     private func makeSettingsSection(rows: [NSView]) -> NSView {
@@ -1912,7 +2008,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         return section
         return section
     }
     }
 
 
-    private func makeSettingsRow(title: String, systemImage: String, accessory: NSView?, tapAction: Selector? = nil) -> NSView {
+    private func makeSettingsRow(localizationKey: String, systemImage: String, accessory: NSView?, tapAction: Selector? = nil) -> NSView {
         let row = NSView()
         let row = NSView()
         row.translatesAutoresizingMaskIntoConstraints = false
         row.translatesAutoresizingMaskIntoConstraints = false
         row.wantsLayer = true
         row.wantsLayer = true
@@ -1927,14 +2023,14 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let icon = NSImageView()
         let icon = NSImageView()
         icon.translatesAutoresizingMaskIntoConstraints = false
         icon.translatesAutoresizingMaskIntoConstraints = false
         icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
         icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
-        icon.image = NSImage(systemSymbolName: systemImage, accessibilityDescription: title)
+        icon.image = NSImage(systemSymbolName: systemImage, accessibilityDescription: L(localizationKey))
         icon.contentTintColor = Theme.brandBlue
         icon.contentTintColor = Theme.brandBlue
 
 
-        let titleLabel = NSTextField(labelWithString: title)
+        let titleLabel = NSTextField(labelWithString: L(localizationKey))
         titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
         titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
         titleLabel.textColor = Theme.primaryText
         titleLabel.textColor = Theme.primaryText
         titleLabel.alignment = .left
         titleLabel.alignment = .left
-        titleLabel.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.rowTitle)
+        titleLabel.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.rowTitle).\(localizationKey)")
 
 
         let rowStack = NSStackView()
         let rowStack = NSStackView()
         rowStack.orientation = .horizontal
         rowStack.orientation = .horizontal
@@ -1997,8 +2093,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             $0.removeFromSuperview()
             $0.removeFromSuperview()
         }
         }
         if savedJobOrder.isEmpty {
         if savedJobOrder.isEmpty {
-            savedJobsPageSubtitleLabel.stringValue = "Save jobs from Home to see them here."
-            let empty = NSTextField(wrappingLabelWithString: "No saved jobs yet. Search on Home, then tap Save on a listing.")
+            savedJobsPageSubtitleLabel.stringValue = L("Save jobs from Home to see them here.")
+            let empty = NSTextField(wrappingLabelWithString: L("No saved jobs yet. Search on Home, then tap Save on a listing."))
             empty.font = .systemFont(ofSize: 14, weight: .regular)
             empty.font = .systemFont(ofSize: 14, weight: .regular)
             empty.textColor = Theme.secondaryText
             empty.textColor = Theme.secondaryText
             empty.alignment = .left
             empty.alignment = .left
@@ -2008,7 +2104,9 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             empty.widthAnchor.constraint(equalTo: savedJobsStack.widthAnchor).isActive = true
             empty.widthAnchor.constraint(equalTo: savedJobsStack.widthAnchor).isActive = true
             return
             return
         }
         }
-        savedJobsPageSubtitleLabel.stringValue = "\(savedJobOrder.count) saved \(savedJobOrder.count == 1 ? "position" : "positions")"
+        savedJobsPageSubtitleLabel.stringValue = savedJobOrder.count == 1
+            ? L("1 saved position")
+            : String(format: L("%d saved positions"), savedJobOrder.count)
         for job in savedJobOrder {
         for job in savedJobOrder {
             let card = makeJobListingCard(job, context: .savedJobsPage)
             let card = makeJobListingCard(job, context: .savedJobsPage)
             savedJobsStack.addArrangedSubview(card)
             savedJobsStack.addArrangedSubview(card)
@@ -2018,27 +2116,27 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func isSavedJobsSidebarIndex(_ index: Int) -> Bool {
     private func isSavedJobsSidebarIndex(_ index: Int) -> Bool {
         guard index >= 0, index < currentSidebarItems.count else { return false }
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "Saved Jobs"
+        return currentSidebarItems[index].title == L("Saved Jobs")
     }
     }
 
 
     private func isHomeSidebarIndex(_ index: Int) -> Bool {
     private func isHomeSidebarIndex(_ index: Int) -> Bool {
         guard index >= 0, index < currentSidebarItems.count else { return false }
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "Home"
+        return currentSidebarItems[index].title == L("Home")
     }
     }
 
 
     private func isSettingsSidebarIndex(_ index: Int) -> Bool {
     private func isSettingsSidebarIndex(_ index: Int) -> Bool {
         guard index >= 0, index < currentSidebarItems.count else { return false }
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "Settings"
+        return currentSidebarItems[index].title == L("Settings")
     }
     }
 
 
     private func isCVMakerSidebarIndex(_ index: Int) -> Bool {
     private func isCVMakerSidebarIndex(_ index: Int) -> Bool {
         guard index >= 0, index < currentSidebarItems.count else { return false }
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "CV Maker"
+        return currentSidebarItems[index].title == L("CV Maker")
     }
     }
 
 
     private func isProfileSidebarIndex(_ index: Int) -> Bool {
     private func isProfileSidebarIndex(_ index: Int) -> Bool {
         guard index >= 0, index < currentSidebarItems.count else { return false }
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "Profile"
+        return currentSidebarItems[index].title == L("Profile")
     }
     }
 
 
     private func updateMainContentVisibility() {
     private func updateMainContentVisibility() {
@@ -2132,17 +2230,17 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     @objc private func didTapFeatureRole() {
     @objc private func didTapFeatureRole() {
         selectFeatureShortcut(.role)
         selectFeatureShortcut(.role)
-        focusSearchField(seed: "Find roles similar to: ")
+        focusSearchField(seed: L("Find roles similar to: "))
     }
     }
 
 
     @objc private func didTapFeatureCompany() {
     @objc private func didTapFeatureCompany() {
         selectFeatureShortcut(.company)
         selectFeatureShortcut(.company)
-        focusSearchField(seed: "Find jobs at company: ")
+        focusSearchField(seed: L("Find jobs at company: "))
     }
     }
 
 
     @objc private func didTapFeatureSkill() {
     @objc private func didTapFeatureSkill() {
         selectFeatureShortcut(.skill)
         selectFeatureShortcut(.skill)
-        focusSearchField(seed: "Find jobs that require skill: ")
+        focusSearchField(seed: L("Find jobs that require skill: "))
     }
     }
 
 
     private func selectFeatureShortcut(_ shortcut: FeatureShortcut) {
     private func selectFeatureShortcut(_ shortcut: FeatureShortcut) {
@@ -2196,7 +2294,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     @objc private func didTapMoreApps() {
     @objc private func didTapMoreApps() {
         guard let url = AppMarketingLinks.developerAppsURL else {
         guard let url = AppMarketingLinks.developerAppsURL else {
-            presentAppMarketingConfigurationAlert(feature: "More Apps")
+            presentAppMarketingConfigurationAlert(feature: L("More Apps"))
             return
             return
         }
         }
         NSWorkspace.shared.open(url)
         NSWorkspace.shared.open(url)
@@ -2204,14 +2302,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func presentAppMarketingConfigurationAlert(feature: String) {
     private func presentAppMarketingConfigurationAlert(feature: String) {
         let alert = NSAlert()
         let alert = NSAlert()
-        alert.messageText = "\(feature) isn’t available yet"
-        alert.informativeText = """
-        Add your Mac App Store IDs in the target’s build settings:
-        • AppStoreAppID — numeric app ID from App Store Connect
-        • AppStoreDeveloperID — numeric developer ID (for your other apps page)
-        """
+        alert.messageText = String(format: L("%@ isn’t available yet"), feature)
+        alert.informativeText = L("Add your Mac App Store IDs in the target’s build settings:\n• AppStoreAppID — numeric app ID from App Store Connect\n• AppStoreDeveloperID — numeric developer ID (for your other apps page)")
         alert.alertStyle = .informational
         alert.alertStyle = .informational
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
         if let window {
             alert.beginSheetModal(for: window)
             alert.beginSheetModal(for: window)
         } else {
         } else {
@@ -2246,7 +2340,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     @objc private func didTapLoadMoreJobs() {
     @objc private func didTapLoadMoreJobs() {
         guard ensureProAccessForJobSearch() else { return }
         guard ensureProAccessForJobSearch() else { return }
-        let prompt = "Show more jobs"
+        let prompt = L("Show more jobs")
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
         if anchorUserJobQuery(excludingLatestUserMessage: prompt) == nil { return }
         if anchorUserJobQuery(excludingLatestUserMessage: prompt) == nil { return }
         appendChatBubble(text: prompt, isUser: true)
         appendChatBubble(text: prompt, isUser: true)
@@ -2309,15 +2403,15 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private func makeAssistantSearchReply(query: String, newJobsCount: Int, isContinuation: Bool) -> String {
     private func makeAssistantSearchReply(query: String, newJobsCount: Int, isContinuation: Bool) -> String {
         if newJobsCount == 0 {
         if newJobsCount == 0 {
             if isContinuation {
             if isContinuation {
-                return "I couldn't find new matches for \u{201C}\(query)\u{201D}. Try a different angle or a more specific keyword."
+                return String(format: L("I couldn't find new matches for “%@”. Try a different angle or a more specific keyword."), query)
             }
             }
-            return "No jobs found for \u{201C}\(query)\u{201D}. Try another title, skill, company, or location."
+            return String(format: L("No jobs found for “%@”. Try another title, skill, company, or location."), query)
         }
         }
-        let plural = newJobsCount == 1 ? "match" : "matches"
+        let matchWord = newJobsCount == 1 ? L("match") : L("matches")
         if isContinuation {
         if isContinuation {
-            return "Here are \(newJobsCount) more \(plural) for \u{201C}\(query)\u{201D}."
+            return String(format: L("Here are %d more %@ for “%@”."), newJobsCount, matchWord, query)
         }
         }
-        return "Found \(newJobsCount) \(plural) for \u{201C}\(query)\u{201D}. Tap Apply to open the listing or Save to revisit later."
+        return String(format: L("Found %d %@ for “%@”. Tap Apply to open the listing or Save to revisit later."), newJobsCount, matchWord, query)
     }
     }
 
 
     private func resolvedSearchQuery(for prompt: String) -> String {
     private func resolvedSearchQuery(for prompt: String) -> String {
@@ -2347,6 +2441,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
 
     private func isContinuationPrompt(_ prompt: String) -> Bool {
     private func isContinuationPrompt(_ prompt: String) -> Bool {
         let normalized = prompt.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
         let normalized = prompt.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+        let showMoreJobs = L("Show more jobs").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+        if normalized == showMoreJobs {
+            return true
+        }
         let continuationPhrases: Set<String> = [
         let continuationPhrases: Set<String> = [
             "more",
             "more",
             "show more",
             "show more",
@@ -2423,7 +2521,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         chatMessages.removeAll()
         chatMessages.removeAll()
         lastSearchResults.removeAll()
         lastSearchResults.removeAll()
         clearChatStack()
         clearChatStack()
-        let welcome = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary."
+        let welcome = L("Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary.")
         chatMessages.append(ChatMessage(role: "assistant", content: welcome))
         chatMessages.append(ChatMessage(role: "assistant", content: welcome))
         appendChatBubble(text: welcome, isUser: false)
         appendChatBubble(text: welcome, isUser: false)
     }
     }
@@ -2620,7 +2718,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         row.translatesAutoresizingMaskIntoConstraints = false
         row.translatesAutoresizingMaskIntoConstraints = false
         let button = HoverableButton()
         let button = HoverableButton()
         button.pointerCursor = true
         button.pointerCursor = true
-        button.title = "Show more jobs"
+        button.title = L("Show more jobs")
         button.font = .systemFont(ofSize: 12, weight: .semibold)
         button.font = .systemFont(ofSize: 12, weight: .semibold)
         button.bezelStyle = .rounded
         button.bezelStyle = .rounded
         button.controlSize = .regular
         button.controlSize = .regular
@@ -2730,10 +2828,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         rowHost.layer?.cornerRadius = 8
         rowHost.layer?.cornerRadius = 8
         rowHost.restingBackgroundColor = isSelected ? Theme.selectionFill : nil
         rowHost.restingBackgroundColor = isSelected ? Theme.selectionFill : nil
         rowHost.hoverBackgroundColor = isSelected ? Theme.selectionFillHover : Theme.sidebarRowHoverFill
         rowHost.hoverBackgroundColor = isSelected ? Theme.selectionFillHover : Theme.sidebarRowHoverFill
-        rowHost.setAccessibilityLabel("Indeed")
+        rowHost.setAccessibilityLabel(L("Indeed"))
         rowHost.setAccessibilityRole(.button)
         rowHost.setAccessibilityRole(.button)
         rowHost.setAccessibilitySelected(isSelected)
         rowHost.setAccessibilitySelected(isSelected)
-        rowHost.setAccessibilityHelp("Open Indeed to search and apply for jobs")
+        rowHost.setAccessibilityHelp(L("Open Indeed to search and apply for jobs"))
 
 
         let row = NSStackView()
         let row = NSStackView()
         row.orientation = .horizontal
         row.orientation = .horizontal
@@ -2749,7 +2847,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         icon.widthAnchor.constraint(equalToConstant: Self.sidebarNavIconSize).isActive = true
         icon.widthAnchor.constraint(equalToConstant: Self.sidebarNavIconSize).isActive = true
         icon.heightAnchor.constraint(equalToConstant: Self.sidebarNavIconSize).isActive = true
         icon.heightAnchor.constraint(equalToConstant: Self.sidebarNavIconSize).isActive = true
 
 
-        let text = NSTextField(labelWithString: "Indeed")
+        let text = NSTextField(labelWithString: L("Indeed"))
         text.font = .systemFont(ofSize: 14, weight: .medium)
         text.font = .systemFont(ofSize: 14, weight: .medium)
         text.textColor = isSelected ? Theme.brandBlue : Theme.secondaryText
         text.textColor = isSelected ? Theme.brandBlue : Theme.secondaryText
         text.refusesFirstResponder = true
         text.refusesFirstResponder = true
@@ -2859,7 +2957,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             let sidebarHorizontalInset = sidebar.edgeInsets.left + sidebar.edgeInsets.right
             let sidebarHorizontalInset = sidebar.edgeInsets.left + sidebar.edgeInsets.right
             rowHost.widthAnchor.constraint(equalTo: sidebar.widthAnchor, constant: -sidebarHorizontalInset).isActive = true
             rowHost.widthAnchor.constraint(equalTo: sidebar.widthAnchor, constant: -sidebarHorizontalInset).isActive = true
 
 
-            if item.title == "Home" {
+            if item.title == L("Home") {
                 addIndeedSidebarLaunchRow()
                 addIndeedSidebarLaunchRow()
             }
             }
         }
         }
@@ -2896,7 +2994,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         proIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
         proIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
         proIcon.contentTintColor = Theme.proAccent
         proIcon.contentTintColor = Theme.proAccent
 
 
-        let proEyebrow = NSTextField(labelWithString: "Premium")
+        let proEyebrow = NSTextField(labelWithString: L("Premium"))
         proEyebrow.font = .systemFont(ofSize: 11, weight: .heavy)
         proEyebrow.font = .systemFont(ofSize: 11, weight: .heavy)
         proEyebrow.textColor = Theme.proAccent
         proEyebrow.textColor = Theme.proAccent
         proEyebrow.alignment = .center
         proEyebrow.alignment = .center
@@ -2906,12 +3004,12 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         eyebrowRow.spacing = 6
         eyebrowRow.spacing = 6
         eyebrowRow.alignment = .centerY
         eyebrowRow.alignment = .centerY
 
 
-        let headline = NSTextField(labelWithString: "Upgrade to Pro")
+        let headline = NSTextField(labelWithString: L("Upgrade to Pro"))
         headline.font = .systemFont(ofSize: 16, weight: .bold)
         headline.font = .systemFont(ofSize: 16, weight: .bold)
         headline.textColor = Theme.primaryText
         headline.textColor = Theme.primaryText
         headline.alignment = .center
         headline.alignment = .center
 
 
-        let upgradeDescription = NSTextField(wrappingLabelWithString: "Unlimited AI matches, smart alerts, and interview prep—all in one place.")
+        let upgradeDescription = NSTextField(wrappingLabelWithString: L("Unlimited AI matches, smart alerts, and interview prep—all in one place."))
         upgradeDescription.font = .systemFont(ofSize: 12, weight: .regular)
         upgradeDescription.font = .systemFont(ofSize: 12, weight: .regular)
         upgradeDescription.textColor = Theme.secondaryText
         upgradeDescription.textColor = Theme.secondaryText
         upgradeDescription.alignment = .center
         upgradeDescription.alignment = .center
@@ -2920,7 +3018,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let innerContentWidth = cardWidth - 28
         let innerContentWidth = cardWidth - 28
         upgradeDescription.preferredMaxLayoutWidth = innerContentWidth
         upgradeDescription.preferredMaxLayoutWidth = innerContentWidth
 
 
-        let upgradeButton = HoverableButton(title: "Try Pro", target: self, action: #selector(didTapUpgradeToPro))
+        let upgradeButton = HoverableButton(title: L("Try Pro"), target: self, action: #selector(didTapUpgradeToPro))
         upgradeButton.isBordered = false
         upgradeButton.isBordered = false
         upgradeButton.bezelStyle = .rounded
         upgradeButton.bezelStyle = .rounded
         upgradeButton.font = .systemFont(ofSize: 13, weight: .bold)
         upgradeButton.font = .systemFont(ofSize: 13, weight: .bold)
@@ -3051,7 +3149,7 @@ private final class OpenAIJobSearchService {
             completion(.failure(NSError(
             completion(.failure(NSError(
                 domain: "OpenAIJobSearchService",
                 domain: "OpenAIJobSearchService",
                 code: 1,
                 code: 1,
-                userInfo: [NSLocalizedDescriptionKey: "Job search is unavailable."]
+                userInfo: [NSLocalizedDescriptionKey: L("Job search is unavailable.")]
             )))
             )))
             return
             return
         }
         }

+ 7 - 7
App for Indeed/Views/LoadingView.swift

@@ -29,14 +29,14 @@ final class LoadingView: NSView {
     private let iconWell = NSView()
     private let iconWell = NSView()
     private let logoView = IndeedLogoView(displayHeight: 40, variant: .compact)
     private let logoView = IndeedLogoView(displayHeight: 40, variant: .compact)
     private let aiBadgeHost = NSView()
     private let aiBadgeHost = NSView()
-    private let aiBadgeLabel = NSTextField(labelWithString: "AI-POWERED")
+    private let aiBadgeLabel = NSTextField(labelWithString: L("AI-POWERED"))
     private let titleLabel = NSTextField(labelWithString: AppMarketingLinks.displayName)
     private let titleLabel = NSTextField(labelWithString: AppMarketingLinks.displayName)
-    private let subtitleLabel = NSTextField(labelWithString: "Find your perfect job with the power of AI.")
-    private let statusLabel = NSTextField(labelWithString: "Starting up…")
+    private let subtitleLabel = NSTextField(labelWithString: L("Find your perfect job with the power of AI."))
+    private let statusLabel = NSTextField(labelWithString: L("Starting up…"))
     private let progressBar = LoadingProgressBarView()
     private let progressBar = LoadingProgressBarView()
     private let thinkingIndicator = ChatThinkingIndicatorView(
     private let thinkingIndicator = ChatThinkingIndicatorView(
         compact: false,
         compact: false,
-        accessibilityLabel: "Loading \(AppMarketingLinks.displayName)"
+        accessibilityLabel: String(format: L("Loading %@"), AppMarketingLinks.displayName)
     )
     )
 
 
     override init(frame frameRect: NSRect) {
     override init(frame frameRect: NSRect) {
@@ -87,7 +87,7 @@ final class LoadingView: NSView {
     func setStatus(_ message: String, progress: CGFloat) {
     func setStatus(_ message: String, progress: CGFloat) {
         statusLabel.stringValue = message
         statusLabel.stringValue = message
         progressBar.setProgress(progress, animated: true)
         progressBar.setProgress(progress, animated: true)
-        setAccessibilityLabel("Loading \(AppMarketingLinks.displayName). \(message)")
+        setAccessibilityLabel(String(format: L("Loading %@. %@"), AppMarketingLinks.displayName, message))
     }
     }
 
 
     func startAnimating() {
     func startAnimating() {
@@ -236,7 +236,7 @@ final class LoadingView: NSView {
         installPageGradient()
         installPageGradient()
         setAccessibilityElement(true)
         setAccessibilityElement(true)
         setAccessibilityRole(.group)
         setAccessibilityRole(.group)
-        setAccessibilityLabel("Loading \(AppMarketingLinks.displayName)")
+        setAccessibilityLabel(String(format: L("Loading %@"), AppMarketingLinks.displayName))
     }
     }
 
 
     private func installPageGradient() {
     private func installPageGradient() {
@@ -449,7 +449,7 @@ private final class LoadingProgressBarView: NSView {
 
 
         setAccessibilityElement(true)
         setAccessibilityElement(true)
         setAccessibilityRole(.progressIndicator)
         setAccessibilityRole(.progressIndicator)
-        setAccessibilityLabel("Loading progress")
+        setAccessibilityLabel(L("Loading progress"))
     }
     }
 }
 }
 
 

+ 74 - 53
App for Indeed/Views/MyProfilePageView.swift

@@ -196,13 +196,13 @@ final class MyProfilePageView: NSView {
     private let interestsField = NSTextField()
     private let interestsField = NSTextField()
     private let languagesField = NSTextField()
     private let languagesField = NSTextField()
     private let referralField = NSTextField()
     private let referralField = NSTextField()
-    private let saveButton = ProfilePrimaryButton(title: "Save Profile  →", target: nil, action: nil)
+    private let saveButton = ProfilePrimaryButton(title: L("Save Profile  →"), target: nil, action: nil)
 
 
     private var nameEmailRow: ProfileDualFieldRow!
     private var nameEmailRow: ProfileDualFieldRow!
     private var phoneJobRow: ProfileDualFieldRow!
     private var phoneJobRow: ProfileDualFieldRow!
 
 
     private let topChrome = NSView()
     private let topChrome = NSView()
-    private let backButton = NSButton(title: "← All profiles", target: nil, action: nil)
+    private let backButton = NSButton(title: L("← All profiles"), target: nil, action: nil)
     private let contextLabel = NSTextField(labelWithString: "")
     private let contextLabel = NSTextField(labelWithString: "")
     private var editingProfileID: UUID?
     private var editingProfileID: UUID?
 
 
@@ -218,6 +218,7 @@ final class MyProfilePageView: NSView {
 
 
     private var referralHelperLabel: NSTextField?
     private var referralHelperLabel: NSTextField?
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
 
     /// Force left-to-right geometry so profile fields span the full width even when the window uses RTL layout.
     /// Force left-to-right geometry so profile fields span the full width even when the window uses RTL layout.
     override var userInterfaceLayoutDirection: NSUserInterfaceLayoutDirection {
     override var userInterfaceLayoutDirection: NSUserInterfaceLayoutDirection {
@@ -235,13 +236,24 @@ final class MyProfilePageView: NSView {
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
     }
 
 
     deinit {
     deinit {
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     required init?(coder: NSCoder) {
     required init?(coder: NSCoder) {
@@ -266,6 +278,15 @@ final class MyProfilePageView: NSView {
         saveButton.applyCurrentAppearance()
         saveButton.applyCurrentAppearance()
     }
     }
 
 
+    func applyLocalizedStrings() {
+        backButton.title = L("← All profiles")
+        saveButton.title = L("Save Profile  →")
+        contextLabel.stringValue = editingProfileID == nil ? L("New profile") : L("Edit profile")
+        renumberWorkExperienceEntries()
+        renumberEducationEntries()
+        ProfileThemeAppearance.refreshFormSubtree(formStack)
+    }
+
     override func viewDidMoveToWindow() {
     override func viewDidMoveToWindow() {
         super.viewDidMoveToWindow()
         super.viewDidMoveToWindow()
         guard window != nil else { return }
         guard window != nil else { return }
@@ -381,7 +402,7 @@ final class MyProfilePageView: NSView {
         contextLabel.translatesAutoresizingMaskIntoConstraints = false
         contextLabel.translatesAutoresizingMaskIntoConstraints = false
         contextLabel.font = .systemFont(ofSize: 15, weight: .semibold)
         contextLabel.font = .systemFont(ofSize: 15, weight: .semibold)
         contextLabel.textColor = ProfilePagePalette.primaryText
         contextLabel.textColor = ProfilePagePalette.primaryText
-        contextLabel.stringValue = "New profile"
+        contextLabel.stringValue = L("New profile")
         contextLabel.backgroundColor = .clear
         contextLabel.backgroundColor = .clear
         contextLabel.isBordered = false
         contextLabel.isBordered = false
         contextLabel.isEditable = false
         contextLabel.isEditable = false
@@ -433,22 +454,22 @@ final class MyProfilePageView: NSView {
         ])
         ])
 
 
         addFullWidthArrangedSubview(
         addFullWidthArrangedSubview(
-            labeledGroup(title: "Profile Name *", field: profileNameField, placeholder: "Marketing Director Profile")
+            labeledGroup(title: L("Profile Name *"), field: profileNameField, placeholder: L("Marketing Director Profile"))
         )
         )
-        addFullWidthArrangedSubview(sectionHeading("Personal Information"))
+        addFullWidthArrangedSubview(sectionHeading(L("Personal Information")))
 
 
-        let nameGroup = labeledGroup(title: "Full Name *", field: fullNameField, placeholder: "John Doe")
-        let emailGroup = labeledGroup(title: "Email *", field: emailField, placeholder: "john@example.com")
+        let nameGroup = labeledGroup(title: L("Full Name *"), field: fullNameField, placeholder: L("John Doe"))
+        let emailGroup = labeledGroup(title: L("Email *"), field: emailField, placeholder: L("john@example.com"))
         nameEmailRow = ProfileDualFieldRow(left: nameGroup, right: emailGroup, spacing: 12)
         nameEmailRow = ProfileDualFieldRow(left: nameGroup, right: emailGroup, spacing: 12)
         addFullWidthArrangedSubview(nameEmailRow)
         addFullWidthArrangedSubview(nameEmailRow)
 
 
-        let phoneGroup = labeledGroup(title: "Phone", field: phoneField, placeholder: "+1 (555) 123-4567")
-        let jobGroup = labeledGroup(title: "Job Title *", field: jobTitleField, placeholder: "Software Engineer")
+        let phoneGroup = labeledGroup(title: L("Phone"), field: phoneField, placeholder: L("+1 (555) 123-4567"))
+        let jobGroup = labeledGroup(title: L("Job Title *"), field: jobTitleField, placeholder: L("Software Engineer"))
         phoneJobRow = ProfileDualFieldRow(left: phoneGroup, right: jobGroup, spacing: 12)
         phoneJobRow = ProfileDualFieldRow(left: phoneGroup, right: jobGroup, spacing: 12)
         addFullWidthArrangedSubview(phoneJobRow)
         addFullWidthArrangedSubview(phoneJobRow)
 
 
         addFullWidthArrangedSubview(
         addFullWidthArrangedSubview(
-            labeledGroup(title: "Address", field: addressField, placeholder: "123 Main St, City, State, ZIP")
+            labeledGroup(title: L("Address"), field: addressField, placeholder: L("123 Main St, City, State, ZIP"))
         )
         )
         addFullWidthArrangedSubview(careerSummaryBlock())
         addFullWidthArrangedSubview(careerSummaryBlock())
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(horizontalSeparator())
@@ -458,8 +479,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
         addFullWidthArrangedSubview(
             multilineProfileBlock(
             multilineProfileBlock(
-                title: "Certificates / Rewards",
-                placeholder: "List your certificates and awards...",
+                title: L("Certificates / Rewards"),
+                placeholder: L("List your certificates and awards..."),
                 field: certificatesField,
                 field: certificatesField,
                 minHeight: 100
                 minHeight: 100
             )
             )
@@ -467,8 +488,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
         addFullWidthArrangedSubview(
             multilineProfileBlock(
             multilineProfileBlock(
-                title: "Interests",
-                placeholder: "List your interests and hobbies...",
+                title: L("Interests"),
+                placeholder: L("List your interests and hobbies..."),
                 field: interestsField,
                 field: interestsField,
                 minHeight: 100
                 minHeight: 100
             )
             )
@@ -476,8 +497,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
         addFullWidthArrangedSubview(
             multilineProfileBlock(
             multilineProfileBlock(
-                title: "Languages",
-                placeholder: "List languages you speak (e.g., English - Native, Spanish - Fluent)...",
+                title: L("Languages"),
+                placeholder: L("List languages you speak (e.g., English - Native, Spanish - Fluent)..."),
                 field: languagesField,
                 field: languagesField,
                 minHeight: 100
                 minHeight: 100
             )
             )
@@ -497,7 +518,7 @@ final class MyProfilePageView: NSView {
 
 
     func prepareNewProfile() {
     func prepareNewProfile() {
         editingProfileID = nil
         editingProfileID = nil
-        contextLabel.stringValue = "New profile"
+        contextLabel.stringValue = L("New profile")
         applyForm(
         applyForm(
             from: SavedProfile(
             from: SavedProfile(
                 id: UUID(),
                 id: UUID(),
@@ -516,7 +537,7 @@ final class MyProfilePageView: NSView {
 
 
     func loadSavedProfile(_ profile: SavedProfile) {
     func loadSavedProfile(_ profile: SavedProfile) {
         editingProfileID = profile.id
         editingProfileID = profile.id
-        contextLabel.stringValue = "Edit profile"
+        contextLabel.stringValue = L("Edit profile")
         applyForm(from: profile)
         applyForm(from: profile)
     }
     }
 
 
@@ -741,7 +762,7 @@ final class MyProfilePageView: NSView {
     }
     }
 
 
     private func careerSummaryBlock() -> NSView {
     private func careerSummaryBlock() -> NSView {
-        let label = NSTextField(labelWithString: "Career Summary")
+        let label = NSTextField(labelWithString: L("Career Summary"))
         label.font = .systemFont(ofSize: 12, weight: .medium)
         label.font = .systemFont(ofSize: 12, weight: .medium)
         label.textColor = ProfilePagePalette.secondaryText
         label.textColor = ProfilePagePalette.secondaryText
         label.translatesAutoresizingMaskIntoConstraints = false
         label.translatesAutoresizingMaskIntoConstraints = false
@@ -763,7 +784,7 @@ final class MyProfilePageView: NSView {
         careerField.setContentHuggingPriority(.defaultLow, for: .horizontal)
         careerField.setContentHuggingPriority(.defaultLow, for: .horizontal)
         careerField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
         careerField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
         careerField.placeholderAttributedString = NSAttributedString(
         careerField.placeholderAttributedString = NSAttributedString(
-            string: "Brief overview of your professional background and key achievements...",
+            string: L("Brief overview of your professional background and key achievements..."),
             attributes: [
             attributes: [
                 .foregroundColor: ProfilePagePalette.secondaryText,
                 .foregroundColor: ProfilePagePalette.secondaryText,
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular),
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular),
@@ -879,16 +900,16 @@ final class MyProfilePageView: NSView {
     }
     }
 
 
     private func referralBlock() -> NSView {
     private func referralBlock() -> NSView {
-        let label = NSTextField(labelWithString: "Referral (Optional)")
+        let label = NSTextField(labelWithString: L("Referral (Optional)"))
         label.font = .systemFont(ofSize: 12, weight: .medium)
         label.font = .systemFont(ofSize: 12, weight: .medium)
         label.textColor = ProfilePagePalette.secondaryText
         label.textColor = ProfilePagePalette.secondaryText
         label.translatesAutoresizingMaskIntoConstraints = false
         label.translatesAutoresizingMaskIntoConstraints = false
         ProfileLayoutEnforcement.applyLeftAlignedTextField(label)
         ProfileLayoutEnforcement.applyLeftAlignedTextField(label)
 
 
-        styleSingleLineField(referralField, placeholder: "Referred by (Company/Person Name)")
+        styleSingleLineField(referralField, placeholder: L("Referred by (Company/Person Name)"))
         let wrap = roundedFieldChrome(containing: referralField, minHeight: 40)
         let wrap = roundedFieldChrome(containing: referralField, minHeight: 40)
 
 
-        let helper = NSTextField(wrappingLabelWithString: "If someone referred you for this job, enter their name or company here")
+        let helper = NSTextField(wrappingLabelWithString: L("If someone referred you for this job, enter their name or company here"))
         helper.font = .systemFont(ofSize: 11, weight: .regular)
         helper.font = .systemFont(ofSize: 11, weight: .regular)
         helper.textColor = ProfilePagePalette.secondaryText
         helper.textColor = ProfilePagePalette.secondaryText
         helper.translatesAutoresizingMaskIntoConstraints = false
         helper.translatesAutoresizingMaskIntoConstraints = false
@@ -925,14 +946,14 @@ final class MyProfilePageView: NSView {
     }
     }
 
 
     private func workExperienceSection() -> NSView {
     private func workExperienceSection() -> NSView {
-        let title = NSTextField(labelWithString: "Work Experience")
+        let title = NSTextField(labelWithString: L("Work Experience"))
         title.font = .systemFont(ofSize: 15, weight: .semibold)
         title.font = .systemFont(ofSize: 15, weight: .semibold)
         title.textColor = ProfilePagePalette.primaryText
         title.textColor = ProfilePagePalette.primaryText
         title.translatesAutoresizingMaskIntoConstraints = false
         title.translatesAutoresizingMaskIntoConstraints = false
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         ProfileLayoutEnforcement.applyLeftAlignedTextField(title)
         ProfileLayoutEnforcement.applyLeftAlignedTextField(title)
 
 
-        let addButton = NSButton(title: "+ Add Another", target: self, action: #selector(didTapAddWorkExperience))
+        let addButton = NSButton(title: L("+ Add Another"), target: self, action: #selector(didTapAddWorkExperience))
         addButton.translatesAutoresizingMaskIntoConstraints = false
         addButton.translatesAutoresizingMaskIntoConstraints = false
         addButton.bezelStyle = .rounded
         addButton.bezelStyle = .rounded
         addButton.isBordered = true
         addButton.isBordered = true
@@ -972,14 +993,14 @@ final class MyProfilePageView: NSView {
     }
     }
 
 
     private func educationSection() -> NSView {
     private func educationSection() -> NSView {
-        let title = NSTextField(labelWithString: "Education")
+        let title = NSTextField(labelWithString: L("Education"))
         title.font = .systemFont(ofSize: 15, weight: .semibold)
         title.font = .systemFont(ofSize: 15, weight: .semibold)
         title.textColor = ProfilePagePalette.primaryText
         title.textColor = ProfilePagePalette.primaryText
         title.translatesAutoresizingMaskIntoConstraints = false
         title.translatesAutoresizingMaskIntoConstraints = false
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         ProfileLayoutEnforcement.applyLeftAlignedTextField(title)
         ProfileLayoutEnforcement.applyLeftAlignedTextField(title)
 
 
-        let addButton = NSButton(title: "+ Add Another", target: self, action: #selector(didTapAddEducation))
+        let addButton = NSButton(title: L("+ Add Another"), target: self, action: #selector(didTapAddEducation))
         addButton.translatesAutoresizingMaskIntoConstraints = false
         addButton.translatesAutoresizingMaskIntoConstraints = false
         addButton.bezelStyle = .rounded
         addButton.bezelStyle = .rounded
         addButton.isBordered = true
         addButton.isBordered = true
@@ -1128,16 +1149,16 @@ final class MyProfilePageView: NSView {
         window?.makeFirstResponder(nil)
         window?.makeFirstResponder(nil)
         let profile = captureSavedProfileForSave()
         let profile = captureSavedProfileForSave()
         var missing: [String] = []
         var missing: [String] = []
-        if profile.profileDisplayName.isEmpty { missing.append("Profile name") }
-        if profile.personal.fullName.isEmpty { missing.append("Full Name") }
-        if profile.personal.email.isEmpty { missing.append("Email") }
-        if profile.personal.jobTitle.isEmpty { missing.append("Job Title") }
+        if profile.profileDisplayName.isEmpty { missing.append(L("Profile name")) }
+        if profile.personal.fullName.isEmpty { missing.append(L("Full Name")) }
+        if profile.personal.email.isEmpty { missing.append(L("Email")) }
+        if profile.personal.jobTitle.isEmpty { missing.append(L("Job Title")) }
         guard missing.isEmpty else {
         guard missing.isEmpty else {
             let alert = NSAlert()
             let alert = NSAlert()
-            alert.messageText = "Complete required fields"
-            alert.informativeText = "Please fill in: " + missing.joined(separator: ", ") + "."
+            alert.messageText = L("Complete required fields")
+            alert.informativeText = String(format: L("Please fill in: %@."), missing.joined(separator: ", "))
             alert.alertStyle = .informational
             alert.alertStyle = .informational
-            alert.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = window {
             if let window = window {
                 alert.beginSheetModal(for: window) { _ in }
                 alert.beginSheetModal(for: window) { _ in }
             } else {
             } else {
@@ -1161,7 +1182,7 @@ private enum ProfileEntryCardLayout {
 private final class WorkExperienceEntryView: NSView {
 private final class WorkExperienceEntryView: NSView {
     var onDelete: (() -> Void)?
     var onDelete: (() -> Void)?
 
 
-    private let subtitleLabel = NSTextField(labelWithString: "Experience 1")
+    private let subtitleLabel = NSTextField(labelWithString: String(format: L("Experience %d"), 1))
     private let deleteButton = NSButton()
     private let deleteButton = NSButton()
     private let jobTitleField = NSTextField()
     private let jobTitleField = NSTextField()
     private let companyField = NSTextField()
     private let companyField = NSTextField()
@@ -1179,7 +1200,7 @@ private final class WorkExperienceEntryView: NSView {
     }
     }
 
 
     func setExperienceIndex(_ index: Int) {
     func setExperienceIndex(_ index: Int) {
-        subtitleLabel.stringValue = "Experience \(index)"
+        subtitleLabel.stringValue = String(format: L("Experience %d"), index)
     }
     }
 
 
     func setDeleteHidden(_ hidden: Bool) {
     func setDeleteHidden(_ hidden: Bool) {
@@ -1236,10 +1257,10 @@ private final class WorkExperienceEntryView: NSView {
         deleteButton.target = self
         deleteButton.target = self
         deleteButton.action = #selector(didTapDelete)
         deleteButton.action = #selector(didTapDelete)
         if #available(macOS 11.0, *) {
         if #available(macOS 11.0, *) {
-            deleteButton.image = NSImage(systemSymbolName: "trash", accessibilityDescription: "Remove experience")
+            deleteButton.image = NSImage(systemSymbolName: "trash", accessibilityDescription: L("Remove experience"))
             deleteButton.imagePosition = .imageOnly
             deleteButton.imagePosition = .imageOnly
         } else {
         } else {
-            deleteButton.title = "Remove"
+            deleteButton.title = L("Remove")
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
         }
         }
 
 
@@ -1255,15 +1276,15 @@ private final class WorkExperienceEntryView: NSView {
         ProfileLayoutEnforcement.applyForcedLTR(to: headerRow)
         ProfileLayoutEnforcement.applyForcedLTR(to: headerRow)
         headerRow.translatesAutoresizingMaskIntoConstraints = false
         headerRow.translatesAutoresizingMaskIntoConstraints = false
 
 
-        let jobGroup = Self.labeledFieldStack(title: "Job Title *", field: jobTitleField, placeholder: "e.g., Software Engineer")
-        let companyGroup = Self.labeledFieldStack(title: "Company Name *", field: companyField, placeholder: "e.g., Google")
+        let jobGroup = Self.labeledFieldStack(title: L("Job Title *"), field: jobTitleField, placeholder: L("e.g., Software Engineer"))
+        let companyGroup = Self.labeledFieldStack(title: L("Company Name *"), field: companyField, placeholder: L("e.g., Google"))
         jobCompanyRow = ProfileDualFieldRow(left: jobGroup, right: companyGroup, spacing: 12)
         jobCompanyRow = ProfileDualFieldRow(left: jobGroup, right: companyGroup, spacing: 12)
 
 
-        let durationGroup = Self.labeledFieldStack(title: "Duration *", field: durationField, placeholder: "e.g., Jan 2020 - Present")
+        let durationGroup = Self.labeledFieldStack(title: L("Duration *"), field: durationField, placeholder: L("e.g., Jan 2020 - Present"))
         let descriptionGroup = Self.multilineLabeledStack(
         let descriptionGroup = Self.multilineLabeledStack(
-            title: "Description",
+            title: L("Description"),
             field: descriptionField,
             field: descriptionField,
-            placeholder: "Describe your responsibilities and achievements...",
+            placeholder: L("Describe your responsibilities and achievements..."),
             minHeight: 120
             minHeight: 120
         )
         )
 
 
@@ -1454,7 +1475,7 @@ private final class WorkExperienceEntryView: NSView {
 private final class EducationEntryView: NSView {
 private final class EducationEntryView: NSView {
     var onDelete: (() -> Void)?
     var onDelete: (() -> Void)?
 
 
-    private let subtitleLabel = NSTextField(labelWithString: "Education 1")
+    private let subtitleLabel = NSTextField(labelWithString: String(format: L("Education %d"), 1))
     private let deleteButton = NSButton()
     private let deleteButton = NSButton()
     private let degreeField = NSTextField()
     private let degreeField = NSTextField()
     private let institutionField = NSTextField()
     private let institutionField = NSTextField()
@@ -1471,7 +1492,7 @@ private final class EducationEntryView: NSView {
     }
     }
 
 
     func setEducationIndex(_ index: Int) {
     func setEducationIndex(_ index: Int) {
-        subtitleLabel.stringValue = "Education \(index)"
+        subtitleLabel.stringValue = String(format: L("Education %d"), index)
     }
     }
 
 
     func setDeleteHidden(_ hidden: Bool) {
     func setDeleteHidden(_ hidden: Bool) {
@@ -1526,10 +1547,10 @@ private final class EducationEntryView: NSView {
         deleteButton.target = self
         deleteButton.target = self
         deleteButton.action = #selector(didTapDelete)
         deleteButton.action = #selector(didTapDelete)
         if #available(macOS 11.0, *) {
         if #available(macOS 11.0, *) {
-            deleteButton.image = NSImage(systemSymbolName: "trash", accessibilityDescription: "Remove education")
+            deleteButton.image = NSImage(systemSymbolName: "trash", accessibilityDescription: L("Remove education"))
             deleteButton.imagePosition = .imageOnly
             deleteButton.imagePosition = .imageOnly
         } else {
         } else {
-            deleteButton.title = "Remove"
+            deleteButton.title = L("Remove")
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
         }
         }
 
 
@@ -1546,21 +1567,21 @@ private final class EducationEntryView: NSView {
         headerRow.translatesAutoresizingMaskIntoConstraints = false
         headerRow.translatesAutoresizingMaskIntoConstraints = false
 
 
         let degreeGroup = WorkExperienceEntryView.labeledFieldStack(
         let degreeGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Degree / program *",
+            title: L("Degree / program *"),
             field: degreeField,
             field: degreeField,
-            placeholder: "e.g., BSc Computer Science"
+            placeholder: L("e.g., BSc Computer Science")
         )
         )
         let institutionGroup = WorkExperienceEntryView.labeledFieldStack(
         let institutionGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Institution *",
+            title: L("Institution *"),
             field: institutionField,
             field: institutionField,
-            placeholder: "e.g., MIT"
+            placeholder: L("e.g., MIT")
         )
         )
         degreeInstitutionRow = ProfileDualFieldRow(left: degreeGroup, right: institutionGroup, spacing: 12)
         degreeInstitutionRow = ProfileDualFieldRow(left: degreeGroup, right: institutionGroup, spacing: 12)
 
 
         let yearGroup = WorkExperienceEntryView.labeledFieldStack(
         let yearGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Year *",
+            title: L("Year *"),
             field: yearField,
             field: yearField,
-            placeholder: "e.g., 2020"
+            placeholder: L("e.g., 2020")
         )
         )
 
 
         let inner = NSStackView(views: [headerRow, degreeInstitutionRow, yearGroup])
         let inner = NSStackView(views: [headerRow, degreeInstitutionRow, yearGroup])

+ 39 - 10
App for Indeed/Views/ProfilesListPageView.swift

@@ -33,12 +33,13 @@ final class ProfilesListPageView: NSView {
     private let scrollView = NSScrollView()
     private let scrollView = NSScrollView()
     private let documentView = ProfilesListDocumentView()
     private let documentView = ProfilesListDocumentView()
     private let contentStack = NSStackView()
     private let contentStack = NSStackView()
-    private let titleLabel = NSTextField(labelWithString: "Profiles")
+    private let titleLabel = NSTextField(labelWithString: L("Profiles"))
     private let subtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let subtitleLabel = NSTextField(wrappingLabelWithString: "")
     private let emptyStateLabel = NSTextField(wrappingLabelWithString: "")
     private let emptyStateLabel = NSTextField(wrappingLabelWithString: "")
-    private let addButton = ProfilesPrimaryButton(title: "Add new profile", target: nil, action: nil)
+    private let addButton = ProfilesPrimaryButton(title: L("Add new profile"), target: nil, action: nil)
     private let pendingFlowLabel = NSTextField(wrappingLabelWithString: "")
     private let pendingFlowLabel = NSTextField(wrappingLabelWithString: "")
     private var appearanceObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
     /// Non-`nil` after **Use Template & Select Profile** until the dashboard clears the flow (e.g. leaving Profile).
     /// Non-`nil` after **Use Template & Select Profile** until the dashboard clears the flow (e.g. leaving Profile).
     private var pendingCVTemplateDisplayName: String?
     private var pendingCVTemplateDisplayName: String?
 
 
@@ -57,13 +58,24 @@ final class ProfilesListPageView: NSView {
         ) { [weak self] _ in
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
             self?.applyCurrentAppearance()
         }
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
     }
 
 
     deinit {
     deinit {
         if let appearanceObserver {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
     }
 
 
     required init?(coder: NSCoder) {
     required init?(coder: NSCoder) {
@@ -88,6 +100,20 @@ final class ProfilesListPageView: NSView {
         }
         }
     }
     }
 
 
+    func applyLocalizedStrings() {
+        titleLabel.stringValue = L("Profiles")
+        subtitleLabel.stringValue = L("Create and manage CV profiles. Each profile stores your details on this Mac.")
+        emptyStateLabel.stringValue = L("No profiles yet. Tap “Add new profile” to create your first one.")
+        addButton.title = L("Add new profile")
+        if let name = pendingCVTemplateDisplayName, !name.isEmpty {
+            pendingFlowLabel.stringValue = String(
+                format: L("You chose the “%@” template. Tap Build CV on a profile to preview your résumé with that layout."),
+                localizedTemplateName(name)
+            )
+        }
+        reloadFromStore()
+    }
+
     func reloadFromStore() {
     func reloadFromStore() {
         for row in contentStack.arrangedSubviews {
         for row in contentStack.arrangedSubviews {
             contentStack.removeArrangedSubview(row)
             contentStack.removeArrangedSubview(row)
@@ -113,7 +139,10 @@ final class ProfilesListPageView: NSView {
     func setPendingCVTemplateDisplayName(_ name: String?) {
     func setPendingCVTemplateDisplayName(_ name: String?) {
         pendingCVTemplateDisplayName = name
         pendingCVTemplateDisplayName = name
         if let name, !name.isEmpty {
         if let name, !name.isEmpty {
-            pendingFlowLabel.stringValue = "You chose the “\(name)” template. Tap Build CV on a profile to preview your résumé with that layout."
+            pendingFlowLabel.stringValue = String(
+                format: L("You chose the “%@” template. Tap Build CV on a profile to preview your résumé with that layout."),
+                localizedTemplateName(name)
+            )
             pendingFlowLabel.isHidden = false
             pendingFlowLabel.isHidden = false
         } else {
         } else {
             pendingFlowLabel.isHidden = true
             pendingFlowLabel.isHidden = true
@@ -128,11 +157,11 @@ final class ProfilesListPageView: NSView {
 
 
         titleLabel.font = .systemFont(ofSize: 22, weight: .semibold)
         titleLabel.font = .systemFont(ofSize: 22, weight: .semibold)
 
 
-        subtitleLabel.stringValue = "Create and manage CV profiles. Each profile stores your details on this Mac."
+        subtitleLabel.stringValue = L("Create and manage CV profiles. Each profile stores your details on this Mac.")
         subtitleLabel.font = .systemFont(ofSize: 13, weight: .regular)
         subtitleLabel.font = .systemFont(ofSize: 13, weight: .regular)
         subtitleLabel.maximumNumberOfLines = 0
         subtitleLabel.maximumNumberOfLines = 0
 
 
-        emptyStateLabel.stringValue = "No profiles yet. Tap “Add new profile” to create your first one."
+        emptyStateLabel.stringValue = L("No profiles yet. Tap “Add new profile” to create your first one.")
         emptyStateLabel.font = .systemFont(ofSize: 13, weight: .regular)
         emptyStateLabel.font = .systemFont(ofSize: 13, weight: .regular)
         emptyStateLabel.textColor = ProfilesListPalette.secondaryText
         emptyStateLabel.textColor = ProfilesListPalette.secondaryText
         emptyStateLabel.isHidden = true
         emptyStateLabel.isHidden = true
@@ -237,9 +266,9 @@ private final class ProfileListRowView: NSView {
     private let profileID: UUID
     private let profileID: UUID
     private let nameLabel = NSTextField(labelWithString: "")
     private let nameLabel = NSTextField(labelWithString: "")
     private let detailLabel = NSTextField(wrappingLabelWithString: "")
     private let detailLabel = NSTextField(wrappingLabelWithString: "")
-    private let buildCVButton = NSButton(title: "Build CV", target: nil, action: nil)
-    private let editButton = NSButton(title: "Edit", target: nil, action: nil)
-    private let deleteButton = NSButton(title: "Delete", target: nil, action: nil)
+    private let buildCVButton = NSButton(title: L("Build CV"), target: nil, action: nil)
+    private let editButton = NSButton(title: L("Edit"), target: nil, action: nil)
+    private let deleteButton = NSButton(title: L("Delete"), target: nil, action: nil)
 
 
     init(profile: SavedProfile, showBuildCV: Bool) {
     init(profile: SavedProfile, showBuildCV: Bool) {
         self.profileID = profile.id
         self.profileID = profile.id
@@ -254,11 +283,11 @@ private final class ProfileListRowView: NSView {
             layer?.cornerCurve = .continuous
             layer?.cornerCurve = .continuous
         }
         }
 
 
-        nameLabel.stringValue = profile.profileDisplayName.isEmpty ? "Untitled profile" : profile.profileDisplayName
+        nameLabel.stringValue = profile.profileDisplayName.isEmpty ? L("Untitled profile") : profile.profileDisplayName
         nameLabel.font = .systemFont(ofSize: 15, weight: .semibold)
         nameLabel.font = .systemFont(ofSize: 15, weight: .semibold)
 
 
         let detailParts = [profile.personal.fullName, profile.personal.email].filter { !$0.isEmpty }
         let detailParts = [profile.personal.fullName, profile.personal.email].filter { !$0.isEmpty }
-        detailLabel.stringValue = detailParts.isEmpty ? "No contact details yet" : detailParts.joined(separator: " · ")
+        detailLabel.stringValue = detailParts.isEmpty ? L("No contact details yet") : detailParts.joined(separator: " · ")
         detailLabel.font = .systemFont(ofSize: 12, weight: .regular)
         detailLabel.font = .systemFont(ofSize: 12, weight: .regular)
         detailLabel.maximumNumberOfLines = 2
         detailLabel.maximumNumberOfLines = 2
 
 

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

@@ -0,0 +1,352 @@
+/* Localizable.strings (العربية) */
+
+// MARK: - شائع
+"OK" = "موافق";
+"Cancel" = "إلغاء";
+"Delete" = "حذف";
+"Remove" = "إزالة";
+"Dismiss" = "تجاهل";
+"English" = "الإنجليزية";
+"Arabic" = "العربية";
+
+// MARK: - شاشة الإطلاق
+"AI-POWERED" = "مدعوم بالذكاء الاصطناعي";
+"Find your perfect job with the power of AI." = "ابحث عن وظيفتك المثالية باستخدام قوة الذكاء الاصطناعي.";
+"Starting up…" = "جاري التشغيل…";
+"Loading progress" = "تقدم التحميل";
+
+// MARK: - حالة الإطلاق
+"Checking your Pro subscription…" = "جاري التحقق من اشتراك الإصدار الاحترافي الخاص بك…";
+"Loading premium plans from the App Store…" = "جاري تحميل الخطط المميزة من متجر التطبيقات…";
+"Preparing your job search workspace…" = "جاري تجهيز مساحة البحث عن وظيفة…";
+"Almost ready…" = "شبه جاهز…";
+
+// MARK: - الشريط الجانبي
+"Home" = "الرئيسية";
+"Saved Jobs" = "الوظائف المحفوظة";
+"CV Maker" = "صانع السيرة الذاتية";
+"Profile" = "الملف الشخصي";
+"Settings" = "الإعدادات";
+"Premium" = "مميز";
+"Indeed" = "إنديد";
+"Open Indeed to search and apply for jobs" = "افتح إنديد للبحث عن الوظائف والتقديم عليها";
+
+// MARK: - لوحة المعلومات / الرئيسية
+"Welcome" = "مرحباً";
+"Send" = "إرسال";
+"Clear chat" = "مسح المحادثة";
+"Remove all messages and start a new conversation" = "إزالة جميع الرسائل وبدء محادثة جديدة";
+"Ask for roles, skills, salary, or job descriptions..." = "اسأل عن الأدوار، المهارات، الراتب، أو وصف الوظيفة...";
+"Ask AI" = "اسأل الذكاء الاصطناعي";
+"1 reply left" = "رد واحد متبقي";
+"Apply" = "تقديم";
+"Save" = "حفظ";
+"Saved" = "تم الحفظ";
+"Remove from saved" = "إزالة من المحفوظات";
+"Show more jobs" = "عرض المزيد من الوظائف";
+"This area is not available in the preview build. Use Home to search jobs." = "هذه المنطقة غير متوفرة في الإصدار التجريبي. استخدم الرئيسية للبحث عن وظائف.";
+"Save jobs from Home to see them here." = "احفظ الوظائف من الرئيسية لتراها هنا.";
+"No saved jobs yet. Search on Home, then tap Save on a listing." = "لا توجد وظائف محفوظة بعد. ابحث في الرئيسية، ثم اضغط على حفظ في القائمة.";
+"Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary." = "أخبرني بالدور الذي تريده وسأقدم لك توصيفات الوظيفة، المهارات الأساسية، وملخص توافق سريع.";
+"1 saved position" = "وظيفة واحدة محفوظة";
+"Delete this profile?" = "حذف هذا الملف الشخصي؟";
+"Find roles similar to: " = "ابحث عن أدوار مشابهة لـ: ";
+"Find jobs at company: " = "ابحث عن وظائف في شركة: ";
+"Find jobs that require skill: " = "ابحث عن وظائف تتطلب مهارة: ";
+"match" = "تطابق";
+"matches" = "تطابقات";
+
+// MARK: - اختصارات الميزات
+"Role" = "الدور";
+"Explore similar or better job roles" = "استكشف أدوار وظيفية مشابهة أو أفضل";
+"Company" = "الشركة";
+"Find opportunities at other companies" = "ابحث عن فرص في شركات أخرى";
+"Skill" = "المهارة";
+"Match jobs that fit your skills" = "طابق الوظائف التي تناسب مهاراتك";
+
+// MARK: - Pro / الاشتراك
+"Upgrade to Pro" = "الترقية إلى الإصدار الاحترافي";
+"You're on Pro" = "أنت على الإصدار الاحترافي";
+"Unlimited AI matches, smart alerts, and interview prep—all in one place." = "مطابقات غير محدودة بالذكاء الاصطناعي، تنبيهات ذكية، والتحضير للمقابلات - كل ذلك في مكان واحد.";
+"Manage billing, renewals, and plans in Premium." = "إدارة الفواتير، التجديدات، والخطط في المميز.";
+"Try Pro" = "جرّب الإصدار الاحترافي";
+"Manage Subscription" = "إدارة الاشتراك";
+"Premium Plans" = "الخطط المميزة";
+"Unlock unlimited access to premium tools and boost your productivity." = "افتح وصولاً غير محدود إلى الأدوات المميزة وعزز إنتاجيتك.";
+"Continue with free plan" = "المتابعة مع الخطة المجانية";
+"Restore Purchase" = "استعادة الشراء";
+"You're subscribed" = "أنت مشترك";
+"Thank you — Pro features are now available." = "شكراً لك — ميزات الإصدار الاحترافي متاحة الآن.";
+"Pro" = "الإصدار الاحترافي";
+"Purchases restored" = "تمت استعادة المشتريات";
+"Your subscription is active." = "اشتراكك نشط.";
+"No subscription found" = "لم يتم العثور على اشتراك";
+"There was nothing to restore for this Apple ID." = "لا يوجد شيء لاستعادته لمعرف Apple هذا.";
+"Something went wrong" = "حدث خطأ ما";
+"That subscription isn’t available from the App Store right now." = "هذا الاشتراك غير متوفر من متجر التطبيقات حالياً.";
+"Unlimited AI job search on Home" = "بحث غير محدود بالذكاء الاصطناعي في الرئيسية";
+"Save jobs & open listings in-app" = "حفظ الوظائف وفتح القوائم داخل التطبيق";
+"CV Maker, profiles & PDF export" = "صانع السيرة الذاتية، الملفات الشخصية وتصدير بي دي إف";
+"Role, company & skill shortcuts" = "اختصارات الدور، الشركة والمهارة";
+
+// MARK: - خطط الدفع
+"Weekly" = "أسبوعي";
+"Flexible and commitment-free" = "مرن وبدون التزام";
+"Monthly" = "شهري";
+"Balanced for regular productivity" = "متوازن للإنتاجية المنتظمة";
+"Yearly" = "سنوي";
+"Best value for long-term users" = "أفضل قيمة للمستخدمين على المدى الطويل";
+"/ week" = "/ أسبوع";
+"/ month" = "/ شهر";
+"/ year" = "/ سنة";
+"3 days free trial" = "3 أيام تجربة مجانية";
+"Perfect for short-term job hunts" = "مثالي للبحث عن وظيفة قصيرة المدى";
+"Cancel anytime" = "ألغِ في أي وقت";
+"Best for regular job seekers" = "الأفضل للباحثين عن عمل بانتظام";
+"Priority support" = "دعم ذو أولوية";
+"Lowest effective monthly cost" = "أقل تكلفة شهرية فعلية";
+"Ideal for long-term use" = "مثالي للاستخدام طويل المدى";
+
+// MARK: - ثقة الدفع
+"Secure Payments" = "مدفوعات آمنة";
+"Your payment is 100% secure." = "مدفوعاتك آمنة بنسبة 100%.";
+"Cancel Anytime" = "ألغِ في أي وقت";
+"No commitment, cancel anytime." = "بدون التزام، ألغِ في أي وقت.";
+"24/7 Support" = "دعم على مدار الساعة";
+"We're here to help you anytime." = "نحن هنا لمساعدتك في أي وقت.";
+"Privacy First" = "الخصوصية أولاً";
+"Your data is safe with us." = "بياناتك آمنة معنا.";
+
+// MARK: - الإعدادات
+"Appearance" = "المظهر";
+"Theme" = "السمة";
+"Language" = "اللغة";
+"Share App" = "مشاركة التطبيق";
+"More Apps" = "تطبيقات أكثر";
+"About" = "حول";
+"Website" = "الموقع الإلكتروني";
+"Support" = "الدعم";
+"Terms of Use" = "شروط الاستخدام";
+"Privacy Policy" = "سياسة الخصوصية";
+"System" = "النظام";
+"Light" = "فاتح";
+"Dark" = "داكن";
+
+// MARK: - الملفات الشخصية
+"Profiles" = "الملفات الشخصية";
+"Add new profile" = "إضافة ملف شخصي جديد";
+"Create and manage CV profiles. Each profile stores your details on this Mac." = "إنشاء وإدارة ملفات السيرة الذاتية. يخزن كل ملف تفاصيلك على جهاز ماك هذا.";
+"No profiles yet. Tap “Add new profile” to create your first one." = "لا توجد ملفات شخصية بعد. اضغط على “إضافة ملف شخصي جديد” لإنشاء أول ملف.";
+"Build CV" = "بناء السيرة الذاتية";
+"Edit" = "تعديل";
+"Untitled profile" = "ملف شخصي بدون عنوان";
+"No contact details yet" = "لا توجد تفاصيل اتصال بعد";
+"← Profiles" = "← الملفات الشخصية";
+
+// MARK: - محرر الملف الشخصي
+"Save Profile  →" = "حفظ الملف الشخصي ←";
+"← All profiles" = "← جميع الملفات الشخصية";
+"New profile" = "ملف شخصي جديد";
+"Edit profile" = "تحرير الملف الشخصي";
+"Profile Name *" = "اسم الملف الشخصي *";
+"Marketing Director Profile" = "ملف مدير التسويق";
+"Personal Information" = "المعلومات الشخصية";
+"Full Name *" = "الاسم الكامل *";
+"John Doe" = "جون دو";
+"Email *" = "البريد الإلكتروني *";
+"john@example.com" = "john@example.com";
+"Phone" = "الهاتف";
+"+1 (555) 123-4567" = "+1 (555) 123-4567";
+"Job Title *" = "المسمى الوظيفي *";
+"Software Engineer" = "مهندس برمجيات";
+"Address" = "العنوان";
+"123 Main St, City, State, ZIP" = "123 الشارع الرئيسي، المدينة، الولاية، الرمز البريدي";
+"Certificates / Rewards" = "الشهادات / الجوائز";
+"List your certificates and awards..." = "قائمة شهاداتك وجوائزك...";
+"Interests" = "الاهتمامات";
+"List your interests and hobbies..." = "قائمة اهتماماتك وهواياتك...";
+"Languages" = "اللغات";
+"List languages you speak (e.g., English - Native, Spanish - Fluent)..." = "قائمة اللغات التي تتحدثها (مثال: الإنجليزية - لغة أم، الإسبانية - بطلاقة)...";
+"Career Summary" = "ملخص مهني";
+"Brief overview of your professional background and key achievements..." = "نظرة عامة مختصرة عن خلفيتك المهنية وإنجازاتك الرئيسية...";
+"Referral (Optional)" = "إحالة (اختياري)";
+"Referred by (Company/Person Name)" = "تمت الإحالة بواسطة (اسم الشركة/الشخص)";
+"If someone referred you for this job, enter their name or company here" = "إذا قام شخص ما بإحالتك لهذه الوظيفة، أدخل اسمه أو شركته هنا";
+"Work Experience" = "الخبرة العملية";
+"Education" = "التعليم";
+"+ Add Another" = "+ إضافة آخر";
+"Complete required fields" = "أكمل الحقول المطلوبة";
+"Remove experience" = "إزالة الخبرة";
+"Remove education" = "إزالة التعليم";
+"Company Name *" = "اسم الشركة *";
+"Duration *" = "المدة *";
+"Description" = "الوصف";
+"e.g., Software Engineer" = "مثال: مهندس برمجيات";
+"e.g., Google" = "مثال: جوجل";
+"e.g., Jan 2020 - Present" = "مثال: يناير 2020 - الحاضر";
+"Describe your responsibilities and achievements..." = "صف مسؤولياتك وإنجازاتك...";
+"Degree / program *" = "الشهادة / البرنامج *";
+"Institution *" = "المؤسسة *";
+"Year *" = "السنة *";
+"e.g., BSc Computer Science" = "مثال: بكالوريوس علوم الحاسوب";
+"e.g., MIT" = "مثال: معهد ماساتشوستس للتكنولوجيا";
+"e.g., 2020" = "مثال: 2020";
+"Profile name" = "اسم الملف الشخصي";
+"Full Name" = "الاسم الكامل";
+"Email" = "البريد الإلكتروني";
+"Job Title" = "المسمى الوظيفي";
+
+// MARK: - صانع السيرة الذاتية
+"Templates" = "القوالب";
+"Polished layouts with live previews — pick a style that fits your story." = "تخطيطات مصقولة مع معاينات حية — اختر نمطاً يناسب قصتك.";
+"Use Template & Select Profile  →" = "استخدم القالب واختر الملف الشخصي ←";
+"All" = "الكل";
+"No templates yet for this category." = "لا توجد قوالب بعد لهذه الفئة.";
+"Pick a template" = "اختر قالباً";
+"Select a template first, then choose a profile to continue." = "حدد قالباً أولاً، ثم اختر ملفاً شخصياً للمتابعة.";
+"Fetching AI-curated templates…" = "جاري جلب القوالب المنظمة بواسطة الذكاء الاصطناعي…";
+"Couldn’t load AI templates — showing the built-in gallery." = "تعذر تحميل قوالب الذكاء الاصطناعي — يتم عرض المعرض المدمج.";
+"Design-Based" = "قائم على التصميم";
+"Profession-Based" = "قائم على المهنة";
+"Professional" = "احترافي";
+"Modern" = "حديث";
+"Creative" = "إبداعي";
+"Minimal" = "بسيط";
+"Executive" = "تنفيذي";
+"ATS layout" = "تخطيط ATS";
+"Sidebar left" = "شريط جانبي أيسر";
+"Sidebar right" = "شريط جانبي أيمن";
+
+// MARK: - معاينة السيرة الذاتية
+"CV preview" = "معاينة السيرة الذاتية";
+"Export PDF…" = "تصدير بي دي إف…";
+"Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules)." = "يتطابق التخطيط مع الصورة المصغرة لصانع السيرة الذاتية لهذا القالب. قم بتصدير ملف بي دي إف يطابق ما تراه هنا (الخطوط، الأعمدة، الألوان، والقواعد).";
+"The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again." = "تعذر تحويل السيرة الذاتية إلى بي دي إف (مخرجات فارغة). حاول تمرير المعاينة حتى يتم ترتيبها، ثم قم بالتصدير مرة أخرى.";
+"Couldn’t save PDF" = "تعذر حفظ ملف بي دي إف";
+"Your name" = "اسمك";
+"Professional headline" = "عنوان احترافي";
+"Experience" = "الخبرة";
+"Highlights" = "أبرز النقاط";
+"Summary" = "ملخص";
+"Contact" = "جهات الاتصال";
+"Skills" = "المهارات";
+"Tools" = "الأدوات";
+"Languages & more" = "اللغات والمزيد";
+"Certificates" = "الشهادات";
+"Referrals" = "الإحالات";
+"Professional Summary" = "الملخص المهني";
+"Selected Experience" = "الخبرة المختارة";
+"Core Competencies" = "الكفاءات الأساسية";
+"Impact" = "التأثير";
+"Add contact in your profile" = "أضف جهة اتصال في ملفك الشخصي";
+"Add contact details in your profile" = "أضف تفاصيل الاتصال في ملفك الشخصي";
+"Add a career summary or interests in your profile to populate this column." = "أضف ملخصاً مهنياً أو اهتمامات في ملفك الشخصي لملء هذا العمود.";
+"CV" = "السيرة الذاتية";
+"Open to relocation" = "مستعد للانتقال";
+"STRENGTHS" = "نقاط القوة";
+"PORTFOLIO SNAPSHOT" = "لمحة عن المحفظة";
+"Close" = "إغلاق";
+"/ day" = "/ يوم";
+"/ %d days" = "/ %d أيام";
+"/ %d weeks" = "/ %d أسابيع";
+"/ %d months" = "/ %d أشهر";
+"/ %d years" = "/ %d سنوات";
+
+// MARK: - أسماء قوالب السيرة الذاتية
+"Paper White" = "أبيض ورقي";
+"Swiss" = "سويسري";
+"Mono" = "أحادي";
+"Airy" = "جيد التهوية";
+"Tabular" = "جدولي";
+"Facet" = "وجه";
+"Corporate" = "شركات";
+"Atlas" = "أطلس";
+"Ledger" = "دفتر الأستاذ";
+"Harbor" = "ميناء";
+"Clear Path" = "مسار واضح";
+"Pinstripe" = "خطوط رفيعة";
+"Briefing" = "إحاطة";
+"Quorum" = "نصاب";
+"Docket" = "جدول الأعمال";
+"Conduit" = "قناة";
+"Principal" = "رئيسي";
+"Charter" = "ميثاق";
+"Vertex" = "قمة";
+"Linea" = "خط";
+"Prism" = "منشور";
+"Circuit" = "دائرة";
+"North" = "شمال";
+"Axis" = "محور";
+"Marigold" = "القطيفة";
+"Ember" = "جمرة";
+"Lattice" = "شبكة";
+"Bloom" = "ازدهار";
+"Studio" = "استوديو";
+"Kite" = "طائرة ورقية";
+"Regent" = "وصي";
+"Monarch" = "ملك";
+"Sterling" = "استرليني";
+"Summit" = "قمة";
+"Estate" = "عقار";
+"Chairman" = "رئيس مجلس الإدارة";
+"Blue Ocean" = "المحيط الأزرق";
+
+// MARK: - محتوى معاينة السيرة الذاتية التجريبي
+"Sarah Johnson" = "سارة جونسون";
+"Senior Product Manager" = "مدير منتج أول";
+"Group PM, Consumer Growth & Activation" = "مدير منتج جماعي، نمو المستهلكين وتفعيلهم";
+"Google · Mountain View, CA · 2019 – Present" = "جوجل · ماونتن فيو، كاليفورنيا · 2019 – الحاضر";
+"Stanford University" = "جامعة ستanford";
+"M.S. Management Science & Engineering" = "ماجستير في علوم الإدارة والهندسة";
+"2014 – 2016" = "2014 – 2016";
+"Mountain View, CA" = "ماونتن فيو، كاليفورنيا";
+"Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences." = "قائد منتج يقدم خارطة الطريق، الاكتشاف، والتحليلات لتجارب المستهلكين واسعة النطاق.";
+"Defined multi-year platform strategy with exec stakeholders and quarterly OKRs." = "حدد استراتيجية المنصة متعددة السنوات مع أصحاب المصلحة التنفيذيين وأهداف النتائج الرئيسية الربع سنوية.";
+"Partnered with engineering and design to launch experiments improving activation by 12%." = "تعاون مع الهندسة والتصميم لإطلاق تجارب حسّنت التنشيط بنسبة 12%.";
+"Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics." = "أنشأ مراجعات الأعمال الربع سنوية مع المالية والذهاب إلى السوق، مما يوفق الإنفاق مع مقاييس النجم الشمالي.";
+"Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks." = "قدم تحولات خارطة الطريق لفريق القيادة وترجم المقايضات إلى طلبات استثمار واضحة.";
+"Figma · SQL · Amplitude · Jira · BigQuery" = "Figma · SQL · Amplitude · Jira · BigQuery";
+"Product Strategy" = "استراتيجية المنتج";
+"A/B Testing" = "اختبار A/B";
+"Roadmapping" = "تخطيط خارطة الطريق";
+"CONTACT" = "جهات الاتصال";
+"SKILLS" = "المهارات";
+"PROFILE" = "الملف الشخصي";
+"EXPERIENCE" = "الخبرة";
+"EDUCATION" = "التعليم";
+"SUMMARY" = "الملخص";
+"PROFESSIONAL SUMMARY" = "الملخص المهني";
+"SELECTED EXPERIENCE" = "الخبرة المختارة";
+
+// MARK: - متصفح الوظائف
+"Return to the previous screen" = "العودة إلى الشاشة السابقة";
+
+// MARK: - الأخطاء
+"We couldn't reach the server. Check your internet connection and try again." = "لم نتمكن من الوصول إلى الخادم. تحقق من اتصالك بالإنترنت وحاول مرة أخرى.";
+"The search was cancelled. Try again when you're ready." = "تم إلغاء البحث. حاول مرة أخرى عندما تكون مستعداً.";
+"Something went wrong while searching. Please try again in a moment." = "حدث خطأ ما أثناء البحث. يرجى المحاولة مرة أخرى بعد قليل.";
+"Job search is unavailable." = "البحث عن الوظائف غير متوفر.";
+
+// MARK: - التنبيهات
+"This profile will be removed from this Mac." = "سيتم إزالة هذا الملف الشخصي من جهاز ماك هذا.";
+
+// MARK: - سلاسل التنسيق
+"Loading %@" = "تحميل %@";
+"Loading %@. %@" = "تحميل %@. %@";
+"Starting %@…" = "بدء %@…";
+"%d replies left" = "%d ردود متبقية";
+"%d saved positions" = "%d وظائف محفوظة";
+"“%@” will be removed from this Mac." = "سيتم إزالة “%@” من جهاز ماك هذا.";
+"%@ isn’t available yet" = "%@ غير متوفر بعد";
+"I couldn't find new matches for “%@”. Try a different angle or a more specific keyword." = "لم أتمكن من العثور على تطابقات جديدة لـ “%@”. جرب زاوية مختلفة أو كلمة رئيسية أكثر تحديداً.";
+"No jobs found for “%@”. Try another title, skill, company, or location." = "لم يتم العثور على وظائف لـ “%@”. جرب عنواناً أو مهارة أو شركة أو موقعاً آخر.";
+"Here are %d more %@ for “%@”." = "إليك %d %@ إضافية لـ “%@”.";
+"Found %d %@ for “%@”. Tap Apply to open the listing or Save to revisit later." = "تم العثور على %d %@ لـ “%@”. اضغط على تقديم لفتح القائمة أو حفظ للعودة إليها لاحقاً.";
+"Get %@" = "احصل على %@";
+"You chose the “%@” template. Tap Build CV on a profile to preview your résumé with that layout." = "لقد اخترت قالب “%@”. اضغط على بناء السيرة الذاتية في ملف شخصي لمعاينة سيرتك الذاتية بهذا التخطيط.";
+"Experience %d" = "خبرة %d";
+"Education %d" = "تعليم %d";
+"Please fill in: %@." = "يرجى ملء: %@.";
+
+// MARK: - متعدد الأسطر
+"Add your Mac App Store IDs in the target’s build settings:\n• AppStoreAppID — numeric app ID from App Store Connect\n• AppStoreDeveloperID — numeric developer ID (for your other apps page)" = "أضف معرفات متجر تطبيقات ماك الخاصة بك في إعدادات بناء الهدف:\n• AppStoreAppID — معرف التطبيق الرقمي من App Store Connect\n• AppStoreDeveloperID — معرف المطور الرقمي (لصفحة تطبيقاتك الأخرى)";

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

@@ -0,0 +1,352 @@
+/* Localizable.strings (English) */
+
+// MARK: - Common
+"OK" = "OK";
+"Cancel" = "Cancel";
+"Delete" = "Delete";
+"Remove" = "Remove";
+"Dismiss" = "Dismiss";
+"English" = "English";
+"Arabic" = "Arabic";
+
+// MARK: - Launch Screen
+"AI-POWERED" = "AI-POWERED";
+"Find your perfect job with the power of AI." = "Find your perfect job with the power of AI.";
+"Starting up…" = "Starting up…";
+"Loading progress" = "Loading progress";
+
+// MARK: - Launch Status
+"Checking your Pro subscription…" = "Checking your Pro subscription…";
+"Loading premium plans from the App Store…" = "Loading premium plans from the App Store…";
+"Preparing your job search workspace…" = "Preparing your job search workspace…";
+"Almost ready…" = "Almost ready…";
+
+// MARK: - Sidebar
+"Home" = "Home";
+"Saved Jobs" = "Saved Jobs";
+"CV Maker" = "CV Maker";
+"Profile" = "Profile";
+"Settings" = "Settings";
+"Premium" = "Premium";
+"Indeed" = "Indeed";
+"Open Indeed to search and apply for jobs" = "Open Indeed to search and apply for jobs";
+
+// MARK: - Dashboard / Home
+"Welcome" = "Welcome";
+"Send" = "Send";
+"Clear chat" = "Clear chat";
+"Remove all messages and start a new conversation" = "Remove all messages and start a new conversation";
+"Ask for roles, skills, salary, or job descriptions..." = "Ask for roles, skills, salary, or job descriptions...";
+"Ask AI" = "Ask AI";
+"1 reply left" = "1 reply left";
+"Apply" = "Apply";
+"Save" = "Save";
+"Saved" = "Saved";
+"Remove from saved" = "Remove from saved";
+"Show more jobs" = "Show more jobs";
+"This area is not available in the preview build. Use Home to search jobs." = "This area is not available in the preview build. Use Home to search jobs.";
+"Save jobs from Home to see them here." = "Save jobs from Home to see them here.";
+"No saved jobs yet. Search on Home, then tap Save on a listing." = "No saved jobs yet. Search on Home, then tap Save on a listing.";
+"Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary." = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary.";
+"1 saved position" = "1 saved position";
+"Delete this profile?" = "Delete this profile?";
+"Find roles similar to: " = "Find roles similar to: ";
+"Find jobs at company: " = "Find jobs at company: ";
+"Find jobs that require skill: " = "Find jobs that require skill: ";
+"match" = "match";
+"matches" = "matches";
+
+// MARK: - Feature Shortcuts
+"Role" = "Role";
+"Explore similar or better job roles" = "Explore similar or better job roles";
+"Company" = "Company";
+"Find opportunities at other companies" = "Find opportunities at other companies";
+"Skill" = "Skill";
+"Match jobs that fit your skills" = "Match jobs that fit your skills";
+
+// MARK: - Pro / Subscription
+"Upgrade to Pro" = "Upgrade to Pro";
+"You're on Pro" = "You're on Pro";
+"Unlimited AI matches, smart alerts, and interview prep—all in one place." = "Unlimited AI matches, smart alerts, and interview prep—all in one place.";
+"Manage billing, renewals, and plans in Premium." = "Manage billing, renewals, and plans in Premium.";
+"Try Pro" = "Try Pro";
+"Manage Subscription" = "Manage Subscription";
+"Premium Plans" = "Premium Plans";
+"Unlock unlimited access to premium tools and boost your productivity." = "Unlock unlimited access to premium tools and boost your productivity.";
+"Continue with free plan" = "Continue with free plan";
+"Restore Purchase" = "Restore Purchase";
+"You're subscribed" = "You're subscribed";
+"Thank you — Pro features are now available." = "Thank you — Pro features are now available.";
+"Pro" = "Pro";
+"Purchases restored" = "Purchases restored";
+"Your subscription is active." = "Your subscription is active.";
+"No subscription found" = "No subscription found";
+"There was nothing to restore for this Apple ID." = "There was nothing to restore for this Apple ID.";
+"Something went wrong" = "Something went wrong";
+"That subscription isn’t available from the App Store right now." = "That subscription isn’t available from the App Store right now.";
+"Unlimited AI job search on Home" = "Unlimited AI job search on Home";
+"Save jobs & open listings in-app" = "Save jobs & open listings in-app";
+"CV Maker, profiles & PDF export" = "CV Maker, profiles & PDF export";
+"Role, company & skill shortcuts" = "Role, company & skill shortcuts";
+
+// MARK: - Paywall Plans
+"Weekly" = "Weekly";
+"Flexible and commitment-free" = "Flexible and commitment-free";
+"Monthly" = "Monthly";
+"Balanced for regular productivity" = "Balanced for regular productivity";
+"Yearly" = "Yearly";
+"Best value for long-term users" = "Best value for long-term users";
+"/ week" = "/ week";
+"/ month" = "/ month";
+"/ year" = "/ year";
+"3 days free trial" = "3 days free trial";
+"Perfect for short-term job hunts" = "Perfect for short-term job hunts";
+"Cancel anytime" = "Cancel anytime";
+"Best for regular job seekers" = "Best for regular job seekers";
+"Priority support" = "Priority support";
+"Lowest effective monthly cost" = "Lowest effective monthly cost";
+"Ideal for long-term use" = "Ideal for long-term use";
+
+// MARK: - Paywall Trust
+"Secure Payments" = "Secure Payments";
+"Your payment is 100% secure." = "Your payment is 100% secure.";
+"Cancel Anytime" = "Cancel Anytime";
+"No commitment, cancel anytime." = "No commitment, cancel anytime.";
+"24/7 Support" = "24/7 Support";
+"We're here to help you anytime." = "We're here to help you anytime.";
+"Privacy First" = "Privacy First";
+"Your data is safe with us." = "Your data is safe with us.";
+
+// MARK: - Settings
+"Appearance" = "Appearance";
+"Theme" = "Theme";
+"Language" = "Language";
+"Share App" = "Share App";
+"More Apps" = "More Apps";
+"About" = "About";
+"Website" = "Website";
+"Support" = "Support";
+"Terms of Use" = "Terms of Use";
+"Privacy Policy" = "Privacy Policy";
+"System" = "System";
+"Light" = "Light";
+"Dark" = "Dark";
+
+// MARK: - Profiles
+"Profiles" = "Profiles";
+"Add new profile" = "Add new profile";
+"Create and manage CV profiles. Each profile stores your details on this Mac." = "Create and manage CV profiles. Each profile stores your details on this Mac.";
+"No profiles yet. Tap “Add new profile” to create your first one." = "No profiles yet. Tap “Add new profile” to create your first one.";
+"Build CV" = "Build CV";
+"Edit" = "Edit";
+"Untitled profile" = "Untitled profile";
+"No contact details yet" = "No contact details yet";
+"← Profiles" = "← Profiles";
+
+// MARK: - Profile Editor
+"Save Profile  →" = "Save Profile  →";
+"← All profiles" = "← All profiles";
+"New profile" = "New profile";
+"Edit profile" = "Edit profile";
+"Profile Name *" = "Profile Name *";
+"Marketing Director Profile" = "Marketing Director Profile";
+"Personal Information" = "Personal Information";
+"Full Name *" = "Full Name *";
+"John Doe" = "John Doe";
+"Email *" = "Email *";
+"john@example.com" = "john@example.com";
+"Phone" = "Phone";
+"+1 (555) 123-4567" = "+1 (555) 123-4567";
+"Job Title *" = "Job Title *";
+"Software Engineer" = "Software Engineer";
+"Address" = "Address";
+"123 Main St, City, State, ZIP" = "123 Main St, City, State, ZIP";
+"Certificates / Rewards" = "Certificates / Rewards";
+"List your certificates and awards..." = "List your certificates and awards...";
+"Interests" = "Interests";
+"List your interests and hobbies..." = "List your interests and hobbies...";
+"Languages" = "Languages";
+"List languages you speak (e.g., English - Native, Spanish - Fluent)..." = "List languages you speak (e.g., English - Native, Spanish - Fluent)...";
+"Career Summary" = "Career Summary";
+"Brief overview of your professional background and key achievements..." = "Brief overview of your professional background and key achievements...";
+"Referral (Optional)" = "Referral (Optional)";
+"Referred by (Company/Person Name)" = "Referred by (Company/Person Name)";
+"If someone referred you for this job, enter their name or company here" = "If someone referred you for this job, enter their name or company here";
+"Work Experience" = "Work Experience";
+"Education" = "Education";
+"+ Add Another" = "+ Add Another";
+"Complete required fields" = "Complete required fields";
+"Remove experience" = "Remove experience";
+"Remove education" = "Remove education";
+"Company Name *" = "Company Name *";
+"Duration *" = "Duration *";
+"Description" = "Description";
+"e.g., Software Engineer" = "e.g., Software Engineer";
+"e.g., Google" = "e.g., Google";
+"e.g., Jan 2020 - Present" = "e.g., Jan 2020 - Present";
+"Describe your responsibilities and achievements..." = "Describe your responsibilities and achievements...";
+"Degree / program *" = "Degree / program *";
+"Institution *" = "Institution *";
+"Year *" = "Year *";
+"e.g., BSc Computer Science" = "e.g., BSc Computer Science";
+"e.g., MIT" = "e.g., MIT";
+"e.g., 2020" = "e.g., 2020";
+"Profile name" = "Profile name";
+"Full Name" = "Full Name";
+"Email" = "Email";
+"Job Title" = "Job Title";
+
+// MARK: - CV Maker
+"Templates" = "Templates";
+"Polished layouts with live previews — pick a style that fits your story." = "Polished layouts with live previews — pick a style that fits your story.";
+"Use Template & Select Profile  →" = "Use Template & Select Profile  →";
+"All" = "All";
+"No templates yet for this category." = "No templates yet for this category.";
+"Pick a template" = "Pick a template";
+"Select a template first, then choose a profile to continue." = "Select a template first, then choose a profile to continue.";
+"Fetching AI-curated templates…" = "Fetching AI-curated templates…";
+"Couldn’t load AI templates — showing the built-in gallery." = "Couldn’t load AI templates — showing the built-in gallery.";
+"Design-Based" = "Design-Based";
+"Profession-Based" = "Profession-Based";
+"Professional" = "Professional";
+"Modern" = "Modern";
+"Creative" = "Creative";
+"Minimal" = "Minimal";
+"Executive" = "Executive";
+"ATS layout" = "ATS layout";
+"Sidebar left" = "Sidebar left";
+"Sidebar right" = "Sidebar right";
+
+// MARK: - CV Preview
+"CV preview" = "CV preview";
+"Export PDF…" = "Export PDF…";
+"Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules)." = "Layout matches the CV Maker thumbnail for this template. Export a PDF that matches what you see here (fonts, columns, colours, and rules).";
+"The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again." = "The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.";
+"Couldn’t save PDF" = "Couldn’t save PDF";
+"Your name" = "Your name";
+"Professional headline" = "Professional headline";
+"Experience" = "Experience";
+"Highlights" = "Highlights";
+"Summary" = "Summary";
+"Contact" = "Contact";
+"Skills" = "Skills";
+"Tools" = "Tools";
+"Languages & more" = "Languages & more";
+"Certificates" = "Certificates";
+"Referrals" = "Referrals";
+"Professional Summary" = "Professional Summary";
+"Selected Experience" = "Selected Experience";
+"Core Competencies" = "Core Competencies";
+"Impact" = "Impact";
+"Add contact in your profile" = "Add contact in your profile";
+"Add contact details in your profile" = "Add contact details in your profile";
+"Add a career summary or interests in your profile to populate this column." = "Add a career summary or interests in your profile to populate this column.";
+"CV" = "CV";
+"Open to relocation" = "Open to relocation";
+"STRENGTHS" = "STRENGTHS";
+"PORTFOLIO SNAPSHOT" = "PORTFOLIO SNAPSHOT";
+"Close" = "Close";
+"/ day" = "/ day";
+"/ %d days" = "/ %d days";
+"/ %d weeks" = "/ %d weeks";
+"/ %d months" = "/ %d months";
+"/ %d years" = "/ %d years";
+
+// MARK: - CV Template Names
+"Paper White" = "Paper White";
+"Swiss" = "Swiss";
+"Mono" = "Mono";
+"Airy" = "Airy";
+"Tabular" = "Tabular";
+"Facet" = "Facet";
+"Corporate" = "Corporate";
+"Atlas" = "Atlas";
+"Ledger" = "Ledger";
+"Harbor" = "Harbor";
+"Clear Path" = "Clear Path";
+"Pinstripe" = "Pinstripe";
+"Briefing" = "Briefing";
+"Quorum" = "Quorum";
+"Docket" = "Docket";
+"Conduit" = "Conduit";
+"Principal" = "Principal";
+"Charter" = "Charter";
+"Vertex" = "Vertex";
+"Linea" = "Linea";
+"Prism" = "Prism";
+"Circuit" = "Circuit";
+"North" = "North";
+"Axis" = "Axis";
+"Marigold" = "Marigold";
+"Ember" = "Ember";
+"Lattice" = "Lattice";
+"Bloom" = "Bloom";
+"Studio" = "Studio";
+"Kite" = "Kite";
+"Regent" = "Regent";
+"Monarch" = "Monarch";
+"Sterling" = "Sterling";
+"Summit" = "Summit";
+"Estate" = "Estate";
+"Chairman" = "Chairman";
+"Blue Ocean" = "Blue Ocean";
+
+// MARK: - CV Demo Preview Content
+"Sarah Johnson" = "Sarah Johnson";
+"Senior Product Manager" = "Senior Product Manager";
+"Group PM, Consumer Growth & Activation" = "Group PM, Consumer Growth & Activation";
+"Google · Mountain View, CA · 2019 – Present" = "Google · Mountain View, CA · 2019 – Present";
+"Stanford University" = "Stanford University";
+"M.S. Management Science & Engineering" = "M.S. Management Science & Engineering";
+"2014 – 2016" = "2014 – 2016";
+"Mountain View, CA" = "Mountain View, CA";
+"Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences." = "Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences.";
+"Defined multi-year platform strategy with exec stakeholders and quarterly OKRs." = "Defined multi-year platform strategy with exec stakeholders and quarterly OKRs.";
+"Partnered with engineering and design to launch experiments improving activation by 12%." = "Partnered with engineering and design to launch experiments improving activation by 12%.";
+"Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics." = "Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics.";
+"Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks." = "Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks.";
+"Figma · SQL · Amplitude · Jira · BigQuery" = "Figma · SQL · Amplitude · Jira · BigQuery";
+"Product Strategy" = "Product Strategy";
+"A/B Testing" = "A/B Testing";
+"Roadmapping" = "Roadmapping";
+"CONTACT" = "CONTACT";
+"SKILLS" = "SKILLS";
+"PROFILE" = "PROFILE";
+"EXPERIENCE" = "EXPERIENCE";
+"EDUCATION" = "EDUCATION";
+"SUMMARY" = "SUMMARY";
+"PROFESSIONAL SUMMARY" = "PROFESSIONAL SUMMARY";
+"SELECTED EXPERIENCE" = "SELECTED EXPERIENCE";
+
+// MARK: - Job Browser
+"Return to the previous screen" = "Return to the previous screen";
+
+// MARK: - Errors
+"We couldn't reach the server. Check your internet connection and try again." = "We couldn't reach the server. Check your internet connection and try again.";
+"The search was cancelled. Try again when you're ready." = "The search was cancelled. Try again when you're ready.";
+"Something went wrong while searching. Please try again in a moment." = "Something went wrong while searching. Please try again in a moment.";
+"Job search is unavailable." = "Job search is unavailable.";
+
+// MARK: - Alerts
+"This profile will be removed from this Mac." = "This profile will be removed from this Mac.";
+
+// MARK: - Format Strings
+"Loading %@" = "Loading %@";
+"Loading %@. %@" = "Loading %@. %@";
+"Starting %@…" = "Starting %@…";
+"%d replies left" = "%d replies left";
+"%d saved positions" = "%d saved positions";
+"“%@” will be removed from this Mac." = "“%@” will be removed from this Mac.";
+"%@ isn’t available yet" = "%@ isn’t available yet";
+"I couldn't find new matches for “%@”. Try a different angle or a more specific keyword." = "I couldn't find new matches for “%@”. Try a different angle or a more specific keyword.";
+"No jobs found for “%@”. Try another title, skill, company, or location." = "No jobs found for “%@”. Try another title, skill, company, or location.";
+"Here are %d more %@ for “%@”." = "Here are %d more %@ for “%@”.";
+"Found %d %@ for “%@”. Tap Apply to open the listing or Save to revisit later." = "Found %d %@ for “%@”. Tap Apply to open the listing or Save to revisit later.";
+"Get %@" = "Get %@";
+"You chose the “%@” template. Tap Build CV on a profile to preview your résumé with that layout." = "You chose the “%@” template. Tap Build CV on a profile to preview your résumé with that layout.";
+"Experience %d" = "Experience %d";
+"Education %d" = "Education %d";
+"Please fill in: %@." = "Please fill in: %@.";
+
+// MARK: - Multi-line
+"Add your Mac App Store IDs in the target’s build settings:\n• AppStoreAppID — numeric app ID from App Store Connect\n• AppStoreDeveloperID — numeric developer ID (for your other apps page)" = "Add your Mac App Store IDs in the target’s build settings:\n• AppStoreAppID — numeric app ID from App Store Connect\n• AppStoreDeveloperID — numeric developer ID (for your other apps page)";