Pārlūkot izejas kodu

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 1 mēnesi atpakaļ
vecāks
revīzija
75f5cafd97

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

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

+ 1 - 0
App for Indeed/AppDelegate.swift

@@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
 
     func applicationWillFinishLaunching(_ notification: Notification) {
         AppAppearanceManager.shared.apply()
+        AppLanguageManager.shared.applyStoredPreferenceOnLaunch()
     }
 
     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 forwardButton = 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 var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
     override func loadView() {
         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.target = self
         dismissEmbeddedButton.action = #selector(dismissEmbedded)
-        dismissEmbeddedButton.toolTip = "Return to the previous screen"
+        dismissEmbeddedButton.toolTip = L("Return to the previous screen")
 
         toolbarContainer.translatesAutoresizingMaskIntoConstraints = false
         toolbarContainer.wantsLayer = true
@@ -112,6 +113,13 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
 
         if let pendingURL {
             webView.load(URLRequest(url: pendingURL))
@@ -123,6 +131,9 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     func loadPage(_ url: URL) {
@@ -166,6 +177,11 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         reloadButton.contentTintColor = accent
     }
 
+    private func applyLocalizedStrings() {
+        dismissEmbeddedButton.title = L("Home")
+        dismissEmbeddedButton.toolTip = L("Return to the previous screen")
+    }
+
     private func updateNavigationButtons() {
         backButton.isEnabled = webView.canGoBack
         forwardButton.isEnabled = webView.canGoForward
@@ -272,7 +288,7 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
         let alert = NSAlert()
         alert.messageText = message
-        alert.addButton(withTitle: NSLocalizedString("OK", comment: "Web alert dismiss"))
+        alert.addButton(withTitle: L("OK"))
         alert.runModal()
         completionHandler()
     }
@@ -285,8 +301,8 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
         let alert = NSAlert()
         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)
     }
 
@@ -299,8 +315,8 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
     ) {
         let alert = NSAlert()
         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))
         field.stringValue = defaultText ?? ""
         alert.accessoryView = field

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

@@ -8,7 +8,7 @@ final class PremiumPlansWindowController: NSWindowController {
     init() {
         let viewController = PremiumPlansViewController()
         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
         // the corners when combined with a custom full-bleed paywall (this window is only shown as a sheet).
         window.styleMask = [.borderless, .closable, .resizable]
@@ -293,7 +293,7 @@ private final class PremiumPlansViewController: NSViewController {
             wantsLayer = true
             layer?.cornerRadius = 15
             bezelStyle = .regularSquare
-            image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Close")
+            image = NSImage(systemSymbolName: "xmark", accessibilityDescription: L("Close"))
             imageScaling = .scaleProportionallyDown
             focusRingType = .none
             translatesAutoresizingMaskIntoConstraints = false
@@ -480,6 +480,7 @@ private final class PremiumPlansViewController: NSViewController {
         let featureLabels: [NSTextField]
         let featureIcons: [NSImageView]
         let purchaseButton: PlanPurchaseHoverButton
+        let billedPillLabel: NSTextField?
     }
 
     private enum FeatureListMetrics {
@@ -497,68 +498,73 @@ private final class PremiumPlansViewController: NSViewController {
     private var premiumCloseButton: PremiumCloseHoverButton?
     private var subscriptionStatusObservation: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
     /// 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 var premiumTitleLabel: NSTextField?
@@ -574,6 +580,9 @@ private final class PremiumPlansViewController: NSViewController {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     override func viewDidLoad() {
@@ -585,6 +594,13 @@ private final class PremiumPlansViewController: NSViewController {
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         subscriptionStatusObservation = NotificationCenter.default.addObserver(
             forName: .subscriptionStatusDidChange,
             object: nil,
@@ -627,13 +643,13 @@ private final class PremiumPlansViewController: NSViewController {
         crownIcon.image = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: nil)
         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.textColor = Theme.primaryText
         title.alignment = .center
         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.textColor = Theme.secondaryText
         subtitle.alignment = .center
@@ -800,7 +816,7 @@ private final class PremiumPlansViewController: NSViewController {
 
         let selectButton = PlanPurchaseHoverButton(
             planId: plan.id,
-            title: "Get \(plan.title)",
+            title: String(format: L("Get %@"), plan.title),
             isPrimaryStyle: plan.highlight,
             target: self,
             action: #selector(didTapSelectPlan)
@@ -830,7 +846,8 @@ private final class PremiumPlansViewController: NSViewController {
                 divider: divider,
                 featureLabels: featureLabels,
                 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 {
         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.alignment = .centerY
@@ -997,10 +1014,10 @@ private final class PremiumPlansViewController: NSViewController {
         subscriptionPrimaryFooterButton = primary.button
 
         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
@@ -1049,8 +1066,8 @@ private final class PremiumPlansViewController: NSViewController {
     }
 
     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 {
@@ -1163,10 +1180,10 @@ private final class PremiumPlansViewController: NSViewController {
     private func periodSuffix(for period: Product.SubscriptionPeriod) -> String {
         let value = period.value
         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 ""
         }
     }
@@ -1185,10 +1202,10 @@ private final class PremiumPlansViewController: NSViewController {
             guard completed else { return }
             AppRatingCoordinator.shared.scheduleReviewAfterSubscriptionPurchase()
             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.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = view.window {
                 alert.beginSheetModal(for: window) { [weak self] _ in
                     self?.dismissPremiumSheetFromParentIfNeeded()
@@ -1212,14 +1229,14 @@ private final class PremiumPlansViewController: NSViewController {
             let active = subscriptionStore.isProActive
             let alert = NSAlert()
             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 {
-                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.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = view.window {
                 alert.beginSheetModal(for: window) { [weak self] _ in
                     if active {
@@ -1241,7 +1258,7 @@ private final class PremiumPlansViewController: NSViewController {
 
     private func presentPurchaseError(_ error: Error) {
         let alert = NSAlert()
-        alert.messageText = "Something went wrong"
+        alert.messageText = L("Something went wrong")
         if let localized = error as? LocalizedError {
             var parts: [String] = []
             if let description = localized.errorDescription {
@@ -1255,7 +1272,7 @@ private final class PremiumPlansViewController: NSViewController {
             alert.informativeText = error.localizedDescription
         }
         alert.alertStyle = .warning
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window = view.window {
             alert.beginSheetModal(for: window)
         } 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() {
         guard let sheet = view.window, let parent = sheet.sheetParent else { return }
         parent.endSheet(sheet)

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

@@ -30,13 +30,13 @@ protocol DashboardDataProviding {
 final class MockDashboardDataProvider: DashboardDataProviding {
     func loadDashboardData() -> DashboardData {
         DashboardData(
-            subtitle: "Find your perfect job with the power of AI.",
+            subtitle: L("Find your perfect job with the power of AI."),
             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: [
                 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.
     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)
 
-        update("Checking your Pro subscription…", 0.38)
+        update(L("Checking your Pro subscription…"), 0.38)
         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()
 
-        update("Preparing your job search workspace…", 0.86)
+        update(L("Preparing your job search workspace…"), 0.86)
         NotificationCenter.default.post(name: .subscriptionStatusDidChange, object: nil)
         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,
                  .cannotConnectToHost,
                  .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:
-                return "The search was cancelled. Try again when you're ready."
+                return L("The search was cancelled. Try again when you're ready.")
             default:
                 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? {
         switch self {
         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)?
 
-    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 documentView = CVPreviewFlippedDocumentView()
     private let contentStack = NSStackView()
@@ -119,6 +119,7 @@ final class CVFilledPreviewPageView: NSView {
 
     private let subtitleLabel = NSTextField(wrappingLabelWithString: "")
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
@@ -138,7 +139,7 @@ final class CVFilledPreviewPageView: NSView {
         exportButton.target = self
         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.maximumNumberOfLines = 0
 
@@ -200,13 +201,24 @@ final class CVFilledPreviewPageView: NSView {
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
 
     deinit {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     @available(*, unavailable)
@@ -227,6 +239,16 @@ final class CVFilledPreviewPageView: NSView {
         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) {
         lastProfile = profile
         lastTemplate = template
@@ -237,8 +259,8 @@ final class CVFilledPreviewPageView: NSView {
         let doc = CVProfileDocumentView(profile: profile, template: template)
         profileDocumentView = 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() {
@@ -263,14 +285,14 @@ final class CVFilledPreviewPageView: NSView {
         // text. Rasterising what is actually drawn on screen preserves the full layout.
         let data = doc.pdfDataMatchingScreenAppearance() ?? doc.dataWithPDF(inside: bounds)
         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
         }
 
         let panel = NSSavePanel()
         panel.canCreateDirectories = true
         panel.allowedContentTypes = [.pdf]
-        let base = lastTemplate?.name ?? "CV"
+        let base = lastTemplate?.name ?? L("CV")
         let safe = base.replacingOccurrences(of: "/", with: "-")
         panel.nameFieldStringValue = "\(safe).pdf"
 
@@ -300,10 +322,10 @@ final class CVFilledPreviewPageView: NSView {
 
     private func presentExportError(_ message: String) {
         let alert = NSAlert()
-        alert.messageText = "Couldn’t save PDF"
+        alert.messageText = L("Couldn't save PDF")
         alert.informativeText = message
         alert.alertStyle = .warning
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
             alert.beginSheetModal(for: window, completionHandler: nil)
         } else {

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

@@ -18,8 +18,8 @@ enum CVCategoryGroup: Hashable {
 
     var title: String {
         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 {
         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 {
         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)
     }
 
+    /// 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.
     var previewImageAssetName: String? { nil }
 
@@ -579,19 +582,20 @@ final class CVMakerPageView: NSView {
     }
 
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
 
     private let pageGradientLayer = CAGradientLayer()
     private let filterChrome = NSVisualEffectView()
     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 familyChipsRow = NSStackView()
     private let scrollView = NSScrollView()
     private let gridDocument = TopFlippedView()
     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 selectedFamily: CVDesignFamily? = nil // nil == "All"
@@ -648,13 +652,24 @@ final class CVMakerPageView: NSView {
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
 
     deinit {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     @available(*, unavailable)
@@ -685,6 +700,16 @@ final class CVMakerPageView: NSView {
         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
 
     private func configureLayout() {
@@ -850,7 +875,7 @@ final class CVMakerPageView: NSView {
         familyChipButtons.removeAll()
 
         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.onSelect = { [weak self] in self?.didSelectFamily(nil) }
         familyChipsRow.addArrangedSubview(allChip)
@@ -904,7 +929,7 @@ final class CVMakerPageView: NSView {
 
         let templates = visibleTemplates
         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.textColor = Palette.secondaryText
             gridStack.addArrangedSubview(empty)
@@ -1044,7 +1069,7 @@ final class CVMakerPageView: NSView {
             chosen = nil
         }
         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
         }
         onContinueToProfileSelection?(template)
@@ -1060,7 +1085,7 @@ final class CVMakerPageView: NSView {
     }
 
     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.isBordered = false
         button.bezelStyle = .rounded
@@ -1082,8 +1107,8 @@ final class CVMakerPageView: NSView {
 
     private func beginLoadingAICatalogIfPossible() {
         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
             DispatchQueue.main.async {
                 guard let self else { return }
@@ -1098,7 +1123,7 @@ final class CVMakerPageView: NSView {
                         self.updateSelectedChipStates()
                     }
                 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
                         self?.subtitleLabel.stringValue = defaultSubtitle
                     }
@@ -1112,7 +1137,7 @@ final class CVMakerPageView: NSView {
         alert.messageText = title
         alert.informativeText = message
         alert.alertStyle = .informational
-        alert.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
             alert.beginSheetModal(for: window)
         } else {
@@ -1383,7 +1408,7 @@ private final class CVTemplateCard: NSView {
         preview.translatesAutoresizingMaskIntoConstraints = false
         previewSurface.addSubview(preview)
 
-        nameLabel.stringValue = template.name
+        nameLabel.stringValue = template.localizedName
         nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
         nameLabel.textColor = palette.primaryText
         nameLabel.isBordered = false

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

@@ -313,8 +313,8 @@ final class CVProfileDocumentView: NSView {
     private func modernClassicBandDocument() -> NSView {
         let theme = template.themeColor
         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()
         header.translatesAutoresizingMaskIntoConstraints = false
@@ -405,10 +405,10 @@ final class CVProfileDocumentView: NSView {
         inner.spacing = 10
         inner.alignment = .leading
         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 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(roleText, font: .systemFont(ofSize: 14, weight: .semibold), color: theme, maxLines: 2))
@@ -426,8 +426,8 @@ final class CVProfileDocumentView: NSView {
 
     private func modernSplitHeaderDocument() -> NSView {
         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 left = NSStackView()
@@ -505,13 +505,13 @@ final class CVProfileDocumentView: NSView {
         v.alignment = .leading
 
         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))
         }
 
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         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() {
                 v.addArrangedSubview(experienceBlock(job: job, compact: compact))
                 if index == 0 {
@@ -522,7 +522,7 @@ final class CVProfileDocumentView: NSView {
 
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         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 {
                 v.addArrangedSubview(educationBlock(edu: edu, compact: compact))
             }
@@ -546,16 +546,16 @@ final class CVProfileDocumentView: NSView {
         }
 
         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))
         }
         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))
         }
         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
     }
@@ -628,8 +628,8 @@ final class CVProfileDocumentView: NSView {
 
     private func creativeSingleColumnDocument() -> NSView {
         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()
         banner.translatesAutoresizingMaskIntoConstraints = false
@@ -669,8 +669,8 @@ final class CVProfileDocumentView: NSView {
         sidebarStack.layer?.cornerRadius = variant % 2 == 0 ? 10 : 8
         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(role, font: .systemFont(ofSize: 13, weight: .medium), color: onSidebar.withAlphaComponent(0.85), maxLines: 2))
         if !profile.personal.email.isEmpty {
@@ -679,7 +679,7 @@ final class CVProfileDocumentView: NSView {
         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(creativeSidebarHeading("STRENGTHS", onSidebar: onSidebar, accent: theme))
+        sidebarStack.addArrangedSubview(creativeSidebarHeading(L("STRENGTHS"), onSidebar: onSidebar, accent: theme))
         for token in skillTokensFromProfile(max: 8) {
             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.spacing = 8
         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(lab)
         v.addSubview(row)
@@ -751,12 +751,12 @@ final class CVProfileDocumentView: NSView {
         stack.alignment = .leading
         stack.addArrangedSubview(creativeMainHeader(theme: theme))
         if let summary = nonEmpty(profile.careerSummary) {
-            stack.addArrangedSubview(sectionHeading("Profile"))
+            stack.addArrangedSubview(sectionHeading(L("Profile")))
             stack.addArrangedSubview(paragraph(summary, compact: false))
         }
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         if !jobs.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Impact"))
+            stack.addArrangedSubview(sectionHeading(L("Impact")))
             for job in jobs {
                 let titleLine = [job.jobTitle, job.company].filter { !$0.isEmpty }.joined(separator: " — ")
                 if !titleLine.isEmpty {
@@ -770,7 +770,7 @@ final class CVProfileDocumentView: NSView {
         }
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         if !schools.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Education"))
+            stack.addArrangedSubview(sectionHeading(L("Education")))
             for edu in schools {
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
             }
@@ -783,12 +783,12 @@ final class CVProfileDocumentView: NSView {
 
     private func buildExecutiveDocument() -> NSView {
         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 {
             !$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 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.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) {
             stack.addArrangedSubview(sectionHeading(summaryTitle))
             stack.addArrangedSubview(paragraph(summary, compact: compact))
@@ -860,7 +860,7 @@ final class CVProfileDocumentView: NSView {
 
         let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
         if !jobs.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Selected Experience"))
+            stack.addArrangedSubview(sectionHeading(L("Selected Experience")))
             for job in jobs {
                 stack.addArrangedSubview(executiveExperienceBlock(job: job, compact: compact))
             }
@@ -868,7 +868,7 @@ final class CVProfileDocumentView: NSView {
 
         let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
         if !schools.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Education"))
+            stack.addArrangedSubview(sectionHeading(L("Education")))
             for edu in schools {
                 stack.addArrangedSubview(educationBlock(edu: edu, compact: compact))
             }
@@ -913,24 +913,24 @@ final class CVProfileDocumentView: NSView {
 
         let skills = skillTokensFromProfile(max: 12)
         if !skills.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Core Competencies"))
+            stack.addArrangedSubview(sectionHeading(L("Core Competencies")))
             for s in skills {
                 stack.addArrangedSubview(label("· \(s)", font: style.bodyFont, color: style.ink, maxLines: 0))
             }
         }
 
         if let cert = nonEmpty(profile.certificates) {
-            stack.addArrangedSubview(sectionHeading("Tools"))
+            stack.addArrangedSubview(sectionHeading(L("Tools")))
             stack.addArrangedSubview(paragraph(cert, compact: true))
         }
 
         if showMetrics, let hi = highlightsBodyText() {
-            stack.addArrangedSubview(sectionHeading("Impact"))
+            stack.addArrangedSubview(sectionHeading(L("Impact")))
             stack.addArrangedSubview(paragraph(hi, compact: true))
         }
 
         if stack.arrangedSubviews.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Contact"))
+            stack.addArrangedSubview(sectionHeading(L("Contact")))
             for line in contactLines() {
                 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)
 
     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 {
             !$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 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
             if let summary = nonEmpty(profile.careerSummary) {
-                stack.addArrangedSubview(sectionHeading("Profile"))
+                stack.addArrangedSubview(sectionHeading(L("Profile")))
                 stack.addArrangedSubview(paragraph(summary, compact: false))
             }
         }
         let appendExperience: () -> Void = { [self] in
             let jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
             if !jobs.isEmpty {
-                stack.addArrangedSubview(sectionHeading("Experience"))
+                stack.addArrangedSubview(sectionHeading(L("Experience")))
                 for job in jobs {
                     stack.addArrangedSubview(experienceBlock(job: job, compact: false))
                 }
@@ -1029,7 +1029,7 @@ final class CVProfileDocumentView: NSView {
         let appendEducation: () -> Void = { [self] in
             let schools = profile.educations.filter { !$0.isEffectivelyEmpty }
             if !schools.isEmpty {
-                stack.addArrangedSubview(sectionHeading("Education"))
+                stack.addArrangedSubview(sectionHeading(L("Education")))
                 for edu in schools {
                     stack.addArrangedSubview(educationBlock(edu: edu, compact: false))
                 }
@@ -1056,13 +1056,13 @@ final class CVProfileDocumentView: NSView {
         stack.alignment = .leading
         let skills = skillTokensFromProfile(max: 12)
         if skills.isEmpty {
-            stack.addArrangedSubview(sectionHeading("Contact"))
+            stack.addArrangedSubview(sectionHeading(L("Contact")))
             for line in contactLines() {
                 stack.addArrangedSubview(label(line, font: style.contactFont, color: style.muted, maxLines: 0))
             }
             return stack
         }
-        stack.addArrangedSubview(sectionHeading("Skills"))
+        stack.addArrangedSubview(sectionHeading(L("Skills")))
         for (i, s) in skills.enumerated() {
             let prefix = numbered ? "\(i + 1). " : "·  "
             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 {
-        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 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 name = label(nameText, font: style.nameFont, color: style.ink, maxLines: 2)
@@ -1256,7 +1256,7 @@ final class CVProfileDocumentView: NSView {
             return "\(a)\(b)".uppercased()
         }
         if let first = parts.first { return String(first.prefix(2)).uppercased() }
-        return "CV"
+        return L("CV")
     }
 
     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.addArrangedSubview(sectionHeading("Contact"))
+        box.addArrangedSubview(sectionHeading(L("Contact")))
         for line in contactLines() {
             box.addArrangedSubview(label(line, font: style.contactFont, color: style.ink, maxLines: 0))
         }
 
         let skills = skillTokensFromProfile(max: 8)
         if !skills.isEmpty {
-            box.addArrangedSubview(sectionHeading("Skills"))
+            box.addArrangedSubview(sectionHeading(L("Skills")))
             if variant % 5 == 2 {
                 box.addArrangedSubview(skillTagRow(theme: template.themeColor, maxTags: 5))
             } else {
@@ -1333,10 +1333,10 @@ final class CVProfileDocumentView: NSView {
         }
 
         if variant % 7 == 3, let tools = nonEmpty(profile.certificates) {
-            box.addArrangedSubview(sectionHeading("Tools"))
+            box.addArrangedSubview(sectionHeading(L("Tools")))
             box.addArrangedSubview(paragraph(tools, compact: true))
         } else if let ancillary = combinedAncillaryText(), !ancillary.isEmpty {
-            box.addArrangedSubview(sectionHeading("Languages & more"))
+            box.addArrangedSubview(sectionHeading(L("Languages & more")))
             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 jobs = profile.workExperiences.filter { !$0.isEffectivelyEmpty }
-        let experienceHeading = sectionHeading("Experience")
+        let experienceHeading = sectionHeading(L("Experience"))
         var experienceBlocks: [NSView] = []
         for job in jobs {
             experienceBlocks.append(experienceBlock(job: job, compact: compact))
@@ -1379,7 +1379,7 @@ final class CVProfileDocumentView: NSView {
         }
         let appendEducation: () -> Void = { [self] in
             if !schools.isEmpty {
-                v.addArrangedSubview(sectionHeading("Education"))
+                v.addArrangedSubview(sectionHeading(L("Education")))
                 educationBlocks.forEach { v.addArrangedSubview($0) }
             }
         }
@@ -1400,15 +1400,15 @@ final class CVProfileDocumentView: NSView {
 
     private func appendCertificatesInterestsReferrals(to v: NSStackView, compact: Bool) {
         if let cert = nonEmpty(profile.certificates) {
-            v.addArrangedSubview(sectionHeading("Certificates"))
+            v.addArrangedSubview(sectionHeading(L("Certificates")))
             v.addArrangedSubview(paragraph(cert, compact: compact))
         }
         if let interests = nonEmpty(profile.interests) {
-            v.addArrangedSubview(sectionHeading("Interests"))
+            v.addArrangedSubview(sectionHeading(L("Interests")))
             v.addArrangedSubview(paragraph(interests, compact: compact))
         }
         if let ref = nonEmpty(profile.referral) {
-            v.addArrangedSubview(sectionHeading("Referrals"))
+            v.addArrangedSubview(sectionHeading(L("Referrals")))
             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.
     private var summarySectionTitle: String {
-        template.family == .professional ? "Profile" : "Summary"
+        template.family == .professional ? L("Profile") : L("Summary")
     }
 
     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
 
 fileprivate enum CVPreviewDemoContent {
-    static let fullName = "Sarah Johnson"
+    static var fullName: String { L("Sarah Johnson") }
     /// 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.
-    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.
-    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).
-    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
@@ -273,11 +273,11 @@ final class CVTemplatePreviewView: NSView {
         inner.orientation = .vertical
         inner.spacing = 5
         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.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(sectionHeading("SKILLS"))
+        inner.addArrangedSubview(sectionHeading(L("SKILLS")))
         if variant % 5 == 2 {
             inner.addArrangedSubview(tagRow(theme: template.themeColor))
         } else {
@@ -286,7 +286,7 @@ final class CVTemplatePreviewView: NSView {
             }
         }
         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))
         }
         box.addArrangedSubview(inner)
@@ -301,11 +301,11 @@ final class CVTemplatePreviewView: NSView {
         let sp: CGFloat = compact ? 6.2 : 6.5
 
         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))
         }
         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.companyLine, font: .systemFont(ofSize: 6.2, weight: .medium), color: self.template.themeColor, alignment: .left, maxLines: 1))
             stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet1, size: sp))
@@ -320,7 +320,7 @@ final class CVTemplatePreviewView: NSView {
             profileBlock()
             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.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 2))
         return stack
@@ -467,7 +467,7 @@ final class CVTemplatePreviewView: NSView {
         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.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])
         top.orientation = .horizontal
@@ -493,9 +493,9 @@ final class CVTemplatePreviewView: NSView {
             box.layer?.cornerRadius = 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(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))
         return box
     }
@@ -520,11 +520,11 @@ final class CVTemplatePreviewView: NSView {
         stack.orientation = .vertical
         stack.spacing = 5
         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("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(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))
         return stack
     }
@@ -623,15 +623,15 @@ final class CVTemplatePreviewView: NSView {
         stack.alignment = .leading
 
         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))
         }
         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))
         }
         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.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))
@@ -654,7 +654,7 @@ final class CVTemplatePreviewView: NSView {
         stack.orientation = .vertical
         stack.spacing = 6
         stack.alignment = .leading
-        stack.addArrangedSubview(sectionHeading("SKILLS"))
+        stack.addArrangedSubview(sectionHeading(L("SKILLS")))
         for (i, s) in CVPreviewDemoContent.skillsList.enumerated() {
             let prefix = numbered ? "\(i + 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.spacing = (compact ? 4 : 5) - (tightLeading ? 1 : 0)
         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(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)
         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(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(sectionHeading("EDUCATION"))
+        stack.addArrangedSubview(sectionHeading(L("EDUCATION")))
         stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: serif, color: ink, alignment: .left, maxLines: 2))
         return stack
     }
@@ -756,14 +756,14 @@ final class CVTemplatePreviewView: NSView {
             stack.layer?.cornerRadius = layoutVariant % 4 == 2 ? 5 : 3
             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 {
             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))
         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))
         }
         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.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(creativeSidebarHeading("STRENGTHS", onSidebar: onSidebar, accent: theme))
+        sidebar.addArrangedSubview(creativeSidebarHeading(L("STRENGTHS"), onSidebar: onSidebar, accent: theme))
         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))
         }
@@ -801,14 +801,14 @@ final class CVTemplatePreviewView: NSView {
         main.spacing = 4 + CGFloat(layoutVariant % 3)
         main.alignment = .leading
         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(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))
         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.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))
 
         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 findJobsCTAPill = HoverableView()
     private let sendIconView = NSImageView()
-    private let sendLabel = NSTextField(labelWithString: "Send")
+    private let sendLabel = NSTextField(labelWithString: L("Send"))
     private let sendContentStack = NSStackView()
     private let findJobsCTAHost = 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 featureCardsRow = NSStackView()
     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 chatDocumentView = JobListingsDocumentView()
     private let chatStack = NSStackView()
@@ -141,7 +141,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private let nonHomeTitleLabel = NSTextField(labelWithString: "")
     private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
     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 savedJobsScrollView = NSScrollView()
     private let savedJobsDocumentView = JobListingsDocumentView()
@@ -193,6 +193,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private weak var sidebarUpgradeButton: HoverableButton?
     private var subscriptionObserver: NSObjectProtocol?
     private var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
     /// Retains the system share picker until the user picks a destination or dismisses the menu.
     private var appSharePicker: NSSharingServicePicker?
 
@@ -226,6 +227,9 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     override func viewDidChangeEffectiveAppearance() {
@@ -241,6 +245,103 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         ) { [weak self] _ in
             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() {
@@ -262,7 +363,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     func render(_ data: DashboardData) {
         dismissIndeedJobBrowserEmbedded()
-        greetingLabel.stringValue = "Welcome"
+        greetingLabel.stringValue = L("Welcome")
         subtitleLabel.stringValue = data.subtitle
         currentSidebarItems = data.sidebarItems
         if selectedSidebarIndex >= currentSidebarItems.count {
@@ -303,7 +404,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
         jobKeywordsField.textColor = Theme.primaryText
         jobKeywordsField.placeholderAttributedString = NSAttributedString(
-            string: "Ask for roles, skills, salary, or job descriptions...",
+            string: L("Ask for roles, skills, salary, or job descriptions..."),
             attributes: [
                 .foregroundColor: Theme.secondaryText,
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular)
@@ -318,11 +419,11 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
         appearanceModeSegment?.selectedSegment = AppAppearanceManager.shared.mode.segmentIndex
         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.isEnabled = !Self.supportedLanguages.isEmpty
+            langPopUp.isEnabled = !AppLanguage.allCases.isEmpty
         }
         cvMakerPageView.applyCurrentAppearance()
         profilesListPageView.applyCurrentAppearance()
@@ -340,7 +441,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func refreshSettingsPageAppearance(in root: NSView) {
         for view in root.subviewsRecursive() {
-            switch view.identifier?.rawValue {
+            guard let rawID = view.identifier?.rawValue else { continue }
+            switch rawID {
             case SettingsAppearanceID.section:
                 view.layer?.backgroundColor = Theme.settingsGroupBackground.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 {
                     icon.contentTintColor = Theme.brandBlue
                 }
-            case SettingsAppearanceID.sectionHeader:
-                (view as? NSTextField)?.textColor = Theme.secondaryText
-            case SettingsAppearanceID.rowTitle:
-                (view as? NSTextField)?.textColor = Theme.primaryText
             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.target = self
         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)
         chatHeaderRow.addArrangedSubview(chatHeaderLeadingSpacer)
         chatHeaderRow.addArrangedSubview(clearChatButton)
@@ -589,6 +691,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             welcomeHeroContent.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, constant: -32)
         ])
         registerSubscriptionObserverOnce()
+        refreshLocalizedStrings()
     }
 
     private func registerSubscriptionObserverOnce() {
@@ -612,15 +715,15 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
         let descriptionWidth: CGFloat = 158
         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
-            upgradeButton.title = "Manage Subscription"
+            upgradeButton.title = L("Manage Subscription")
         } 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
-            upgradeButton.title = "Try Pro"
+            upgradeButton.title = L("Try Pro")
         }
         updateFreeJobSearchQuotaLabel()
     }
@@ -635,8 +738,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let remaining = FreeTierJobSearchQuota.remainingUserMessages(isProActive: false)
         freeJobSearchQuotaLabel.isHidden = false
         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.
@@ -696,7 +799,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         featureCardsRow.alignment = .top
         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)),
             ("building.2", "Company", "Find opportunities at other companies", #selector(didTapFeatureCompany)),
             ("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 {
             let card = FeatureShortcutCardView(
                 symbolName: spec.symbol,
-                title: spec.title,
-                subtitle: spec.subtitle,
+                title: L(spec.titleKey),
+                subtitle: L(spec.subtitleKey),
                 target: self,
                 action: spec.action
             )
@@ -841,7 +944,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func jobListingHostSubtitle(_ job: JobListing) -> String {
         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.") {
             return String(host.dropFirst(4))
@@ -943,7 +1046,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         descriptionField.tag = 502
         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.cardContext = context
         applyButton.isBordered = false
@@ -962,7 +1065,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         applyButton.setContentCompressionResistancePriority(.required, for: .horizontal)
 
         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.cardContext = context
         savedButton.setButtonType(.toggle)
@@ -986,7 +1089,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let dismissButton = JobPayloadButton()
         dismissButton.jobPayload = job
         dismissButton.cardContext = context
-        dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Dismiss")
+        dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: L("Dismiss"))
         dismissButton.imagePosition = .imageOnly
         dismissButton.imageScaling = .scaleProportionallyDown
         dismissButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
@@ -995,7 +1098,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         dismissButton.contentTintColor = Theme.secondaryText
         dismissButton.target = self
         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.wantsLayer = true
         dismissButton.layer?.cornerRadius = 8
@@ -1199,7 +1302,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let willSave = !isJobSaved(job)
         applySavedState(willSave, for: job)
         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)
         styleJobSavedButton(sender)
         if isSavedJobsSidebarIndex(selectedSidebarIndex) {
@@ -1313,10 +1416,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
         jobSearchIcon.translatesAutoresizingMaskIntoConstraints = false
         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
 
-        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 ctaCorner = ctaHeight / 2
@@ -1366,7 +1469,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         findJobsButton.pointerCursor = true
         findJobsButton.target = self
         findJobsButton.action = #selector(didSubmitSearch)
-        findJobsButton.setAccessibilityLabel("Send")
+        findJobsButton.setAccessibilityLabel(L("Send"))
         findJobsButton.hoverHandler = { [weak self] hovering in
             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.alignment = .center
         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])
         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.
     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)
     }
 
@@ -1719,13 +1822,13 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     private func confirmDeleteProfile(id: UUID) {
         let displayName = SavedProfilesStore.profile(id: id)?.profileDisplayName ?? ""
         let alert = NSAlert()
-        alert.messageText = "Delete this profile?"
+        alert.messageText = L("Delete this profile?")
         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.addButton(withTitle: "Cancel")
-        alert.addButton(withTitle: "Delete")
+        alert.addButton(withTitle: L("Cancel"))
+        alert.addButton(withTitle: L("Delete"))
         guard let window = window else {
             let response = alert.runModal()
             if response == .alertSecondButtonReturn {
@@ -1758,19 +1861,19 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         contentStack.alignment = .leading
         contentStack.translatesAutoresizingMaskIntoConstraints = false
 
-        let appearanceTitle = NSTextField(labelWithString: "Appearance")
+        let appearanceTitle = NSTextField(labelWithString: L("Appearance"))
         appearanceTitle.font = .systemFont(ofSize: 12, weight: .semibold)
         appearanceTitle.textColor = Theme.secondaryText
         appearanceTitle.alignment = .left
-        appearanceTitle.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.sectionHeader)
+        appearanceTitle.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.sectionHeader).Appearance")
 
         let themeSegment = makeAppearanceModeSegment()
         appearanceModeSegment = themeSegment
         let langPopUp = makeLanguagePopUp()
         languagePopUp = langPopUp
         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])
@@ -1780,21 +1883,21 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         appearanceStack.translatesAutoresizingMaskIntoConstraints = false
 
         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.textColor = Theme.secondaryText
         aboutTitle.alignment = .left
-        aboutTitle.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.sectionHeader)
+        aboutTitle.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.sectionHeader).About")
 
         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])
@@ -1823,7 +1926,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func makeAppearanceModeSegment() -> NSSegmentedControl {
         let segment = NSSegmentedControl(
-            labels: ["System", "Light", "Dark"],
+            labels: [L("System"), L("Light"), L("Dark")],
             trackingMode: .selectOne,
             target: self,
             action: #selector(appearanceModeChanged(_:))
@@ -1841,30 +1944,24 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         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 {
         let popup = NSPopUpButton(frame: .zero, pullsDown: false)
         popup.translatesAutoresizingMaskIntoConstraints = false
         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.target = self
         popup.action = #selector(languageChanged(_:))
-        popup.isEnabled = !Self.supportedLanguages.isEmpty
+        popup.isEnabled = !AppLanguage.allCases.isEmpty
         popup.setContentHuggingPriority(.required, for: .horizontal)
         popup.setContentCompressionResistancePriority(.required, for: .horizontal)
         return popup
@@ -1872,8 +1969,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     @objc private func languageChanged(_ sender: NSPopUpButton) {
         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 {
@@ -1912,7 +2008,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         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()
         row.translatesAutoresizingMaskIntoConstraints = false
         row.wantsLayer = true
@@ -1927,14 +2023,14 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let icon = NSImageView()
         icon.translatesAutoresizingMaskIntoConstraints = false
         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
 
-        let titleLabel = NSTextField(labelWithString: title)
+        let titleLabel = NSTextField(labelWithString: L(localizationKey))
         titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
         titleLabel.textColor = Theme.primaryText
         titleLabel.alignment = .left
-        titleLabel.identifier = NSUserInterfaceItemIdentifier(SettingsAppearanceID.rowTitle)
+        titleLabel.identifier = NSUserInterfaceItemIdentifier("\(SettingsAppearanceID.rowTitle).\(localizationKey)")
 
         let rowStack = NSStackView()
         rowStack.orientation = .horizontal
@@ -1997,8 +2093,8 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             $0.removeFromSuperview()
         }
         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.textColor = Theme.secondaryText
             empty.alignment = .left
@@ -2008,7 +2104,9 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             empty.widthAnchor.constraint(equalTo: savedJobsStack.widthAnchor).isActive = true
             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 {
             let card = makeJobListingCard(job, context: .savedJobsPage)
             savedJobsStack.addArrangedSubview(card)
@@ -2018,27 +2116,27 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func isSavedJobsSidebarIndex(_ index: Int) -> Bool {
         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 {
         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 {
         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 {
         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 {
         guard index >= 0, index < currentSidebarItems.count else { return false }
-        return currentSidebarItems[index].title == "Profile"
+        return currentSidebarItems[index].title == L("Profile")
     }
 
     private func updateMainContentVisibility() {
@@ -2132,17 +2230,17 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     @objc private func didTapFeatureRole() {
         selectFeatureShortcut(.role)
-        focusSearchField(seed: "Find roles similar to: ")
+        focusSearchField(seed: L("Find roles similar to: "))
     }
 
     @objc private func didTapFeatureCompany() {
         selectFeatureShortcut(.company)
-        focusSearchField(seed: "Find jobs at company: ")
+        focusSearchField(seed: L("Find jobs at company: "))
     }
 
     @objc private func didTapFeatureSkill() {
         selectFeatureShortcut(.skill)
-        focusSearchField(seed: "Find jobs that require skill: ")
+        focusSearchField(seed: L("Find jobs that require skill: "))
     }
 
     private func selectFeatureShortcut(_ shortcut: FeatureShortcut) {
@@ -2196,7 +2294,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     @objc private func didTapMoreApps() {
         guard let url = AppMarketingLinks.developerAppsURL else {
-            presentAppMarketingConfigurationAlert(feature: "More Apps")
+            presentAppMarketingConfigurationAlert(feature: L("More Apps"))
             return
         }
         NSWorkspace.shared.open(url)
@@ -2204,14 +2302,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func presentAppMarketingConfigurationAlert(feature: String) {
         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.addButton(withTitle: "OK")
+        alert.addButton(withTitle: L("OK"))
         if let window {
             alert.beginSheetModal(for: window)
         } else {
@@ -2246,7 +2340,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     @objc private func didTapLoadMoreJobs() {
         guard ensureProAccessForJobSearch() else { return }
-        let prompt = "Show more jobs"
+        let prompt = L("Show more jobs")
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
         if anchorUserJobQuery(excludingLatestUserMessage: prompt) == nil { return }
         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 {
         if newJobsCount == 0 {
             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 {
-            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 {
@@ -2347,6 +2441,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
 
     private func isContinuationPrompt(_ prompt: String) -> Bool {
         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> = [
             "more",
             "show more",
@@ -2423,7 +2521,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         chatMessages.removeAll()
         lastSearchResults.removeAll()
         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))
         appendChatBubble(text: welcome, isUser: false)
     }
@@ -2620,7 +2718,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         row.translatesAutoresizingMaskIntoConstraints = false
         let button = HoverableButton()
         button.pointerCursor = true
-        button.title = "Show more jobs"
+        button.title = L("Show more jobs")
         button.font = .systemFont(ofSize: 12, weight: .semibold)
         button.bezelStyle = .rounded
         button.controlSize = .regular
@@ -2730,10 +2828,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         rowHost.layer?.cornerRadius = 8
         rowHost.restingBackgroundColor = isSelected ? Theme.selectionFill : nil
         rowHost.hoverBackgroundColor = isSelected ? Theme.selectionFillHover : Theme.sidebarRowHoverFill
-        rowHost.setAccessibilityLabel("Indeed")
+        rowHost.setAccessibilityLabel(L("Indeed"))
         rowHost.setAccessibilityRole(.button)
         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()
         row.orientation = .horizontal
@@ -2749,7 +2847,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         icon.widthAnchor.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.textColor = isSelected ? Theme.brandBlue : Theme.secondaryText
         text.refusesFirstResponder = true
@@ -2859,7 +2957,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
             let sidebarHorizontalInset = sidebar.edgeInsets.left + sidebar.edgeInsets.right
             rowHost.widthAnchor.constraint(equalTo: sidebar.widthAnchor, constant: -sidebarHorizontalInset).isActive = true
 
-            if item.title == "Home" {
+            if item.title == L("Home") {
                 addIndeedSidebarLaunchRow()
             }
         }
@@ -2896,7 +2994,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         proIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
         proIcon.contentTintColor = Theme.proAccent
 
-        let proEyebrow = NSTextField(labelWithString: "Premium")
+        let proEyebrow = NSTextField(labelWithString: L("Premium"))
         proEyebrow.font = .systemFont(ofSize: 11, weight: .heavy)
         proEyebrow.textColor = Theme.proAccent
         proEyebrow.alignment = .center
@@ -2906,12 +3004,12 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         eyebrowRow.spacing = 6
         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.textColor = Theme.primaryText
         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.textColor = Theme.secondaryText
         upgradeDescription.alignment = .center
@@ -2920,7 +3018,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         let innerContentWidth = cardWidth - 28
         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.bezelStyle = .rounded
         upgradeButton.font = .systemFont(ofSize: 13, weight: .bold)
@@ -3051,7 +3149,7 @@ private final class OpenAIJobSearchService {
             completion(.failure(NSError(
                 domain: "OpenAIJobSearchService",
                 code: 1,
-                userInfo: [NSLocalizedDescriptionKey: "Job search is unavailable."]
+                userInfo: [NSLocalizedDescriptionKey: L("Job search is unavailable.")]
             )))
             return
         }

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

@@ -29,14 +29,14 @@ final class LoadingView: NSView {
     private let iconWell = NSView()
     private let logoView = IndeedLogoView(displayHeight: 40, variant: .compact)
     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 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 thinkingIndicator = ChatThinkingIndicatorView(
         compact: false,
-        accessibilityLabel: "Loading \(AppMarketingLinks.displayName)"
+        accessibilityLabel: String(format: L("Loading %@"), AppMarketingLinks.displayName)
     )
 
     override init(frame frameRect: NSRect) {
@@ -87,7 +87,7 @@ final class LoadingView: NSView {
     func setStatus(_ message: String, progress: CGFloat) {
         statusLabel.stringValue = message
         progressBar.setProgress(progress, animated: true)
-        setAccessibilityLabel("Loading \(AppMarketingLinks.displayName). \(message)")
+        setAccessibilityLabel(String(format: L("Loading %@. %@"), AppMarketingLinks.displayName, message))
     }
 
     func startAnimating() {
@@ -236,7 +236,7 @@ final class LoadingView: NSView {
         installPageGradient()
         setAccessibilityElement(true)
         setAccessibilityRole(.group)
-        setAccessibilityLabel("Loading \(AppMarketingLinks.displayName)")
+        setAccessibilityLabel(String(format: L("Loading %@"), AppMarketingLinks.displayName))
     }
 
     private func installPageGradient() {
@@ -449,7 +449,7 @@ private final class LoadingProgressBarView: NSView {
 
         setAccessibilityElement(true)
         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 languagesField = 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 phoneJobRow: ProfileDualFieldRow!
 
     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 var editingProfileID: UUID?
 
@@ -218,6 +218,7 @@ final class MyProfilePageView: NSView {
 
     private var referralHelperLabel: NSTextField?
     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.
     override var userInterfaceLayoutDirection: NSUserInterfaceLayoutDirection {
@@ -235,13 +236,24 @@ final class MyProfilePageView: NSView {
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
 
     deinit {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     required init?(coder: NSCoder) {
@@ -266,6 +278,15 @@ final class MyProfilePageView: NSView {
         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() {
         super.viewDidMoveToWindow()
         guard window != nil else { return }
@@ -381,7 +402,7 @@ final class MyProfilePageView: NSView {
         contextLabel.translatesAutoresizingMaskIntoConstraints = false
         contextLabel.font = .systemFont(ofSize: 15, weight: .semibold)
         contextLabel.textColor = ProfilePagePalette.primaryText
-        contextLabel.stringValue = "New profile"
+        contextLabel.stringValue = L("New profile")
         contextLabel.backgroundColor = .clear
         contextLabel.isBordered = false
         contextLabel.isEditable = false
@@ -433,22 +454,22 @@ final class MyProfilePageView: NSView {
         ])
 
         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)
         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)
         addFullWidthArrangedSubview(phoneJobRow)
 
         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(horizontalSeparator())
@@ -458,8 +479,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
             multilineProfileBlock(
-                title: "Certificates / Rewards",
-                placeholder: "List your certificates and awards...",
+                title: L("Certificates / Rewards"),
+                placeholder: L("List your certificates and awards..."),
                 field: certificatesField,
                 minHeight: 100
             )
@@ -467,8 +488,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
             multilineProfileBlock(
-                title: "Interests",
-                placeholder: "List your interests and hobbies...",
+                title: L("Interests"),
+                placeholder: L("List your interests and hobbies..."),
                 field: interestsField,
                 minHeight: 100
             )
@@ -476,8 +497,8 @@ final class MyProfilePageView: NSView {
         addFullWidthArrangedSubview(horizontalSeparator())
         addFullWidthArrangedSubview(
             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,
                 minHeight: 100
             )
@@ -497,7 +518,7 @@ final class MyProfilePageView: NSView {
 
     func prepareNewProfile() {
         editingProfileID = nil
-        contextLabel.stringValue = "New profile"
+        contextLabel.stringValue = L("New profile")
         applyForm(
             from: SavedProfile(
                 id: UUID(),
@@ -516,7 +537,7 @@ final class MyProfilePageView: NSView {
 
     func loadSavedProfile(_ profile: SavedProfile) {
         editingProfileID = profile.id
-        contextLabel.stringValue = "Edit profile"
+        contextLabel.stringValue = L("Edit profile")
         applyForm(from: profile)
     }
 
@@ -741,7 +762,7 @@ final class MyProfilePageView: 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.textColor = ProfilePagePalette.secondaryText
         label.translatesAutoresizingMaskIntoConstraints = false
@@ -763,7 +784,7 @@ final class MyProfilePageView: NSView {
         careerField.setContentHuggingPriority(.defaultLow, for: .horizontal)
         careerField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
         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: [
                 .foregroundColor: ProfilePagePalette.secondaryText,
                 .font: NSFont.systemFont(ofSize: 14, weight: .regular),
@@ -879,16 +900,16 @@ final class MyProfilePageView: 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.textColor = ProfilePagePalette.secondaryText
         label.translatesAutoresizingMaskIntoConstraints = false
         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 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.textColor = ProfilePagePalette.secondaryText
         helper.translatesAutoresizingMaskIntoConstraints = false
@@ -925,14 +946,14 @@ final class MyProfilePageView: 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.textColor = ProfilePagePalette.primaryText
         title.translatesAutoresizingMaskIntoConstraints = false
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         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.bezelStyle = .rounded
         addButton.isBordered = true
@@ -972,14 +993,14 @@ final class MyProfilePageView: NSView {
     }
 
     private func educationSection() -> NSView {
-        let title = NSTextField(labelWithString: "Education")
+        let title = NSTextField(labelWithString: L("Education"))
         title.font = .systemFont(ofSize: 15, weight: .semibold)
         title.textColor = ProfilePagePalette.primaryText
         title.translatesAutoresizingMaskIntoConstraints = false
         title.setContentHuggingPriority(.defaultLow, for: .horizontal)
         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.bezelStyle = .rounded
         addButton.isBordered = true
@@ -1128,16 +1149,16 @@ final class MyProfilePageView: NSView {
         window?.makeFirstResponder(nil)
         let profile = captureSavedProfileForSave()
         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 {
             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.addButton(withTitle: "OK")
+            alert.addButton(withTitle: L("OK"))
             if let window = window {
                 alert.beginSheetModal(for: window) { _ in }
             } else {
@@ -1161,7 +1182,7 @@ private enum ProfileEntryCardLayout {
 private final class WorkExperienceEntryView: NSView {
     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 jobTitleField = NSTextField()
     private let companyField = NSTextField()
@@ -1179,7 +1200,7 @@ private final class WorkExperienceEntryView: NSView {
     }
 
     func setExperienceIndex(_ index: Int) {
-        subtitleLabel.stringValue = "Experience \(index)"
+        subtitleLabel.stringValue = String(format: L("Experience %d"), index)
     }
 
     func setDeleteHidden(_ hidden: Bool) {
@@ -1236,10 +1257,10 @@ private final class WorkExperienceEntryView: NSView {
         deleteButton.target = self
         deleteButton.action = #selector(didTapDelete)
         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
         } else {
-            deleteButton.title = "Remove"
+            deleteButton.title = L("Remove")
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
         }
 
@@ -1255,15 +1276,15 @@ private final class WorkExperienceEntryView: NSView {
         ProfileLayoutEnforcement.applyForcedLTR(to: headerRow)
         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)
 
-        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(
-            title: "Description",
+            title: L("Description"),
             field: descriptionField,
-            placeholder: "Describe your responsibilities and achievements...",
+            placeholder: L("Describe your responsibilities and achievements..."),
             minHeight: 120
         )
 
@@ -1454,7 +1475,7 @@ private final class WorkExperienceEntryView: NSView {
 private final class EducationEntryView: NSView {
     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 degreeField = NSTextField()
     private let institutionField = NSTextField()
@@ -1471,7 +1492,7 @@ private final class EducationEntryView: NSView {
     }
 
     func setEducationIndex(_ index: Int) {
-        subtitleLabel.stringValue = "Education \(index)"
+        subtitleLabel.stringValue = String(format: L("Education %d"), index)
     }
 
     func setDeleteHidden(_ hidden: Bool) {
@@ -1526,10 +1547,10 @@ private final class EducationEntryView: NSView {
         deleteButton.target = self
         deleteButton.action = #selector(didTapDelete)
         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
         } else {
-            deleteButton.title = "Remove"
+            deleteButton.title = L("Remove")
             deleteButton.font = .systemFont(ofSize: 12, weight: .medium)
         }
 
@@ -1546,21 +1567,21 @@ private final class EducationEntryView: NSView {
         headerRow.translatesAutoresizingMaskIntoConstraints = false
 
         let degreeGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Degree / program *",
+            title: L("Degree / program *"),
             field: degreeField,
-            placeholder: "e.g., BSc Computer Science"
+            placeholder: L("e.g., BSc Computer Science")
         )
         let institutionGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Institution *",
+            title: L("Institution *"),
             field: institutionField,
-            placeholder: "e.g., MIT"
+            placeholder: L("e.g., MIT")
         )
         degreeInstitutionRow = ProfileDualFieldRow(left: degreeGroup, right: institutionGroup, spacing: 12)
 
         let yearGroup = WorkExperienceEntryView.labeledFieldStack(
-            title: "Year *",
+            title: L("Year *"),
             field: yearField,
-            placeholder: "e.g., 2020"
+            placeholder: L("e.g., 2020")
         )
 
         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 documentView = ProfilesListDocumentView()
     private let contentStack = NSStackView()
-    private let titleLabel = NSTextField(labelWithString: "Profiles")
+    private let titleLabel = NSTextField(labelWithString: L("Profiles"))
     private let subtitleLabel = 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 var appearanceObserver: NSObjectProtocol?
+    private var languageObserver: NSObjectProtocol?
     /// Non-`nil` after **Use Template & Select Profile** until the dashboard clears the flow (e.g. leaving Profile).
     private var pendingCVTemplateDisplayName: String?
 
@@ -57,13 +58,24 @@ final class ProfilesListPageView: NSView {
         ) { [weak self] _ in
             self?.applyCurrentAppearance()
         }
+        languageObserver = NotificationCenter.default.addObserver(
+            forName: AppLanguageManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyLocalizedStrings()
+        }
         applyCurrentAppearance()
+        applyLocalizedStrings()
     }
 
     deinit {
         if let appearanceObserver {
             NotificationCenter.default.removeObserver(appearanceObserver)
         }
+        if let languageObserver {
+            NotificationCenter.default.removeObserver(languageObserver)
+        }
     }
 
     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() {
         for row in contentStack.arrangedSubviews {
             contentStack.removeArrangedSubview(row)
@@ -113,7 +139,10 @@ final class ProfilesListPageView: NSView {
     func setPendingCVTemplateDisplayName(_ name: String?) {
         pendingCVTemplateDisplayName = name
         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
         } else {
             pendingFlowLabel.isHidden = true
@@ -128,11 +157,11 @@ final class ProfilesListPageView: NSView {
 
         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.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.textColor = ProfilesListPalette.secondaryText
         emptyStateLabel.isHidden = true
@@ -237,9 +266,9 @@ private final class ProfileListRowView: NSView {
     private let profileID: UUID
     private let nameLabel = NSTextField(labelWithString: "")
     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) {
         self.profileID = profile.id
@@ -254,11 +283,11 @@ private final class ProfileListRowView: NSView {
             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)
 
         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.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)";