From 2feabafc4e4a2c66f88df764af0d4a2b55a75d51 Mon Sep 17 00:00:00 2001 From: max0071001 <48833066+max0071001@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:08:14 +0800 Subject: [PATCH 1/3] Add English and Traditional Chinese interface localization --- Package.swift | 5 +- Sources/DigUpApp/AddFolders.swift | 39 +- Sources/DigUpApp/App.swift | 39 +- Sources/DigUpApp/AppController.swift | 7 +- Sources/DigUpApp/AppModel.swift | 67 +-- Sources/DigUpApp/AppSettings.swift | 5 +- Sources/DigUpApp/CodeEditors.swift | 3 +- Sources/DigUpApp/Components.swift | 27 +- Sources/DigUpApp/DebugHooks.swift | 5 +- Sources/DigUpApp/Engine.swift | 9 +- Sources/DigUpApp/Estimates.swift | 9 +- Sources/DigUpApp/FolderChoices.swift | 37 +- Sources/DigUpApp/Hotkey.swift | 13 +- Sources/DigUpApp/ModelDownload.swift | 29 +- Sources/DigUpApp/Moments.swift | 7 +- Sources/DigUpApp/Onboarding.swift | 139 +++--- Sources/DigUpApp/PowerPolicy.swift | 7 +- Sources/DigUpApp/Previews.swift | 17 +- Sources/DigUpApp/Results.swift | 61 +-- Sources/DigUpApp/SearchPanel.swift | 5 +- Sources/DigUpApp/SearchPanelView.swift | 83 ++-- Sources/DigUpApp/SearchWindow.swift | 79 ++-- Sources/DigUpApp/SettingsWindow.swift | 159 +++---- Sources/DigUpApp/StatusMenu.swift | 27 +- Sources/DigUpLocalization/L10n.swift | 15 + .../Resources/en.lproj/InfoPlist.strings | 5 + .../Resources/en.lproj/Localizable.strings | 405 ++++++++++++++++++ .../Resources/zh-Hant.lproj/InfoPlist.strings | 5 + .../zh-Hant.lproj/Localizable.strings | 405 ++++++++++++++++++ .../LocalizationTests.swift | 68 +++ build.sh | 8 + docs/localization.md | 36 ++ docs/localization/en-general.png | Bin 0 -> 90093 bytes docs/localization/zh-Hant-general.png | Bin 0 -> 88610 bytes 34 files changed, 1399 insertions(+), 426 deletions(-) create mode 100644 Sources/DigUpLocalization/L10n.swift create mode 100644 Sources/DigUpLocalization/Resources/en.lproj/InfoPlist.strings create mode 100644 Sources/DigUpLocalization/Resources/en.lproj/Localizable.strings create mode 100644 Sources/DigUpLocalization/Resources/zh-Hant.lproj/InfoPlist.strings create mode 100644 Sources/DigUpLocalization/Resources/zh-Hant.lproj/Localizable.strings create mode 100644 Tests/DigUpLocalizationTests/LocalizationTests.swift create mode 100644 docs/localization.md create mode 100644 docs/localization/en-general.png create mode 100644 docs/localization/zh-Hant-general.png diff --git a/Package.swift b/Package.swift index 91709b06..1a942337 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,7 @@ import PackageDescription let package = Package( name: "DigUp", + defaultLocalization: "en", platforms: [.macOS(.v14)], products: [ .executable(name: "digup", targets: ["digup"]), @@ -16,6 +17,7 @@ let package = Package( targets: [ // The engine: crawler, extractors, index store, the embedder interface and worker client, search. No UI. .target(name: "DigUpKit"), + .target(name: "DigUpLocalization", resources: [.process("Resources")]), // llama.cpp (scripts/build-llama.sh builds it into Vendor/llama) and EmbeddingGemma 2 on it. .systemLibrary(name: "CLlama", path: "Sources/CLlama"), .target( @@ -30,11 +32,12 @@ let package = Package( // "DigUp": on a case-insensitive disk it would collide with the `digup` CLI binary.) .executableTarget( name: "DigUpApp", - dependencies: ["DigUpKit", .product(name: "Sparkle", package: "Sparkle")], + dependencies: ["DigUpKit", "DigUpLocalization", .product(name: "Sparkle", package: "Sparkle")], swiftSettings: [.defaultIsolation(MainActor.self)], linkerSettings: [.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@executable_path/../Frameworks"])] ), .testTarget(name: "DigUpKitTests", dependencies: ["DigUpKit"]), + .testTarget(name: "DigUpLocalizationTests", dependencies: ["DigUpLocalization"]), .testTarget(name: "LlamaRuntimeTests", dependencies: ["LlamaRuntime"]), ] ) diff --git a/Sources/DigUpApp/AddFolders.swift b/Sources/DigUpApp/AddFolders.swift index 2130277d..02c9e377 100644 --- a/Sources/DigUpApp/AddFolders.swift +++ b/Sources/DigUpApp/AddFolders.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import SwiftUI @@ -54,7 +55,7 @@ final class AddFoldersController { add: { [weak self] in self?.add() }) let sheet = NSWindow(contentViewController: NSHostingController(rootView: view)) sheet.styleMask = [.titled] - sheet.title = code ? "Code Folders" : "Add Folders" + sheet.title = code ? L10n.text("Code Folders") : L10n.text("Add Folders") self.sheet = sheet choices.startEstimates() log("add folders: \(choices.rows.count) to choose from") @@ -105,17 +106,17 @@ private struct AddFoldersView: View { VStack(spacing: 0) { VStack(alignment: .leading, spacing: 16) { if choices.code { - StepTitle(title: "Search your code", - subtitle: "Pick the folders that hold your projects; open one to pick among its repos. " - + "DigUp reads each once, then what changes. Code is searched on its own: type " - + "code: in the search panel.") + StepTitle(title: L10n.text("Search your code"), + subtitle: L10n.text("Pick the folders that hold your projects; open one to pick among its repos. ") + + L10n.text("DigUp reads each once, then what changes. Code is searched on its own: type ") + + L10n.text("code: in the search panel.")) } else { - StepTitle(title: "Add folders to search", - subtitle: "DigUp reads a new folder once, then keeps up with what changes in it. " - + "Open a folder to pick some of its subfolders.") + StepTitle(title: L10n.text("Add folders to search"), + subtitle: L10n.text("DigUp reads a new folder once, then keeps up with what changes in it. ") + + L10n.text("Open a folder to pick some of its subfolders.")) } ScrollView { - FolderList(choices: choices, addTitle: "Choose Another Folder…", add: pick) + FolderList(choices: choices, addTitle: L10n.text("Choose Another Folder…"), add: pick) if choices.code { CodeFolderNote().padding(.top, 8) } } .scrollBounceBehavior(.basedOnSize) @@ -130,9 +131,9 @@ private struct AddFoldersView: View { .foregroundStyle(.secondary) .lineLimit(1) Spacer(minLength: 8) - Button("Cancel", action: cancel) + Button(L10n.text("Cancel"), action: cancel) .keyboardShortcut(.cancelAction) - Button("Add", action: add) + Button(L10n.text("Add"), action: add) .keyboardShortcut(.defaultAction) .disabled(!choices.changesSomething) } @@ -144,15 +145,15 @@ private struct AddFoldersView: View { } private var summary: String { - guard choices.changesSomething else { return "Check the folders to add" } - guard choices.estimatesDone else { return "Looking at the folders…" } - let time = choices.totalSeconds < 1 ? "under a second" : "about \(roughDuration(choices.totalSeconds))" + guard choices.changesSomething else { return L10n.text("Check the folders to add") } + guard choices.estimatesDone else { return L10n.text("Looking at the folders…") } + let time = choices.totalSeconds < 1 ? L10n.text("under a second") : L10n.format("about %1$@", String(describing: roughDuration(choices.totalSeconds))) if choices.code { - return "Adds \(time) of indexing and \(roughBytes(choices.totalBytes))" + return L10n.format("Adds %1$@ of indexing and %2$@", String(describing: time), String(describing: roughBytes(choices.totalBytes))) } let later = choices.totalLaterSeconds - return "Adds \(time) of indexing" - + (later >= 30 ? ", and \(roughDuration(later)) more for long files" : "") + return L10n.format("Adds %1$@ of indexing", String(describing: time)) + + (later >= 30 ? L10n.format(", and %1$@ more for long files", String(describing: roughDuration(later))) : "") } } @@ -160,8 +161,8 @@ private struct AddFoldersView: View { struct CodeFolderNote: View { var body: some View { Label { - Text("Code search reads source files, notebooks and a repo's own docs. It skips what git ignores, vendored " - + "and generated code, data files, and anything that looks like a key or a password.") + Text(L10n.text("Code search reads source files, notebooks and a repo's own docs. It skips what git ignores, vendored ") + + L10n.text("and generated code, data files, and anything that looks like a key or a password.")) .fixedSize(horizontal: false, vertical: true) } icon: { Image(systemName: "info.circle") diff --git a/Sources/DigUpApp/App.swift b/Sources/DigUpApp/App.swift index 6c190f3e..e50139d8 100644 --- a/Sources/DigUpApp/App.swift +++ b/Sources/DigUpApp/App.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit @main @@ -60,31 +61,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let mainMenu = NSMenu() let appMenu = NSMenu() - appMenu.addItem(withTitle: "About DigUp", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), + appMenu.addItem(withTitle: L10n.text("About DigUp"), action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: "") - appMenu.addItem(ActionMenuItem("Check for Updates…") { [weak app] in app?.updates.checkNow() }) + appMenu.addItem(ActionMenuItem(L10n.text("Check for Updates…")) { [weak app] in app?.updates.checkNow() }) appMenu.addItem(.separator()) - appMenu.addItem(ActionMenuItem("Settings…", key: ",") { [weak app] in app?.showSettings() }) + appMenu.addItem(ActionMenuItem(L10n.text("Settings…"), key: ",") { [weak app] in app?.showSettings() }) appMenu.addItem(.separator()) - appMenu.addItem(withTitle: "Hide DigUp", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h") - appMenu.addItem(withTitle: "Quit DigUp", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") + appMenu.addItem(withTitle: L10n.text("Hide DigUp"), action: #selector(NSApplication.hide(_:)), keyEquivalent: "h") + appMenu.addItem(withTitle: L10n.text("Quit DigUp"), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") - let fileMenu = NSMenu(title: "File") - fileMenu.addItem(ActionMenuItem("New Search", key: "n") { [weak app] in app?.showSearchWindow(query: "") }) - fileMenu.addItem(withTitle: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w") + let fileMenu = NSMenu(title: L10n.text("File")) + fileMenu.addItem(ActionMenuItem(L10n.text("New Search"), key: "n") { [weak app] in app?.showSearchWindow(query: "") }) + fileMenu.addItem(withTitle: L10n.text("Close"), action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w") - let editMenu = NSMenu(title: "Edit") - editMenu.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z") - editMenu.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "Z") + let editMenu = NSMenu(title: L10n.text("Edit")) + editMenu.addItem(withTitle: L10n.text("Undo"), action: Selector(("undo:")), keyEquivalent: "z") + editMenu.addItem(withTitle: L10n.text("Redo"), action: Selector(("redo:")), keyEquivalent: "Z") editMenu.addItem(.separator()) - editMenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x") - editMenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c") - editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") - editMenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") - - let windowMenu = NSMenu(title: "Window") - windowMenu.addItem(withTitle: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m") - windowMenu.addItem(withTitle: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "") + editMenu.addItem(withTitle: L10n.text("Cut"), action: #selector(NSText.cut(_:)), keyEquivalent: "x") + editMenu.addItem(withTitle: L10n.text("Copy"), action: #selector(NSText.copy(_:)), keyEquivalent: "c") + editMenu.addItem(withTitle: L10n.text("Paste"), action: #selector(NSText.paste(_:)), keyEquivalent: "v") + editMenu.addItem(withTitle: L10n.text("Select All"), action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") + + let windowMenu = NSMenu(title: L10n.text("Window")) + windowMenu.addItem(withTitle: L10n.text("Minimize"), action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m") + windowMenu.addItem(withTitle: L10n.text("Close"), action: #selector(NSWindow.performClose(_:)), keyEquivalent: "") NSApp.windowsMenu = windowMenu for submenu in [appMenu, fileMenu, editMenu, windowMenu] { diff --git a/Sources/DigUpApp/AppController.swift b/Sources/DigUpApp/AppController.swift index 84dea19b..dcd1b70c 100644 --- a/Sources/DigUpApp/AppController.swift +++ b/Sources/DigUpApp/AppController.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import ServiceManagement @@ -184,14 +185,14 @@ final class AppController { } /// Asks for folders with the standard open panel (as a sheet when there's a window). - func pickFolders(for window: NSWindow?, prompt: String = "Add", then done: @escaping ([URL]) -> Void) { + func pickFolders(for window: NSWindow?, prompt: String = L10n.text("Add"), then done: @escaping ([URL]) -> Void) { let panel = NSOpenPanel() panel.canChooseDirectories = true panel.canChooseFiles = false panel.allowsMultipleSelection = true panel.canCreateDirectories = false panel.prompt = prompt - panel.message = "Choose folders for DigUp to search." + panel.message = L10n.text("Choose folders for DigUp to search.") let finish = { (response: NSApplication.ModalResponse) in done(response == .OK ? panel.urls.map { AppSettings.folderURL($0.path) } : []) } @@ -216,7 +217,7 @@ final class AppController { /// Nil when `combo` is now the hotkey, else why it can't be. func setHotkey(_ combo: KeyCombo) -> String? { if let conflict = combo.systemConflict { return conflict } - guard hotkeys.register(combo) else { return "Another app is using \(combo.display)" } + guard hotkeys.register(combo) else { return L10n.format("Another app is using %1$@", String(describing: combo.display)) } AppSettings.save(hotkey: combo.spec) model.settings.hotkey = combo.spec model.hotkey = combo diff --git a/Sources/DigUpApp/AppModel.swift b/Sources/DigUpApp/AppModel.swift index 9577a4ab..5dc0830a 100644 --- a/Sources/DigUpApp/AppModel.swift +++ b/Sources/DigUpApp/AppModel.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import Observation @@ -23,35 +24,35 @@ final class AppModel { /// One line for the menu and the windows: what the index is doing. var statusLine: String { let status = self.status - guard hasFolders else { return "No folders chosen yet" } + guard hasFolders else { return L10n.text("No folders chosen yet") } switch status.activity { - case .starting: return "Starting…" + case .starting: return L10n.text("Starting…") case .failed(let problem): return "⚠︎ \(problem)" - case .syncing: return "Checking folders…" + case .syncing: return L10n.text("Checking folders…") case .indexing(let done, let total): let percent = total > 0 ? Int((Double(done) / Double(total) * 100).rounded(.down)) : 0 - return "Indexing \(percent)% · \(done.formatted()) of \(total.formatted())" + return L10n.format("Indexing %1$@%% · %2$@ of %3$@", String(describing: percent), String(describing: done.formatted()), String(describing: total.formatted())) case .finishing(let done, let total, let files): let percent = total > 0 ? Int((Double(done) / Double(total) * 100).rounded(.down)) : 0 - let which = files > 0 ? "\(files.formatted()) long \(files == 1 ? "file" : "files")" : "long files" - return "Reading the rest of \(which) · \(percent)%" + let which = files > 0 ? L10n.format("%1$@ long %2$@", String(describing: files.formatted()), String(describing: files == 1 ? "file" : "files")) : L10n.text("long files") + return L10n.format("Reading the rest of %1$@ · %2$@%%", String(describing: which), String(describing: percent)) case .idle: if let line = downloadLine { return line } if let code = status.code.indexing, code.total > 0 { let percent = Int((Double(code.done) / Double(code.total) * 100).rounded(.down)) - return "Indexing code \(percent)% · \(code.done.formatted()) of \(code.total.formatted())" + return L10n.format("Indexing code %1$@%% · %2$@ of %3$@", String(describing: percent), String(describing: code.done.formatted()), String(describing: code.total.formatted())) } - let waiting = "\(status.pending.formatted()) \(status.pending == 1 ? "file" : "files") waiting" - if status.pending > 0, status.paused { return "Paused · \(waiting)" } + let waiting = L10n.format("%1$@ %2$@ waiting", String(describing: status.pending.formatted()), String(describing: status.pending == 1 ? "file" : "files")) + if status.pending > 0, status.paused { return L10n.format("Paused · %1$@", String(describing: waiting)) } if status.pending > 0, let reason = status.waitingFor { return "\(waiting) · \(reason)" } - if status.pending > 0 { return "\(waiting) to be indexed" } + if status.pending > 0 { return L10n.format("%1$@ to be indexed", String(describing: waiting)) } if status.unfinished > 0 { - let rest = "\(status.unfinished.formatted()) long \(status.unfinished == 1 ? "file" : "files") to finish" - if status.paused { return "Paused · \(rest)" } + let rest = L10n.format("%1$@ long %2$@ to finish", String(describing: status.unfinished.formatted()), String(describing: status.unfinished == 1 ? "file" : "files")) + if status.paused { return L10n.format("Paused · %1$@", String(describing: rest)) } if let reason = status.waitingFor { return "\(rest) · \(reason)" } } - return "\(status.searchable.formatted()) \(status.searchable == 1 ? "file" : "files") indexed" - + (status.paused ? " · paused" : "") + return L10n.format("%1$@ %2$@ indexed", String(describing: status.searchable.formatted()), String(describing: status.searchable == 1 ? "file" : "files")) + + (status.paused ? L10n.text(" · paused") : "") } } @@ -59,11 +60,11 @@ final class AppModel { private var downloadLine: String? { switch download.phase { case .ready: nil - case .downloading: "Downloading the model · \(Int(download.fraction * 100))%" - case .verifying: "Checking the model download…" - case .retrying: "Model download: waiting for the network" - case .failed: "⚠︎ The model download stopped" - case .needed: status.pending > 0 ? "The model isn't downloaded yet" : nil + case .downloading: L10n.format("Downloading the model · %1$@%%", String(describing: Int(download.fraction * 100))) + case .verifying: L10n.text("Checking the model download…") + case .retrying: L10n.text("Model download: waiting for the network") + case .failed: L10n.text("⚠︎ The model download stopped") + case .needed: status.pending > 0 ? L10n.text("The model isn't downloaded yet") : nil } } @@ -83,25 +84,25 @@ final class AppModel { /// One line about the code index, for Settings → Code: "812 code files indexed", "Indexing code 42%…". var codeStatusLine: String { let code = status.code - guard hasCode else { return "Code search is off" } - if !download.isReady { return "Code is read once the model has downloaded" } + guard hasCode else { return L10n.text("Code search is off") } + if !download.isReady { return L10n.text("Code is read once the model has downloaded") } if let indexing = code.indexing, indexing.total > 0 { let percent = Int((Double(indexing.done) / Double(indexing.total) * 100).rounded(.down)) - return "Indexing code \(percent)% · \(indexing.done.formatted()) of \(indexing.total.formatted())" + return L10n.format("Indexing code %1$@%% · %2$@ of %3$@", String(describing: percent), String(describing: indexing.done.formatted()), String(describing: indexing.total.formatted())) } - let waiting = code.pending > 0 ? " · \(code.pending.formatted()) waiting" : "" - if code.pending > 0, status.paused { return "Paused · \(code.pending.formatted()) code files waiting" } - if code.pending > 0, let reason = status.waitingFor { return "\(code.pending.formatted()) code files waiting · \(reason)" } - return "\(code.searchable.formatted()) code \(code.searchable == 1 ? "file" : "files") indexed" + waiting + let waiting = code.pending > 0 ? L10n.format(" · %1$@ waiting", String(describing: code.pending.formatted())) : "" + if code.pending > 0, status.paused { return L10n.format("Paused · %1$@ code files waiting", String(describing: code.pending.formatted())) } + if code.pending > 0, let reason = status.waitingFor { return L10n.format("%1$@ code files waiting · %2$@", String(describing: code.pending.formatted()), String(describing: reason)) } + return L10n.format("%1$@ code %2$@ indexed", String(describing: code.searchable.formatted()), String(describing: code.searchable == 1 ? "file" : "files")) + waiting } /// A few words for the search bar while results may be incomplete: "Indexing 42%", "Downloading model · 42%". var activityBadge: String? { - if download.isActive { return "Downloading model · \(Int(download.fraction * 100))%" } + if download.isActive { return L10n.format("Downloading model · %1$@%%", String(describing: Int(download.fraction * 100))) } if case .indexing(let done, let total) = status.activity, total > 0 { - return "Indexing \(Int(Double(done) / Double(total) * 100))%" + return L10n.format("Indexing %1$@%%", String(describing: Int(Double(done) / Double(total) * 100))) } - if status.paused, status.pending > 0 { return "Indexing paused" } + if status.paused, status.pending > 0 { return L10n.text("Indexing paused") } return nil } @@ -109,19 +110,19 @@ final class AppModel { var meaningUnavailable: String? { guard !download.isReady else { return nil } if download.isActive { - return "Matching words only until the model has downloaded (\(Int(download.fraction * 100))%)" + return L10n.format("Matching words only until the model has downloaded (%1$@%%)", String(describing: Int(download.fraction * 100))) } - return "Matching words only: the model isn't downloaded" + return L10n.text("Matching words only: the model isn't downloaded") } /// Why results may be incomplete right now, or nil. var resultsCaveat: String? { if let meaningUnavailable { return meaningUnavailable } if case .indexing(let done, let total) = status.activity, total > 0 { - return "Still indexing (\(Int(Double(done) / Double(total) * 100))%): some files aren't in yet" + return L10n.format("Still indexing (%1$@%%): some files aren't in yet", String(describing: Int(Double(done) / Double(total) * 100))) } if status.pending > 0 { - return "\(status.pending.formatted()) \(status.pending == 1 ? "file isn't" : "files aren't") indexed yet" + return L10n.format("%1$@ %2$@ indexed yet", String(describing: status.pending.formatted()), String(describing: status.pending == 1 ? "file isn't" : "files aren't")) } return nil } diff --git a/Sources/DigUpApp/AppSettings.swift b/Sources/DigUpApp/AppSettings.swift index a7c51dcf..e9f93ce7 100644 --- a/Sources/DigUpApp/AppSettings.swift +++ b/Sources/DigUpApp/AppSettings.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import DigUpKit import Foundation @@ -45,8 +46,8 @@ nonisolated struct AppSettings: Sendable { static let defaultHotkey = "cmd+shift+space" /// The battery switch, in onboarding and Settings. - static let batteryTitle = "Index on battery power" - static let batteryDetail = "When it's off, a folder's first pass waits for a charger. New files always go in right away." + static let batteryTitle = L10n.text("Index on battery power") + static let batteryDetail = L10n.text("When it's off, a folder's first pass waits for a charger. New files always go in right away.") /// Where settings are saved. Reads see launch arguments first either way. (UserDefaults is thread-safe.) nonisolated(unsafe) static let defaults: UserDefaults = { diff --git a/Sources/DigUpApp/CodeEditors.swift b/Sources/DigUpApp/CodeEditors.swift index 7139cc85..0e62346b 100644 --- a/Sources/DigUpApp/CodeEditors.swift +++ b/Sources/DigUpApp/CodeEditors.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit @@ -52,7 +53,7 @@ nonisolated struct CodeEditor: Identifiable, Hashable, Sendable { ] /// Each file opens in the app macOS opens it with, at its start. - static let defaultApp = CodeEditor(id: "", name: "The file's default app", method: .plain) + static let defaultApp = CodeEditor(id: "", name: L10n.text("The file's default app"), method: .plain) /// Where code results open now (`AppController.setCodeEditor`). @MainActor static var current = defaultApp diff --git a/Sources/DigUpApp/Components.swift b/Sources/DigUpApp/Components.swift index ea0ad122..c6f40a5d 100644 --- a/Sources/DigUpApp/Components.swift +++ b/Sources/DigUpApp/Components.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import SwiftUI @@ -87,7 +88,7 @@ struct MomentBar: View { } } .frame(height: 4) - .accessibilityLabel("At \(ResultRow.clock(start)) of \(ResultRow.clock(duration))") + .accessibilityLabel(L10n.format("At %1$@ of %2$@", String(describing: ResultRow.clock(start)), String(describing: ResultRow.clock(duration)))) } } @@ -133,13 +134,13 @@ extension FileKind { func counted(_ count: Int) -> String { let one = count == 1 let noun = switch self { - case .screenshot: one ? "screenshot" : "screenshots" - case .image: one ? "image" : "images" - case .pdf: one ? "PDF" : "PDFs" - case .doc: one ? "document" : "documents" - case .audio: one ? "audio file" : "audio files" - case .video: one ? "video" : "videos" - case .code: one ? "code file" : "code files" + case .screenshot: one ? L10n.text("screenshot") : L10n.text("screenshots") + case .image: one ? L10n.text("image") : L10n.text("images") + case .pdf: one ? "PDF" : L10n.text("PDFs") + case .doc: one ? L10n.text("document") : L10n.text("documents") + case .audio: one ? L10n.text("audio file") : L10n.text("audio files") + case .video: one ? L10n.text("video") : L10n.text("videos") + case .code: one ? L10n.text("code file") : L10n.text("code files") } return "\(count.formatted()) \(noun)" } @@ -194,11 +195,11 @@ struct ShortcutRecorder: View { Button { setRecording(!recording) } label: { Group { if recording { - Text("Press the new shortcut…").foregroundStyle(.secondary) + Text(L10n.text("Press the new shortcut…")).foregroundStyle(.secondary) } else if let combo { Keycaps(caps: combo.caps, size: capSize) } else { - Text("Click to set a shortcut").foregroundStyle(.secondary) + Text(L10n.text("Click to set a shortcut")).foregroundStyle(.secondary) } } .font(.system(size: 13)) @@ -213,8 +214,8 @@ struct ShortcutRecorder: View { } .buttonStyle(.plain) .background(KeyCatcher(active: recording, onKey: handle, onResign: { setRecording(false) })) - .accessibilityLabel(recording ? "Recording a new shortcut" : "Shortcut \(combo?.display ?? "none"), click to change") - Text(problem ?? (recording ? "Esc cancels" : "Click to change")) + .accessibilityLabel(recording ? L10n.text("Recording a new shortcut") : L10n.format("Shortcut %1$@, click to change", String(describing: combo?.display ?? L10n.text("none")))) + Text(problem ?? (recording ? L10n.text("Esc cancels") : L10n.text("Click to change"))) .font(.system(size: 11)) .foregroundStyle(problem == nil ? AnyShapeStyle(.tertiary) : AnyShapeStyle(Color.red)) .multilineTextAlignment(.center) @@ -234,7 +235,7 @@ struct ShortcutRecorder: View { .numericPad]) if event.keyCode == 53, flags.isEmpty { return setRecording(false) } // Esc guard let combo = KeyCombo(keyCode: event.keyCode, flags: flags) else { - problem = "Use ⌘, ⌥ or ⌃ with a key" + problem = L10n.text("Use ⌘, ⌥ or ⌃ with a key") return } if let why = apply(combo) { diff --git a/Sources/DigUpApp/DebugHooks.swift b/Sources/DigUpApp/DebugHooks.swift index 6370930a..301850ca 100644 --- a/Sources/DigUpApp/DebugHooks.swift +++ b/Sources/DigUpApp/DebugHooks.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import Quartz import ServiceManagement @@ -143,10 +144,10 @@ enum DebugHooks { let menu = app.statusMenu! DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { log("menu: \(menu.describeMenu())") - menu.performItem(titled: "Pause Indexing") + menu.performItem(titled: L10n.text("Pause Indexing")) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { log("menu after Pause: \(menu.describeMenu())") - menu.performItem(titled: "Resume Indexing") + menu.performItem(titled: L10n.text("Resume Indexing")) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { log("menu after Resume: \(menu.describeMenu())") } diff --git a/Sources/DigUpApp/Engine.swift b/Sources/DigUpApp/Engine.swift index 2baade18..4dc09a1b 100644 --- a/Sources/DigUpApp/Engine.swift +++ b/Sources/DigUpApp/Engine.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import DigUpKit import Foundation @@ -195,7 +196,7 @@ nonisolated final class Engine: @unchecked Sendable { func start() { searchQueue.async { [self] in guard openSearcher() else { - update { $0.activity = .failed("Can't open the index") } + update { $0.activity = .failed(L10n.text("Can't open the index")) } return } indexQueue.async { [self] in startHelper() } @@ -493,7 +494,7 @@ nonisolated final class Engine: @unchecked Sendable { try process.run() } catch { log("indexer: can't start \(process.executableURL?.path ?? "the helper"): \(error)") - update { $0.activity = .failed("Can't start indexing") } + update { $0.activity = .failed(L10n.text("Can't start indexing")) } return } helper = process @@ -584,7 +585,7 @@ nonisolated final class Engine: @unchecked Sendable { log("indexer: the model or settings changed, indexing everything again (was: \(event["from"] as? String ?? "?"))") case "error": log("indexer: \(event["message"] as? String ?? "error")") - if event["fatal"] as? Bool == true { update { $0.activity = .failed("Indexing stopped") } } + if event["fatal"] as? Bool == true { update { $0.activity = .failed(L10n.text("Indexing stopped")) } } default: break } @@ -618,7 +619,7 @@ nonisolated final class Engine: @unchecked Sendable { log("indexer: the model changed, the code index starts over") case "error": log("indexer: code: \(event["message"] as? String ?? "error")") - if event["fatal"] as? Bool == true { update { $0.activity = .failed("Indexing stopped") } } + if event["fatal"] as? Bool == true { update { $0.activity = .failed(L10n.text("Indexing stopped")) } } default: break } diff --git a/Sources/DigUpApp/Estimates.swift b/Sources/DigUpApp/Estimates.swift index 5d47648f..9d5fedbe 100644 --- a/Sources/DigUpApp/Estimates.swift +++ b/Sources/DigUpApp/Estimates.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import DigUpKit import Foundation @@ -127,15 +128,15 @@ nonisolated final class LineBuffer: @unchecked Sendable { /// "≈ 8 s", "≈ 2 min", or "< 1 s" for a few short files. nonisolated func approximately(_ seconds: Double) -> String { - seconds < 0.5 ? "< 1 s" : "≈ \(roughDuration(seconds))" + seconds < 0.5 ? L10n.text("< 1 s") : "≈ \(roughDuration(seconds))" } /// "8 s", "2 min", "1.5 h": the precision an estimate deserves. nonisolated func roughDuration(_ seconds: Double) -> String { switch seconds { - case ..<90: "\(Int(seconds.rounded())) s" - case ..<5400: "\(Int((seconds / 60).rounded())) min" - default: String(format: "%.1f h", seconds / 3600) + case ..<90: L10n.format("%1$@ s", String(describing: Int(seconds.rounded()))) + case ..<5400: L10n.format("%1$@ min", String(describing: Int((seconds / 60).rounded()))) + default: String(format: L10n.text("%.1f h"), seconds / 3600) } } diff --git a/Sources/DigUpApp/FolderChoices.swift b/Sources/DigUpApp/FolderChoices.swift index a0d654cd..bfe598db 100644 --- a/Sources/DigUpApp/FolderChoices.swift +++ b/Sources/DigUpApp/FolderChoices.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import Observation @@ -82,8 +83,8 @@ final class FolderChoices { expanded.insert(row.path) } else if selection.isSearched(path) { let by = selection.searchedAs(path) - note = "“\(folder.lastPathComponent)” is searched already" - + (by == nil || by == path ? "." : ", as part of “\((by! as NSString).lastPathComponent)”.") + note = L10n.format("“%1$@” is searched already", String(describing: folder.lastPathComponent)) + + (by == nil || by == path ? "." : L10n.format(", as part of “%1$@”.", String(describing: (by! as NSString).lastPathComponent))) } else { rows.append(folder) selection.setFolder(path, on: true) @@ -109,7 +110,7 @@ final class FolderChoices { selection.setSubfolder(path, on: false) deeper.append(path) } else { - note = "“\(folder.lastPathComponent)” isn't searched anyway." + note = L10n.format("“%1$@” isn't searched anyway.", String(describing: folder.lastPathComponent)) } } estimate(rows.filter { row in deeper.contains { FolderSelection.isInside($0, row.path) } }) @@ -257,10 +258,10 @@ private struct FolderRow: View { let row: URL @State private var showAll = false - static let laterHelp = "The first pass takes this long. Long PDFs and documents get their first pages in it, " - + "and the rest afterwards, once everything else is in." - static let codeHelp = "How long reading this code takes, once, and about how much room its index needs. After " - + "that, only what changes is read again." + static let laterHelp = L10n.text("The first pass takes this long. Long PDFs and documents get their first pages in it, ") + + L10n.text("and the rest afterwards, once everything else is in.") + static let codeHelp = L10n.text("How long reading this code takes, once, and about how much room its index needs. After ") + + L10n.text("that, only what changes is read again.") /// Subfolders shown before "Show N more". private static let shortList = 8 @@ -279,7 +280,7 @@ private struct FolderRow: View { SubfolderRow(choices: choices, subfolder: subfolder) } if !showAll, subfolders.count > Self.shortList { - Button("Show \(subfolders.count - Self.shortList) more") { showAll = true } + Button(L10n.format("Show %1$@ more", String(describing: subfolders.count - Self.shortList))) { showAll = true } .buttonStyle(.link) .font(.system(size: 11)) .frame(maxWidth: .infinity, alignment: .leading) @@ -316,9 +317,9 @@ private struct FolderRow: View { .buttonStyle(.plain) .opacity(canOpen ? 1 : 0) .disabled(!canOpen) - .help(open ? "Hide its subfolders" : "Choose among its subfolders") - .accessibilityLabel(open ? "Hide the subfolders of \(row.lastPathComponent)" - : "Show the subfolders of \(row.lastPathComponent)") + .help(open ? L10n.text("Hide its subfolders") : L10n.text("Choose among its subfolders")) + .accessibilityLabel(open ? L10n.format("Hide the subfolders of %1$@", String(describing: row.lastPathComponent)) + : L10n.format("Show the subfolders of %1$@", String(describing: row.lastPathComponent))) Checkbox(mark: mark, label: row.lastPathComponent) { choices.toggle(row) } Image(nsImage: NSWorkspace.shared.icon(forFile: row.path)) .resizable() @@ -326,15 +327,15 @@ private struct FolderRow: View { VStack(alignment: .leading, spacing: 3) { Text(row.lastPathComponent).font(.system(size: 13, weight: .semibold)) if let estimate, estimate.failed { - Text("Couldn't look inside it; it can still be searched.") + Text(L10n.text("Couldn't look inside it; it can still be searched.")) .font(.system(size: 11)) .foregroundStyle(.secondary) } else if let estimate, !estimate.readable { HStack(spacing: 6) { - Text("macOS isn't letting DigUp read it.") + Text(L10n.text("macOS isn't letting DigUp read it.")) .font(.system(size: 11)) .foregroundStyle(.orange) - Button("Allow in Settings…", action: openFolderPrivacySettings) + Button(L10n.text("Allow in Settings…"), action: openFolderPrivacySettings) .buttonStyle(.link) .font(.system(size: 11)) } @@ -342,8 +343,8 @@ private struct FolderRow: View { KindCounts(counts: part.files) } else { Text(estimate == nil ? tildePath(row.path) - : estimate!.files.isEmpty ? (choices.code ? "No code here" : "Nothing to search here yet") - : "Nothing to search in the subfolders checked") + : estimate!.files.isEmpty ? (choices.code ? L10n.text("No code here") : L10n.text("Nothing to search here yet")) + : L10n.text("Nothing to search in the subfolders checked")) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -361,7 +362,7 @@ private struct FolderRow: View { .font(.system(size: 10).monospacedDigit()) .foregroundStyle(.tertiary) } else if let later = part?.later, later >= 30 { - Text("+ \(roughDuration(later)) later") + Text(L10n.format("+ %1$@ later", String(describing: roughDuration(later)))) .font(.system(size: 10).monospacedDigit()) .foregroundStyle(.tertiary) } @@ -403,7 +404,7 @@ private struct SubfolderRow: View { Spacer(minLength: 8) Text(approximately(subfolder.seconds) + (choices.code ? " · \(roughBytes(subfolder.bytes))" - : subfolder.laterSeconds >= 30 ? " + \(roughDuration(subfolder.laterSeconds)) later" : "")) + : subfolder.laterSeconds >= 30 ? L10n.format(" + %1$@ later", String(describing: roughDuration(subfolder.laterSeconds))) : "")) .font(.system(size: 11, weight: .medium).monospacedDigit()) .foregroundStyle(on ? .secondary : .tertiary) .help(choices.code ? FolderRow.codeHelp : FolderRow.laterHelp) diff --git a/Sources/DigUpApp/Hotkey.swift b/Sources/DigUpApp/Hotkey.swift index 22f29fec..6749f1b0 100644 --- a/Sources/DigUpApp/Hotkey.swift +++ b/Sources/DigUpApp/Hotkey.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import Carbon.HIToolbox @@ -105,13 +106,13 @@ nonisolated struct KeyCombo: Equatable, Sendable { } guard taken else { return nil } switch (Int(keyCode), Int(carbonModifiers)) { - case (kVK_Space, cmdKey): return "Spotlight uses \(display)" - case (kVK_Space, cmdKey | optionKey): return "Finder's search window uses \(display)" - case (kVK_Space, controlKey), (kVK_Space, controlKey | optionKey): return "Switching input sources uses \(display)" - case (kVK_Space, controlKey | cmdKey): return "The emoji picker uses \(display)" + case (kVK_Space, cmdKey): return L10n.format("Spotlight uses %1$@", String(describing: display)) + case (kVK_Space, cmdKey | optionKey): return L10n.format("Finder's search window uses %1$@", String(describing: display)) + case (kVK_Space, controlKey), (kVK_Space, controlKey | optionKey): return L10n.format("Switching input sources uses %1$@", String(describing: display)) + case (kVK_Space, controlKey | cmdKey): return L10n.format("The emoji picker uses %1$@", String(describing: display)) case (kVK_ANSI_3, cmdKey | shiftKey), (kVK_ANSI_4, cmdKey | shiftKey), (kVK_ANSI_5, cmdKey | shiftKey): - return "Screenshots use \(display)" - default: return "macOS uses \(display) for one of its shortcuts" + return L10n.format("Screenshots use %1$@", String(describing: display)) + default: return L10n.format("macOS uses %1$@ for one of its shortcuts", String(describing: display)) } } } diff --git a/Sources/DigUpApp/ModelDownload.swift b/Sources/DigUpApp/ModelDownload.swift index 30074b32..3b0adf6f 100644 --- a/Sources/DigUpApp/ModelDownload.swift +++ b/Sources/DigUpApp/ModelDownload.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import DigUpKit import Foundation import Observation @@ -65,15 +66,15 @@ final class ModelDownload { /// "42% · about 1 min left", "Checking the download…", "Waiting for the network…" var progressText: String { switch phase { - case .needed: return "Not downloaded yet" + case .needed: return L10n.text("Not downloaded yet") case .downloading: let percent = "\(Int((fraction * 100).rounded(.down)))%" guard let left = secondsLeft else { return percent } - return percent + " · " + (left < 50 ? "less than a minute left" : "about \(roughDuration(left)) left") - case .verifying: return "Checking the download…" - case .retrying: return "Waiting for the network…" + return percent + " · " + (left < 50 ? L10n.text("less than a minute left") : L10n.format("about %1$@ left", String(describing: roughDuration(left)))) + case .verifying: return L10n.text("Checking the download…") + case .retrying: return L10n.text("Waiting for the network…") case .failed(let message): return message - case .ready: return "Ready" + case .ready: return L10n.text("Ready") } } @@ -104,13 +105,13 @@ final class ModelDownload { var folder = self.folder try folder.setResourceValues(values) } catch { - phase = .failed("Can't create \(tildePath(folder.path))") + phase = .failed(L10n.format("Can't create %1$@", String(describing: tildePath(folder.path)))) log("model: can't create \(folder.path): \(error)") return } let needed = ModelFiles.missing(in: folder).reduce(0) { $0 + $1.bytes } - partialBytes(of: file) + 200_000_000 if let free = freeSpace(), free < needed { - phase = .failed("Needs \(bytes(needed)) free on this disk") + phase = .failed(L10n.format("Needs %1$@ free on this disk", String(describing: bytes(needed)))) log("model: needs \(bytes(needed)) free, \(bytes(free)) is free") return } @@ -154,7 +155,7 @@ final class ModelDownload { }, waiting: { [weak self] in DispatchQueue.main.async { [weak self] in guard let self, attempt == current else { return } - phase = .retrying("Waiting for a network connection") + phase = .retrying(L10n.text("Waiting for a network connection")) log("model: waiting for a network connection") } }, done: { [weak self] outcome in @@ -211,7 +212,7 @@ final class ModelDownload { failedChecks += 1 received = bytesOnDisk() if failedChecks >= 2 { - phase = .failed("The download was damaged twice. Try again later.") + phase = .failed(L10n.text("The download was damaged twice. Try again later.")) } else { next() } @@ -356,13 +357,13 @@ nonisolated final class FileFetcher: NSObject, URLSessionDataDelegate, @unchecke guard range.hasPrefix("bytes \(written)-") else { // Not where we asked: start the file over next time. try? FileManager.default.removeItem(at: partial) - problem = ("The download couldn't resume", true) + problem = (L10n.text("The download couldn't resume"), true) return completionHandler(.cancel) } case 200: written = 0 // the whole file, from the start default: - problem = ("Hugging Face answered \(status)", status == 429 || status >= 500) + problem = (L10n.format("Hugging Face answered %1$@", String(describing: status)), status == 429 || status >= 500) return completionHandler(.cancel) } do { @@ -375,7 +376,7 @@ nonisolated final class FileFetcher: NSObject, URLSessionDataDelegate, @unchecke self.handle = handle completionHandler(.allow) } catch { - problem = ("Can't save the model: \(error.localizedDescription)", false) + problem = (L10n.format("Can't save the model: %1$@", String(describing: error.localizedDescription)), false) completionHandler(.cancel) } } @@ -385,7 +386,7 @@ nonisolated final class FileFetcher: NSObject, URLSessionDataDelegate, @unchecke try handle?.write(contentsOf: data) written += Int64(data.count) } catch { - problem = ("Can't save the model: \(error.localizedDescription)", false) + problem = (L10n.format("Can't save the model: %1$@", String(describing: error.localizedDescription)), false) dataTask.cancel() return } @@ -409,7 +410,7 @@ nonisolated final class FileFetcher: NSObject, URLSessionDataDelegate, @unchecke } else if written == file.bytes { done(.downloaded(partial)) } else { - done(.failed("The download stopped early", retry: true)) + done(.failed(L10n.text("The download stopped early"), retry: true)) } } } diff --git a/Sources/DigUpApp/Moments.swift b/Sources/DigUpApp/Moments.swift index 16a149f3..d58d6fd9 100644 --- a/Sources/DigUpApp/Moments.swift +++ b/Sources/DigUpApp/Moments.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AVFoundation import AVKit import AppKit @@ -43,9 +44,9 @@ extension ResultRow { /// opens with your words in Preview's search, but Preview doesn't go to the page by itself. Code opens in the /// editor chosen for it: "Open in Zed". var openTitle: String { - if kind == .code, !CodeEditor.current.id.isEmpty { return "Open in \(CodeEditor.current.name)" } - if let moment, moment >= 1, Opener.jumpsToMoments(url) { return "Open at \(Self.clock(moment))" } - return "Open" + if kind == .code, !CodeEditor.current.id.isEmpty { return L10n.format("Open in %1$@", String(describing: CodeEditor.current.name)) } + if let moment, moment >= 1, Opener.jumpsToMoments(url) { return L10n.format("Open at %1$@", String(describing: Self.clock(moment))) } + return L10n.text("Open") } /// The line a code result opens at: where the name is defined or the query's words are, else where its stretch diff --git a/Sources/DigUpApp/Onboarding.swift b/Sources/DigUpApp/Onboarding.swift index 867b10d6..d52aab89 100644 --- a/Sources/DigUpApp/Onboarding.swift +++ b/Sources/DigUpApp/Onboarding.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import Observation @@ -35,7 +36,7 @@ final class OnboardingController: NSObject, NSWindowDelegate { window.titlebarAppearsTransparent = true window.titleVisibility = .hidden window.isMovableByWindowBackground = true - window.title = "Welcome to DigUp" + window.title = L10n.text("Welcome to DigUp") window.isReleasedWhenClosed = false window.delegate = self state.close = { [weak self] in self?.close() } @@ -190,7 +191,7 @@ final class OnboardingState { } func skipFolders(from window: NSWindow?) { - app.pickFolders(for: window, prompt: "Skip") { [weak self] picked in + app.pickFolders(for: window, prompt: L10n.text("Skip")) { [weak self] picked in guard !picked.isEmpty else { return } self?.folders.skip(picked) } @@ -260,7 +261,7 @@ struct OnboardingView: View { .lineLimit(1) } if state.step != .welcome, state.step != .ready { - Button("Back") { state.back() } + Button(L10n.text("Back")) { state.back() } } Button(continueTitle) { state.next() } .keyboardShortcut(.defaultAction) @@ -273,26 +274,26 @@ struct OnboardingView: View { private var continueTitle: String { switch state.step { - case .welcome: "Get Started" - case .folders: "Continue" - case .code: "Continue" - case .shortcut: "Continue" - case .ready: "Done" + case .welcome: L10n.text("Get Started") + case .folders: L10n.text("Continue") + case .code: L10n.text("Continue") + case .shortcut: L10n.text("Continue") + case .ready: L10n.text("Done") } } private var codeSummary: String { - guard !state.codeFolders.roots.isEmpty else { return "Check the folders with your code" } - guard state.codeFolders.estimatesDone else { return "Looking at your code…" } + guard !state.codeFolders.roots.isEmpty else { return L10n.text("Check the folders with your code") } + guard state.codeFolders.estimatesDone else { return L10n.text("Looking at your code…") } let seconds = state.codeFolders.totalSeconds - return (seconds < 1 ? "Under a second" : "About \(roughDuration(seconds))") + return (seconds < 1 ? L10n.text("Under a second") : L10n.format("About %1$@", String(describing: roughDuration(seconds)))) + ", \(roughBytes(state.codeFolders.totalBytes))" } private var foldersSummary: String { - guard !state.folders.roots.isEmpty else { return "Choose at least one folder" } - guard state.folders.estimatesDone else { return "Looking at your folders…" } - return "First pass: about \(roughDuration(state.folders.totalSeconds))" + guard !state.folders.roots.isEmpty else { return L10n.text("Choose at least one folder") } + guard state.folders.estimatesDone else { return L10n.text("Looking at your folders…") } + return L10n.format("First pass: about %1$@", String(describing: roughDuration(state.folders.totalSeconds))) } } @@ -309,7 +310,7 @@ private struct StepDots: View { } } .accessibilityElement(children: .ignore) - .accessibilityLabel("Step \(current + 1) of \(count)") + .accessibilityLabel(L10n.format("Step %1$@ of %2$@", String(describing: current + 1), String(describing: count))) } } @@ -322,20 +323,20 @@ struct DownloadStatus: View { switch download.phase { case .ready: Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) - Text("Model ready") + Text(L10n.text("Model ready")) case .failed(let message): Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.orange) Text(message).lineLimit(1) - Button("Try Again") { download.start() }.controlSize(.small) + Button(L10n.text("Try Again")) { download.start() }.controlSize(.small) case .needed: Image(systemName: "arrow.down.circle").foregroundStyle(.secondary) - Text("Model not downloaded") - Button("Download") { download.start() }.controlSize(.small) + Text(L10n.text("Model not downloaded")) + Button(L10n.text("Download")) { download.start() }.controlSize(.small) default: ProgressView(value: download.fraction) .progressViewStyle(.circular) .controlSize(.small) - Text("Downloading the model · \(download.progressText)") + Text(L10n.format("Downloading the model · %1$@", String(describing: download.progressText))) .lineLimit(1) } } @@ -354,17 +355,17 @@ private struct WelcomeStep: View { Text("DigUp") .font(.system(size: 30, weight: .bold)) .padding(.top, 10) - Text("Describe it. Dig it up.") + Text(L10n.text("Describe it. Dig it up.")) .font(.system(size: 16)) .foregroundStyle(.secondary) .padding(.top, 2) VStack(alignment: .leading, spacing: 16) { - Feature(symbol: "camera.viewfinder", title: "Screenshots and photos", - example: "“that payment error”, “the zebra”, “whiteboard from Tuesday”") - Feature(symbol: "doc.text", title: "PDFs and documents", - example: "“the clause about pets in the lease”, “flight receipt”") - Feature(symbol: "waveform", title: "Moments inside audio and video", - example: "“where they talk about sleep”, “the part with the drums”") + Feature(symbol: "camera.viewfinder", title: L10n.text("Screenshots and photos"), + example: L10n.text("“that payment error”, “the zebra”, “whiteboard from Tuesday”")) + Feature(symbol: "doc.text", title: L10n.text("PDFs and documents"), + example: L10n.text("“the clause about pets in the lease”, “flight receipt”")) + Feature(symbol: "waveform", title: L10n.text("Moments inside audio and video"), + example: L10n.text("“where they talk about sleep”, “the part with the drums”")) } .padding(.top, 30) HStack(alignment: .top, spacing: 12) { @@ -372,8 +373,8 @@ private struct WelcomeStep: View { .font(.system(size: 18)) .foregroundStyle(.secondary) .frame(width: 30) - Text("Everything is indexed and searched on this Mac. DigUp downloads its AI model (865 MB) once; " - + "after that it works offline, and nothing you index ever leaves your Mac.") + Text(L10n.text("Everything is indexed and searched on this Mac. DigUp downloads its AI model (865 MB) once; ") + + L10n.text("after that it works offline, and nothing you index ever leaves your Mac.")) .font(.system(size: 12)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -428,12 +429,12 @@ private struct FoldersStep: View { var body: some View { VStack(alignment: .leading, spacing: 18) { - StepTitle(title: "What should DigUp search?", - subtitle: "Pick folders, or open one to pick some of its subfolders. You can change this " - + "anytime in Settings.") + StepTitle(title: L10n.text("What should DigUp search?"), + subtitle: L10n.text("Pick folders, or open one to pick some of its subfolders. You can change this ") + + L10n.text("anytime in Settings.")) ScrollView { VStack(spacing: 6) { - FolderList(choices: state.folders, addTitle: "Add Folder…") { + FolderList(choices: state.folders, addTitle: L10n.text("Add Folder…")) { state.addFolders(from: NSApp.keyWindow) } SkipChoices(state: state) @@ -442,8 +443,8 @@ private struct FoldersStep: View { .scrollBounceBehavior(.basedOnSize) HStack(alignment: .top, spacing: 8) { Image(systemName: "info.circle").foregroundStyle(.secondary) - Text("macOS will ask before DigUp can read folders like Desktop, Documents and Downloads. " - + "DigUp only reads files: it never changes, moves or uploads them.") + Text(L10n.text("macOS will ask before DigUp can read folders like Desktop, Documents and Downloads. ") + + L10n.text("DigUp only reads files: it never changes, moves or uploads them.")) .fixedSize(horizontal: false, vertical: true) } .font(.system(size: 11)) @@ -474,25 +475,25 @@ private struct SkipChoices: View { Image(systemName: "minus.circle.fill").foregroundStyle(.secondary) } .buttonStyle(.plain) - .help("Search this folder again") + .help(L10n.text("Search this folder again")) } .font(.system(size: 12)) } Button { state.skipFolders(from: NSApp.keyWindow) } label: { - Label("Skip a Folder…", systemImage: "plus") + Label(L10n.text("Skip a Folder…"), systemImage: "plus") } .controlSize(.small) HStack(spacing: 8) { - Text("File types") - TextField("Extensions", text: $state.skippedTypesText, prompt: Text("e.g. heic, txt")) + Text(L10n.text("File types")) + TextField(L10n.text("Extensions"), text: $state.skippedTypesText, prompt: Text(L10n.text("e.g. heic, txt"))) .labelsHidden() .controlSize(.small) .onSubmit { state.folders.setTypes(state.skippedTypesText) } } .font(.system(size: 12)) - Text("Code, keys, certificates, hidden folders, app bundles and caches are always skipped.") + Text(L10n.text("Code, keys, certificates, hidden folders, app bundles and caches are always skipped.")) .font(.system(size: 11)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -500,7 +501,7 @@ private struct SkipChoices: View { .padding(.top, 8) .padding(.leading, 4) } label: { - Text("Skip some folders or file types") + Text(L10n.text("Skip some folders or file types")) .font(.system(size: 12, weight: .medium)) .foregroundStyle(.secondary) } @@ -519,17 +520,17 @@ private struct CodeStep: View { var body: some View { VStack(alignment: .leading, spacing: 16) { - StepTitle(title: "Search your code too?", - subtitle: "DigUp can find code by what it does: type code: in the search panel, as in " - + "“code: retry with backoff”. It gets an index of its own and never shows up in other " - + "searches. You can turn it on later in Settings → Code.") - SwitchRow(title: "Search my code", detail: "Off unless you turn it on.", isOn: $state.searchCode) + StepTitle(title: L10n.text("Search your code too?"), + subtitle: L10n.text("DigUp can find code by what it does: type code: in the search panel, as in ") + + L10n.text("“code: retry with backoff”. It gets an index of its own and never shows up in other ") + + L10n.text("searches. You can turn it on later in Settings → Code.")) + SwitchRow(title: L10n.text("Search my code"), detail: L10n.text("Off unless you turn it on."), isOn: $state.searchCode) .padding(14) .background(RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color.primary.opacity(0.04))) if state.searchCode { ScrollView { VStack(spacing: 6) { - FolderList(choices: state.codeFolders, addTitle: "Add Folder…") { + FolderList(choices: state.codeFolders, addTitle: L10n.text("Add Folder…")) { state.addCodeFolders(from: NSApp.keyWindow) } CodeFolderNote().padding(.top, 4) @@ -537,8 +538,8 @@ private struct CodeStep: View { } .scrollBounceBehavior(.basedOnSize) HStack { - Text("Open code in") - Picker("Open code in", selection: Binding(get: { CodeEditor.current.id }, + Text(L10n.text("Open code in")) + Picker(L10n.text("Open code in"), selection: Binding(get: { CodeEditor.current.id }, set: { state.app.setCodeEditor($0) })) { ForEach(CodeEditor.installed) { editor in Text(editor.name).tag(editor.id) } Text(CodeEditor.defaultApp.name).tag(CodeEditor.defaultApp.id) @@ -560,17 +561,17 @@ private struct ShortcutStep: View { var body: some View { VStack(spacing: 0) { - StepTitle(title: "Open DigUp from anywhere", - subtitle: "Press the shortcut in any app, describe what you're looking for, and press Return.") + StepTitle(title: L10n.text("Open DigUp from anywhere"), + subtitle: L10n.text("Press the shortcut in any app, describe what you're looking for, and press Return.")) ShortcutRecorder(combo: state.model.hotkey, apply: { state.app.setHotkey($0) }, listening: { state.app.hotkeyRecording($0) }, capSize: 20) .padding(.top, 34) Group { if state.hotkeyWorked { - Label("That's it. It works from any app.", systemImage: "checkmark.circle.fill") + Label(L10n.text("That's it. It works from any app."), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } else if let hotkey = state.model.hotkey { - Text("Try it now: press \(hotkey.display).") + Text(L10n.format("Try it now: press %1$@.", String(describing: hotkey.display))) .foregroundStyle(.secondary) } } @@ -578,15 +579,15 @@ private struct ShortcutStep: View { .padding(.top, 16) .animation(.default, value: state.hotkeyWorked) VStack(alignment: .leading, spacing: 9) { - Text("IN THE SEARCH PANEL") + Text(L10n.text("IN THE SEARCH PANEL")) .font(.system(size: 10, weight: .semibold)) .tracking(0.6) .foregroundStyle(.secondary) - KeyTip(caps: ["↩"], text: "Open the file") - KeyTip(caps: ["⌘", "↩"], text: "Show it in Finder") - KeyTip(caps: ["Space"], text: "Quick Look, after arrowing to a result") - KeyTip(caps: ["⌘", "C"], text: "Copy the file, to paste anywhere") - KeyTip(caps: ["esc"], text: "Close") + KeyTip(caps: ["↩"], text: L10n.text("Open the file")) + KeyTip(caps: ["⌘", "↩"], text: L10n.text("Show it in Finder")) + KeyTip(caps: ["Space"], text: L10n.text("Quick Look, after arrowing to a result")) + KeyTip(caps: ["⌘", "C"], text: L10n.text("Copy the file, to paste anywhere")) + KeyTip(caps: ["esc"], text: L10n.text("Close")) } .padding(18) .frame(maxWidth: 340, alignment: .leading) @@ -618,7 +619,7 @@ private struct ReadyStep: View { .font(.system(size: 60)) .foregroundStyle(Brand.gradient) .padding(.top, 8) - Text("You're all set") + Text(L10n.text("You're all set")) .font(.system(size: 26, weight: .bold)) .padding(.top, 14) Text(progressNote) @@ -629,7 +630,7 @@ private struct ReadyStep: View { .frame(maxWidth: 470) .padding(.top, 8) VStack(alignment: .leading, spacing: 16) { - SwitchRow(title: "Open DigUp when you log in", detail: "So the shortcut always works.", + SwitchRow(title: L10n.text("Open DigUp when you log in"), detail: L10n.text("So the shortcut always works."), isOn: $state.launchAtLogin) Divider() SwitchRow(title: AppSettings.batteryTitle, detail: AppSettings.batteryDetail, @@ -641,7 +642,7 @@ private struct ReadyStep: View { .padding(.top, 28) HStack(spacing: 8) { Image(systemName: "sparkle.magnifyingglass") - Text("DigUp lives in the menu bar, which shows how indexing is going.") + Text(L10n.text("DigUp lives in the menu bar, which shows how indexing is going.")) } .font(.system(size: 12)) .foregroundStyle(.secondary) @@ -652,15 +653,15 @@ private struct ReadyStep: View { private var progressNote: String { let seconds = state.folders.totalSeconds, later = state.folders.totalLaterSeconds - let time = seconds > 0 ? " (about \(roughDuration(seconds)) on this Mac)" : "" - let rest = later >= 30 ? " Long PDFs and documents get their first pages in that time, and the rest " - + "afterwards (about \(roughDuration(later)) more)." : "" + let time = seconds > 0 ? L10n.format(" (about %1$@ on this Mac)", String(describing: roughDuration(seconds))) : "" + let rest = later >= 30 ? L10n.text(" Long PDFs and documents get their first pages in that time, and the rest ") + + L10n.format("afterwards (about %1$@ more).", String(describing: roughDuration(later))) : "" if state.download.isReady { - return "DigUp is reading your folders\(time), newest files first. You can search right away; " - + "results fill in as it goes." + rest + return L10n.format("DigUp is reading your folders%1$@, newest files first. You can search right away; ", String(describing: time)) + + L10n.text("results fill in as it goes.") + rest } - return "DigUp starts reading your folders\(time) once the model has downloaded " - + "(\(state.download.progressText)). Until then, search finds files by name." + rest + return L10n.format("DigUp starts reading your folders%1$@ once the model has downloaded ", String(describing: time)) + + L10n.format("(%1$@). Until then, search finds files by name.", String(describing: state.download.progressText)) + rest } } diff --git a/Sources/DigUpApp/PowerPolicy.swift b/Sources/DigUpApp/PowerPolicy.swift index 67c239ba..e05a3685 100644 --- a/Sources/DigUpApp/PowerPolicy.swift +++ b/Sources/DigUpApp/PowerPolicy.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import Foundation import IOKit.ps @@ -53,12 +54,12 @@ final class PowerPolicy { nonisolated static func backfillBlocker(indexOnBattery: Bool) -> String? { let info = ProcessInfo.processInfo - if info.isLowPowerModeEnabled { return "Low Power Mode" } + if info.isLowPowerModeEnabled { return L10n.text("Low Power Mode") } switch info.thermalState { - case .serious, .critical: return "Mac is warm" + case .serious, .critical: return L10n.text("Mac is warm") default: break } - if !indexOnBattery, !onACPower() { return "on battery" } + if !indexOnBattery, !onACPower() { return L10n.text("on battery") } return nil } diff --git a/Sources/DigUpApp/Previews.swift b/Sources/DigUpApp/Previews.swift index 247abeeb..608a00c7 100644 --- a/Sources/DigUpApp/Previews.swift +++ b/Sources/DigUpApp/Previews.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AVFoundation import AppKit import DigUpKit @@ -145,7 +146,7 @@ struct PreviewPane: View { .buttonStyle(.plain) .font(.system(size: 11)) .foregroundStyle(.secondary) - .help("Show in Finder") + .help(L10n.text("Show in Finder")) Text(row.details) .font(.system(size: 11)) .foregroundStyle(.secondary) @@ -170,7 +171,7 @@ struct PreviewActions: View { HStack(spacing: 8) { Button(row.openTitle, action: open) .keyboardShortcut(.defaultAction) - Button("Show in Finder", action: reveal) + Button(L10n.text("Show in Finder"), action: reveal) } .controlSize(controlSize) } @@ -288,7 +289,7 @@ struct AudioMoment: View { .foregroundStyle(Color.accentColor) } .buttonStyle(.plain) - .help(row.loc.map { "Play from \(ResultRow.clock($0))" } ?? "Play") + .help(row.loc.map { L10n.format("Play from %1$@", String(describing: ResultRow.clock($0))) } ?? L10n.text("Play")) } if let loc = row.loc { VStack(spacing: 5) { @@ -297,7 +298,7 @@ struct AudioMoment: View { playhead: playback.isCurrent(row) ? playback.time : nil) .frame(maxWidth: 260) } - Text(playback.isCurrent(row) ? "\(ResultRow.clock(playback.time)) – playing from " + Text(playback.isCurrent(row) ? L10n.format("%1$@ – playing from ", String(describing: ResultRow.clock(playback.time))) + ResultRow.clock(loc) : span(from: loc)) .font(.system(size: 11).monospacedDigit()) .foregroundStyle(.secondary) @@ -311,7 +312,7 @@ struct AudioMoment: View { /// "0:25 – 0:55 of 3:12" private func span(from start: Double) -> String { let end = row.locEnd ?? start + 30 - let total = row.duration.map { " of " + ResultRow.clock($0) } ?? "" + let total = row.duration.map { L10n.text(" of ") + ResultRow.clock($0) } ?? "" return ResultRow.clock(start) + " – " + ResultRow.clock(end) + total } } @@ -356,8 +357,8 @@ struct PlayButton: View { .overlay(Circle().strokeBorder(.white.opacity(0.7), lineWidth: 1.5)) } .buttonStyle(.plain) - .help(row.moment.map { "Play from \(ResultRow.clock($0))" } ?? "Play") - .accessibilityLabel(row.moment.map { "Play from \(ResultRow.clock($0))" } ?? "Play") + .help(row.moment.map { L10n.format("Play from %1$@", String(describing: ResultRow.clock($0))) } ?? L10n.text("Play")) + .accessibilityLabel(row.moment.map { L10n.format("Play from %1$@", String(describing: ResultRow.clock($0))) } ?? L10n.text("Play")) } } @@ -394,7 +395,7 @@ struct Passage: View { guard let part = row.loc.map(Int.init), let parts = row.info["chunks"].flatMap(Int.init), parts > 1 else { return nil } - return "Part \(part) of \(parts)" + return L10n.format("Part %1$@ of %2$@", String(describing: part), String(describing: parts)) } /// Up to ~900 characters from just before the first marked word, the words marked like a search in Preview. diff --git a/Sources/DigUpApp/Results.swift b/Sources/DigUpApp/Results.swift index 55c3c13d..97236f14 100644 --- a/Sources/DigUpApp/Results.swift +++ b/Sources/DigUpApp/Results.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit @@ -69,7 +70,7 @@ struct ResultRow: Identifiable, Equatable { var place: String? { guard let loc else { return nil } switch segment { - case .page, .pageImage: return "p. \(Int(loc))" + case .page, .pageImage: return L10n.format("p. %1$@", String(describing: Int(loc))) case .frame: return Self.clock(loc) case .audio: return kind == .video ? "♪ \(Self.clock(loc))" : Self.clock(loc) case .lines: return Int(loc) == Int(locEnd ?? loc) ? "L\(Int(loc))" : "L\(Int(loc))–\(Int(locEnd ?? loc))" @@ -83,8 +84,8 @@ struct ResultRow: Identifiable, Equatable { guard let loc else { return nil } let (first, last) = (Int(loc), Int(locEnd ?? loc)) switch segment { - case .lines: return first == last ? "line \(first)" : "lines \(first)–\(last)" - case .cells: return first == last ? "cell \(first)" : "cells \(first)–\(last)" + case .lines: return first == last ? L10n.format("line %1$@", String(describing: first)) : L10n.format("lines %1$@–%2$@", String(describing: first), String(describing: last)) + case .cells: return first == last ? L10n.format("cell %1$@", String(describing: first)) : L10n.format("cells %1$@–%2$@", String(describing: first), String(describing: last)) default: return nil } } @@ -96,19 +97,19 @@ struct ResultRow: Identifiable, Equatable { /// Why it's here, in a few words: "Matches the picture", "Matches the frame at 0:06". var reason: String { - guard matchedMeaning else { return matchedWords ? "Has your words" : "Matches its name" } + guard matchedMeaning else { return matchedWords ? L10n.text("Has your words") : L10n.text("Matches its name") } let place = loc.map(Self.clock) ?? "" - let page = loc.map { "page \(Int($0))" } ?? "a page" + let page = loc.map { L10n.format("page %1$@", String(describing: Int($0))) } ?? L10n.text("a page") switch segment { - case .image: return kind == .screenshot ? "Matches what's on screen" : "Matches the picture" - case .ocr: return "Matches the text in it" - case .frame: return "Matches the frame at \(place)" - case .audio: return kind == .video ? "Matches the sound at \(place)" : "Matches what's heard at \(place)" - case .page: return "Matches the text on \(page)" - case .pageImage: return "Matches \(page)" - case .chunk: return "Matches its text" - case .name: return "Matches its name" - case .lines, .cells: return "Matches \(span ?? "the code")" + case .image: return kind == .screenshot ? L10n.text("Matches what's on screen") : L10n.text("Matches the picture") + case .ocr: return L10n.text("Matches the text in it") + case .frame: return L10n.format("Matches the frame at %1$@", String(describing: place)) + case .audio: return kind == .video ? L10n.format("Matches the sound at %1$@", String(describing: place)) : L10n.format("Matches what's heard at %1$@", String(describing: place)) + case .page: return L10n.format("Matches the text on %1$@", String(describing: page)) + case .pageImage: return L10n.format("Matches %1$@", String(describing: page)) + case .chunk: return L10n.text("Matches its text") + case .name: return L10n.text("Matches its name") + case .lines, .cells: return L10n.format("Matches %1$@", String(describing: span ?? L10n.text("the code"))) } } @@ -118,9 +119,9 @@ struct ResultRow: Identifiable, Equatable { if kind == .code { var parts = [CodeExtractor.language(of: url.path)] if let cells = info["cells"].flatMap(Int.init) { - parts.append(cells == 1 ? "1 cell" : "\(cells) cells") + parts.append(cells == 1 ? L10n.text("1 cell") : L10n.format("%1$@ cells", String(describing: cells))) } else if let lines = info["lines"].flatMap(Int.init) { - parts.append(lines == 1 ? "1 line" : "\(lines.formatted()) lines") + parts.append(lines == 1 ? L10n.text("1 line") : L10n.format("%1$@ lines", String(describing: lines.formatted()))) } parts.append(modified.formatted(date: .abbreviated, time: .omitted)) if size > 0 { parts.append(bytes(size)) } @@ -131,9 +132,9 @@ struct ResultRow: Identifiable, Equatable { // A long file still being read (`Indexer.finishLongFiles`): what isn't read yet can't match. let readTo = info["read_to"].flatMap(Int.init) if let pages = info["pages"].flatMap(Int.init) { - parts.append((pages == 1 ? "1 page" : "\(pages) pages") + (readTo.map { ", \($0) read so far" } ?? "")) + parts.append((pages == 1 ? L10n.text("1 page") : L10n.format("%1$@ pages", String(describing: pages))) + (readTo.map { L10n.format(", %1$@ read so far", String(describing: $0)) } ?? "")) } else if readTo != nil { - parts.append("partly read so far") + parts.append(L10n.text("partly read so far")) } if let duration { parts.append(Self.clock(duration)) } if size > 0 { parts.append(bytes(size)) } @@ -169,12 +170,12 @@ enum ResultGroupKind: CaseIterable { var title: String { switch self { - case .screenshots: "Screenshots" - case .images: "Images" - case .documents: "Documents" - case .video: "Video" - case .audio: "Audio" - case .code: "Code" + case .screenshots: L10n.text("Screenshots") + case .images: L10n.text("Images") + case .documents: L10n.text("Documents") + case .video: L10n.text("Video") + case .audio: L10n.text("Audio") + case .code: L10n.text("Code") } } @@ -246,13 +247,13 @@ struct ResultSet: Equatable { extension FileKind { var title: String { switch self { - case .screenshot: "Screenshot" - case .image: "Image" + case .screenshot: L10n.text("Screenshot") + case .image: L10n.text("Image") case .pdf: "PDF" - case .doc: "Document" - case .audio: "Audio" - case .video: "Video" - case .code: "Code" + case .doc: L10n.text("Document") + case .audio: L10n.text("Audio") + case .video: L10n.text("Video") + case .code: L10n.text("Code") } } diff --git a/Sources/DigUpApp/SearchPanel.swift b/Sources/DigUpApp/SearchPanel.swift index 39793f00..0bb5d22c 100644 --- a/Sources/DigUpApp/SearchPanel.swift +++ b/Sources/DigUpApp/SearchPanel.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import Observation @@ -108,7 +109,7 @@ final class SearchPanelController { installKeyMonitor() if activity == nil { activity = ProcessInfo.processInfo.beginActivity(options: .userInitiatedAllowingIdleSystemSleep, - reason: "Search panel open") + reason: L10n.text("Search panel open")) } let shown = Date() DispatchQueue.main.async { @@ -348,7 +349,7 @@ final class SearchPanelController { pasteboard.clearContents() // As a file, like ⌘C in Finder: paste into a folder, a chat or a mail. pasteboard.writeObjects([row.url as NSURL]) - flash("Copied \(row.name)") + flash(L10n.format("Copied %1$@", String(describing: row.name))) } /// The search window, with this query (and only one kind, if given): for a bigger look. diff --git a/Sources/DigUpApp/SearchPanelView.swift b/Sources/DigUpApp/SearchPanelView.swift index dbed626d..148f5def 100644 --- a/Sources/DigUpApp/SearchPanelView.swift +++ b/Sources/DigUpApp/SearchPanelView.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import SwiftUI @@ -30,12 +31,12 @@ struct SearchPanelView: View { .font(.system(size: controller.isCode ? 17 : 20, weight: .semibold)) .foregroundStyle(Brand.gradient) .frame(width: 24) - .help(controller.isCode ? "Searching your code" : "") + .help(controller.isCode ? L10n.text("Searching your code") : "") // With code search on, one example is code's: nothing else in the panel shows "code:". SearchField(text: $controller.query, fontSize: 22, placeholder: controller.model.hasCode - ? "Describe it: “zebra in a video”, “code: retry with backoff”…" - : "Describe it: “payment error screenshot”, “zebra in a video”…") { + ? L10n.text("Describe it: “zebra in a video”, “code: retry with backoff”…") + : L10n.text("Describe it: “payment error screenshot”, “zebra in a video”…")) { controller.field = $0 } if controller.phase == .words, controller.encoder == .starting { @@ -61,18 +62,18 @@ struct SearchPanelView: View { } else if controller.results.isEmpty { VStack(spacing: 8) { if controller.phase == .words { - Text("Searching…").foregroundStyle(.secondary) + Text(L10n.text("Searching…")).foregroundStyle(.secondary) } else if !controller.model.hasFolders { - Text("No folders chosen yet").font(.system(size: 15, weight: .semibold)) - Text("DigUp only searches the folders you pick.").foregroundStyle(.secondary) - Button("Choose Folders…") { controller.chooseFolders() } + Text(L10n.text("No folders chosen yet")).font(.system(size: 15, weight: .semibold)) + Text(L10n.text("DigUp only searches the folders you pick.")).foregroundStyle(.secondary) + Button(L10n.text("Choose Folders…")) { controller.chooseFolders() } .padding(.top, 4) } else if controller.isCode { - Text("No good matches in your code").font(.system(size: 15, weight: .semibold)) - Text("Try saying what the code does, or a name that's in it.").foregroundStyle(.secondary) + Text(L10n.text("No good matches in your code")).font(.system(size: 15, weight: .semibold)) + Text(L10n.text("Try saying what the code does, or a name that's in it.")).foregroundStyle(.secondary) } else { - Text("No good matches").font(.system(size: 15, weight: .semibold)) - Text("Try what you'd see or hear in it, or words you remember from it.") + Text(L10n.text("No good matches")).font(.system(size: 15, weight: .semibold)) + Text(L10n.text("Try what you'd see or hear in it, or words you remember from it.")) .foregroundStyle(.secondary) } } @@ -121,31 +122,31 @@ struct SearchPanelView: View { private var keysText: String { let row = controller.selectedRow if let row, row.kind == .code { - let line = row.segment == .lines ? " at line \(row.codeLine)" : "" - return "↩ \(row.openTitle)\(line) ⌘↩ Reveal ␣ Quick Look ⌘C Copy" + let line = row.segment == .lines ? L10n.format(" at line %1$@", String(describing: row.codeLine)) : "" + return L10n.format("↩ %1$@%2$@ ⌘↩ Reveal ␣ Quick Look ⌘C Copy", String(describing: row.openTitle), String(describing: line)) } - let place = row?.jump.map { " at \($0)" } ?? "" - return "↩ \(row?.openTitle ?? "Open") ⌘↩ Reveal ␣ Quick Look\(place) ⌘C Copy" + let place = row?.jump.map { L10n.format(" at %1$@", String(describing: $0)) } ?? "" + return L10n.format("↩ %1$@ ⌘↩ Reveal ␣ Quick Look%2$@ ⌘C Copy", String(describing: row?.openTitle ?? L10n.text("Open")), String(describing: place)) } private var statusText: String { if let note = controller.note { return note } switch controller.phase { case .words: - return controller.encoder == .starting ? "Matching words; meaning search is warming up…" : "Matching words…" + return controller.encoder == .starting ? L10n.text("Matching words; meaning search is warming up…") : L10n.text("Matching words…") case .wordsOnly: - return controller.model.meaningUnavailable ?? "Words only: meaning search isn't available" + return controller.model.meaningUnavailable ?? L10n.text("Words only: meaning search isn't available") case .codeHint, .codeOff: - return controller.phase == .codeOff ? "Code search is off" : "Searching your code" + return controller.phase == .codeOff ? L10n.text("Code search is off") : L10n.text("Searching your code") case .meaning, .empty: if controller.isCode, let indexing = controller.model.status.code.indexing, indexing.total > 0 { - return "Still reading code (\(Int(Double(indexing.done) / Double(indexing.total) * 100))%): some isn't in yet" + return L10n.format("Still reading code (%1$@%%): some isn't in yet", String(describing: Int(Double(indexing.done) / Double(indexing.total) * 100))) } if !controller.isCode, let caveat = controller.model.resultsCaveat { return caveat } let count = controller.results.strongRows.count let weaker = controller.results.weaker.count - return (count == 1 ? "1 result" : "\(count) results") + (controller.isCode ? " in your code" : "") - + (weaker > 0 ? " · \(weaker) weaker" : "") + return (count == 1 ? L10n.text("1 result") : L10n.format("%1$@ results", String(describing: count))) + (controller.isCode ? L10n.text(" in your code") : "") + + (weaker > 0 ? L10n.format(" · %1$@ weaker", String(describing: weaker)) : "") } } } @@ -173,14 +174,14 @@ private struct ResultList: View { Button { controller.openInWindow(group.kind) } label: { - Text("Show all \(group.total) \(group.kind.title.lowercased())…") + Text(L10n.format("Show all %1$@ %2$@…", String(describing: group.total), String(describing: group.kind.title.lowercased()))) .font(.system(size: 11.5, weight: .medium)) .foregroundStyle(Color.accentColor) .padding(.horizontal, 10) .padding(.vertical, 3) } .buttonStyle(.plain) - .help("Open them in the DigUp window (⌘O shows everything)") + .help(L10n.text("Open them in the DigUp window (⌘O shows everything)")) } } if !controller.results.weaker.isEmpty { @@ -284,7 +285,7 @@ private struct WeakerRow: View { Image(systemName: "chevron.right") .font(.system(size: 9.5, weight: .bold)) .rotationEffect(.degrees(expanded ? 90 : 0)) - Text(expanded ? "Weaker matches" : "\(count) weaker \(count == 1 ? "match" : "matches")") + Text(expanded ? L10n.text("Weaker matches") : L10n.format("%1$@ weaker %2$@", String(describing: count), String(describing: count == 1 ? "match" : "matches"))) .font(.system(size: 11.5, weight: .medium)) Spacer() } @@ -307,10 +308,10 @@ private struct WeakerExplainer: View { Image(systemName: "line.3.horizontal.decrease.circle") .font(.system(size: 30, weight: .light)) .foregroundStyle(.tertiary) - Text(expanded ? "Weaker matches" : "\(count) weaker \(count == 1 ? "match" : "matches")") + Text(expanded ? L10n.text("Weaker matches") : L10n.format("%1$@ weaker %2$@", String(describing: count), String(describing: count == 1 ? "match" : "matches"))) .font(.system(size: 14, weight: .semibold)) - Text("They match much less well than the results above. Press Return to " - + (expanded ? "fold them away." : "show them.")) + Text(L10n.text("They match much less well than the results above. Press Return to ") + + (expanded ? L10n.text("fold them away.") : L10n.text("show them."))) .font(.system(size: 12)) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -376,7 +377,7 @@ struct SearchField: NSViewRepresentable { field.cell?.isScrollable = true field.cell?.wraps = false field.delegate = context.coordinator - field.setAccessibilityLabel("Search") + field.setAccessibilityLabel(L10n.text("Search")) onMake(field) return field } @@ -425,7 +426,7 @@ final class FocusReportingField: NSTextField { struct FileDragArea: NSViewRepresentable { let url: URL /// "Open at 0:06": what double-click and the menu's first item do. - var openTitle = "Open" + var openTitle = L10n.text("Open") let onClick: () -> Void let onOpen: () -> Void @@ -441,7 +442,7 @@ struct FileDragArea: NSViewRepresentable { final class FileDragView: NSView, NSDraggingSource { var url: URL? - var openTitle = "Open" + var openTitle = L10n.text("Open") var onClick: () -> Void = {} var onOpen: () -> Void = {} private var mouseDownEvent: NSEvent? @@ -483,13 +484,13 @@ final class FileDragView: NSView, NSDraggingSource { onClick() let menu = NSMenu() menu.addItem(ActionMenuItem(openTitle) { [onOpen] in onOpen() }) - menu.addItem(ActionMenuItem("Show in Finder") { NSWorkspace.shared.activateFileViewerSelecting([url]) }) + menu.addItem(ActionMenuItem(L10n.text("Show in Finder")) { NSWorkspace.shared.activateFileViewerSelecting([url]) }) menu.addItem(.separator()) - menu.addItem(ActionMenuItem("Copy") { + menu.addItem(ActionMenuItem(L10n.text("Copy")) { NSPasteboard.general.clearContents() NSPasteboard.general.writeObjects([url as NSURL]) }) - menu.addItem(ActionMenuItem("Copy Path") { + menu.addItem(ActionMenuItem(L10n.text("Copy Path")) { NSPasteboard.general.clearContents() NSPasteboard.general.setString(url.path, forType: .string) }) @@ -507,8 +508,8 @@ final class FileDragView: NSView, NSDraggingSource { private struct CodeHint: View { let controller: SearchPanelController - static let examples = ["code: retry a request with backoff", "code: where the settings are saved", - "code: parse the command line options"] + static let examples = [L10n.text("code: retry a request with backoff"), L10n.text("code: where the settings are saved"), + L10n.text("code: parse the command line options")] var body: some View { VStack(spacing: 10) { @@ -516,17 +517,17 @@ private struct CodeHint: View { .font(.system(size: 26, weight: .medium)) .foregroundStyle(Brand.gradient) if controller.phase == .codeOff { - Text("Code search is off").font(.system(size: 15, weight: .semibold)) - Text("DigUp can find code by what it does, in the folders you pick. Code gets an index of its own " - + "and never shows up in other searches.") + Text(L10n.text("Code search is off")).font(.system(size: 15, weight: .semibold)) + Text(L10n.text("DigUp can find code by what it does, in the folders you pick. Code gets an index of its own ") + + L10n.text("and never shows up in other searches.")) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .frame(maxWidth: 420) - Button("Choose Code Folders…") { controller.setUpCodeSearch() } + Button(L10n.text("Choose Code Folders…")) { controller.setUpCodeSearch() } .padding(.top, 4) } else { - Text("Search your code").font(.system(size: 15, weight: .semibold)) - Text("Say what the code does, or type a name that's in it.").foregroundStyle(.secondary) + Text(L10n.text("Search your code")).font(.system(size: 15, weight: .semibold)) + Text(L10n.text("Say what the code does, or type a name that's in it.")).foregroundStyle(.secondary) VStack(spacing: 6) { ForEach(Self.examples, id: \.self) { example in Button { controller.tryExample(example) } label: { diff --git a/Sources/DigUpApp/SearchWindow.swift b/Sources/DigUpApp/SearchWindow.swift index 4fc54cc6..9821d114 100644 --- a/Sources/DigUpApp/SearchWindow.swift +++ b/Sources/DigUpApp/SearchWindow.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import Observation @@ -380,7 +381,7 @@ struct SearchWindowView: View { Image(systemName: "xmark.circle.fill").foregroundStyle(.tertiary) } .buttonStyle(.plain) - .help("Clear") + .help(L10n.text("Clear")) } } .padding(.horizontal, 12) @@ -398,7 +399,7 @@ struct SearchWindowView: View { .foregroundStyle(search.showsPreview ? AnyShapeStyle(Color.accentColor) : AnyShapeStyle(.secondary)) } .buttonStyle(.plain) - .help(search.showsPreview ? "Hide Preview" : "Show Preview") + .help(search.showsPreview ? L10n.text("Hide Preview") : L10n.text("Show Preview")) Button { app.showSettings() } label: { @@ -407,7 +408,7 @@ struct SearchWindowView: View { .foregroundStyle(.secondary) } .buttonStyle(.plain) - .help("Settings (⌘,)") + .help(L10n.text("Settings (⌘,)")) } .padding(.leading, 78) // beside the window buttons .frame(height: 52) @@ -421,12 +422,12 @@ struct SearchWindowView: View { if !model.hasFolders { VStack(spacing: 0) { BrandMark() - Text("Choose folders to search").font(.system(size: 20, weight: .bold)).padding(.top, 18) - Text("DigUp only looks where you tell it to.") + Text(L10n.text("Choose folders to search")).font(.system(size: 20, weight: .bold)).padding(.top, 18) + Text(L10n.text("DigUp only looks where you tell it to.")) .font(.system(size: 13)) .foregroundStyle(.secondary) .padding(.top, 6) - Button("Choose Folders…") { app.chooseFolders() } + Button(L10n.text("Choose Folders…")) { app.chooseFolders() } .controlSize(.large) .keyboardShortcut(.defaultAction) .padding(.top, 18) @@ -441,9 +442,9 @@ struct SearchWindowView: View { ProgressView().controlSize(.small).frame(maxWidth: .infinity, maxHeight: .infinity) } else if search.results.isEmpty { EmptyState(symbol: search.isCode ? FileKind.code.symbol : "magnifyingglass", - title: "No good matches for “\(search.query.trimmingCharacters(in: .whitespaces))”", - message: search.isCode ? "Try saying what the code does, or a name that's in it." - : "Try describing what's in it: what you'd see or hear, or words you remember.") + title: L10n.format("No good matches for “%1$@”", String(describing: search.query.trimmingCharacters(in: .whitespaces))), + message: search.isCode ? L10n.text("Try saying what the code does, or a name that's in it.") + : L10n.text("Try describing what's in it: what you'd see or hear, or words you remember.")) } else if search.isCode { CodeResultsList(search: search) } else { @@ -460,7 +461,7 @@ struct SearchWindowView: View { Button(model.statusLine) { app.showSettings(.folders) } .buttonStyle(.plain) .lineLimit(1) - .help("Folders and indexing") + .help(L10n.text("Folders and indexing")) Spacer() if search.hasResults, !search.query.isEmpty { Text(model.resultsCaveat ?? resultCount).lineLimit(1) @@ -469,11 +470,11 @@ struct SearchWindowView: View { Button { app.updates.checkNow() } label: { - Label("DigUp \(version) is available", systemImage: "arrow.down.circle.fill") + Label(L10n.format("DigUp %1$@ is available", String(describing: version)), systemImage: "arrow.down.circle.fill") .foregroundStyle(Brand.ember) } .buttonStyle(.plain) - .help("See what's new and update") + .help(L10n.text("See what's new and update")) } } .font(.system(size: 11)) @@ -484,15 +485,15 @@ struct SearchWindowView: View { /// Examples of what to type: code's with the Code chip on, and one of them with code search on (as the panel's). private var placeholder: String { - if search.filter == .code { return "Describe the code: “retry a request with backoff”, a name in it…" } - return model.hasCode ? "Describe it: “zebra in a video”, “code: retry with backoff”…" - : "Describe it: “receipt from the hardware store”, “zebra in a video”…" + if search.filter == .code { return L10n.text("Describe the code: “retry a request with backoff”, a name in it…") } + return model.hasCode ? L10n.text("Describe it: “zebra in a video”, “code: retry with backoff”…") + : L10n.text("Describe it: “receipt from the hardware store”, “zebra in a video”…") } private var resultCount: String { let count = search.results.ranked.count - return (count == 1 ? "1 result" : "\(count) results") - + (search.results.weaker.isEmpty ? "" : " · \(search.results.weaker.count) weaker") + return (count == 1 ? L10n.text("1 result") : L10n.format("%1$@ results", String(describing: count))) + + (search.results.weaker.isEmpty ? "" : L10n.format(" · %1$@ weaker", String(describing: search.results.weaker.count))) } } @@ -504,7 +505,7 @@ private struct KindFilters: View { var body: some View { HStack(spacing: 6) { - chip(title: "All", symbol: nil, count: nil, selected: search.filter == nil && !search.isCode) { + chip(title: L10n.text("All"), symbol: nil, count: nil, selected: search.filter == nil && !search.isCode) { search.filter = nil } ForEach(ResultGroupKind.files, id: \.self) { kind in @@ -515,7 +516,7 @@ private struct KindFilters: View { } if showsCode { Divider().frame(height: 16).padding(.horizontal, 4) - chip(title: "Code", symbol: FileKind.code.symbol, count: search.isCode ? search.counts[.code] : nil, + chip(title: L10n.text("Code"), symbol: FileKind.code.symbol, count: search.isCode ? search.counts[.code] : nil, selected: search.isCode, shortcut: 7) { search.filter = search.filter == .code ? nil : .code } @@ -723,7 +724,7 @@ private struct CodeResultRow: View { .fill(selected ? Color.accentColor.opacity(0.22) : Color.clear)) .contentShape(Rectangle()) .accessibilityElement(children: .ignore) - .accessibilityLabel("Code, \(row.name), \(row.reason)") + .accessibilityLabel(L10n.format("Code, %1$@, %2$@", String(describing: row.name), String(describing: row.reason))) .accessibilityAddTraits(selected ? .isSelected : []) } } @@ -740,17 +741,17 @@ private struct CodeOffOrHint: View { .font(.system(size: 34, weight: .medium)) .foregroundStyle(Brand.gradient) if phase == .codeOff { - Text("Code search is off").font(.system(size: 17, weight: .semibold)) - Text("DigUp can find code by what it does, in the folders you pick. Code gets an index of its own and " - + "never shows up in other searches.") + Text(L10n.text("Code search is off")).font(.system(size: 17, weight: .semibold)) + Text(L10n.text("DigUp can find code by what it does, in the folders you pick. Code gets an index of its own and ") + + L10n.text("never shows up in other searches.")) .font(.system(size: 13)) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .frame(maxWidth: 420) - Button("Choose Code Folders…", action: chooseFolders).padding(.top, 6) + Button(L10n.text("Choose Code Folders…"), action: chooseFolders).padding(.top, 6) } else { - Text("Search your code").font(.system(size: 17, weight: .semibold)) - Text("Say what the code does, or type a name that's in it.") + Text(L10n.text("Search your code")).font(.system(size: 17, weight: .semibold)) + Text(L10n.text("Say what the code does, or type a name that's in it.")) .font(.system(size: 13)) .foregroundStyle(.secondary) } @@ -796,7 +797,7 @@ struct WeakerToggle: View { Image(systemName: "chevron.right") .font(.system(size: 10, weight: .semibold)) .rotationEffect(.degrees(expanded ? 90 : 0)) - Text(expanded ? "Weaker matches" : "\(count) weaker \(count == 1 ? "match" : "matches")") + Text(expanded ? L10n.text("Weaker matches") : L10n.format("%1$@ weaker %2$@", String(describing: count), String(describing: count == 1 ? "match" : "matches"))) .font(.system(size: 12, weight: .medium)) } .foregroundStyle(.secondary) @@ -842,25 +843,25 @@ private struct SearchHome: View { @State private var kinds: [FileKind: Int] = [:] private static let examples: [(text: String, kind: ResultGroupKind)] = [ - ("a screenshot of an error message", .screenshots), ("receipt", .documents), ("whiteboard notes", .images), - ("someone laughing", .audio), ("a dog on the beach", .video), ("slides about the budget", .documents), + (L10n.text("a screenshot of an error message"), .screenshots), ("receipt", .documents), (L10n.text("whiteboard notes"), .images), + (L10n.text("someone laughing"), .audio), (L10n.text("a dog on the beach"), .video), (L10n.text("slides about the budget"), .documents), ] var body: some View { VStack(spacing: 0) { BrandMark() - Text("Describe it. Dig it up.") + Text(L10n.text("Describe it. Dig it up.")) .font(.system(size: 24, weight: .bold)) .padding(.top, 18) - Text("Type what you remember in the search field above: what's in it, what it looks or sounds like, or " - + "words from it.") + Text(L10n.text("Type what you remember in the search field above: what's in it, what it looks or sounds like, or ") + + L10n.text("words from it.")) .font(.system(size: 13)) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: 440) .padding(.top, 6) - Text("TRY") + Text(L10n.text("TRY")) .font(.system(size: 10, weight: .semibold)) .tracking(0.8) .foregroundStyle(.tertiary) @@ -876,15 +877,15 @@ private struct SearchHome: View { if !kinds.isEmpty { HStack(spacing: 8) { KindCounts(counts: kinds) - Text("in \(folders)") + Text(L10n.format("in %1$@", String(describing: folders))) } } else if model.status.searchable > 0 { - Text("\(model.status.searchable.formatted()) files to search in \(folders)") + Text(L10n.format("%1$@ files to search in %2$@", String(describing: model.status.searchable.formatted()), String(describing: folders))) } if let hotkey = model.hotkey { HStack(spacing: 6) { Keycaps(caps: hotkey.caps, size: 10) - Text("opens DigUp from any app") + Text(L10n.text("opens DigUp from any app")) } } } @@ -905,12 +906,12 @@ private struct SearchHome: View { /// With code search on, one of code's too. private var examples: [(text: String, kind: ResultGroupKind)] { - Self.examples + (model.hasCode ? [("code: where the settings are saved", .code)] : []) + Self.examples + (model.hasCode ? [(L10n.text("code: where the settings are saved"), .code)] : []) } private var folders: String { let names = model.settings.roots.map(\.lastPathComponent) - return names.count <= 3 ? names.formatted(.list(type: .and)) : "\(names.count) folders" + return names.count <= 3 ? names.formatted(.list(type: .and)) : L10n.format("%1$@ folders", String(describing: names.count)) } } @@ -954,7 +955,7 @@ private struct ExampleChip: View { .contentShape(Capsule()) } .buttonStyle(.plain) - .help("Search for “\(text)”") + .help(L10n.format("Search for “%1$@”", String(describing: text))) .onHover { inside in hovering = inside if inside { NSCursor.pointingHand.push() } else { NSCursor.pop() } diff --git a/Sources/DigUpApp/SettingsWindow.swift b/Sources/DigUpApp/SettingsWindow.swift index a40a0a59..34c7b7e3 100644 --- a/Sources/DigUpApp/SettingsWindow.swift +++ b/Sources/DigUpApp/SettingsWindow.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import DigUpKit import Observation @@ -8,10 +9,10 @@ enum SettingsTab: String, CaseIterable { var title: String { switch self { - case .general: "General" - case .folders: "Folders" - case .code: "Code" - case .storage: "Storage" + case .general: L10n.text("General") + case .folders: L10n.text("Folders") + case .code: L10n.text("Code") + case .storage: L10n.text("Storage") } } @@ -197,8 +198,8 @@ private struct GeneralSettings: View { listening: { state.app.hotkeyRecording($0) }, capSize: 13) } label: { VStack(alignment: .leading, spacing: 2) { - Text("Search shortcut") - Text("Opens the search panel from any app.") + Text(L10n.text("Search shortcut")) + Text(L10n.text("Opens the search panel from any app.")) .font(.system(size: 11)) .foregroundStyle(.secondary) } @@ -210,8 +211,8 @@ private struct GeneralSettings: View { state.launchAtLogin = state.app.launchesAtLogin })) { VStack(alignment: .leading, spacing: 2) { - Text("Open DigUp when you log in") - Text(state.loginProblem ?? "So the shortcut always works.") + Text(L10n.text("Open DigUp when you log in")) + Text(state.loginProblem ?? L10n.text("So the shortcut always works.")) .font(.system(size: 11)) .foregroundStyle(state.loginProblem == nil ? AnyShapeStyle(.secondary) : AnyShapeStyle(.red)) } @@ -223,18 +224,18 @@ private struct GeneralSettings: View { state.checksForUpdates = on })) { VStack(alignment: .leading, spacing: 2) { - Text("Check for updates automatically") - Text("Once a day DigUp asks GitHub for its newest version; nothing about your files is sent. " - + "Updates install with one click.") + Text(L10n.text("Check for updates automatically")) + Text(L10n.text("Once a day DigUp asks GitHub for its newest version; nothing about your files is sent. ") + + L10n.text("Updates install with one click.")) .font(.system(size: 11)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } LabeledContent { - Button("Check Now") { state.app.updates.checkNow() } + Button(L10n.text("Check Now")) { state.app.updates.checkNow() } } label: { - Text(state.model.updateAvailable.map { "DigUp \($0) is available" } + Text(state.model.updateAvailable.map { L10n.format("DigUp %1$@ is available", String(describing: $0)) } ?? "DigUp \(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "")") } } @@ -256,7 +257,7 @@ private struct FolderSettings: View { Section { if let summaries = state.summaries { if summaries.isEmpty { - Text("No folders yet. Add the ones DigUp should search.") + Text(L10n.text("No folders yet. Add the ones DigUp should search.")) .foregroundStyle(.secondary) } ForEach(summaries) { summary in @@ -266,11 +267,11 @@ private struct FolderSettings: View { ProgressView().controlSize(.small).frame(maxWidth: .infinity) } } header: { - Text("Folders to search") + Text(L10n.text("Folders to search")) } footer: { HStack { Button(action: state.addFolders) { - Label("Add Folders…", systemImage: "plus") + Label(L10n.text("Add Folders…"), systemImage: "plus") } Spacer() } @@ -289,25 +290,25 @@ private struct FolderSettings: View { Image(systemName: "minus.circle.fill").foregroundStyle(.secondary) } .buttonStyle(.plain) - .help("Search this folder again") + .help(L10n.text("Search this folder again")) } } if state.model.settings.excludedFolders.isEmpty { - Text("None. Code projects, app bundles and caches are skipped anyway (code has its own tab).") + Text(L10n.text("None. Code projects, app bundles and caches are skipped anyway (code has its own tab).")) .foregroundStyle(.secondary) } } header: { - Text("Skip these folders") + Text(L10n.text("Skip these folders")) } footer: { HStack { Button { - state.app.pickFolders(for: NSApp.keyWindow, prompt: "Skip") { folders in + state.app.pickFolders(for: NSApp.keyWindow, prompt: L10n.text("Skip")) { folders in guard !folders.isEmpty else { return } state.app.setExcluded(state.model.settings.excludedFolders + folders.filter { !state.model.settings.excludedFolders.contains($0) }) } } label: { - Label("Skip a Folder…", systemImage: "plus") + Label(L10n.text("Skip a Folder…"), systemImage: "plus") } Spacer() } @@ -315,11 +316,11 @@ private struct FolderSettings: View { Section { TypeExclusions(types: state.model.settings.excludedTypes) { state.app.setExcludedTypes($0) } } header: { - Text("Skip these file types") + Text(L10n.text("Skip these file types")) } footer: { - Text("DigUp only reads pictures, PDFs, documents (txt, md, rtf, docx, odt, html), audio and video here; " - + "code is read only in the code folders (the Code tab). Keys, certificates and password files are " - + "never read, nor are hidden folders like ~/.ssh.") + Text(L10n.text("DigUp only reads pictures, PDFs, documents (txt, md, rtf, docx, odt, html), audio and video here; ") + + L10n.text("code is read only in the code folders (the Code tab). Keys, certificates and password files are ") + + L10n.text("never read, nor are hidden folders like ~/.ssh.")) .font(.system(size: 11)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -341,19 +342,19 @@ private struct FolderSettings: View { .onChange(of: state.model.status) { state.statusChanged() } .confirmationDialog(removalTitle, isPresented: Binding(get: { removing != nil }, set: { if !$0 { removing = nil } })) { - Button("Remove", role: .destructive) { + Button(L10n.text("Remove"), role: .destructive) { guard let removing else { return } state.app.removeFolder(URL(fileURLWithPath: removing.path)) Task { await state.refresh() } } } message: { - Text("Its \(removing?.files.formatted() ?? "") files leave the index. Adding it back later indexes them " - + "again.") + Text(L10n.format("Its %1$@ files leave the index. Adding it back later indexes them ", String(describing: removing?.files.formatted() ?? "")) + + L10n.text("again.")) } } private var removalTitle: String { - "Stop searching “\(removing.map { ($0.path as NSString).lastPathComponent } ?? "")”?" + L10n.format("Stop searching “%1$@”?", String(describing: removing.map { ($0.path as NSString).lastPathComponent } ?? "")) } } @@ -365,7 +366,7 @@ private struct TypeExclusions: View { @FocusState private var focused: Bool var body: some View { - TextField("Extensions", text: $text, prompt: Text("e.g. heic, txt")) + TextField(L10n.text("Extensions"), text: $text, prompt: Text(L10n.text("e.g. heic, txt"))) .labelsHidden() .focused($focused) .onSubmit { apply(text) } @@ -393,7 +394,7 @@ private struct IndexProgress: View { } Spacer() if model.hasFolders { - Button(model.status.paused ? "Resume" : "Pause", action: togglePause) + Button(model.status.paused ? L10n.text("Resume") : L10n.text("Pause"), action: togglePause) } } } @@ -401,12 +402,12 @@ private struct IndexProgress: View { /// "357 of 365 files searchable · 8 waiting · 2 long files to finish" private var detail: String { let status = model.status - var text = "\(status.searchable.formatted()) of \(status.files.formatted()) files searchable" - if status.pending > 0 { text += " · \(status.pending.formatted()) waiting" } + var text = L10n.format("%1$@ of %2$@ files searchable", String(describing: status.searchable.formatted()), String(describing: status.files.formatted())) + if status.pending > 0 { text += L10n.format(" · %1$@ waiting", String(describing: status.pending.formatted())) } if status.unfinished > 0 { - text += " · \(status.unfinished.formatted()) long \(status.unfinished == 1 ? "file" : "files") to finish" + text += L10n.format(" · %1$@ long %2$@ to finish", String(describing: status.unfinished.formatted()), String(describing: status.unfinished == 1 ? "file" : "files")) } - if model.download.isActive { text += " · model \(model.download.progressText)" } + if model.download.isActive { text += L10n.format(" · model %1$@", String(describing: model.download.progressText)) } return text } } @@ -425,14 +426,14 @@ private struct FolderStatusRow: View { HStack(spacing: 6) { Text((summary.path as NSString).lastPathComponent).font(.system(size: 13, weight: .semibold)) if summary.missing || summary.unreadable { - Text(summary.missing ? "Not connected" : "No access") + Text(summary.missing ? L10n.text("Not connected") : L10n.text("No access")) .font(.system(size: 10, weight: .semibold)) .padding(.horizontal, 6) .padding(.vertical, 1) .background(Capsule().fill(Color.orange.opacity(0.2))) } if summary.unreadable { - Button("Allow in Settings…", action: openFolderPrivacySettings) + Button(L10n.text("Allow in Settings…"), action: openFolderPrivacySettings) .buttonStyle(.link) .font(.system(size: 11)) } @@ -448,8 +449,8 @@ private struct FolderStatusRow: View { } Spacer(minLength: 8) VStack(alignment: .trailing, spacing: 2) { - Text(summary.missing || summary.unreadable ? "Kept for when it's back" - : "\(summary.indexed.formatted()) of \(summary.files.formatted()) indexed") + Text(summary.missing || summary.unreadable ? L10n.text("Kept for when it's back") + : L10n.format("%1$@ of %2$@ indexed", String(describing: summary.indexed.formatted()), String(describing: summary.files.formatted()))) .font(.system(size: 12, weight: .medium).monospacedDigit()) if !states.isEmpty { Text(states).font(.system(size: 11)).foregroundStyle(.secondary) @@ -459,18 +460,18 @@ private struct FolderStatusRow: View { Image(systemName: "minus.circle.fill").foregroundStyle(.secondary) } .buttonStyle(.plain) - .help("Stop searching this folder") - .accessibilityLabel("Remove \((summary.path as NSString).lastPathComponent)") + .help(L10n.text("Stop searching this folder")) + .accessibilityLabel(L10n.format("Remove %1$@", String(describing: (summary.path as NSString).lastPathComponent))) } .padding(.vertical, 2) } private var states: String { - [summary.pending > 0 ? "\(summary.pending.formatted()) waiting" : nil, + [summary.pending > 0 ? L10n.format("%1$@ waiting", String(describing: summary.pending.formatted())) : nil, summary.unfinished > 0 - ? "\(summary.unfinished.formatted()) long \(summary.unfinished == 1 ? "file" : "files") to finish" : nil, - summary.skipped > 0 ? "\(summary.skipped.formatted()) skipped" : nil, - summary.failed > 0 ? "\(summary.failed.formatted()) couldn't be read" : nil].compactMap { $0 } + ? L10n.format("%1$@ long %2$@ to finish", String(describing: summary.unfinished.formatted()), String(describing: summary.unfinished == 1 ? "file" : "files")) : nil, + summary.skipped > 0 ? L10n.format("%1$@ skipped", String(describing: summary.skipped.formatted())) : nil, + summary.failed > 0 ? L10n.format("%1$@ couldn't be read", String(describing: summary.failed.formatted())) : nil].compactMap { $0 } .joined(separator: " · ") } } @@ -480,44 +481,44 @@ private struct StorageSettings: View { var body: some View { Form { - Section("Model") { + Section(L10n.text("Model")) { LabeledContent("EmbeddingGemma 2") { Text("Google · Apache 2.0 · 865 MB") } - LabeledContent("Status") { + LabeledContent(L10n.text("Status")) { HStack(spacing: 8) { - Text(state.model.download.isReady ? "Downloaded" : state.model.download.progressText) + Text(state.model.download.isReady ? L10n.text("Downloaded") : state.model.download.progressText) switch state.model.download.phase { case .needed, .failed: - Button("Download") { state.model.download.start() }.controlSize(.small) + Button(L10n.text("Download")) { state.model.download.start() }.controlSize(.small) default: EmptyView() } } } - LabeledContent("Location") { + LabeledContent(L10n.text("Location")) { PathButton(url: state.model.settings.modelsDirectory) } } - Section("Index") { - LabeledContent("Size on disk", value: bytes(state.indexSize)) + Section(L10n.text("Index")) { + LabeledContent(L10n.text("Size on disk"), value: bytes(state.indexSize)) if state.model.hasCode { - LabeledContent("Code index", value: bytes(state.codeIndexSize)) + LabeledContent(L10n.text("Code index"), value: bytes(state.codeIndexSize)) } - LabeledContent("Location") { + LabeledContent(L10n.text("Location")) { PathButton(url: state.model.settings.indexDirectory) } } - Section("Privacy") { - Text("DigUp reads the folders you choose and keeps what it learns in its index, on this Mac. " - + "It goes online only to download its model, once, and to check for updates (General), " - + "which sends nothing about your files.") + Section(L10n.text("Privacy")) { + Text(L10n.text("DigUp reads the folders you choose and keeps what it learns in its index, on this Mac. ") + + L10n.text("It goes online only to download its model, once, and to check for updates (General), ") + + L10n.text("which sends nothing about your files.")) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } Section { Text("DigUp \(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "") · " - + "EmbeddingGemma 2 by Google (Apache 2.0), run by llama.cpp (MIT).") + + L10n.text("EmbeddingGemma 2 by Google (Apache 2.0), run by llama.cpp (MIT).")) .font(.system(size: 11)) .foregroundStyle(.secondary) } @@ -537,10 +538,10 @@ private struct CodeSettings: View { Form { Section { VStack(alignment: .leading, spacing: 6) { - Text("Search your code").font(.system(size: 13, weight: .semibold)) - Text("Find code by what it does, or by a name in it: type code: in the search panel, as in " - + "“code: retry with backoff”. Code has an index of its own, and it never shows up in " - + "other searches.") + Text(L10n.text("Search your code")).font(.system(size: 13, weight: .semibold)) + Text(L10n.text("Find code by what it does, or by a name in it: type code: in the search panel, as in ") + + L10n.text("“code: retry with backoff”. Code has an index of its own, and it never shows up in ") + + L10n.text("other searches.")) .font(.system(size: 11)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -562,44 +563,44 @@ private struct CodeSettings: View { } Section { if state.codeSummaries.isEmpty { - Text("No code folders yet. Add the folders that hold your projects.") + Text(L10n.text("No code folders yet. Add the folders that hold your projects.")) .foregroundStyle(.secondary) } ForEach(state.codeSummaries) { summary in FolderStatusRow(summary: summary) { removing = summary } } } header: { - Text("Code folders") + Text(L10n.text("Code folders")) } footer: { HStack { Button(action: state.addCodeFolders) { - Label(state.model.hasCode ? "Add Folders…" : "Choose Code Folders…", systemImage: "plus") + Label(state.model.hasCode ? L10n.text("Add Folders…") : L10n.text("Choose Code Folders…"), systemImage: "plus") } Spacer() } } Section { - Picker("Open code in", selection: Binding(get: { CodeEditor.current.id }, + Picker(L10n.text("Open code in"), selection: Binding(get: { CodeEditor.current.id }, set: { state.app.setCodeEditor($0) })) { ForEach(CodeEditor.installed) { editor in Text(editor.name).tag(editor.id) } Text(CodeEditor.defaultApp.name).tag(CodeEditor.defaultApp.id) } } footer: { - Text("A result opens there at the line that matched. Space shows the whole file in Quick Look.") + Text(L10n.text("A result opens there at the line that matched. Space shows the whole file in Quick Look.")) .font(.system(size: 11)) .foregroundStyle(.secondary) } Section { - Text("The code index keeps your code's text on this Mac, as the main index keeps your documents'. " - + "It skips what git ignores, vendored and generated code, data files, and anything that looks " - + "like a key or a password.") + Text(L10n.text("The code index keeps your code's text on this Mac, as the main index keeps your documents'. ") + + L10n.text("It skips what git ignores, vendored and generated code, data files, and anything that looks ") + + L10n.text("like a key or a password.")) .font(.system(size: 11)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) if state.model.hasCode { HStack { Spacer() - Button("Turn Off Code Search…", role: .destructive) { turningOff = true } + Button(L10n.text("Turn Off Code Search…"), role: .destructive) { turningOff = true } } } } @@ -607,23 +608,23 @@ private struct CodeSettings: View { .formStyle(.grouped) .scrollDisabled(true) .onChange(of: state.model.status) { state.statusChanged() } - .confirmationDialog("Turn off code search?", isPresented: $turningOff) { - Button("Turn Off", role: .destructive) { + .confirmationDialog(L10n.text("Turn off code search?"), isPresented: $turningOff) { + Button(L10n.text("Turn Off"), role: .destructive) { state.app.applyCodeFolders([], excluded: []) Task { await state.refresh() } } } message: { - Text("The code index (\(bytes(state.codeIndexSize))) is deleted. Turning it on again reads the code again.") + Text(L10n.format("The code index (%1$@) is deleted. Turning it on again reads the code again.", String(describing: bytes(state.codeIndexSize)))) } - .confirmationDialog("Stop searching the code in “\(removing.map { ($0.path as NSString).lastPathComponent } ?? "")”?", + .confirmationDialog(L10n.format("Stop searching the code in “%1$@”?", String(describing: removing.map { ($0.path as NSString).lastPathComponent } ?? "")), isPresented: Binding(get: { removing != nil }, set: { if !$0 { removing = nil } })) { - Button("Remove", role: .destructive) { + Button(L10n.text("Remove"), role: .destructive) { guard let removing else { return } state.app.removeCodeFolder(URL(fileURLWithPath: removing.path)) Task { await state.refresh() } } } message: { - Text("Its \(removing?.files.formatted() ?? "") code files leave the code index.") + Text(L10n.format("Its %1$@ code files leave the code index.", String(describing: removing?.files.formatted() ?? ""))) } } } @@ -642,6 +643,6 @@ private struct PathButton: View { } } .buttonStyle(.plain) - .help("Show in Finder") + .help(L10n.text("Show in Finder")) } } diff --git a/Sources/DigUpApp/StatusMenu.swift b/Sources/DigUpApp/StatusMenu.swift index 54a894f9..bada8a43 100644 --- a/Sources/DigUpApp/StatusMenu.swift +++ b/Sources/DigUpApp/StatusMenu.swift @@ -1,3 +1,4 @@ +import DigUpLocalization import AppKit import SwiftUI @@ -132,39 +133,39 @@ final class StatusMenuController: NSObject, NSMenuDelegate { menu.addItem(header) menu.addItem(.separator()) if let version = model.updateAvailable { - menu.addItem(ActionMenuItem("Update to DigUp \(version)…") { [weak self] in self?.onCheckForUpdates() }) + menu.addItem(ActionMenuItem(L10n.format("Update to DigUp %1$@…", String(describing: version))) { [weak self] in self?.onCheckForUpdates() }) menu.addItem(.separator()) } if !model.hasFolders { - menu.addItem(ActionMenuItem("Choose Folders to Search…") { [weak self] in self?.onChooseFolders() }) + menu.addItem(ActionMenuItem(L10n.text("Choose Folders to Search…")) { [weak self] in self?.onChooseFolders() }) } - let search = ActionMenuItem("Search…") { [weak self] in self?.onSearch() } + let search = ActionMenuItem(L10n.text("Search…")) { [weak self] in self?.onSearch() } if let hotkey = model.hotkey, let (key, modifiers) = hotkey.menuEquivalent { search.keyEquivalent = key search.keyEquivalentModifierMask = modifiers } menu.addItem(search) - menu.addItem(ActionMenuItem("Open DigUp") { [weak self] in self?.onOpenWindow() }) + menu.addItem(ActionMenuItem(L10n.text("Open DigUp")) { [weak self] in self?.onOpenWindow() }) if model.hasFolders { menu.addItem(.separator()) let paused = model.status.paused - menu.addItem(ActionMenuItem(paused ? "Resume Indexing" : "Pause Indexing") { [weak self] in + menu.addItem(ActionMenuItem(paused ? L10n.text("Resume Indexing") : L10n.text("Pause Indexing")) { [weak self] in self?.onPauseChanged(!paused) }) } switch model.download.phase { case .failed, .needed: - menu.addItem(ActionMenuItem("Download the Model") { [weak self] in self?.model.download.start() }) + menu.addItem(ActionMenuItem(L10n.text("Download the Model")) { [weak self] in self?.model.download.start() }) default: break } menu.addItem(.separator()) - menu.addItem(ActionMenuItem("Settings…", key: ",") { [weak self] in self?.onSettings() }) + menu.addItem(ActionMenuItem(L10n.text("Settings…"), key: ",") { [weak self] in self?.onSettings() }) if model.updateAvailable == nil { - menu.addItem(ActionMenuItem("Check for Updates…") { [weak self] in self?.onCheckForUpdates() }) + menu.addItem(ActionMenuItem(L10n.text("Check for Updates…")) { [weak self] in self?.onCheckForUpdates() }) } - menu.addItem(ActionMenuItem("Quit DigUp", key: "q") { NSApp.terminate(nil) }) + menu.addItem(ActionMenuItem(L10n.text("Quit DigUp"), key: "q") { NSApp.terminate(nil) }) } /// The menu as text, for the -debugMenu launch argument (menus can't be clicked from a script). @@ -216,13 +217,13 @@ struct MenuStatusView: View { private var detail: String? { if model.download.isActive { return model.download.progressText } if case .failed(let message) = model.download.phase { return message } - guard model.hasFolders else { return "Pick the folders it should search." } + guard model.hasFolders else { return L10n.text("Pick the folders it should search.") } let missing = Set(model.status.missingRoots), unreadable = Set(model.status.unreadableRoots) let names = model.settings.roots.map { root in - root.lastPathComponent + (missing.contains(root.path) ? " (not connected)" - : unreadable.contains(root.path) ? " (no access)" : "") + root.lastPathComponent + (missing.contains(root.path) ? L10n.text(" (not connected)") + : unreadable.contains(root.path) ? L10n.text(" (no access)") : "") } - return "In " + names.joined(separator: ", ") + return L10n.text("In ") + names.joined(separator: ", ") } } diff --git a/Sources/DigUpLocalization/L10n.swift b/Sources/DigUpLocalization/L10n.swift new file mode 100644 index 00000000..3e63bfd5 --- /dev/null +++ b/Sources/DigUpLocalization/L10n.swift @@ -0,0 +1,15 @@ +import Foundation + +/// Interface localization only. Queries, file paths, persisted settings and helper protocols keep their original values. +public enum L10n { + static let bundle = Bundle.module + + public static func text(_ key: String) -> String { + bundle.localizedString(forKey: key, value: nil, table: "Localizable") + } + + /// Positional arguments let a translation reorder display values without changing their formatting. + public static func format(_ key: String, _ arguments: CVarArg...) -> String { + String(format: text(key), locale: Locale.current, arguments: arguments) + } +} diff --git a/Sources/DigUpLocalization/Resources/en.lproj/InfoPlist.strings b/Sources/DigUpLocalization/Resources/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..640c8a6e --- /dev/null +++ b/Sources/DigUpLocalization/Resources/en.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSAppleEventsUsageDescription" = "DigUp asks QuickTime Player to open a recording at the moment that matched your search."; +"NSDesktopFolderUsageDescription" = "DigUp indexes the folders you choose so you can search them by describing what's inside. Everything stays on this Mac."; +"NSDocumentsFolderUsageDescription" = "DigUp indexes the folders you choose so you can search them by describing what's inside. Everything stays on this Mac."; +"NSDownloadsFolderUsageDescription" = "DigUp indexes the folders you choose so you can search them by describing what's inside. Everything stays on this Mac."; +"NSRemovableVolumesUsageDescription" = "DigUp indexes the folders you choose so you can search them by describing what's inside. Everything stays on this Mac."; diff --git a/Sources/DigUpLocalization/Resources/en.lproj/Localizable.strings b/Sources/DigUpLocalization/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000..f0a42a1f --- /dev/null +++ b/Sources/DigUpLocalization/Resources/en.lproj/Localizable.strings @@ -0,0 +1,405 @@ +/* DigUp interface strings. Positional %@ arguments contain already formatted display values. */ +" (about %1$@ on this Mac)" = " (about %1$@ on this Mac)"; +" (no access)" = " (no access)"; +" (not connected)" = " (not connected)"; +" + %1$@ later" = " + %1$@ later"; +" Long PDFs and documents get their first pages in that time, and the rest " = " Long PDFs and documents get their first pages in that time, and the rest "; +" at %1$@" = " at %1$@"; +" at line %1$@" = " at line %1$@"; +" in your code" = " in your code"; +" of " = " of "; +" · %1$@ long %2$@ to finish" = " · %1$@ long %2$@ to finish"; +" · %1$@ waiting" = " · %1$@ waiting"; +" · %1$@ weaker" = " · %1$@ weaker"; +" · model %1$@" = " · model %1$@"; +" · paused" = " · paused"; +"%.1f h" = "%.1f h"; +"%1$@ %2$@ indexed" = "%1$@ %2$@ indexed"; +"%1$@ %2$@ indexed yet" = "%1$@ %2$@ indexed yet"; +"%1$@ %2$@ waiting" = "%1$@ %2$@ waiting"; +"%1$@ cells" = "%1$@ cells"; +"%1$@ code %2$@ indexed" = "%1$@ code %2$@ indexed"; +"%1$@ code files waiting · %2$@" = "%1$@ code files waiting · %2$@"; +"%1$@ couldn't be read" = "%1$@ couldn't be read"; +"%1$@ files to search in %2$@" = "%1$@ files to search in %2$@"; +"%1$@ folders" = "%1$@ folders"; +"%1$@ lines" = "%1$@ lines"; +"%1$@ long %2$@" = "%1$@ long %2$@"; +"%1$@ long %2$@ to finish" = "%1$@ long %2$@ to finish"; +"%1$@ min" = "%1$@ min"; +"%1$@ of %2$@ files searchable" = "%1$@ of %2$@ files searchable"; +"%1$@ of %2$@ indexed" = "%1$@ of %2$@ indexed"; +"%1$@ pages" = "%1$@ pages"; +"%1$@ results" = "%1$@ results"; +"%1$@ s" = "%1$@ s"; +"%1$@ skipped" = "%1$@ skipped"; +"%1$@ to be indexed" = "%1$@ to be indexed"; +"%1$@ waiting" = "%1$@ waiting"; +"%1$@ weaker %2$@" = "%1$@ weaker %2$@"; +"%1$@ – playing from " = "%1$@ – playing from "; +"(%1$@). Until then, search finds files by name." = "(%1$@). Until then, search finds files by name."; +"+ %1$@ later" = "+ %1$@ later"; +", %1$@ read so far" = ", %1$@ read so far"; +", and %1$@ more for long files" = ", and %1$@ more for long files"; +", as part of “%1$@”." = ", as part of “%1$@”."; +"1 cell" = "1 cell"; +"1 line" = "1 line"; +"1 page" = "1 page"; +"1 result" = "1 result"; +"< 1 s" = "< 1 s"; +"A result opens there at the line that matched. Space shows the whole file in Quick Look." = "A result opens there at the line that matched. Space shows the whole file in Quick Look."; +"About %1$@" = "About %1$@"; +"About DigUp" = "About DigUp"; +"Add" = "Add"; +"Add Folders" = "Add Folders"; +"Add Folders…" = "Add Folders…"; +"Add Folder…" = "Add Folder…"; +"Add folders to search" = "Add folders to search"; +"Adds %1$@ of indexing" = "Adds %1$@ of indexing"; +"Adds %1$@ of indexing and %2$@" = "Adds %1$@ of indexing and %2$@"; +"All" = "All"; +"Allow in Settings…" = "Allow in Settings…"; +"Another app is using %1$@" = "Another app is using %1$@"; +"At %1$@ of %2$@" = "At %1$@ of %2$@"; +"Audio" = "Audio"; +"Back" = "Back"; +"Can't create %1$@" = "Can't create %1$@"; +"Can't open the index" = "Can't open the index"; +"Can't save the model: %1$@" = "Can't save the model: %1$@"; +"Can't start indexing" = "Can't start indexing"; +"Cancel" = "Cancel"; +"Check Now" = "Check Now"; +"Check for Updates…" = "Check for Updates…"; +"Check for updates automatically" = "Check for updates automatically"; +"Check the folders to add" = "Check the folders to add"; +"Check the folders with your code" = "Check the folders with your code"; +"Checking folders…" = "Checking folders…"; +"Checking the download…" = "Checking the download…"; +"Checking the model download…" = "Checking the model download…"; +"Choose Another Folder…" = "Choose Another Folder…"; +"Choose Code Folders…" = "Choose Code Folders…"; +"Choose Folders to Search…" = "Choose Folders to Search…"; +"Choose Folders…" = "Choose Folders…"; +"Choose among its subfolders" = "Choose among its subfolders"; +"Choose at least one folder" = "Choose at least one folder"; +"Choose folders for DigUp to search." = "Choose folders for DigUp to search."; +"Choose folders to search" = "Choose folders to search"; +"Clear" = "Clear"; +"Click to change" = "Click to change"; +"Click to set a shortcut" = "Click to set a shortcut"; +"Close" = "Close"; +"Code" = "Code"; +"Code Folders" = "Code Folders"; +"Code folders" = "Code folders"; +"Code index" = "Code index"; +"Code is read once the model has downloaded" = "Code is read once the model has downloaded"; +"Code search is off" = "Code search is off"; +"Code search reads source files, notebooks and a repo's own docs. It skips what git ignores, vendored " = "Code search reads source files, notebooks and a repo's own docs. It skips what git ignores, vendored "; +"Code, %1$@, %2$@" = "Code, %1$@, %2$@"; +"Code, keys, certificates, hidden folders, app bundles and caches are always skipped." = "Code, keys, certificates, hidden folders, app bundles and caches are always skipped."; +"Continue" = "Continue"; +"Copied %1$@" = "Copied %1$@"; +"Copy" = "Copy"; +"Copy Path" = "Copy Path"; +"Copy the file, to paste anywhere" = "Copy the file, to paste anywhere"; +"Couldn't look inside it; it can still be searched." = "Couldn't look inside it; it can still be searched."; +"Cut" = "Cut"; +"Describe it. Dig it up." = "Describe it. Dig it up."; +"Describe it: “payment error screenshot”, “zebra in a video”…" = "Describe it: “payment error screenshot”, “zebra in a video”…"; +"Describe it: “receipt from the hardware store”, “zebra in a video”…" = "Describe it: “receipt from the hardware store”, “zebra in a video”…"; +"Describe it: “zebra in a video”, “code: retry with backoff”…" = "Describe it: “zebra in a video”, “code: retry with backoff”…"; +"Describe the code: “retry a request with backoff”, a name in it…" = "Describe the code: “retry a request with backoff”, a name in it…"; +"DigUp %1$@ is available" = "DigUp %1$@ is available"; +"DigUp can find code by what it does, in the folders you pick. Code gets an index of its own " = "DigUp can find code by what it does, in the folders you pick. Code gets an index of its own "; +"DigUp can find code by what it does, in the folders you pick. Code gets an index of its own and " = "DigUp can find code by what it does, in the folders you pick. Code gets an index of its own and "; +"DigUp can find code by what it does: type code: in the search panel, as in " = "DigUp can find code by what it does: type code: in the search panel, as in "; +"DigUp is reading your folders%1$@, newest files first. You can search right away; " = "DigUp is reading your folders%1$@, newest files first. You can search right away; "; +"DigUp lives in the menu bar, which shows how indexing is going." = "DigUp lives in the menu bar, which shows how indexing is going."; +"DigUp only looks where you tell it to." = "DigUp only looks where you tell it to."; +"DigUp only reads files: it never changes, moves or uploads them." = "DigUp only reads files: it never changes, moves or uploads them."; +"DigUp only reads pictures, PDFs, documents (txt, md, rtf, docx, odt, html), audio and video here; " = "DigUp only reads pictures, PDFs, documents (txt, md, rtf, docx, odt, html), audio and video here; "; +"DigUp only searches the folders you pick." = "DigUp only searches the folders you pick."; +"DigUp reads a new folder once, then keeps up with what changes in it. " = "DigUp reads a new folder once, then keeps up with what changes in it. "; +"DigUp reads each once, then what changes. Code is searched on its own: type " = "DigUp reads each once, then what changes. Code is searched on its own: type "; +"DigUp reads the folders you choose and keeps what it learns in its index, on this Mac. " = "DigUp reads the folders you choose and keeps what it learns in its index, on this Mac. "; +"DigUp starts reading your folders%1$@ once the model has downloaded " = "DigUp starts reading your folders%1$@ once the model has downloaded "; +"Document" = "Document"; +"Documents" = "Documents"; +"Done" = "Done"; +"Download" = "Download"; +"Download the Model" = "Download the Model"; +"Downloaded" = "Downloaded"; +"Downloading model · %1$@%%" = "Downloading model · %1$@%%"; +"Downloading the model · %1$@" = "Downloading the model · %1$@"; +"Downloading the model · %1$@%%" = "Downloading the model · %1$@%%"; +"Edit" = "Edit"; +"EmbeddingGemma 2 by Google (Apache 2.0), run by llama.cpp (MIT)." = "EmbeddingGemma 2 by Google (Apache 2.0), run by llama.cpp (MIT)."; +"Esc cancels" = "Esc cancels"; +"Everything is indexed and searched on this Mac. DigUp downloads its AI model (865 MB) once; " = "Everything is indexed and searched on this Mac. DigUp downloads its AI model (865 MB) once; "; +"Extensions" = "Extensions"; +"File" = "File"; +"File types" = "File types"; +"Find code by what it does, or by a name in it: type code: in the search panel, as in " = "Find code by what it does, or by a name in it: type code: in the search panel, as in "; +"Finder's search window uses %1$@" = "Finder's search window uses %1$@"; +"First pass: about %1$@" = "First pass: about %1$@"; +"Folders" = "Folders"; +"Folders and indexing" = "Folders and indexing"; +"Folders to search" = "Folders to search"; +"General" = "General"; +"Get Started" = "Get Started"; +"Has your words" = "Has your words"; +"Hide DigUp" = "Hide DigUp"; +"Hide Preview" = "Hide Preview"; +"Hide its subfolders" = "Hide its subfolders"; +"Hide the subfolders of %1$@" = "Hide the subfolders of %1$@"; +"How long reading this code takes, once, and about how much room its index needs. After " = "How long reading this code takes, once, and about how much room its index needs. After "; +"Hugging Face answered %1$@" = "Hugging Face answered %1$@"; +"IN THE SEARCH PANEL" = "IN THE SEARCH PANEL"; +"Image" = "Image"; +"Images" = "Images"; +"In " = "In "; +"Index" = "Index"; +"Index on battery power" = "Index on battery power"; +"Indexing %1$@%%" = "Indexing %1$@%%"; +"Indexing %1$@%% · %2$@ of %3$@" = "Indexing %1$@%% · %2$@ of %3$@"; +"Indexing code %1$@%% · %2$@ of %3$@" = "Indexing code %1$@%% · %2$@ of %3$@"; +"Indexing paused" = "Indexing paused"; +"Indexing stopped" = "Indexing stopped"; +"It goes online only to download its model, once, and to check for updates (General), " = "It goes online only to download its model, once, and to check for updates (General), "; +"It skips what git ignores, vendored and generated code, data files, and anything that looks " = "It skips what git ignores, vendored and generated code, data files, and anything that looks "; +"Its %1$@ code files leave the code index." = "Its %1$@ code files leave the code index."; +"Its %1$@ files leave the index. Adding it back later indexes them " = "Its %1$@ files leave the index. Adding it back later indexes them "; +"Kept for when it's back" = "Kept for when it's back"; +"Location" = "Location"; +"Looking at the folders…" = "Looking at the folders…"; +"Looking at your code…" = "Looking at your code…"; +"Looking at your folders…" = "Looking at your folders…"; +"Low Power Mode" = "Low Power Mode"; +"Mac is warm" = "Mac is warm"; +"Matches %1$@" = "Matches %1$@"; +"Matches its name" = "Matches its name"; +"Matches its text" = "Matches its text"; +"Matches the frame at %1$@" = "Matches the frame at %1$@"; +"Matches the picture" = "Matches the picture"; +"Matches the sound at %1$@" = "Matches the sound at %1$@"; +"Matches the text in it" = "Matches the text in it"; +"Matches the text on %1$@" = "Matches the text on %1$@"; +"Matches what's heard at %1$@" = "Matches what's heard at %1$@"; +"Matches what's on screen" = "Matches what's on screen"; +"Matching words only until the model has downloaded (%1$@%%)" = "Matching words only until the model has downloaded (%1$@%%)"; +"Matching words only: the model isn't downloaded" = "Matching words only: the model isn't downloaded"; +"Matching words; meaning search is warming up…" = "Matching words; meaning search is warming up…"; +"Matching words…" = "Matching words…"; +"Minimize" = "Minimize"; +"Model" = "Model"; +"Model download: waiting for the network" = "Model download: waiting for the network"; +"Model not downloaded" = "Model not downloaded"; +"Model ready" = "Model ready"; +"Moments inside audio and video" = "Moments inside audio and video"; +"Needs %1$@ free on this disk" = "Needs %1$@ free on this disk"; +"New Search" = "New Search"; +"No access" = "No access"; +"No code folders yet. Add the folders that hold your projects." = "No code folders yet. Add the folders that hold your projects."; +"No code here" = "No code here"; +"No folders chosen yet" = "No folders chosen yet"; +"No folders yet. Add the ones DigUp should search." = "No folders yet. Add the ones DigUp should search."; +"No good matches" = "No good matches"; +"No good matches for “%1$@”" = "No good matches for “%1$@”"; +"No good matches in your code" = "No good matches in your code"; +"None. Code projects, app bundles and caches are skipped anyway (code has its own tab)." = "None. Code projects, app bundles and caches are skipped anyway (code has its own tab)."; +"Not connected" = "Not connected"; +"Not downloaded yet" = "Not downloaded yet"; +"Nothing to search here yet" = "Nothing to search here yet"; +"Nothing to search in the subfolders checked" = "Nothing to search in the subfolders checked"; +"Off unless you turn it on." = "Off unless you turn it on."; +"Once a day DigUp asks GitHub for its newest version; nothing about your files is sent. " = "Once a day DigUp asks GitHub for its newest version; nothing about your files is sent. "; +"Open" = "Open"; +"Open DigUp" = "Open DigUp"; +"Open DigUp from anywhere" = "Open DigUp from anywhere"; +"Open DigUp when you log in" = "Open DigUp when you log in"; +"Open a folder to pick some of its subfolders." = "Open a folder to pick some of its subfolders."; +"Open at %1$@" = "Open at %1$@"; +"Open code in" = "Open code in"; +"Open in %1$@" = "Open in %1$@"; +"Open the file" = "Open the file"; +"Open them in the DigUp window (⌘O shows everything)" = "Open them in the DigUp window (⌘O shows everything)"; +"Opens the search panel from any app." = "Opens the search panel from any app."; +"PDFs" = "PDFs"; +"PDFs and documents" = "PDFs and documents"; +"Part %1$@ of %2$@" = "Part %1$@ of %2$@"; +"Paste" = "Paste"; +"Pause" = "Pause"; +"Pause Indexing" = "Pause Indexing"; +"Paused · %1$@" = "Paused · %1$@"; +"Paused · %1$@ code files waiting" = "Paused · %1$@ code files waiting"; +"Pick folders, or open one to pick some of its subfolders. You can change this " = "Pick folders, or open one to pick some of its subfolders. You can change this "; +"Pick the folders it should search." = "Pick the folders it should search."; +"Pick the folders that hold your projects; open one to pick among its repos. " = "Pick the folders that hold your projects; open one to pick among its repos. "; +"Play" = "Play"; +"Play from %1$@" = "Play from %1$@"; +"Press the new shortcut…" = "Press the new shortcut…"; +"Press the shortcut in any app, describe what you're looking for, and press Return." = "Press the shortcut in any app, describe what you're looking for, and press Return."; +"Privacy" = "Privacy"; +"Quick Look, after arrowing to a result" = "Quick Look, after arrowing to a result"; +"Quit DigUp" = "Quit DigUp"; +"Reading the rest of %1$@ · %2$@%%" = "Reading the rest of %1$@ · %2$@%%"; +"Ready" = "Ready"; +"Recording a new shortcut" = "Recording a new shortcut"; +"Redo" = "Redo"; +"Remove" = "Remove"; +"Remove %1$@" = "Remove %1$@"; +"Resume" = "Resume"; +"Resume Indexing" = "Resume Indexing"; +"Say what the code does, or type a name that's in it." = "Say what the code does, or type a name that's in it."; +"Screenshot" = "Screenshot"; +"Screenshots" = "Screenshots"; +"Screenshots and photos" = "Screenshots and photos"; +"Screenshots use %1$@" = "Screenshots use %1$@"; +"Search" = "Search"; +"Search for “%1$@”" = "Search for “%1$@”"; +"Search my code" = "Search my code"; +"Search panel open" = "Search panel open"; +"Search shortcut" = "Search shortcut"; +"Search this folder again" = "Search this folder again"; +"Search your code" = "Search your code"; +"Search your code too?" = "Search your code too?"; +"Searching your code" = "Searching your code"; +"Searching…" = "Searching…"; +"Search…" = "Search…"; +"See what's new and update" = "See what's new and update"; +"Select All" = "Select All"; +"Settings (⌘,)" = "Settings (⌘,)"; +"Settings…" = "Settings…"; +"Shortcut %1$@, click to change" = "Shortcut %1$@, click to change"; +"Show %1$@ more" = "Show %1$@ more"; +"Show Preview" = "Show Preview"; +"Show all %1$@ %2$@…" = "Show all %1$@ %2$@…"; +"Show in Finder" = "Show in Finder"; +"Show it in Finder" = "Show it in Finder"; +"Show the subfolders of %1$@" = "Show the subfolders of %1$@"; +"Size on disk" = "Size on disk"; +"Skip" = "Skip"; +"Skip a Folder…" = "Skip a Folder…"; +"Skip some folders or file types" = "Skip some folders or file types"; +"Skip these file types" = "Skip these file types"; +"Skip these folders" = "Skip these folders"; +"So the shortcut always works." = "So the shortcut always works."; +"Spotlight uses %1$@" = "Spotlight uses %1$@"; +"Starting…" = "Starting…"; +"Status" = "Status"; +"Step %1$@ of %2$@" = "Step %1$@ of %2$@"; +"Still indexing (%1$@%%): some files aren't in yet" = "Still indexing (%1$@%%): some files aren't in yet"; +"Still reading code (%1$@%%): some isn't in yet" = "Still reading code (%1$@%%): some isn't in yet"; +"Stop searching the code in “%1$@”?" = "Stop searching the code in “%1$@”?"; +"Stop searching this folder" = "Stop searching this folder"; +"Stop searching “%1$@”?" = "Stop searching “%1$@”?"; +"Storage" = "Storage"; +"Switching input sources uses %1$@" = "Switching input sources uses %1$@"; +"TRY" = "TRY"; +"That's it. It works from any app." = "That's it. It works from any app."; +"The code index (%1$@) is deleted. Turning it on again reads the code again." = "The code index (%1$@) is deleted. Turning it on again reads the code again."; +"The code index keeps your code's text on this Mac, as the main index keeps your documents'. " = "The code index keeps your code's text on this Mac, as the main index keeps your documents'. "; +"The download couldn't resume" = "The download couldn't resume"; +"The download stopped early" = "The download stopped early"; +"The download was damaged twice. Try again later." = "The download was damaged twice. Try again later."; +"The emoji picker uses %1$@" = "The emoji picker uses %1$@"; +"The file's default app" = "The file's default app"; +"The first pass takes this long. Long PDFs and documents get their first pages in it, " = "The first pass takes this long. Long PDFs and documents get their first pages in it, "; +"The model isn't downloaded yet" = "The model isn't downloaded yet"; +"They match much less well than the results above. Press Return to " = "They match much less well than the results above. Press Return to "; +"Try Again" = "Try Again"; +"Try describing what's in it: what you'd see or hear, or words you remember." = "Try describing what's in it: what you'd see or hear, or words you remember."; +"Try it now: press %1$@." = "Try it now: press %1$@."; +"Try saying what the code does, or a name that's in it." = "Try saying what the code does, or a name that's in it."; +"Try what you'd see or hear in it, or words you remember from it." = "Try what you'd see or hear in it, or words you remember from it."; +"Turn Off" = "Turn Off"; +"Turn Off Code Search…" = "Turn Off Code Search…"; +"Turn off code search?" = "Turn off code search?"; +"Type what you remember in the search field above: what's in it, what it looks or sounds like, or " = "Type what you remember in the search field above: what's in it, what it looks or sounds like, or "; +"Under a second" = "Under a second"; +"Undo" = "Undo"; +"Update to DigUp %1$@…" = "Update to DigUp %1$@…"; +"Updates install with one click." = "Updates install with one click."; +"Use ⌘, ⌥ or ⌃ with a key" = "Use ⌘, ⌥ or ⌃ with a key"; +"Video" = "Video"; +"Waiting for a network connection" = "Waiting for a network connection"; +"Waiting for the network…" = "Waiting for the network…"; +"Weaker matches" = "Weaker matches"; +"Welcome to DigUp" = "Welcome to DigUp"; +"What should DigUp search?" = "What should DigUp search?"; +"When it's off, a folder's first pass waits for a charger. New files always go in right away." = "When it's off, a folder's first pass waits for a charger. New files always go in right away."; +"Window" = "Window"; +"Words only: meaning search isn't available" = "Words only: meaning search isn't available"; +"You're all set" = "You're all set"; +"a dog on the beach" = "a dog on the beach"; +"a page" = "a page"; +"a screenshot of an error message" = "a screenshot of an error message"; +"about %1$@" = "about %1$@"; +"about %1$@ left" = "about %1$@ left"; +"after that it works offline, and nothing you index ever leaves your Mac." = "after that it works offline, and nothing you index ever leaves your Mac."; +"afterwards (about %1$@ more)." = "afterwards (about %1$@ more)."; +"again." = "again."; +"and generated code, data files, and anything that looks like a key or a password." = "and generated code, data files, and anything that looks like a key or a password."; +"and never shows up in other searches." = "and never shows up in other searches."; +"and the rest afterwards, once everything else is in." = "and the rest afterwards, once everything else is in."; +"anytime in Settings." = "anytime in Settings."; +"audio file" = "audio file"; +"audio files" = "audio files"; +"cell %1$@" = "cell %1$@"; +"cells %1$@–%2$@" = "cells %1$@–%2$@"; +"code file" = "code file"; +"code files" = "code files"; +"code is read only in the code folders (the Code tab). Keys, certificates and password files are " = "code is read only in the code folders (the Code tab). Keys, certificates and password files are "; +"code: in the search panel." = "code: in the search panel."; +"code: parse the command line options" = "code: parse the command line options"; +"code: retry a request with backoff" = "code: retry a request with backoff"; +"code: where the settings are saved" = "code: where the settings are saved"; +"document" = "document"; +"documents" = "documents"; +"e.g. heic, txt" = "e.g. heic, txt"; +"fold them away." = "fold them away."; +"image" = "image"; +"images" = "images"; +"in %1$@" = "in %1$@"; +"less than a minute left" = "less than a minute left"; +"like a key or a password." = "like a key or a password."; +"line %1$@" = "line %1$@"; +"lines %1$@–%2$@" = "lines %1$@–%2$@"; +"long files" = "long files"; +"macOS isn't letting DigUp read it." = "macOS isn't letting DigUp read it."; +"macOS uses %1$@ for one of its shortcuts" = "macOS uses %1$@ for one of its shortcuts"; +"macOS will ask before DigUp can read folders like Desktop, Documents and Downloads. " = "macOS will ask before DigUp can read folders like Desktop, Documents and Downloads. "; +"never read, nor are hidden folders like ~/.ssh." = "never read, nor are hidden folders like ~/.ssh."; +"never shows up in other searches." = "never shows up in other searches."; +"on battery" = "on battery"; +"opens DigUp from any app" = "opens DigUp from any app"; +"other searches." = "other searches."; +"p. %1$@" = "p. %1$@"; +"page %1$@" = "page %1$@"; +"partly read so far" = "partly read so far"; +"results fill in as it goes." = "results fill in as it goes."; +"screenshot" = "screenshot"; +"screenshots" = "screenshots"; +"searches. You can turn it on later in Settings → Code." = "searches. You can turn it on later in Settings → Code."; +"show them." = "show them."; +"slides about the budget" = "slides about the budget"; +"someone laughing" = "someone laughing"; +"that, only what changes is read again." = "that, only what changes is read again."; +"under a second" = "under a second"; +"video" = "video"; +"videos" = "videos"; +"which sends nothing about your files." = "which sends nothing about your files."; +"whiteboard notes" = "whiteboard notes"; +"words from it." = "words from it."; +"“%1$@” is searched already" = "“%1$@” is searched already"; +"“%1$@” isn't searched anyway." = "“%1$@” isn't searched anyway."; +"“code: retry with backoff”. Code has an index of its own, and it never shows up in " = "“code: retry with backoff”. Code has an index of its own, and it never shows up in "; +"“code: retry with backoff”. It gets an index of its own and never shows up in other " = "“code: retry with backoff”. It gets an index of its own and never shows up in other "; +"“that payment error”, “the zebra”, “whiteboard from Tuesday”" = "“that payment error”, “the zebra”, “whiteboard from Tuesday”"; +"“the clause about pets in the lease”, “flight receipt”" = "“the clause about pets in the lease”, “flight receipt”"; +"“where they talk about sleep”, “the part with the drums”" = "“where they talk about sleep”, “the part with the drums”"; +"↩ %1$@ ⌘↩ Reveal ␣ Quick Look%2$@ ⌘C Copy" = "↩ %1$@ ⌘↩ Reveal ␣ Quick Look%2$@ ⌘C Copy"; +"↩ %1$@%2$@ ⌘↩ Reveal ␣ Quick Look ⌘C Copy" = "↩ %1$@%2$@ ⌘↩ Reveal ␣ Quick Look ⌘C Copy"; +"⚠︎ The model download stopped" = "⚠︎ The model download stopped"; +"the code" = "the code"; +"none" = "none"; diff --git a/Sources/DigUpLocalization/Resources/zh-Hant.lproj/InfoPlist.strings b/Sources/DigUpLocalization/Resources/zh-Hant.lproj/InfoPlist.strings new file mode 100644 index 00000000..9a15a5be --- /dev/null +++ b/Sources/DigUpLocalization/Resources/zh-Hant.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSAppleEventsUsageDescription" = "DigUp 會請 QuickTime Player 開啟錄音或影片,並跳到符合搜尋內容的時間點。"; +"NSDesktopFolderUsageDescription" = "DigUp 會為你選擇的資料夾建立索引,讓你依內容描述搜尋檔案。所有內容都留在這台 Mac 上。"; +"NSDocumentsFolderUsageDescription" = "DigUp 會為你選擇的資料夾建立索引,讓你依內容描述搜尋檔案。所有內容都留在這台 Mac 上。"; +"NSDownloadsFolderUsageDescription" = "DigUp 會為你選擇的資料夾建立索引,讓你依內容描述搜尋檔案。所有內容都留在這台 Mac 上。"; +"NSRemovableVolumesUsageDescription" = "DigUp 會為你選擇的資料夾建立索引,讓你依內容描述搜尋檔案。所有內容都留在這台 Mac 上。"; diff --git a/Sources/DigUpLocalization/Resources/zh-Hant.lproj/Localizable.strings b/Sources/DigUpLocalization/Resources/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..0aabda3a --- /dev/null +++ b/Sources/DigUpLocalization/Resources/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,405 @@ +/* DigUp interface strings. Positional %@ arguments contain already formatted display values. */ +" (about %1$@ on this Mac)" = "(這台 Mac 約需 %1$@)"; +" (no access)" = "(沒有存取權限)"; +" (not connected)" = "(未連接)"; +" + %1$@ later" = ",稍後另需 %1$@"; +" Long PDFs and documents get their first pages in that time, and the rest " = " 較長的 PDF 與文件會先讀取前幾頁,其餘內容"; +" at %1$@" = ",%1$@"; +" at line %1$@" = ",第 %1$@ 行"; +" in your code" = ",位於程式碼中"; +" of " = "/"; +" · %1$@ long %2$@ to finish" = " · %1$@ 個較長檔案尚未完成"; +" · %1$@ waiting" = " · %1$@ 個待處理"; +" · %1$@ weaker" = " · %1$@ 筆較不相關"; +" · model %1$@" = " · 模型:%1$@"; +" · paused" = " · 已暫停"; +"%.1f h" = "%.1f 小時"; +"%1$@ %2$@ indexed" = "%1$@ 個檔案已建立索引"; +"%1$@ %2$@ indexed yet" = "%1$@ 個檔案尚未建立索引"; +"%1$@ %2$@ waiting" = "%1$@ 個檔案待處理"; +"%1$@ cells" = "%1$@ 個儲存格"; +"%1$@ code %2$@ indexed" = "%1$@ 個程式碼檔案已建立索引"; +"%1$@ code files waiting · %2$@" = "%1$@ 個程式碼檔案待處理 · %2$@"; +"%1$@ couldn't be read" = "%1$@ 個無法讀取"; +"%1$@ files to search in %2$@" = "%2$@ 中有 %1$@ 個檔案可搜尋"; +"%1$@ folders" = "%1$@ 個資料夾"; +"%1$@ lines" = "%1$@ 行"; +"%1$@ long %2$@" = "%1$@ 個較長檔案"; +"%1$@ long %2$@ to finish" = "%1$@ 個較長檔案尚未完成"; +"%1$@ min" = "%1$@ 分鐘"; +"%1$@ of %2$@ files searchable" = "%2$@ 個檔案中,%1$@ 個可搜尋"; +"%1$@ of %2$@ indexed" = "%2$@ 個檔案中,%1$@ 個已建立索引"; +"%1$@ pages" = "%1$@ 頁"; +"%1$@ results" = "%1$@ 筆結果"; +"%1$@ s" = "%1$@ 秒"; +"%1$@ skipped" = "%1$@ 個已略過"; +"%1$@ to be indexed" = "%1$@,等待建立索引"; +"%1$@ waiting" = "%1$@ 個待處理"; +"%1$@ weaker %2$@" = "%1$@ 筆較不相關的結果"; +"%1$@ – playing from " = "%1$@ – 播放起點:"; +"(%1$@). Until then, search finds files by name." = "(%1$@)。在此之前,可依檔名搜尋。"; +"+ %1$@ later" = "稍後另需 %1$@"; +", %1$@ read so far" = ",已讀取 %1$@ 頁"; +", and %1$@ more for long files" = ",較長檔案另需 %1$@"; +", as part of “%1$@”." = ",包含於「%1$@」。"; +"1 cell" = "1 個儲存格"; +"1 line" = "1 行"; +"1 page" = "1 頁"; +"1 result" = "1 筆結果"; +"< 1 s" = "不到 1 秒"; +"A result opens there at the line that matched. Space shows the whole file in Quick Look." = "搜尋結果會在所選 App 中開啟並跳到符合的行。按空白鍵可用「快速查看」預覽整份檔案。"; +"About %1$@" = "約 %1$@"; +"About DigUp" = "關於 DigUp"; +"Add" = "加入"; +"Add Folders" = "加入資料夾"; +"Add Folders…" = "加入資料夾…"; +"Add Folder…" = "加入資料夾…"; +"Add folders to search" = "加入搜尋資料夾"; +"Adds %1$@ of indexing" = "建立索引另需 %1$@"; +"Adds %1$@ of indexing and %2$@" = "建立索引另需 %1$@,使用約 %2$@"; +"All" = "全部"; +"Allow in Settings…" = "在系統設定中允許…"; +"Another app is using %1$@" = "另一個 App 正在使用 %1$@"; +"At %1$@ of %2$@" = "%1$@,總長 %2$@"; +"Audio" = "音訊"; +"Back" = "上一步"; +"Can't create %1$@" = "無法建立 %1$@"; +"Can't open the index" = "無法開啟索引"; +"Can't save the model: %1$@" = "無法儲存模型:%1$@"; +"Can't start indexing" = "無法開始建立索引"; +"Cancel" = "取消"; +"Check Now" = "立即檢查"; +"Check for Updates…" = "檢查更新…"; +"Check for updates automatically" = "自動檢查更新"; +"Check the folders to add" = "勾選要加入的資料夾"; +"Check the folders with your code" = "勾選存放程式碼的資料夾"; +"Checking folders…" = "檢查資料夾中…"; +"Checking the download…" = "檢查下載內容中…"; +"Checking the model download…" = "檢查模型下載中…"; +"Choose Another Folder…" = "選擇其他資料夾…"; +"Choose Code Folders…" = "選擇程式碼資料夾…"; +"Choose Folders to Search…" = "選擇搜尋資料夾…"; +"Choose Folders…" = "選擇資料夾…"; +"Choose among its subfolders" = "選擇子資料夾"; +"Choose at least one folder" = "請至少選擇一個資料夾"; +"Choose folders for DigUp to search." = "選擇要讓 DigUp 搜尋的資料夾。"; +"Choose folders to search" = "選擇搜尋資料夾"; +"Clear" = "清除"; +"Click to change" = "按一下更改"; +"Click to set a shortcut" = "按一下設定快捷鍵"; +"Close" = "關閉"; +"Code" = "程式碼"; +"Code Folders" = "程式碼資料夾"; +"Code folders" = "程式碼資料夾"; +"Code index" = "程式碼索引"; +"Code is read once the model has downloaded" = "模型下載完成後會讀取程式碼"; +"Code search is off" = "程式碼搜尋已關閉"; +"Code search reads source files, notebooks and a repo's own docs. It skips what git ignores, vendored " = "程式碼搜尋會讀取原始碼、筆記本與儲存庫的文件。它會略過 git 忽略的內容、第三方"; +"Code, %1$@, %2$@" = "程式碼,%1$@,%2$@"; +"Code, keys, certificates, hidden folders, app bundles and caches are always skipped." = "程式碼、金鑰、憑證、隱藏資料夾、App 與快取一律略過。"; +"Continue" = "繼續"; +"Copied %1$@" = "已複製 %1$@"; +"Copy" = "複製"; +"Copy Path" = "複製路徑"; +"Copy the file, to paste anywhere" = "複製檔案,可貼到其他地方"; +"Couldn't look inside it; it can still be searched." = "無法檢視內部內容,仍可納入搜尋。"; +"Cut" = "剪下"; +"Describe it. Dig it up." = "說出印象,找出檔案。"; +"Describe it: “payment error screenshot”, “zebra in a video”…" = "描述你要找的內容,例如「付款錯誤截圖」、「影片中的斑馬」…"; +"Describe it: “receipt from the hardware store”, “zebra in a video”…" = "描述你要找的內容,例如「五金行收據」、「影片中的斑馬」…"; +"Describe it: “zebra in a video”, “code: retry with backoff”…" = "描述你要找的內容,例如「影片中的斑馬」、「code: 逐次延長等待時間後重試」…"; +"Describe the code: “retry a request with backoff”, a name in it…" = "描述程式碼,例如「逐次延長等待時間後重試請求」,或輸入其中的名稱…"; +"DigUp %1$@ is available" = "DigUp %1$@ 已推出"; +"DigUp can find code by what it does, in the folders you pick. Code gets an index of its own " = "DigUp 可依功能描述尋找所選資料夾中的程式碼。程式碼使用獨立索引,"; +"DigUp can find code by what it does, in the folders you pick. Code gets an index of its own and " = "DigUp 可依功能描述尋找所選資料夾中的程式碼。程式碼使用獨立索引,"; +"DigUp can find code by what it does: type code: in the search panel, as in " = "DigUp 可依功能描述尋找程式碼:在搜尋面板輸入 code:,例如"; +"DigUp is reading your folders%1$@, newest files first. You can search right away; " = "DigUp 正在讀取資料夾%1$@,優先處理最新檔案。你可以立即搜尋;"; +"DigUp lives in the menu bar, which shows how indexing is going." = "DigUp 常駐選單列,並顯示索引建立進度。"; +"DigUp only looks where you tell it to." = "DigUp 只搜尋你指定的資料夾。"; +"DigUp only reads files: it never changes, moves or uploads them." = "DigUp 只讀取檔案,不會修改、移動或上傳。"; +"DigUp only reads pictures, PDFs, documents (txt, md, rtf, docx, odt, html), audio and video here; " = "DigUp 在這裡只讀取圖片、PDF、文件(txt、md、rtf、docx、odt、html)、音訊與影片;"; +"DigUp only searches the folders you pick." = "DigUp 只搜尋你選擇的資料夾。"; +"DigUp reads a new folder once, then keeps up with what changes in it. " = "DigUp 首次讀取新資料夾的完整內容,之後持續處理其中的變更。"; +"DigUp reads each once, then what changes. Code is searched on its own: type " = "DigUp 首次讀取完整內容,之後只處理變更。程式碼獨立搜尋:請在搜尋面板輸入"; +"DigUp reads the folders you choose and keeps what it learns in its index, on this Mac. " = "DigUp 讀取你選擇的資料夾,並將索引儲存在這台 Mac 上。"; +"DigUp starts reading your folders%1$@ once the model has downloaded " = "模型下載完成後,DigUp 會開始讀取資料夾%1$@"; +"Document" = "文件"; +"Documents" = "文件"; +"Done" = "完成"; +"Download" = "下載"; +"Download the Model" = "下載模型"; +"Downloaded" = "已下載"; +"Downloading model · %1$@%%" = "下載模型中 · %1$@%%"; +"Downloading the model · %1$@" = "下載模型中 · %1$@"; +"Downloading the model · %1$@%%" = "下載模型中 · %1$@%%"; +"Edit" = "編輯"; +"EmbeddingGemma 2 by Google (Apache 2.0), run by llama.cpp (MIT)." = "Google 的 EmbeddingGemma 2(Apache 2.0),由 llama.cpp(MIT)執行。"; +"Esc cancels" = "按 Esc 取消"; +"Everything is indexed and searched on this Mac. DigUp downloads its AI model (865 MB) once; " = "所有索引與搜尋都在這台 Mac 上進行。DigUp 只需下載一次 AI 模型(865 MB);"; +"Extensions" = "副檔名"; +"File" = "檔案"; +"File types" = "檔案類型"; +"Find code by what it does, or by a name in it: type code: in the search panel, as in " = "依功能描述或名稱尋找程式碼:在搜尋面板輸入 code:,例如"; +"Finder's search window uses %1$@" = "Finder 搜尋視窗已使用 %1$@"; +"First pass: about %1$@" = "首次建立索引:約 %1$@"; +"Folders" = "資料夾"; +"Folders and indexing" = "資料夾與索引"; +"Folders to search" = "搜尋範圍"; +"General" = "一般"; +"Get Started" = "開始使用"; +"Has your words" = "包含搜尋文字"; +"Hide DigUp" = "隱藏 DigUp"; +"Hide Preview" = "隱藏預覽"; +"Hide its subfolders" = "隱藏子資料夾"; +"Hide the subfolders of %1$@" = "隱藏 %1$@ 的子資料夾"; +"How long reading this code takes, once, and about how much room its index needs. After " = "首次讀取程式碼所需的時間,以及索引的預估使用空間。"; +"Hugging Face answered %1$@" = "Hugging Face 回傳狀態碼 %1$@"; +"IN THE SEARCH PANEL" = "在搜尋面板中"; +"Image" = "圖片"; +"Images" = "圖片"; +"In " = "搜尋範圍:"; +"Index" = "索引"; +"Index on battery power" = "使用電池時建立索引"; +"Indexing %1$@%%" = "建立索引中 %1$@%%"; +"Indexing %1$@%% · %2$@ of %3$@" = "建立索引中 %1$@%% · %2$@/%3$@"; +"Indexing code %1$@%% · %2$@ of %3$@" = "建立程式碼索引中 %1$@%% · %2$@/%3$@"; +"Indexing paused" = "索引建立已暫停"; +"Indexing stopped" = "索引建立已停止"; +"It goes online only to download its model, once, and to check for updates (General), " = "只有首次下載模型與檢查更新時才會連線(可在「一般」中設定),"; +"It skips what git ignores, vendored and generated code, data files, and anything that looks " = "它會略過 git 忽略的檔案、第三方與自動產生的程式碼、資料檔,以及看起來"; +"Its %1$@ code files leave the code index." = "其中的 %1$@ 個程式碼檔案將移出索引。"; +"Its %1$@ files leave the index. Adding it back later indexes them " = "其中的 %1$@ 個檔案將移出索引。日後重新加入時,會"; +"Kept for when it's back" = "保留索引,等待重新連接"; +"Location" = "位置"; +"Looking at the folders…" = "檢查資料夾中…"; +"Looking at your code…" = "檢查程式碼中…"; +"Looking at your folders…" = "檢查資料夾中…"; +"Low Power Mode" = "低耗電模式"; +"Mac is warm" = "Mac 溫度偏高"; +"Matches %1$@" = "%1$@ 符合"; +"Matches its name" = "檔名符合"; +"Matches its text" = "文字內容符合"; +"Matches the frame at %1$@" = "%1$@ 的畫面符合"; +"Matches the picture" = "圖片內容符合"; +"Matches the sound at %1$@" = "%1$@ 的聲音符合"; +"Matches the text in it" = "其中的文字符合"; +"Matches the text on %1$@" = "%1$@ 的文字符合"; +"Matches what's heard at %1$@" = "%1$@ 的音訊內容符合"; +"Matches what's on screen" = "螢幕內容符合"; +"Matching words only until the model has downloaded (%1$@%%)" = "模型下載完成前僅比對文字(%1$@%%)"; +"Matching words only: the model isn't downloaded" = "僅比對文字:尚未下載模型"; +"Matching words; meaning search is warming up…" = "比對文字中;語意搜尋正在準備…"; +"Matching words…" = "比對文字中…"; +"Minimize" = "縮小"; +"Model" = "模型"; +"Model download: waiting for the network" = "模型下載:等待網路連線"; +"Model not downloaded" = "尚未下載模型"; +"Model ready" = "模型已就緒"; +"Moments inside audio and video" = "音訊與影片中的片段"; +"Needs %1$@ free on this disk" = "此磁碟需要 %1$@ 可用空間"; +"New Search" = "新增搜尋"; +"No access" = "沒有存取權限"; +"No code folders yet. Add the folders that hold your projects." = "尚未加入程式碼資料夾。請加入存放專案的資料夾。"; +"No code here" = "這裡沒有程式碼"; +"No folders chosen yet" = "尚未選擇資料夾"; +"No folders yet. Add the ones DigUp should search." = "尚未加入資料夾。請加入要讓 DigUp 搜尋的資料夾。"; +"No good matches" = "找不到相關結果"; +"No good matches for “%1$@”" = "找不到與「%1$@」相關的結果"; +"No good matches in your code" = "找不到相關的程式碼"; +"None. Code projects, app bundles and caches are skipped anyway (code has its own tab)." = "無。程式專案、App 與快取一律略過;程式碼請在專用分頁設定。"; +"Not connected" = "未連接"; +"Not downloaded yet" = "尚未下載"; +"Nothing to search here yet" = "目前沒有可搜尋的內容"; +"Nothing to search in the subfolders checked" = "勾選的子資料夾沒有可搜尋的內容"; +"Off unless you turn it on." = "需自行啟用,預設關閉。"; +"Once a day DigUp asks GitHub for its newest version; nothing about your files is sent. " = "DigUp 每天向 GitHub 檢查一次新版本,不會傳送你的檔案資訊。"; +"Open" = "開啟"; +"Open DigUp" = "開啟 DigUp"; +"Open DigUp from anywhere" = "隨時開啟 DigUp"; +"Open DigUp when you log in" = "登入時開啟 DigUp"; +"Open a folder to pick some of its subfolders." = "展開資料夾可選取部分子資料夾。"; +"Open at %1$@" = "從 %1$@ 開啟"; +"Open code in" = "開啟程式碼的 App"; +"Open in %1$@" = "在 %1$@ 中開啟"; +"Open the file" = "開啟檔案"; +"Open them in the DigUp window (⌘O shows everything)" = "在 DigUp 視窗中開啟(⌘O 顯示全部結果)"; +"Opens the search panel from any app." = "在任何 App 中開啟搜尋面板。"; +"PDFs" = "PDF"; +"PDFs and documents" = "PDF 與文件"; +"Part %1$@ of %2$@" = "第 %1$@ 部分,共 %2$@ 部分"; +"Paste" = "貼上"; +"Pause" = "暫停"; +"Pause Indexing" = "暫停建立索引"; +"Paused · %1$@" = "已暫停 · %1$@"; +"Paused · %1$@ code files waiting" = "已暫停 · %1$@ 個程式碼檔案待處理"; +"Pick folders, or open one to pick some of its subfolders. You can change this " = "選擇資料夾,也可展開後只選取部分子資料夾。你可以"; +"Pick the folders it should search." = "選擇要搜尋的資料夾。"; +"Pick the folders that hold your projects; open one to pick among its repos. " = "選擇存放專案的資料夾,也可展開後選取其中的儲存庫。"; +"Play" = "播放"; +"Play from %1$@" = "從 %1$@ 播放"; +"Press the new shortcut…" = "請按下新的快捷鍵…"; +"Press the shortcut in any app, describe what you're looking for, and press Return." = "在任何 App 中按下快捷鍵,描述要找的內容,再按 Return。"; +"Privacy" = "隱私"; +"Quick Look, after arrowing to a result" = "用方向鍵選取結果後,開啟「快速查看」"; +"Quit DigUp" = "結束 DigUp"; +"Reading the rest of %1$@ · %2$@%%" = "讀取 %1$@ 的剩餘內容 · %2$@%%"; +"Ready" = "已就緒"; +"Recording a new shortcut" = "等待輸入新快捷鍵"; +"Redo" = "重做"; +"Remove" = "移除"; +"Remove %1$@" = "移除 %1$@"; +"Resume" = "繼續"; +"Resume Indexing" = "繼續建立索引"; +"Say what the code does, or type a name that's in it." = "描述程式碼的功能,或輸入其中的名稱。"; +"Screenshot" = "截圖"; +"Screenshots" = "截圖"; +"Screenshots and photos" = "截圖與照片"; +"Screenshots use %1$@" = "螢幕截圖已使用 %1$@"; +"Search" = "搜尋"; +"Search for “%1$@”" = "搜尋「%1$@」"; +"Search my code" = "搜尋我的程式碼"; +"Search panel open" = "搜尋面板已開啟"; +"Search shortcut" = "搜尋快捷鍵"; +"Search this folder again" = "重新納入搜尋範圍"; +"Search your code" = "搜尋你的程式碼"; +"Search your code too?" = "也要搜尋程式碼嗎?"; +"Searching your code" = "搜尋程式碼中"; +"Searching…" = "搜尋中…"; +"Search…" = "搜尋…"; +"See what's new and update" = "查看新版內容並更新"; +"Select All" = "全選"; +"Settings (⌘,)" = "設定(⌘,)"; +"Settings…" = "設定…"; +"Shortcut %1$@, click to change" = "快捷鍵 %1$@,按一下更改"; +"Show %1$@ more" = "再顯示 %1$@ 個"; +"Show Preview" = "顯示預覽"; +"Show all %1$@ %2$@…" = "顯示全部 %1$@ 筆%1$@…"; +"Show in Finder" = "在 Finder 中顯示"; +"Show it in Finder" = "在 Finder 中顯示"; +"Show the subfolders of %1$@" = "顯示 %1$@ 的子資料夾"; +"Size on disk" = "磁碟使用量"; +"Skip" = "略過"; +"Skip a Folder…" = "略過資料夾…"; +"Skip some folders or file types" = "略過部分資料夾或檔案類型"; +"Skip these file types" = "略過這些檔案類型"; +"Skip these folders" = "略過這些資料夾"; +"So the shortcut always works." = "讓快捷鍵隨時可用。"; +"Spotlight uses %1$@" = "Spotlight 已使用 %1$@"; +"Starting…" = "啟動中…"; +"Status" = "狀態"; +"Step %1$@ of %2$@" = "第 %1$@ 步,共 %2$@ 步"; +"Still indexing (%1$@%%): some files aren't in yet" = "仍在建立索引(%1$@%%):部分檔案尚未納入"; +"Still reading code (%1$@%%): some isn't in yet" = "仍在讀取程式碼(%1$@%%):部分尚未納入"; +"Stop searching the code in “%1$@”?" = "要停止搜尋「%1$@」中的程式碼嗎?"; +"Stop searching this folder" = "停止搜尋此資料夾"; +"Stop searching “%1$@”?" = "要停止搜尋「%1$@」嗎?"; +"Storage" = "儲存空間"; +"Switching input sources uses %1$@" = "切換輸入法已使用 %1$@"; +"TRY" = "試試看"; +"That's it. It works from any app." = "就這麼簡單,在任何 App 中都能使用。"; +"The code index (%1$@) is deleted. Turning it on again reads the code again." = "將刪除程式碼索引(%1$@)。再次啟用時會重新讀取程式碼。"; +"The code index keeps your code's text on this Mac, as the main index keeps your documents'. " = "程式碼索引將程式文字儲存在這台 Mac 上,與文件索引相同。"; +"The download couldn't resume" = "無法續傳下載"; +"The download stopped early" = "下載提前中止"; +"The download was damaged twice. Try again later." = "下載內容連續兩次損毀,請稍後再試。"; +"The emoji picker uses %1$@" = "表情符號選擇器已使用 %1$@"; +"The file's default app" = "檔案的預設 App"; +"The first pass takes this long. Long PDFs and documents get their first pages in it, " = "首次建立索引所需的時間。較長的 PDF 與文件會先讀取前幾頁,"; +"The model isn't downloaded yet" = "尚未下載模型"; +"They match much less well than the results above. Press Return to " = "這些結果比上方結果的相關性低。按 Return 可"; +"Try Again" = "再試一次"; +"Try describing what's in it: what you'd see or hear, or words you remember." = "試著描述其中的畫面、聲音,或你記得的文字。"; +"Try it now: press %1$@." = "現在試試看:按下 %1$@。"; +"Try saying what the code does, or a name that's in it." = "試著描述程式碼的功能,或輸入其中的名稱。"; +"Try what you'd see or hear in it, or words you remember from it." = "試著描述其中的畫面、聲音,或你記得的文字。"; +"Turn Off" = "關閉"; +"Turn Off Code Search…" = "關閉程式碼搜尋…"; +"Turn off code search?" = "要關閉程式碼搜尋嗎?"; +"Type what you remember in the search field above: what's in it, what it looks or sounds like, or " = "在上方搜尋欄輸入你記得的內容:裡面有什麼、畫面或聲音,或是"; +"Under a second" = "不到一秒"; +"Undo" = "復原"; +"Update to DigUp %1$@…" = "更新至 DigUp %1$@…"; +"Updates install with one click." = "按一下即可安裝更新。"; +"Use ⌘, ⌥ or ⌃ with a key" = "請搭配 ⌘、⌥ 或 ⌃ 與其他按鍵"; +"Video" = "影片"; +"Waiting for a network connection" = "等待網路連線"; +"Waiting for the network…" = "等待網路連線…"; +"Weaker matches" = "較不相關的結果"; +"Welcome to DigUp" = "歡迎使用 DigUp"; +"What should DigUp search?" = "要讓 DigUp 搜尋哪些資料夾?"; +"When it's off, a folder's first pass waits for a charger. New files always go in right away." = "關閉時,首次建立資料夾索引會等到接上電源。新增檔案仍會立即納入索引。"; +"Window" = "視窗"; +"Words only: meaning search isn't available" = "僅比對文字:語意搜尋目前無法使用"; +"You're all set" = "設定完成"; +"a dog on the beach" = "沙灘上的狗"; +"a page" = "頁面"; +"a screenshot of an error message" = "錯誤訊息截圖"; +"about %1$@" = "約 %1$@"; +"about %1$@ left" = "約剩 %1$@"; +"after that it works offline, and nothing you index ever leaves your Mac." = "之後即可離線使用,索引內容不會離開你的 Mac。"; +"afterwards (about %1$@ more)." = "稍後再讀取(另需約 %1$@)。"; +"again." = "重新建立索引。"; +"and generated code, data files, and anything that looks like a key or a password." = "與自動產生的程式碼、資料檔,以及看起來像金鑰或密碼的內容。"; +"and never shows up in other searches." = "不會出現在其他搜尋結果中。"; +"and the rest afterwards, once everything else is in." = "待其他檔案完成後,再讀取剩餘內容。"; +"anytime in Settings." = "隨時在「設定」中調整。"; +"audio file" = "音訊檔案"; +"audio files" = "音訊檔案"; +"cell %1$@" = "第 %1$@ 個儲存格"; +"cells %1$@–%2$@" = "第 %1$@–%2$@ 個儲存格"; +"code file" = "程式碼檔案"; +"code files" = "程式碼檔案"; +"code is read only in the code folders (the Code tab). Keys, certificates and password files are " = "程式碼只在「程式碼」分頁所選的資料夾中讀取。金鑰、憑證與密碼檔案"; +"code: in the search panel." = "code:。"; +"code: parse the command line options" = "code: 解析命令列選項"; +"code: retry a request with backoff" = "code: 逐次延長等待時間後重試請求"; +"code: where the settings are saved" = "code: 設定儲存在哪裡"; +"document" = "文件"; +"documents" = "文件"; +"e.g. heic, txt" = "例如 heic、txt"; +"fold them away." = "收合。"; +"image" = "圖片"; +"images" = "圖片"; +"in %1$@" = "搜尋範圍:%1$@"; +"less than a minute left" = "剩餘不到一分鐘"; +"like a key or a password." = "像金鑰或密碼的內容。"; +"line %1$@" = "第 %1$@ 行"; +"lines %1$@–%2$@" = "第 %1$@–%2$@ 行"; +"long files" = "較長檔案"; +"macOS isn't letting DigUp read it." = "macOS 尚未允許 DigUp 讀取此資料夾。"; +"macOS uses %1$@ for one of its shortcuts" = "macOS 已將 %1$@ 用於系統快捷鍵"; +"macOS will ask before DigUp can read folders like Desktop, Documents and Downloads. " = "讀取「桌面」、「文件」、「下載項目」等資料夾前,macOS 會先詢問你的許可。"; +"never read, nor are hidden folders like ~/.ssh." = "以及 ~/.ssh 等隱藏資料夾一律不讀取。"; +"never shows up in other searches." = "不會出現在其他搜尋結果中。"; +"on battery" = "使用電池中"; +"opens DigUp from any app" = "在任何 App 中開啟 DigUp"; +"other searches." = "其他搜尋結果中。"; +"p. %1$@" = "第 %1$@ 頁"; +"page %1$@" = "第 %1$@ 頁"; +"partly read so far" = "已讀取部分內容"; +"results fill in as it goes." = "結果會隨進度逐漸增加。"; +"screenshot" = "截圖"; +"screenshots" = "截圖"; +"searches. You can turn it on later in Settings → Code." = "搜尋結果中。你也可以稍後在「設定 → 程式碼」中啟用。"; +"show them." = "展開。"; +"slides about the budget" = "預算相關簡報"; +"someone laughing" = "有人在笑"; +"that, only what changes is read again." = "之後只會重新讀取變更的內容。"; +"under a second" = "不到一秒"; +"video" = "影片"; +"videos" = "影片"; +"which sends nothing about your files." = "不會傳送你的檔案資訊。"; +"whiteboard notes" = "白板筆記"; +"words from it." = "其中的文字。"; +"“%1$@” is searched already" = "「%1$@」已在搜尋範圍中"; +"“%1$@” isn't searched anyway." = "「%1$@」原本就不在搜尋範圍中。"; +"“code: retry with backoff”. Code has an index of its own, and it never shows up in " = "「code: 逐次延長等待時間後重試」。程式碼使用獨立索引,不會出現在"; +"“code: retry with backoff”. It gets an index of its own and never shows up in other " = "「code: 逐次延長等待時間後重試」。它使用獨立索引,不會出現在其他"; +"“that payment error”, “the zebra”, “whiteboard from Tuesday”" = "「付款錯誤」、「斑馬」、「星期二的白板」"; +"“the clause about pets in the lease”, “flight receipt”" = "「租約裡的寵物條款」、「機票收據」"; +"“where they talk about sleep”, “the part with the drums”" = "「談到睡眠的地方」、「有鼓聲的片段」"; +"↩ %1$@ ⌘↩ Reveal ␣ Quick Look%2$@ ⌘C Copy" = "↩ %1$@ ⌘↩ 顯示位置 ␣ 快速查看%2$@ ⌘C 複製"; +"↩ %1$@%2$@ ⌘↩ Reveal ␣ Quick Look ⌘C Copy" = "↩ %1$@%2$@ ⌘↩ 顯示位置 ␣ 快速查看 ⌘C 複製"; +"⚠︎ The model download stopped" = "⚠︎ 模型下載已停止"; +"the code" = "程式碼"; +"none" = "未設定"; diff --git a/Tests/DigUpLocalizationTests/LocalizationTests.swift b/Tests/DigUpLocalizationTests/LocalizationTests.swift new file mode 100644 index 00000000..b02029bb --- /dev/null +++ b/Tests/DigUpLocalizationTests/LocalizationTests.swift @@ -0,0 +1,68 @@ +import Foundation +import Testing +@testable import DigUpLocalization + +struct LocalizationTests { + private func localizedBundle(_ language: String) throws -> Bundle { + let path = try #require(L10n.bundle.path(forResource: language, ofType: "lproj")) + return try #require(Bundle(path: path)) + } + + private func strings(_ language: String) throws -> [String: String] { + let bundle = try localizedBundle(language) + let url = try #require(bundle.url(forResource: "Localizable", withExtension: "strings")) + return try #require(PropertyListSerialization.propertyList(from: Data(contentsOf: url), format: nil) as? [String: String]) + } + + @Test func languagesHaveTheSameKeysAndEnglishFallsBackToItsKeys() throws { + let english = try strings("en") + let chinese = try strings("zh-Hant") + #expect(english.count > 350) + #expect(Set(english.keys) == Set(chinese.keys)) + for (key, value) in english { #expect(key == value) } + #expect(chinese.values.allSatisfy { !$0.isEmpty }) + #expect(L10n.text("missing.translation.key") == "missing.translation.key") + } + + @Test func translatedFormatsUseOnlyExistingArguments() throws { + let english = try strings("en") + let chinese = try strings("zh-Hant") + let pattern = try NSRegularExpression(pattern: #"%(\d+)\$@"#) + func arguments(_ value: String) -> Set { + Set(pattern.matches(in: value, range: NSRange(value.startIndex..., in: value)).map { + (value as NSString).substring(with: $0.range(at: 1)) + }) + } + for (key, value) in chinese { + let translatedArguments = arguments(value) + let originalArguments = arguments(english[key]!) + #expect(translatedArguments.isSubset(of: originalArguments)) + if let count = originalArguments.compactMap(Int.init).max() { + let values: [CVarArg] = (1...count).map { "⟦\($0)⟧" } + let formatted = String(format: value, arguments: values) + for argument in translatedArguments { #expect(formatted.contains("⟦\(argument)⟧")) } + } + } + } + + @Test func dynamicValuesAndLiteralPercentSurviveTranslation() throws { + let english = try localizedBundle("en") + let chinese = try localizedBundle("zh-Hant") + let key = "Indexing %1$@%% · %2$@ of %3$@" + let args: [CVarArg] = ["42", "21", "50"] + #expect(String(format: english.localizedString(forKey: key, value: nil, table: nil), arguments: args) + == "Indexing 42% · 21 of 50") + #expect(String(format: chinese.localizedString(forKey: key, value: nil, table: nil), arguments: args) + == "建立索引中 42% · 21/50") + #expect(chinese.localizedString(forKey: "Settings…", value: nil, table: nil) == "設定…") + } + + @Test func permissionDescriptionsAreLocalized() throws { + for language in ["en", "zh-Hant"] { + let bundle = try localizedBundle(language) + let value = bundle.localizedString(forKey: "NSDocumentsFolderUsageDescription", value: nil, table: "InfoPlist") + #expect(value.contains("DigUp")) + #expect(value != "NSDocumentsFolderUsageDescription") + } + } +} diff --git a/build.sh b/build.sh index a4e94ad9..95b4080f 100755 --- a/build.sh +++ b/build.sh @@ -46,6 +46,14 @@ xcrun actool --compile "$APP/Contents/Resources" --platform macosx --target-devi cp "$ICON" "$APP/Contents/Resources/AppIcon.icns" # over actool's plainer fallback: ours has simplified 16 and 32 px cp Resources/Acknowledgements.txt "$APP/Contents/Resources/Acknowledgements.txt" +# SwiftPM's localization bundle and main-bundle permission descriptions. +ditto "$BIN_DIR/DigUp_DigUpLocalization.bundle" "$APP/Contents/Resources/DigUp_DigUpLocalization.bundle" +for localization in Sources/DigUpLocalization/Resources/*.lproj; do + language="$(basename "$localization")" + mkdir -p "$APP/Contents/Resources/$language" + cp "$localization/InfoPlist.strings" "$APP/Contents/Resources/$language/InfoPlist.strings" +done + # Sparkle (in-app updates): the framework in Frameworks/, where the app's runpath looks. Its XPC services are for # sandboxed apps and its headers for building against it: left out. FW="$APP/Contents/Frameworks/Sparkle.framework" diff --git a/docs/localization.md b/docs/localization.md new file mode 100644 index 00000000..d86884b6 --- /dev/null +++ b/docs/localization.md @@ -0,0 +1,36 @@ +# Interface localization + +DigUp follows the language selected for the app in macOS, with English as its development language and fallback. +Traditional Chinese uses `zh-Hant`, including the system's `zh-TW` preference. + +The `DigUpLocalization` target contains `L10n` and these resources: + +``` +Sources/DigUpLocalization/Resources/ +├── en.lproj/ Localizable.strings, InfoPlist.strings +└── zh-Hant.lproj/ Localizable.strings, InfoPlist.strings +``` + +`L10n.text` looks up static interface text. `L10n.format` formats display values with positional `%1$@`, `%2$@`, etc. +The English format is also the lookup key. Pass already formatted strings (for example a count's `.formatted()` value), +and escape a literal percent as `%%` in format strings. Translations may reorder or omit an argument; Chinese need not +include an English singular/plural noun supplied by the calling code. Keep both English noun forms for existing English +plural behavior. `String(format:)` duration formats such as `%.1f h` are translated before their existing formatting call. + +To add a language, add its `.lproj` directory with both files and the same interface keys. Keep permission descriptions +consistent with the English Info.plist. Run `swift test`, build with `./build.sh`, and inspect the packaged app's UI in +both languages, including long explanatory text and strings with counts, percent signs, and file names. + +`build.sh` embeds the SwiftPM resource bundle in `Contents/Resources`. It also copies each language's `InfoPlist.strings` +into the main app bundle so macOS can localize its permission prompts. This step matters: launching from the build +folder alone does not prove that the distributed `.app` includes its translations. + +Search terms, tokenizer behavior, model prompts, file paths, stored settings keys, helper commands and diagnostics are +not localized. Sample queries shown in the UI may be translated; they are examples, not changes to search processing. +System error descriptions and third-party UI use the localization supplied by macOS or the dependency. + +## Example: the same build in two languages + +| English | Traditional Chinese | +| --- | --- | +| ![English General settings](localization/en-general.png) | ![Traditional Chinese General settings](localization/zh-Hant-general.png) | diff --git a/docs/localization/en-general.png b/docs/localization/en-general.png new file mode 100644 index 0000000000000000000000000000000000000000..fe94dab791b739f779d102d26ad1e6a59c8b489c GIT binary patch literal 90093 zcmeFZbyQSs+cpm4C?PR~(lN9kB@IIksicT>-;|W}&>%H5NQ<-rf|P_bDBTDs2uLF! z-3{L!^!E07-h2K2`Tg^)^}Vdcnmussy{|a0Gmi5(uTWKGIb3XVY&0}9Tm^aQhiGVE zBpMoM4)X@^mv?dNZNLY*(?dBawBmls72u1FnU;dNk`fv#aE*xuMkhzRj%ot@ilI~d zzLrI2LA!SKcMuv{h!q<6R~u#E6ZI1V{G#sp`3cGd{oMkL%)IvZHE0gi)R<{&0QkUq zDzEK?hIX3~^^30XkYNiAO#)3pTJjMBeJu^6T3;ij$xS|BP&Xf%6-@ZV@);%zEt2Kh za%Plwt@@rr*zm>t-jrP09MV>nj7&{VTAFbvbnmt>Iw2uppgeBi&OlxAX6Z)b>4@h> zLtg##?#sHvub%6bqaLGEOB?$W(sb=U_<71i8I z`nP`)%7clK^^pI07hz0u43R1=`OeMe9}oWHdA}Zgov!4+-*ojKt_9$U!Dpb-lRx_K z&!vB-^}jt03r7h3dN#BP?QP?~41rLUc8C1m2d6CIZ`sL-R>=8podJe+^Do!V@-5~;kpJYJ~U|DsEhYTtM~#={>nS5R2Yn(ljXOdh)ae!4MP+}CHlj_vm{ zWa@*6=pg4u9pW61i*z*J8Z^|7*_8K%Me>icgh~wOsi-XKzqix(=!AZQkO z2}?5csc{{28W7dgb(8rmn9fp0-5$~Dni(9zU0Ge+_~K_zJ>;cH=XhLxj1b`S9?z=h zD&8^h+HTvciWP_wS+7tpn~xItu29=(cU&7ag2RZCb0c}IE%CG3)nc}E_wOl*m|Gv0 zW8ipq=7Q87V5G1RRIk138RAi&8GBM#6m|_ykhZZl{`V%ZB_h?tr>pm;M$Gt|Y)sa) z^``K|aqSH~;QWkDX5R=2=4!Mr{cxi#QP)np_XS^J^=9MgP|LteA*UxD&Q5JRE^)c; z3Mf%-E+x{cM>u?nV!jC<+qZFvrhA5bsIKvwOpS5v#KOokw=IJ7`oo)cCtUI^ZNM zx&L+e&8>vE!LIT6J3(0z;%i%3eX_|toSm6^8+=nOQ8RN8KCCpKxn*N3*L(*rJ&5<8 z$(jPU%h1lvMj+rYub?H!ty%xPl-S^qJbBc>(!UWR?;4gre%kbNA~Ib-m}1+$B2iME z#>-mNhRDlJ&$TkS58>pk&nFRNt&Lpf!e*W10|YhOX=r_J-&{03{GG>)Ll*0X14-Sd zkQ}3izUTS7(EHy232n=X7So}09+aftndzZ&n^e2w0X^Vaxsh@G=568$V1pn{2@)J{ zW9q4h?vEgOF{O+jB;LJ4?YN@~mU zqM>_~AQ+!#OvQbZ`o(XlAyonClOPd;-4<-{nXc;@kJ*Si$`&HY+@Bw6_3cC^WOn@0 z_OgU5Di#A%>s1qWe`T*xT#n+ywweiOf*%HP6OpG2*UZMY#_-k0IJQuP9?6+<&rtT4E!b=xVJ>?0PUq_7U{j8sD=T1(bK+l!m0v=6?9KMS~NZ9_^q$orLN}a zK?mYeSvt$(7L;?xX75JnRN3NsVu@nw=1-;NUq+Bm=mc?gY#|b)(Mi}erSi92EqbqP z1%W%xlg%LMqvTTsM-wy+grwpnEmHt}=hKOF0Vg4Y0^J6~Zn4R(4OkfH);g~sy}iD= zO?!ubEw{*9r_8u156gjwTTa!xTls#bkX>Y+ix*URIal(X3g~^;-9S56^}$lZS@vR)f=x2|*SjMa{FanCAY~*a$uI-FmbX&!#tM{KXJpWN-vV%2tVQ9) zW0k!(%K8nJv3V-{1SK|%?W%RMu-pmzriO1VivPG0?U53vD!fWz@3z}1NU9&W%}cV2 z-};xG()=Kok~zkxzeb|E}!;?dPZs{2fkke8>)xK|rpmaD1CpAGTlAhXo?^Czc3ca$b*f-G?g6GMM zcLfZ{xf@^^6n52Fh3SG&leS|PdxTgUzqxSFfXWNk$YglUY>;y^ItEJ3DUsinOY5Q2 zNuP50r#H4B{X^Qh4^biH=5$@(S|_|aC5^)H%W$5yeMfhGYku5*e{lZSD$!%n7OVkQ zAt#X1{9EnWPEQ=hQ^bu$^=>&1#D?_SbRnnj#ERSL6g-&(%_(17w>YFS?}YOsj^o?X zV9}392@1BJVw;Yg@r~-m{g4ydK7&4Wv3p5koJYlm{{XH^PfakO(z|@nNgrJimuB_1 zWW+QlW1;pU+ydAuO3(1rTY|X)$B0(@?x%Uultl?jr+?d_edpDu(|rDGa~@!>HJ)y? z0m>n42gm$uQ*P4gFAQpgk*xq{Qs+%I76EXaM>f^~#WzTZ;PjN|W}3NRPkS*~6X>|d zTc{g6C@rdU>m?+Aqx_sGa)Qp2&kZtV6Lxj4vljOQ$iul0nMp);_yO8dB)dXKq;06$C=Uwvg=isj`^If3%x4-Oyn=vVQsMc;=W|$4)kS0 zj|L~FvO%%>H&6}yx92wku>7-_$u7iN@H4G{DO4fe6gfH=S_k$Bl z)S}WHB6*Plzu^0wpQZv4g!bW8XdYa`{Ng=5Ey2wgm5Zh(vJ&PDuXp^E;ZRG;0jD-; zQg|PWxXXuR+yt`G@jJe3i+Y!^#OG=y%CP4;LLpYawW9<*Xr2|~Fc(7OBY53;!8i=x zzFLZul_Vmh2vfeV^d2l^=Z<5~O5OIi9hZP?_y3HjuH(}wG(={`2O!Phso->0jrKZQD&qio6?`t9v}e-GeqZ;FfeD=Wc;$s+9`jW zGrf-7+xcY^Q)iOK0V%S$GbDVj9o#mALmI5mGkz6Da51``{~cWA0*~z_i|^ z=VpVI_t$r3mB!`>zDdU(_O|^|!;IYMY2Y8>qm2iZZ6j~gF=l8bJrNO<7;o9Qx`vc@)^TOTmO%Ll|nTzpLGdQQOUz!0li zvpQEonbYMB7?JKsneiDy#9CR$lLWY;DrLcJa?CBTtIy~W`}#qQNj<2>_c-z!VELI} ziKYJ(;2Yt9zBH=Lt3)Y(E<14doT?99{j)6{?NTGjGOy#Eikkx&X>LG=bS&4vnYkPPW)Fuwzs+UIC??&5TFvzCjPZ>CYUzJn-kddhwG zdmBg6&)wsn2))o;6X3ywHP+=Br>g?Vo16AF5lhtgGs7w)g98@ z(M~{HsZRnActa&GhY_cIH)ES?+zo)W9;9hX(-(pIBjx?^+h%8~Iy2q6J(&ttTVJdsiiHR^^3(+$I!SuCod+$|Udg&klb;~#MOmKj~ zhwN%saz{Qz^0u-`IgkGpr!>UW8YmNf`}IDcH8QJ@=EB3(m4z6e*S)2Ml$nhMK}LHq zA2wWGoaRA&!MHT`^a%bI>*Y>ITvEdVmpLRF3Z8&ibA!LSic0Uynjs(P0!U&$XcCAg z`8espt&{Wik4ywAq|rXHlY~H~Aog`DujM3B(dI%|*Ll!(U8rjPY%oRc=Ebj}@zQhc zo6?+)b)+&n{zPCDas2B8#JMeOd~VlcN#$p$A4j>>(_gQOq!;++JUllOfUiuA;T)(e zyz**pL|uh_i?+a`l}dPT5U$=4WD0n(6?4lu(aPmd+PIw+x9@!jz-)1Pmif8CvVm|i z1<#n&L0)HDF;i)q0qTl1g)!?dA-1;K&fHD>VEpCoBll8i@}v?%Kav*i?dqF3wkbhx zHQ>vP2bwsN(y#If2?*}VhdYQ#i;jn`N~}hpO~IgRP;6-Wdq?{!PktXxg21Dqh*MJI zPukcMj8<2Ar{#eJe)g=br!`6eW-@|~RcVF!aBS~Tq{)G4#EQrbWRX%{KeWYQAMu`u z-v9PBNv?E*`Pd#T+N`L6tOub^!rf7}XT$=d*O5xf3YZdJo4OgqV$Ud{LNPV0Dswv1WKIq;xE8}FxmY&$TzLy{cYJ%1Ej{5NM88>)ZwSJl1L2SUkdCZc6f)apSf#C5% zoHbk#1^N)nnb*ha0k-Cj7RM6QXafj;MW*s7m(R%fr7aw?#f2V$cD`?`LMSVrfVe3% z`Y|~nCbBMsuoq~bjd4O>vHu{OQoy;^*Kj~;*KQMlBQ&hT9OH@4J;D~4`o+mooN>MC z{9s}fkFq3lx00xu?#g^zgEvFXuMNjPM(MuhBZAD9Y5<5GziUQffH2etWM!n&n{q#cR8|6u6|KzoZAg#ZLy4)bOuRWUE4U$fs~$6xNNgHTx;Bvk2mi@cT9 zmKVKVc>_S1b?Rv!(!OQaD$UF}MN?z8zqAbhWZ~m-Go1tTVXtF|08RUr1ndQi9KHX_ zXT4z0b-;k#hqDDpY4qlejjl6-2+E?l#xtg8ACEUoEOK3WcZAEFfD3v4*&y*Ts^6~& z5a+9ghT%aUW3IzO@q`v)x_7vU7emC)j0xlE1g#lf#Ysh1O7vr4`ED_Ad zk)wfM$k5GsyW9Nx9aqj+im%tRti7yihdg%Aw2YE^WTvH^=kS-~CUnQTk{#kq5Rm-! zZg3KTJdFySITb@`&EpJ-4bi|%{@8j5ap;}fO-)k)`x9NFR~mYY6>uaPx$C%M!W1H`|(d(T_9q*;khz60rS6gEgT7Z)ut{zg!m+WjmG ziP3iOI6@m6UwAo_F5?Y;8D>cZZ2IvIc`h#(orJTN*aBNs>T86Ezph;kqdOWwQ}*PCj>0_FF;+O)#jf}zk5W11^;f0}OUNwc zULMyKAk#zP8&26I2PEK%uV0d8G!Ln)W0Axlr0nuy`8?Z zTHYfL?eK%>;bh!KbbX+rsbSRh_)UAWG|wbxsxhYWdcSP20Jh}ZHUqXj?~eYvzZA*< z3lfcVBU&OmU~F>Pt`k!G@TR91>v7}^>69UIcM)x2(e&&mFxsL7c(c_L)8nY1B%h&q ztJL22bRyvt301e3EdwmV=T@k&49)|Qv#l2EVPWypwQ_|uV+6TuNZd*}S@Cm*ZoC!BkCq^yt#yxwH5N2gp83)pKiNIj~4;-zyaG)GjB2 z_Rn<%vJgL0$J9kO)r=aJ*9RN>iq+d>KT}Vtnd3u}5KJYJX5X6gsRJ1c88%%7P0cev zhK_u)s?r=KGQvzx_djcnM!hzbk9i_|+Zm)?LcZ~FD`>U2uGn=F*c2~&t1YG)Up|5c_SWp|EV&OY3{VNirgk zU3PFb=|LjCXt?v=P$Cdj>ebxq+k?N2dq*(l0(h+}En@W~NW;J&68Om+4b~nRda&u7WJj-&F~SS*(5Sms z>8m1LQZVwr`iZY2Q!NloYSz zZ#nd$g$OmF%kF?u5S^C3t%_0#3xt-H@Dxv0-f$<$yikD&#C2mzT}DWPbvj0O-?ih! z;bVPJgR)=N^BO_oj}HoYA}Sw>2RncF53NgZ!_-EQhykd4Wj5?&Ip;dv2uI!8!wG&H zwyk~{p;ofF>M~IibR}XUSmROO!h1e&c3!$iK{2hk+TsrEYWokOoN|x%ws%udm_Q>P zWC~9FLQtmfu1Xxu#G2sY10x8gbm>z5O?Xo+htN_ z1SfiIlow3j3WbF;@k}YREm9BgR_JqLODzh|ue3dt_+`CRnSk&%1v!1CGiY%iV3zom zc&}TxrF(9q1V{4jJnalg_y&X;R$4MOM3k0T=6zcv5z1`kXCvj1IMQyZ-o#R`YHb^* zJN`aUNK0uqykfbUr_gZ2cvK;6tJ`xfl=(@>y$0|1Cf6J`r+}d34wuVI+Tc!ybmPj; zg__aivNuTTT}VAb+h0K&kMLLrMabd&LKFil^woEk zhnbTQ)2$~n0k-eDn5>w}bE1R`Si;3|42^HoKN~eMJnq&E%Q)-Gpj=dwXq-OZa8oD0 zxC_8@@8<8@$Lcpovc9}9@a3l|;!MXh;2{X*2!mD~P2!r9cS!)_#`N(#wv`yr`8 zI<~ZA%j+u#1q}EFe3=EIHd*5|zxs-!rV*tBM`WQh^U!XagqF2!zBiF`T`0NOV*y4&ZA%)C zicpR2VO^!;JpsF5^0`2x?HWETrXG>(aM%1}4WQVEaTIYk z6W*0)DNpEpk-0N?fh`skSGKa2zic}i0rs-zkpjRjGaPpl8knUdx$<~jrh*B zdW+f_K{#78TD-U&Qf*kUQSQ3KU_J&I8lI1Ap{E%;LX+hz$W@~DO$+8ZNK?sKmE+we zcP%|4WYF1dx*vmhI4hA^HGp^LNl0RKbBGp?4h+R!{7B0q=cpfu=!_A(o^8qZGyFnB z4|wEB1-OxfF%tS9oLIrN(w4A{qtFZ%pG8fd*G#>Gp|5v!%$)Cw9eu#h?N|8hW8z5Z z*a^L()X?7r#Ado8@mXx*hfd;}FFk&|=~9<;$PaE$K&NYVUPjC30(fD8JMgb;VMgtKxBZ!;L+`Ei(pbF>5~*isii@1;KUQM9 zay}aXYn}goqGJe!vMpnQ>LVM0P|ey*=zFp$HjMfO8C4hQjZb%N8+|t$4wb1)=H6`u zpFO+D!7>Hx(Io}Fq-F`iv8OG#aTa*ls+jK(&uQa_vh3SL7!S=Qd;4VkA7?=&o?dd} z+!27OV#*GvGOcTXYK(?^K5A>@boZP;vDLdm%>0cRX9_cnZQWN<_-Q6CC`r#zK@##f zVvH0(JN}}Ob4%P?h*K%XeXm{XY(@TLSX-Hz(f@+ZFCX5U;$=xneB z`<7@ET7Q4bgf$%Aie`Es(Uz5Y`r{rAd9o)_!;5TzFDCiquO&M_maG2)>;ByJtXP9z zYk;`%$*Ho?VmubvbH9+GRfEIYarctyxv=qCHWYx2@ZgHzd3H zhuB>*xiI|(7jUlQBs~}!2nEv>y^x@$V8_?ePpkD{H;NflP#ai`z zl+`;w$SZj#0N=^u<@SmoqahfhSBm$i{c`Lw(zP|++4ECt;xGZq=7WulJT7w~0 z{Nt86#JoueiHys$rSQYX!-mR!jJZ!5n)(`gKF5o9WU9avY~E2;7W*Fz!$K zu=Qb~fV!!uvYFZ&FMN7Tm*>HPj%OP?6j9s^92uY98n{pX+N?50KRUz?%A)Z#6N*q- z4F4%c$T0EYLo5O+((h6sAyN2LxX(t199rXMC!GeA(&11=TGAq&$^yjq6pJt*)+>3( zid1}sl{4jRfT-)6u+Me9D7ic-8L#L=Ff^>&+CPxepd5bCL%PBJ)ht^y`8{6Zz`DxFpigzdr8K1tEMN>n|x*d!JBW1k?a2+fFK1`(&W`!AdR#o{2>?KeJBBPYZ(iQx%mu2pB>>W+j7kx@4KA% z$tN{*TQZ;RH!9w&RL_w9M{yXR%_!3Dh>@PAe6TrK`?})Ed9jk9%v)v!3FQ*@M_|r; z<@!p8Gm16JPkq~=@9FuBnXFLd7p&HX58e;?pVe^cR=urqpZyq=BmHW;{{|bfe%*tb zuhTByb^A2fkGd(Y;j#OM01ygz=8x*M@1V5)GA@Aju130+5~DVaM>;^LEQ5q*=M3;q zNoOGDLfZiB&hYHC>C$t@#3=u|J)m-w#cmR9^CcIpb+eTy!bduJHnn0o=TwDMIhRIc ziJDEpB|#8^tuI}ouCg@T?z+pFtaz0JH0Jx&i|9zQ7mp6Op6uI%!Hb@gr{PK7J~#vx z9COywxjS_F*%*+d^`iURKa&AeYFOCBm5Z499B@^pSPU;uMu#Ey>LnoVQ9zWSQ%@0$ z+r~xGn)yy5kVJvhA!s>*+Nqb@O0Y+UL_c2m@!GUq-Fg_$0IZxV=E>lbMrqZYYn@M@ zZ#6&h8EMVS4A8k{$Um zB}z3rx6`5?B4C4(CZvj5)QA@dp-n`G$dD?7u)3@HvxlA!U{01vs61p_A(X|rS8CKi z`I}NC1c`BN%Bc4X@u;-hz`gpK#dJ5-<~W&DJrL3v+a+$|M*2@hH%@PalYM|l(LqUI zHR19isdUlfm+u!Zx9~3qd4=r1itofm^D2$vV|g&%P!lhG7cnn~rWQjgh%Caa{D&4~ z8RADR{X5CX%P5igq&{Mc`pI7*)E<^O-!=dxKh7Y5!z%XAEz5z;%XhehyV^{DB`?VQ zhgHNIv`URWfRs`^;uKWx?FjJf#yq77iaS4?VRi)#?g!kUp zAP+0SpC{uqs z1Gaz9y#Xo6C;gpQGXGyUqF%}Z!FAr%5&FBkf3_HW=?7AClFhw}8-EYzSBqD zl}ro>+uC*q#4~<00tU58ZxWSX(NpKLUaC%1G5Ux~3EnmESb2?dFNu@`ro%d@J>MpC zo642}5tQQ5vFA!)MN1E7lb&Mg_jyOo%5bwQB-ekNe#W_RJ5NEpj6HS*rj>(iJySO^!-KF-_QIsC)D#bK}X6LKJE6mc>g4Srsg4fC{1K zFskpyC&TM`ux0{)>eerc4W6M`p(e-T2*2|SDrdU*yI9P-oVeX4#xF@bCoE?)ZI+}T zrd@8{Fl2P`OQ>*Yfu%;bc~r;%WDy0>g}0W{J?(z9VuapN&wbV3kMg&-B>e<|ELAvr z)fnCL>NNroBI*pFzS`-$Kt-8W?-Sd zxDh~DXhRVKiI$D}Fg_s%0GPr673sGD^f-}hw6!|GN)1;9;EM@T@zD7j{*nl9RAyO) zyxZhHpldCI(5R9*eYV3<$=9$*fvIY(>Zis(24}3EJ#_~B!rKCqPvP4D7ldT#rIYe@ zPt#csUWVoMHHOz$`I-+mKF@--IX<0apizq;2@wT@xc~zLZhJ(Xt~2=_QyO1%PnFyQ z=uX|f?`zM}8(a7en4Hky^f|sji9i8@rnHJ%bHX1z#Ll8kt=J-5Wa_DHjKYCA12!c) zLB7uqn-x`}pGo2pg6f%PH=hf(Zk4?UtZN%EOx?ALVPysI3juqB?O=iWEWz`=VfC_= zHQL*J_v*EU%Uq+a1w_r}l4U#i#(@BsX`Zw`@P{`!mbCN3W9Js(zPz}3@=9&yW0#zZ z`LqrcWHycseF`8sY_33Rgf}8|6O$N7*SU?$3ZdAPFWeq9IJ$ha%8q)Y`Q2bFYLps& z3UND__PsRO?GsRGd1{3bd`$JU8C`++*{x^awB8a;y)5~3{^7!P*H*xrd1;39rxiIt zX|?m=$BB;sB_RO^ffLP+NL>N>&V}C5z~GXe12UWJ#SI7Jb1$rm@1Phh1YOGvP)dT3 zKrKpeVVkuv3hd#b+dkn$+=cQ(VcUsm@3+`AixsCJ&aQWQP)SX(yDsfIqWCTLs8r>M zniwjt17vsax<49pKixOH+}D|M&h*_1pm`O=OC{ENYmTGgJ73A0o*dVw86|`6fg<}+ z&?nB0$IWPui^x9|cHNBT9lvdlH6BBHH!>nwwotP~xr2KxIEruTHlj4)I{TcdLpso7 zHma8W2fZZ4m4H-orv(c;;NIKAz{d?#sTaw|cW^)Xwx#IL8QgKd;fNoHbAG&?Cx&+R z1M?4>Nh>$OSp4;VRhIG0?iniE>lQFPAr*Rhs>PYw8NCXO^`1&KmuP|R!qEc&H?wCs z#0ztDyxgMbyjyEqtM>NXZrV05Bba2;4(sl74*#6h4~O@EXRhz|AR3gDi{gZGesRbWngL8gWBQ>4xtL8a4%&5SD8s$*hs&-h2TI4-WL1&$%ng+nNHm-e)rnUx9qUJ(#=BZjtapa21K7hRW-O*d z^z;H(6qIURS;DcutLh!DV_nkf54ZpJQ*dznAsC3k5|7RSw)-LF0Od{GKp2x9fjdhO zsCg}&7D2A>zL;!@x$Mtantwm^i4~*?gfgg;I2Fftwm)kUq48?tqD*Tnz5ykcPqr#qe8TLQA92X z(?M+TfsOgAyy4`f>xO~ofmN*Gv<=SjH5U*&i1TuOaAwDI=p(b7UByW%v6>mV6at~@ z+hjW}cfA&(RfF;<9V$r0v=`Ua#N9BvW*RWcc7BFIDYPlwD$AFE8zqaOT)&|Id2zJ?7TVA@7i7Y?9$y= z8H`CLMMkZ4HWe>ZQ;&xVoe2{F)Qbyw>I)17duYnV$16W#U)obBaR4Qs*IaJZ-LAy7Z(^Y*g@)} z&Mz!l)>I#j2ToI$Mhi|eM&*R9Te^?|QfqX=l+G`=e&iD(ms@E7$F_2ftX3A|x-nK3 zlg-;D_rtnr(I4w#RP zm$t<^8)l7>6A-oK7t5Iqd4@g37Wv~j-txA%UNq_cO6!a=^_COy_kvFfMtYTIApP+0zDo23TjKw{p?&%ZhuGWX(1sp+qC zL%B)%*z|Xf8BPOG$fA^Ce1PlHML+I`6i*VB(#hc0-*$zsU^AmJls~9-F|){S5Z z9JXwAK>P@6EC{HEw}eA05MxHptQQL^7YzK-Iy~CJ5kI_ zQS(LvdFh{+7FPpdg%NmsY0f*c3o|XHd{J_H1teZItR)8DZcEYF4u@Vp^sptLpr=|w z?vH7T*(dmscRUs6epH?g&8B_$NFT73E0yb$x_X{oM@q9VWb$Qr;ZMJxJ>7ABI~6y#Q3BjU;A{Do`vg0mUCeES&58TT_#|aYiE!10CBV;rw!zk(am1q8TT8 z_;CQmTmDP&`o{*ugro(dB%fih1MCV^Iy}Q~k1%i+E{vGP1T(blXpuXUZ$UCm)uLo1 zb*kv^wn4b7TukO2(vjhCZ_G*ZY?4JdjQp<_YYb>mzllaKT_D9lZ0^+7^O+VOX|y3H zy|2i=hi1-VWd*zCY1UDAmhP71r$5LH_$cMHxw?Y{KbF=lpD}R_J&KGH4jU-Y z)WzjY)q7gFe%6%WNZ~ppy9n!b^7RTn3*o7#_M+KMHE`L)gV$m3 z`~>^A0Kx`c2^i%OT$85FeZJ(M0sZTf_w)Ak?h|HouP1GCh0ko@WWX6d#<7%erX2$@4Z8-GG2LNu82@52rnIsY7~EK% z#?P#;RiSZA+Delv$2ssr(V44qD0?M03S}k#XC(0u67D`rR7iguiE;EikK};Ry4BlX7`NK4n4m zkCl#A@-hs(Hk>jZbX=@9U0Bos2g5^sd!uoejrOo-pwib1p?|@2;soP`;?=WU6qP*w~c=QVU5Bl>!QS%QK;afMjwjB(ZW-R zfR#Y?Y*0%?z+}c29EqhOQM~C$AzKFoVrij5hloiG=+mc)h_YrhyWKAXt+%KZ9B`JT zjQs9I^t8KtAl{mM&^k9up7V0j>E+D(n4O?xG?grs*tH%T}Ysp784r{vogvjLwJlHWHHxpm*kd!(!x ziM9n$&vLXXEY-@K&uv5zfrD1B*B6#S&vx?h;T> z0i0SA!_)E6=hZ=@*s@FCU&Sf2ifxCgyz002@p{{FZ|B9D8S~mvLF5SbC;<6t8lMj`TybeDCav zownFhwvZ6~fFL0J^W?d#ThJQ>qxvbhr)1+{qZD_A_({}&#VBxknqJC+k*?&>penne zHXbY-znh7BoF*YXcY^(iM)J+tG4T@Fo@=zn&yrbxO-x1YOWLo;k9Z$m)zAVtm3$8; z@kF9Qhi15+*UHSWS{Bi#f=`m!IdF?Oyb*-9zyRER!siJV$o zt=}~X6W^JiAacCMZi__{HbWXkRp=tpRF-G~ik;kZF)o^H?y- z^Yv%b&hUfR5$n{NC6o$i^OK|>~-VUVHV7`BN0lNA0x*>v3#v9?>bd3 z5Mt0MZsMY~H;Ea(|HxBtEXo2_)w%%Sqer5|`|H{1MazD--j0H^(Oaxt0LOrvaK`s$ z3ki39^j5G`y-dVk$nl|l>KvplLTZ*E_X;9ueoAP0&bRY)r|ioWd=`&}zpP=O##{kO zSG7b#OG27VLLLB@>ixz?gSuPW`^h^wHOmjS4kfJBR+ za?Xl((c7{@7uUa{AS9fd==A}}f%ejQq)U*zG_5Vjd9$uKsAOWjTX|EBrumCR`5iDq+5_5wpV7kW-#S^~1ySkzIh>)}inKQ3z=Y+C^fF8rN zDcC1LpLp9e-ZKO=DS>Rg?_ra?8bmk1{OFdxA|x3RV$DQm|Jm?rZvtI>1YoHJ-{&5q ztV*1e-1n6A3Id9y@Ua|)O%W)|EQ+Otwo)#HIOswz~XXN*o>dDWf*VI z@@tq7OZ>SQS72PGH%Jz4n~@?1bcHY(0^%|6m8WL?S<`U5h$WQAY^6vWoS>8;*5Dv@ zcr{r+p=9~Y0Zf2UW`S$&0COWypqw_ z7g;DDbAyn!zfYFJ_E9}hSKfA$vtG3x2#T$~ek)Jby@f8F^+r99$-f@~*9e^ahk(z( z#e%9T*yC1jGRgu}O6)SwD{n_-FBcB@gyU{O=6F zb|XM)zLhDj|1(AM3qRIp1d=<&bG$sL4fH==;R}e#NlD+`vMVL>UoDnd0j%MFZv`~q z|4!3S1x)zABJpbz{XZrWx%-B(hV0v@B;Z@!_Z;4+bRnJCE2E4*Y|bwc0aR11f>C$; zojY~7{9~EOfxVUCn_^`psY|K(^|`20FYmlpKnWp`{<$(|KQ>+UP&eL#Z-nS+qQ2v1KC8#v>D*VA(RDoSxY`yZj~dW_}|P5};hxm^9>T^a3nv zZ{h%^&AIK~|5kRuL+Yi_J}d)(zDfp=0KCOx(Z9yV>&@3{vp!zg1{6-|?iG@cYH9`t z{fj0A7-E8@wAkDuYbZ)SwJcBuZ`{+N;`*@>>mgM-JPaP^-G%t4jQXF>{~(~{_{5ZI z^PEy|@ZL$=i4?7gB27y-8jj_E_%?s{c8?Xk;wGx;An(bC6DeCLEgyFXamdN+^LG({ zxA{X)XBuakWm@e8?AJ?3$1v0RU%PVI{(1W^D*(`u*sm#Fv&|VTTDqR?vijxqo3*r} zx1*elJ^zy4mQY4xHyy9Ax;`#jqHBmLjmN&a-_yDCFOGt$44M)gAXzA;PGElL1g(*4gjE*pZBou->? zEv}fFf4%_Ku6Yo$?2ezEWrfAY`afMDAmNR|!5I7xEB<9w3XNNMw(0H2O0)^e)SCam%*5#)w2>66|gid$&3t+#z zzW@bVbJ1y_%J2zKX1SQ{cm*n{k2;qq9B_fTyy@1Bo|AeO5hL6C2PuEa2CDz<{>=dK zLLcx-b5Vq;C!j&Wm+g12<}5QBt95T1*miSB9n|R|^o{i7Kq|X%%p{y0m4ZgJiZ#^( zN$SpDF8|e2GxIQMF-3Pe>18gS4x}1Qfp+a#apR5vSsL>OC|k=Eyrxk{!4!NyATYTjM(4A3D# zh@Kt{$-hf}`WU9O*-EuO`C{^|$OR5~7>nZ9j{8Ukxb25zO?fN~9RVnIQ0` zr-=#pf_WW?IpOznSb}wEYXPARpFaOKtt45n!-lpPGvL;^n>P3HCg)vlZo}-GXlMr{JGa;DTpou1P%~k z+dKBKXVE?R?ma#7vVB-Mn%F)$M>NuxQI`t9S!nLu)?ePvKHKuGq|)(UPMtn8-`9HRMQjp^rx5!N2ggm5`;=5e(y;x$vbAX=B(p(rj3@I>2N_3^B zh#$v^D^N=V!@k2&yPS=x5kE!p`ur;2MD5w-2nj`=-obnI1{F_0I8C~ZUhd@2I>Hs} zpOR~HmPH6`)%eLATlBw7sSiOg`Tls5H$-t-)=SHfBdgiUcKR1Xj@_hn-<<)(Dr`+0XrDT&UyN!ch9)2so>3{)>ZbmBW5#CZ2!$OqXFY;`hg7bCd9;#0iXc&1DAGI91f*9(lh6dDNC)Yl2#8cemm&n| zB1L*r5KyF}h8{qA7X_q*DyW3s;hS--^}Md<^*h}M``Sm#>slene`dBZ#(n<=A+5RB zr~!xf8W8q4exCVqCtKF$nx*M%53K>J&pqMdN#p5ZWA?@&*f@qUfG}9P5M%Yx3seOB zIWOp!+aCU?)%Mz3R+UdPFR{JWO%}A*>X^z=+3Dt-6tJwsa-;Pl&b){L@)yfnPiujI zL%kFp-2%nX%0>2?u_R=R{0*xN?&NAZ)v=hdS{IW0V!u+ox!IVJno3?$IMy}RyYUNq zh-AysgQahljy~xulIEoA3G?Av9_z;X5nY|hOw z%VUGdM!Y#h;Ds#BbAu)!jf z*HU(}QWm@6KHJ-8dDLcEU<-X>S}hpxO+$Sqa${3F+bewiHZa1fmnxpQ$d;e6Hhp?L zO>>H~AKHjAYo*-wHd!ZX#1gd?${&9VVfiqL)w5xF6Kp>>8(L#K4ZcG&`3JMvjKav! z{dGt)WLBtF9{jPkaOs9iDTg;X-!h;?s`*3LT0WjrVSCxO<8P7_zSS%})V{0Yqip(* z#Xgd?sayC&UKr&(fU>d)Q!wE%)_Nz^P!K1_N={Dh`) zuPsf=b0(OOt1Kg6_p9UuK?D_WfNQB!S;4mbrZ%%oC6IcjdvT|!SEXRSP#Yd1Ov^%w zWfly!$99V5meb7u49BG4azUV@y8NJij%kfYN|IRv(Pl+#eelBa;KgOqTyG4g2aw&p z6oXvpx&Qdg7H)oh`%HOLD%Y^am@TL){E;V`T(uzG2B6B?xaIddLpC@N8SY)Y0`=>2 zFY~U#@F|4I*H?ikK z^#@#ai{22OTuTYsPjRYrI2zGD)~CPmvGsh&2(B`Qesm!8g3UtGz%Wi!z8F`cP+G$V zwpbC|;iq8$zxQc97b%-2rrryI$DT(y}y@&-Z_NVt?a zEc9tzivPZ>`v#v8j1;mNyqkXkEAoUUc3-q@SwFOvfXpo`)MtH6Q|ehl^vc(>7OLAH zQSa-SRLYpeVNOS`dZ1{!b5s1qxnmDt&2dChhqPoLf63-7#}gzCm#zQlRi?qTUmSE3 zUEW+mkU!inBnCf6T>BXuS~3@IB59BuNU=d_Ra#bd85VSidCnIg%uA#Vl_Uwj(+Ru} zDW5jwqm{TV!;&jzwL*8vRgMD}?*zU~wkxaOxxr4`LNNu0aPIFrT^K8C;*ghQC+c`$ zf#wH>3H3BOc5@g$d-&rNXbXYTC^QyoBQ#{dTZOd3#zuSuJU;Hl!c1>dUxW>h$$jEZ z(|TX8@h!f_4ZgMyz{@s%KO8$KH$q6p4vU;RT9h>M)f2}=V|PE>#*J>+pC_$ZP@#i1 z2!-+Bzd~@dKM?%v5iqOUUfm*unn_$5?vrW#^wvuGWWl{UG5a58*qkD(O)nPcD6}5k zp~_+^$x>1+WX=#6N%6fE{c7uK5%Y7S+S=CT(fsf`0wJ#?MTj zEeQFjFiCCG7KUg|huG}0W(dJowOoB~t&asAk8xm#f_!KZhvAQg8*p1RdeADMs^SU*H9S7PDpjRxywE`Kq#DW5u-C+chZmZ zc{;JQCttn&Ud%hjSONoMx6TxN_dt2F2pX|5-?-z{m~3M($Xq9K@2`7cur({RHEWK| zpLS*PCX$A|+V;*zo-@~&|9W~#0{aU0ejxKq7Z<^|~B>ctH zh)0oM0TND}!hUxfcORr04&Q^3KcJ^E!Ym2#M_S9wE~XPvL?>P)R6NgrzuCG#m9|)< zPbargeA>H7JYr$s+z*B7%w+f-{Fn;ZskkVO~52E`3&kl9PEaxK2xD zk@L1x%JAr6mL)eaN2$n5J@l}~KNkebvnQ^$t&42K_ZT)|q1>B7^!*;f!5aAo-OEYp z_wDqxnpTU_G#aW6RQ82iE0|ByF+rnmN78*ss}-n7w74@7^8P}wz=p%GYcHi_k!1J* z4qb;qhqXZ(hnunT%&fi9&p)`gawbc`iFtj&=D2ZN;PsfF)eOvyTr{KfmTjGW65AH* za@elELE44{hC?`d6*qkn)!^oga_>Ry?W+dv^L zsftl&y50$eov8ro#FvC;!@@KK>&2edRujKS484BYkuTKkhChzUB6W?%{DI=eh)oe4 zJEgEcB)K;pNHs&{)szm}UqMJJT~x96Qgt1<#|R~&<@q_O{)rIvDUcD^u1qBqx!{Ji zy;A+E&)`x@Sah)}{m11?VDjxJ>C{hM$zRDQ^jm#jvGpz)ai?`B?^?)mg70MON-ABaC`rBW1+A zTa<`;A3)r?IR#ArFUgd?VY3c!L5$5hrcGVJ+8gJzy*J{y{UiCDHJSDKVE*t4g9SyM zWoY;ciFJw0n@pGaZcP__aaH1yr3<4x{ZNjQp(fqGr`Q%p@E!}Shio=xoJ6j*E8MqZ zn+>cak>bUtu1UH#^L^_LR7O?Fb__pTy*{EQp^wZZ*6YPy4VPGN&M!<*hOrO9q4szR zwy1YS7f~7BpY|!&pFm%xTJzCFNX@r1xR!d@ZT8~RE3r0zEX-QEh#@XdN|g%~Ax?3$ zVI?2CFbJ7jwEa|5PETxe*98Cjn=aAmF_Pe-E?d6-T8pocw@AWES^DpaXznkR7``sP z5=ha6ygk}u>T_<4pKSMUCfa)~)+}8N8){;I*DnxDXDBgp?MtqE%L-? zHPrs0jc{1Btw%HOMpqeSNUl+&Y|m5G=$DQA>5b|ZDsJC#fJZ~RDO%xl80>(fpDffF{8ax1C(A=q-$>)_NfGjA*bTvcD2#l`Wt&m+{sznG zI!oJ<-0nNELCum5FVWkALca3}s(Q;v+(}>meV!9BN_1;Q%Q8y$9L};mR3JWmvQwMh zP{6`hW?`lO=3FBA?SSpkws%We=_w4;^kGVA`p#qJ99Valm?~c3{P)3#XC5&znX_?)XdUaKwt)Ue6^9 z;324!n~zu0JHWw(g6I(utO|_YpRKEt5xq_piLtYUm^g4n9)e2Xac%Z5;-o)rfH?nY z$h+dY>>J?W#LPyp+?0nAE47$KuvyH!cgn%bf+`})45?m|5+m53uo)C+qO1o z+3BauX@yf~#z+pE2<76#yYI1dyzEp)4OWknarTW@9W^<5U;>TGZ2O4ogN6Y>UUT2t*EQ02KOw?*(x z)SLcZfq#Qx!!CWkVHZr-5fgOls4T5$bEm}zEEv2aTyEu3<2plQ0%im-o-F#8M?h@F z!d%XfYYW_pZI5XzjAvf86gm?>Y*D(r8||}dHf=idu9fWUl=^HgRYw*`tL4|UH5evC z6d@E4V@>6LET^2kl91HSXWQ=d4=2Y!;c~+@cP2EYSMaDsupo&3-59lShTOqmTn88*ru6KmLn_QkFz9X*%i9fcnJ^hvE5_i!hGAT1t%MC2`-SjigWf>oq%asHj~Z6 zERXO4SIpxtMZ@Iiqus!@T@`0l{wGes5e1gVqn1qNED7!)yTc&NFQEOP(Ke-afTyNi$~*Ifo(a-%ezs&~mtjeVVd* zF3!v1-UeRc(kSq!@oc}|Q1?gh(qo|gW&Qw%9P4=m0t*Y&X69N(PqF65ch;e)kV>J0 z$gSW9^bihW$QYHIiTDLg>fkNs(Hs?&@0(!fk9lC;(4?y}Rr~wB~9bSY+JYZ4b?P%q} zPm;RY*ShPw^yEi*w5p6I-DSyycavoM65m_V-7pAo~c zEl3DgwQBhgwR$VeU#|Ep;z31YQtUlu%p($-Z!C6{(;~X_ypz8oA`hbp@j=vTpqC z!{c>~&*!yTt66tL{&BtX%sKmOZ@E=d2UR?8|P7@4QARaFDk36v3h}w|gn#*zwt;`;LLIPtH0E z4g<{ScHim@9u)(Oj^;(Bz3|jW;WXZq-_*rSqLHFh!sei>0d~CGxJ7{9q~h{p^N+Gy zf*>bH)fXC1k8MuXjCdX>Z1K4ruHdLdbE-+5kGe?A#L*5_$&O3lwU2`xK#0F^oCv5O zW0B9(C8FoD4QXTMzWK?lLM1l`SZoD^IQ(UvfAK9{a#`BTWwJ304=bW%zPb?5+u%{T zsg3#?H1GMzw*9rk(z2)gbj8zenm#Si9=BhycM zbB_Vfrm$VfLUyot7$$r3Z^1$E36~BhDY-mX)}8eVvV^;!+*Up7fSRYL=8ID5c@!-f zNXsmS^0fPCfS}MSP@`H%{m3cU?31kAQwNw?dTK-#z8iQ{wjcH7nlB0tl&2yV231+K zLdYkeH`+BA*-tHmRv6mr4X7hNtFiDFFq@=c7tG9ssB}R8~TIn+V)~o~0gPLeW z$9_J`9PRML`cCx8jGw=a1ndn%&8%)H4TWGAH#_sas$n~UbqpC_L=5r=zsKyQwIG`v*)U6DqHy-_Ajz%9K3LnZ{hCUZi%emXKQ3@Q z?o1fBt}&X`hStDmK%4yT4iLn*e(c(1*uB(B=fSWw^VR3}?KJ^BU+A`> zpE3RKg|TR&vmrNCOKCCb1Jjp{zn}rFZ7^2GqN@KvJDyL3-qg6jht@ z<%@%1vqnfb`Le`N9phCzv_*_{8X|>lU<%i~punDoZj2D)^yLGOgDk^U(@!-tIPn$H zYb(0n3SO6m`tX;Fm_Z_rK=A<`^GF|*h0DWQ#siOX&VXt-EzTo>o8J$E0eAnKT|H>P z*mpg^W**?=IHj>dGRNpEm_4EJOS5M~)I2*mQ>fN_`u%KDy4g6I;;*4cu}yO_vuQjG z&3(ANGwl(Bjk9#-G^fxMDEqaizuo`)M*LT$OclZpXmB&bk>v8>R=uJ3JQvq1T2ce} z9A+@rDuE~t?zy)Pd3A?)jsfnszmO6I&_6W4gzsdwHCOR+a6H!PE2=sku(8jiw&A&K zBBU5-n`v9+B^Va6*EgUUQlAp@Jl=Bm{n#a(JJe+i*A)iWMuk${N+_tD>uJu?(|-wz z4$>6RwjenE@N|YeN%2f%s#f{yDfg1sHT1$U(phle6?3hh5BR{O`yE#dS-?uetJ|Vz z;))sYbQ_q~#BDB-L=_T;d?u8`28URUnBU@;{9>0F;^Y84zM8EA4X4LzV|C@)iA{C@ zpOr?ul6C7}vZQ5RJVXt)^9QaR^b?#X{V6#cZ^~lUHjxb}#W6QsLJG4yR91sS|of)?UhTL=dn=8<6}!4`0EM&YBn`Iar-F zDbE6c;gMUt*4Dqz25^vVah*lS0>qE}rPwo5#L|~%e0pqDN{fq4dc4OS3#q6w>ogHv zIQ%CsHTAS~piT^}f#tza$}h7t&V}nXqN^HHd_|M|YYUW@?n$A#l?X&C>WiJuZpJ@V zvcDJKLG&46Pn(N7fMGg{&g9NQu#XlndhnuIV9`PiYhdIZGfhyYObscd%dPSNWJ+p{ zk7(TJ;!_)Vq|BLV6=&*U27mMV;BW9~5Zeh2_&`X8d3 zkpEfO!NO3h8+XDDu-3N|6`)o!`#3~F!_5PV=1^KVzJna7CNm@#^2ds4!oo8Xd+*}R zytv3fUOGq?^IHw{gvgx*lGd7qyW1620M@3E(7cAn9(jdHK2~k)nWh z2Ns2+nof(oS*=IkHRl^wTs(4Z312@=RO*qbvx5D`W5(5-{qb<5sAjQ+UgCZ-xmK$P zMt3#0z1fI3?8_EPgQ%5v2a}`u=>j#b5bC8KwZ}Ri9Fi( z3&bJ-B#Ws)%O&57i?^rN9$wJAd{!1IGLElqt0^FFCu|`x@#9|P8QYlW7oV|IgB~!? zb`Gk|@FAFvJ?E*9&hHkIzjk@EZhU)2@@AkeQk2Ud6nVA}n2;sSH^6K;hCed#Mf@BJ zEqXsXBU?+txZ6~n+MHs+uY7saJQU=PI;H*AvXWwV-&W*K zy++*nShPQa1$o?j%Xfxr)ha&3D-G7i$1h|f3|VcEow)4%{8Uf8NPDwLSj5daTn$rf zAE<-aNTd&kQX2Ge>DVnDUsu*=&G!Zha~MZhBv-|sHsN>-Z|>Ak2*uM~{)@VuiU8vx zkIV5CX=lsH5-8tS=5c(cE&r2)Rt)^?xNqQuUM(WeG}@`|D)PA*n!+moTw;Ap zFU~S!cKW$t3OFEWxZEJM!gz&DpovkZ1~SJHKNZBpb9y=AJ06=jE93oF4G69(+aE9E zP!rN`GOH3!jgx<5m3`gX?)s!I_k(%(zHe8Ta7?UIRav^%>lCAB<%1_14Us$^+)F$u zl}EC>)XrWn$!+XLL{EIs^6|co#WlBTeLK8LrZVS;JB1ClogSJMF8!FVyBX_PEB6wp z#M3Bee2AR37$#sT&04tDRa-~Wb>gL#qFEna1&JmmMAP5quuzRzMpcv@ff|M5F-XX0F{?r}5gbgQBNOi<59u|aeYzw$?eF0>9k6=|^dnMuV%YyK zl@sUE=XkobWL@(JmyRSD<7}ERG;>LUWkaAw%q*SKAQ@hv{}UX(g_6S2Pwxxx$@=bI~K6Dpxpryj#qQjmxdB3aDVE%^}Aexeb|(FIQvjay1$82XjFz@mOGnQV{h`InJR39aV6T7SHRMk0b4vowE|XMn zdJ1|yW?VLSSKD6^nE~&7im&KSK}8NFzY58FO0HYC!tK!WrDQBaS4R$F!1xaB* zMQ#$Q(RW2%gx}3%XJTo2!tB;9+2OClvR`G7QH_?`21c1Ov(km{myQl)U4_Z{=rJEb zVI=$U)$ubdsR>y-Sa_An$hhJ77w0cXDeg>9y{xL?z!l}pZ`a0SmsR3_zFiiS66&Kp zhqWrLpXU_p>{M$-Uz&yF#(6laUjx+p3>NK&5DpeJ^#;6(BUPrSh|C7>JGpe~R;+`r z{E)cl{qzMOE;wR~lU&h~jwW2`i4gdHmmAM?ou-a2f6KqK|;v;=;zqU zS&f8Uh!D0lZ|cE9U_n{=sF9`Y^x^zwl;sgSVND?uYH5m9);2}1;A)$nTxy4Gw2q;j z;(&9PBGQ3TsD0UNLzQ+`wBRGJQ4vO)`w$rQvg~xU9{ApO7TOF$U zM{O6@)wS4_gy%shI+e{b`V85VJ9bkor^p{iv6!F%@5Ql5 zW;!)8b}kFrM661Ke=6}hXAG;J1Q*?WbJ?P%$a8;@{n;^Z6j`c%xxYZP`V~sw5j7N5 zDq=&Mf-)%Z>!M5-A7R@|f36DGz|Hi{6fR1xAyMJEaUk=lkocUc&n`6^SqWq;i{2Y- z4?rMdC&*IMwxwH(N!~=I5{v4#S8}5GpM3kg6qOOVah8xVUY5}6I-O=isu*UL0z*LA zgX6;a=*84hY3yUWuJ2u!%FlH6$ymPo$@yBkKzf5+i2B5`nawBcJi)kXjxAjAfEE4N zCV-q6D>e=|I_{S3QntjI;KnJQK>8A?S#=7Qx5rQI_A^sPM{~Et+9@sgv2cBx3|)D- zrWq9Qmr^Ij)lK)*F%c22M0XH2=Ln

j?KA*iBM*=$GuaBT0_?)r|uI>ZAv+1LTLZ z;x-z1=BYLnQK8!)Zb=Pli}u99d3Fjy4;0&)Xa z0zNSZ(KJ~L$gJ{C^UXg58l*+PC{Ls#he_cfB>3Vi(VBSsZJ(Y^x?nbkW{1lR8n?l8 zXp_;obMU0!=BFfF<{+|PSBeF6In?sGO1Kbx&Xd8+0wNs%W52(u0O%n>?E*T=S%MDu zrLs%rh1Tt#*|lNAP`Z6?XfyP@ebj;)(q%pZu@0^@V%&TpS1pa!0`lj)`5BE{&hgP= z$H4*UWJ8{K{I-0GAwl~?4Whi6P&n9+D)<7an>bZ7Nz<4hACRo^|tQ**0D=yn#$nZeLg}o{|*3-_c?{>Qi3br6O;O z$a);JFolGTzkB^&{*|r2Ntm%7f1<7M7v76C3E}JXd5(-x>#+)urMxz2{I)@7*a1>X z16dIL5S7;Qc`2)DGr`y<|BLa;cu$?cr_e(I__)+f%LlHu%H<+`AAr_YM&pby3>7tZ zTF{sLIq2Qy^N!tf%mbRsTouCSjY)gRa;iS)CJrtafWjSnFgSQ^m?;C~8xr?oF(2*? zI4=_iD44RG#*4}DB0U#6nd(xh3<45#&kF`-s(e>Sy$~FtzKFXyzpvsYo6Svi#qYRk zsh_p=AS?39ln9cX|MqO#Hkubhb5fCdtFQ9QE>gekefu_B(<~DObqiT{Bw|!_sQl>u zWv80V*Uz-%p_^fQH_YRpMx0d1t0sa2-Xm=AryE`z^<@+p#k6tHR5P3$Cf&747>ifT zU!dc_xJgudn2iSMcKCj;Ba~8QWFHEDM7LdhaD)`htdK&B8+5Rj6}uE3BHwT9H|u@G zh=}f#tDqwCre1%lW{2c#mY(ILPw_g6n ztY6kCo5?{BS)jkX_~G$Dx4=S*??E;T{hoaF(VR7hSIXe>GcbPMlTP`!zMoR{m?^Oq zOP>BbHH@7M38q5sU1H?8*?+ek9)5v^dhAje-M1+IhNf1pG(madxXZ~>;Qp<6<;j%- zr^Re8t{EJiIMxg8E*>Jk*-d$!P%ycvoblyz*y?EYmpT6Imekak`8Iv==T}okf9>rj zG7HacZQQEkNk4&q&NXki8yP%ODxCH9isI`psP?A~U)(0HM5uIpHvSn?{^0K7cwO9F z!<+XKeahp-VwqAB$>rmBS#(|DCir>A4(&_&{QdXzS?Pc6qRM^SZ&nS1F0C#$;Gchf zW_sH{d*e9T$Z_=4B(#TnyzdUJ1sYv3M<8m!xYV|UKw;7>CN=`d1p|2&lp8lO5h^g9 zj*Z5IXq7cLAT(gw9bcw8yhbps>@T-YH}TQGs?p5s_8}M_@F%gg_8}0kaLIO6_E1CvUbh5fPbE zIbu#_*CUT}DE*{~JMqe!ZI^{HNdu?llTgWJLm;wJJUHW4agT1;*hkDaKy zvEMJ>+&h0QKlaGO!mHmS+Jn~C1?B?Q*v{MUF+rI1oAYvul?2UqMtw9cFl2s?dGV$< zdV)RZr(|HTpc(RXo$j{Wnji3+ZsPAbw_OXj*FSndHC+kBHH|kEV6&AMwQqp4{}g&M zHZPjW!nsHKgeBLv+Lrin=O|mf==`-QF&nLcJkmdzWwF=!3)IqFUo1}Uv0sh zo+uUw$K#Q&zSGrl8(rYNO~~Y_l>*5{#^m>9Uncqc8a$T zv~6?Nn|o4Z;>-$}mFJ`=so`-dHa=8k0zXCR@+{EJvqY&PbC;r~q#e$#ZRBtUTdTT1bRP0AwvQ@W0Ux|Dl_JC@Q;j11Y zK6~;4)=ug`COQa9>v}Y|Tai-l44)NwgR3@+xAhZ-N!1^Ve8VA%`+Wl z15q+{U`^_syiv7U%yx(G?8(JziJwJNA;hnA87+yF$93J^I!OXEj9bWhjTrzMI5@Ob<6XE~ z+j3e_@&N2tTr9RCywa2+5)kysd>j=Q^;aH0hVGTQU7?jD|Vaj7Z<>_fMKHSNplGVmL8YiN>!9!b{P`1h(B5Gp@F z5FDvK1!$KUz+93rN6u#c7*V~ym@auaUTmVzq1z#PKvI({4z}?Tlyp;J!o)OdhWMgk zEX=h5;QIIyh)hI!Go(fRG}siBC(~ZnPQ9Ot(|2~5^bS!PMbO3hHWXz zY~vb_SiU`v5H(utuxg0v;xivNvsqh`h-Zcm3JCgiY77DEkcnAeTa(rGCTiP9-+?>g zLUUf$Y={ay7c0Ms665uG>mm5mLL{X|-ww!}0FMX5LiLy?KY2lFDYpBF0jL(n^QWTh zKQR=!!D`Hv>zJ=LhaL5Gw9Lgxp3^z?!pI-b$TzPM|sTL>q&+L zRcDSe--*GY81Jk@>7SP9i$5(bzI$k|0oHu$&;23|gDE%unuKH*#weF{bDtp80Z-S0 z+(o{h9pwo%uSdi;z*V7L+?o9AKB~h{Sk9HaaUaDi7&mt^-!LD@xMdmtg!pTULg58$ zoU}08e#XM7P=m+5KZ$Vd@IpF(vVlqhG3nU`v%T2a58-Q!4Ssv1m=AXcT!vtR$o+oS zHAVWPC*1`(!{K#GJ#3gW>qOGQn<`g!L1FEq1{G9#2jp4JW#6==BO{eL5@oJlr&UBN zGV-=}+%@u}!&b$poAA>(iMy_J_UcpDoA$=7LRFx*A$n5D2h~G;4qb~hT5@m%ir+%{ zhsa7(oo#7Mhu{laupwY{+A4>4RScf@G82_awkwx?Mr`RubF}|pi(nkWF5P~vo9`8~ zk#>v%H2KDKv#U~J070`AcA!aK@tCR6oavN@{kJB9op zc9``2f%;1l06Ic=2rjOo_JYXfPE7)-7~@V4_wLU{ELcczR-A5;;*pe<)8eUCva1$y zCm-29wi+c}4SUplxwHZ zdgG3pyX4pR2x!dXb!UG_BnuAzbqE|kflPl~%57vO5U!=54Z`z(IrFYt{yXA`?eeA) zjo?o$l-7Pwm=WZiIi=t|SaSc1b=Pvvzlavb7s6_JCon{c5N<*XSThXH)-`vk(-MSj z&6UUoEfR$0&&*e?~rVfD-^076^#EF4&DDknhrY;s|d;%BRhLh`pj%+QD zmKrD*(h7gg22FFkBV!~h`d0gX)?hfUb7@~eF2_+wThod%Sx7j&RbKGrnL4JA_gyve zFMR5w+}W#syeA8?joo#xhwpn$ZqUU<1T!nT(BV0-2fu+L7wnJNdzPSrfdq?VR_Hur z)W>~HpwShl++S6uflGL^(ofFlPc8*1z;5naJ{4j^Vf>A*j1xYl4=o~->AZq_8$ z0(^fUDzD)R;y}_?IOLJFnNc-xok-lzXS$%QquiC=N*P=ak^vy3I=YYO-^IzBPy|4O z4a=YI_w(-obLTahazF`w*+y-~&vwE(^eo2de#8D^6-dY}iDjq`;CfFvsas-0XhwF@ zzvjw;lUXfI!eu;+8d)qQ(cQHLNF+hvXJ6?Lto#Mh`DbFJ#^Yo0-tUY%4TCf8T0sCP zD7Pdo?e_--ngYYpaDjSacmZ!?^D4wTa+UhF=itV-H}#D`jXVUrmf6Xcf%{cMH~73> zGgI^9*0&k}Fsro&{A<2~<_fq*pq`*f)zXFuX-&&TzL8{4(3R2l8-SSl?Od_B2Fjyi zX{rNwMEvm*)Bc-i5ph_MZyU4}JK}TjtWU zN$bs3_*`6*lfH2dl;Ll&u8d_>{>P_@oB62<_*iI5D8vjX2Pj)~lFCuUK+p`w&tZ0n z`~*5fB&DR*gZ`tMKx(@e&mYN)ep-!lm+8ti7n0{e?tWcNQUk3SnL4@#;l#*Y@jL{W#j2{1JYj6kAzKsfj#=vpg$PR5<96KS`K4r@_C{ z*`Mp1-5tL+U;~dhxxeQKNRWaPekA{NR1bIPVt{2|GnP?UnPg+;+6xW%K-=y7On_XfWn(J}fSEM;qNvc$U(#V>9H`?Brvuv7TFJuj`w)x`o6fI)K)= z3`1ApSvCW18ysr@isXM`1onG^+k??+$q@dLSmHKnz z>k|L!Ib9Dw<95DvHB9|TO=ad=Ijue06;|gN06!1YY~?&#i1B3S@>N&BYG zKo_2>B9$Pf2%Z>2SCl;h=TZAY6durZ)wKNnvwokfp76b3Niqwe5vBxU*YrW+asoHx zd$#ZyVWkh!?N8;x*x}qOdSY+}bP{M@%Hwy?lP1n5Z$eteh04LQKY8KD7|IkvGIdQz zIZW|cFo!*>uN&$vQoG6;hz5I)*3;Mymw&Iq`Ec za$q@N9+2Tm3C7_ypndZ)|DO|!gVZb zH8kQB^z4TrXzL9mAd24{C;skk|JQ-V%=L&hl6LE#mWP=i?fR(U-7LeW0wSSPD{}Vo7nES%Ewq&KTMjVi#^#ScVX~ zq~dJ^HYC2y4V?=;vq^Be9||?1_MYi?L$je$Tk`6w8euAR=hq42a`F-gvos6Jo`{E~ zKE~=(eRwkVK`>Ymkfg-!%HH?;{VW&=#cvTMn9UugwmFx@*GqpkNf=H8b02qMGkivn z!b2E*KFZS#b+7xWK(td=-VK8Tj4(W{*r`i65R-oNN6b!2 zSqbLS57o;bztbi3~IQrEM$6akZBUViZjMB)G1ofB0P#&e$eH*zjidbuFg!OJ&Z6 zo5-PUr&axijMv|PRz6+1&vE_z_W;+ZXBVJ3QpwNPp9#V|i=!8;Vb52A$)Z$t zw)o0dwT|mj&6}Jk3wGHx-fZq?29czYOAuE42ki(=m~gPEF#2u9<08ecE$4WKbZQZ% zMg>tG24=~&Y_aj$=0=(w!sDH`GiKr_Pn%9=;pa~rxaedg4U6Fe}h{t4RoUw<}M%pk%!LsTlgj z#t_d+c#+}+d5g_+Dzhn%j7@_n<%J3>dS*iyd&AykVU*jO#*+j7c=SfY#&!(q&b}d< z0^?{kXo)nLpC(;nTL~W1!EbYLo@rJ-!RbpDDKIwWeIo*Itfg69NNvAO0P04if;R|~ zJFG!Oau@HaFvIfC+Vy`u3=X!h)5HMc=!nuS$~Da70n zTs%y0Jlu#Z?k+cz5A|yMz6uP<hRCmB&2e?wPH)~VLfJ}-GuzmU`{vY@kBwIz zBULlWEs&~V5EVUf-#3SIQs z6$=Y$qtd8D3fjwfiHiZW$JfGw;%d^W3(8gMR7>tw|8w8`YvYxmR1mTj>5mEsJA=Bt z5=m&w%dAZkMvQWTvb7IGrSDprV4ujqzuvvZ-eXMS7Pety^4WNk&yodC|HI)$auV{m zlUJq|9YYGRe&~S8#?|~|p%-Nn&6SgZ(;7Lel)-ZTxHX23zAp zL$JiNqmD{Ev~ljm7lH049rlAj4*76js z?XGbO8G@^XE$8y~?~@ug90P86_^-I`Q@Z6NXTzkzXvn+CF6haAvYKFWeMErcECM_Z zg3#oDMS**gXOolHTcF65w)+PU@125ZwHHe3_pH+4hA6J@*Z;(dxPRu5;8R76pBd!8 ztizh4p#Nz0&Hs7{*!F4R;7f*nUac|tm(ujtuXgqV6(IFova0(dgYP_!^vSyO-UZiY z=)XQ&0gnK@EdlM%-$9T695TU*fX69@tiPZBUjzE<7*YsU=qG|Fa%tTFQI~-Jm)pOe zPX8QK1M>I-624!3Em&8)9)0>_pgqDdAb3+yRuC%iwh73pM;YvyaY)Yp`l3077g(XW zE?e)n{YirNK-zCdMs@XfJ^X*hG@M*v80@&=FZRzVWQg9mxk=;u_3^i2|#w` z6|a+CWrqe*!z0|{r!Xa#+*qbAiXN}KF5A5O6yz76TNE5?LjEClBy!B^ZHOrJhj?-x-b`ejH(xb>SIqA~j3qgRGIg=?2dMX{|-NLl( zFTYh=#j$z**DQ%nQjjWO#cOj~8F{s@yW@7h)ZTOMpn)3s{%nuPpXP8AoMS*zv6!14 z@vp?te+JX{F=%sDwiUMhzds8M*lnB$n-fd@$5r?L{`8+k_kY_kH9fOCLcs&V%w;(x2~WDIOIlx0$5eH-n3WwSuknWZ_$B2szt>E#FFsPEYB?{;fsm#uI8ju}KvtB0*EH;`8ElkWe1_p@Tef4wq`1_{}KUw)ADH_zcei}}~im}B{C*UiD5 zBq#-3pSB|&zih)#t4f^*ztZFX-c)ohKS~z1?|K$iGr0PR1}{OQ+c$vLME(z3bn!2m zpFx7-25oZ&mv+|VyaCrA&M^hNuiTGb4w3~I`%Q&HkiJl_AO*8?e|Wz%@UmQm5m>FY z(V4+LrPjBIe~#tf9w=Cj_1CKind~q9X@65Fglt6(YxD~W&*?rm#;d#)`v1oelv`{F zI_yjP!%Sff4rUwJWQLy`rj)?75Bv83fs@P=Fa&i|n#@Z7S@6HV2EYm~!s~}9f8`1P zPyXpG-xutE`q1D=Evvo^>2DzScLUcx3&)}u0+Q?mhyJWgsVUNQu&f0bcDS{(GG1z> zCsUi=&8-5gw?bf~m1~rGLjvxYV$UMh{yAoU8;-l8!N5Wxa~oXszX6a?Z^!^x_T?3ktFMLOU#h3h1x(oS z{naw|_o0k$LXaywd$&?tLcy+!R3zfZt|2AaD5_0q;5j&mT4S>5y;oHrX=6v0i_1m> zgtzRSLqOVkp~?~<_7xaSKy$(SH-Le_@>g2frS@yG#AmBee(Gw|6Zrf4L$FIO4W|ZV zHN+{EpuUVEbaF`g!($eyo>_NjkEZ(u051K2WTm4Ez4CLcJAcY;qg?|s=(vz%bTi$# zLj5}HJ@fz_t{I$Zz2R-I(JD}+UV1O?9>C{_bD*KnualfQR_O`yE)EsN5!V0&xM9Y& zjwpVewKi-W?LYSekme#8v>6zJZ)wYlm(0~E;UZbv?w*nfl^GoFDaA6-=S0uv>#9Xc zD}Q16|5_wPqS?Z7%V~W?U6+E!ak?#Bg4L1t z8VrdO9>7kyB?h}Kb}C{c;m54dcWCD+Asbl?7`2qZ{ccMKwmDEu?mcJs+&) zTKcCrQ&pOusRl<`|5cj`KVazA|CF|!_SOW1c*7dNS%tEE{$Q#ZFs-z|ty-S?$^~=_ z)E3~n)msd`j0*xn`)(GAlClP0nVCoDRO(MaXSN$b;L|opPI1rzTgj6mz_|;-@j6U6 z3wuG7685FN8>n673)lRYu1R+oH6DygLo667UHuF*QIZoDCZfMR{_-_EkmYo=xv%BS zmYm3Tv8|$_T#O6NwlM)fjKfbgu6}X%mJ&kkRF`St%s-@Q$2k3_OU)Mtfu*rE8d8)3 z`k)oZxAGCNc_&KOgQC5x%IF5CmI2ed-o4_&{z>e$pK9PMl&QEdTn)iN4IPlF5>I0c zIIk`R^hqz-DY}A0r;8eE%Lk6}Enwl!b0x?VA)MB*D#afUaE+~&v%s~C{d;E{n8wHU z>>o~_T5K|23@(~1b~q(dW_&URGB_|UH=dQ23bDW$*&kKI?wWZ^uqEkp42gGh2=aj2 zioG6K+C{GYtz817?73n^^(Rw4fpRp( zyUxjIU`>8tXmnvi8NP>Wr%}cSI912fRiGgzv0`Y{6wUzH;|@qzbnbnUpwh7mLAZLy z)&I|t8XT$l=#mVe@Rmj@Ar3=>NQ4UCl!|~JIeF!Li|qveK{S% z6}cuULUhT;UhtE;xhE)7t0UTlbcC2Ab^#fIbuDfyqPe^2{bHKa5NJpwrZ;50#0(5O zp1_>Pb)O%Vk|vH$1JheZ7WCilwHSZkdw&Jjdz2|~Z}tKi2k}{w!Pl2UX}b4|c0nu5 z;TzO0jp2bgrw?M_1Z(z6%1gpAz0ovy@4+{_{r|D|=HXEGecZn^Wn>#!vNL1fLUzW! z7Fm*|$i9XUY6#hlJ=tZ+7NP8v%eURR^;m6~ z1F-L6uh*|7nlG)X`=tLF-3GUqr5~{{fElW{AzKna+W&5h@xDKko2`~sn&&OtMhCMq z^^6ug;4)E7@CSODr2v^XHoJieZV&JWyK@Ca1+3&9bFX^D04oPy%0JB<4 zU~2JmGqu{7(wT-Br1dvCi)`Ygxsbx4t`*Ivm%z|*n*{yrXWv!`QmI=rQ))Ii!n&<_ z&7|2x2;Y{P@04UxuQ3O!vzy-le(KuhA0HRwCC9{vX|`ND#EqvbTj&!Va0&FdCD0vcFFA1A>a*5k$(X88g49_OQ;imucol zZMnZ-XLgjbNx%!x54S*tT)M!@Smo-1;~KcP_ODy0WuuJ}epEo%Lg{0v&N|Eygs|@2>clo@tX(wWt-2%`I29$YK%mWD!pLXkKU2Lz4vdOgk2Jsuu zDy93NIXWVa!M&gQA?ThxyZVpv_46TAv-v$d6bZDm^0rP_=~g?VSM#FJN2&O{0&>?A z$h2)J=sYJs=X7RUl&K`9G9rQr*n;6LhCWI0%}0y*@j6ABw=dCF;?Wk_F`hk4OQ{d; zeCB5V^eAg_&9U6{ztaa>P$V!9DN`Dyv0B0vtlE#H-Y=%?bL?Nxb=lV6js0>%yVL9o zh&Y?yscZ7E3ZpUN$+%oygcNSsKcq+fqL zjiED_hI;9GHa(Mi|4mSG3FM5wT(5&WT6wS=bXvd0qjlpC0UA|al?l(3eO+MT5b!&b z%4dDK#eVoNW)7c)>N` z224)#BNHd%=I?L&V^ug_f|b@ldp+WY*c!pqZ7hr3poFy+($d{^BA7BtPB%~%DBR6xc@YGKq0FK8Bk04&3%Fn7Pa*1Kz zrQ(2O{lgcaK~vrt4}ex= zRU+uTC%QS=fL#(_2yGrxV~r^!xpsj&QBn^0_I%z@-EXnM;?iG8T4V0BNmv5QKz|D< z)dAoqB9VU6N1;5T+3J402TR`N_ioT0=*aKSZU5YRqcD>Y+?*m7OXuHx0bQ1@NWDLO z`6{gdo*`u&*^<0^_s!uAT9nV3&*1o!wq9eE;}Hn>mlh0MSD}P{?E!lnK3(MzLa60c z?B31`b`;VG(U&zJ1|}7^&G9ITn<=hWQq^ONPFV_g*r1bE6Es-5v(ZeQBD9I+Jit_jxbT%WWbd?gOHGL(%3*vl$ynFnQKHARFkH zDd7>H+CG14ihK3j+DE`MHjP#&kB=QA&Y~)!#Cc~3Ec;nEzNWENr6d%}VV;K0^apjGR99R9TV8RUc{|~c zFadq*v;DOI;Cl3Ca(~RDZ}fcZ!}@D8`^~c%Ih8-3WlfDdYLxLP7rHvcK7H0g2V2%+6$Gu zvf1u%LEzbtw!rn7Y(;GZd4W!L^G6BU8J)w`U}q~)G&{&RwH}T;e{LfjZSsWH=Yl;Y zhwxR84iYU`C6FIDU4i7h1YruF^N&@dqdC*;Y`R&4uVCX}SUy|>o3(?F$I#}<-mvlc zvRwQVRdnui%n)^_&=N2}dJ430#EIUq&P0~9U6CIcvZy&lixtG2^>7_c6`OS$-2!1T ztMg~i8NTRzXw+sa&q}8SkH?m}fT(8Eb(&ZHIW^U9oMUfm{B@kNR6c9jNNr?x#pq-F zR_v{G{`Hm+M>Gqq8hw(QDbHO2Z?~G**AH*8$zh|koDmWXwbqlq+2}S&Vvpi9q24#j zOqRpuHS3v{``Y?UQDT_tP^tE_ReUNi9^K~`>!Fr-4>k0@O$JGPVu551 zh6S%zNs1f&UP#FQM5uA?z5nn4=;pZ{|KDxRgQif49;hn4mtBB@qW6Hc=ln}`CL z8%E{x3cx(h!f2)DmM3N8W~!})PU0(#qEV4S`zg;Mv0ii=*R4?lFy@5E%QTmqJX%6d=NGyWIlDR;uAw0((SF;Ke#2<_UDmdm{$q@P(JrO*vd?LnkkE67 z?gcLe0I8bTgE*SgW+j47waE^5qr#dg50PkjF>s5IMCpm&^YI1bg@h+kcMuyT9rRjJ zEm2FebgW$)qc5dsO(&yh?u%nR*89;fuFnhM?K^xUdzv3CynI%OFqszfxcRcjj_`c{1`LlQ0YLlIM{{{f1f5b~=Qp+MX62V`v-?#wW;aJ`NDi+&a@wphU~S>?9amB9dAQ7{~OQ%qjAUZEpAsQ_wx`Q#Up zdeUlUhxGkE-XSF%W^)|f@@+PStH4|C!Byzs$;pAQ(RHs_1-Z6HU-O;UX}FBlY_qj~ zYM^h%SM&3X4bDOnT_Z|66>!mBt9<=Cevqo4;>& zc5+qs<-8Yx2lUNZ>?Yp(ljipaXWM-~N{4GwxoDK$)t-dzxSm39%p{ZdsJ?f#l4=v; z1V$S;lmc(H5eYmmy`;-Z{rY8;Qn*8T`SVKe#YDff1Y40<|hGwm$TPGt#QPcY{@ z7#37AW9dxVoyUug9}(BwXl(o;|H2a|6&|&ONS)ovH8CD;J2YBLw3hz%d+qOWB3O4f z`iAhGla(pwl&Ruq+7YBS{q<8jKiqpM_G|0}@{C^pxVQfuj2y47`^S6t$AT%@Z!z8f zvEh9f*{QQtjEOn1`M$of_SSWly&JW*XM%Wf$~|~xZ30SiD8OUoBWfiF>~vJRqNToV zM-?O;O9YZ=t)eIt`Hst|sNM0=-r$tM)xE@=AGQae7V=O|8F4!RMA8eOolq^ekD<5| zNXV8j|Ag>{k1WT?e>wn2R;5d%O8eIyr=?dATV%dr$%rSmI@Zt=NP^<_H{qdU!wmIL-PDO)~*)#H5XxWSVMk^m%8mWY4DVJ$Mr7D$%ZykEQXB7 z{vk@B&rKOV09c|7ce$L|<)kCU6X1*x@@NvYJ2{)p4{VO+wCJwTG!#Hu-xXB2gD5o# zZc&6zpQ+_D*KrzpLf6QtsV4C3eG#_9&5{B;kp!dEAy^O4Ai41K;MOL*oGI=Y;CYQF zL2P%zwaal9Yny%emoObe^FJ039t?#Ct|_zlrX6I(G}=Fo2_$LQn6+)JELhf-x{_L~^><#QyvN zx?E%~lBgz^*d^JNwO~`(5zL^0ypMMKi;^*8C{%U;l1*u9$pEM#5U-1_g0Ek`kE{wK z*lnf=sHjVdKSOriJ_PU$xEph|?+OMEC8TQ{|EIN3U zKZ3SO8P%K!nH(iG#;^WY`g;H7xb=ESu;s||u`Tm7B|O?S$uxkqQ{%%02kUI*@i=C1 zMyQz+%yMWa61n2@;OsnE23eV+s>YjnLAUN+*R&TKwojor)0<^7!nTM!?n~0l9x7>b zIo|KD?LXC?-y;12<+Pjt&4%@MR2nhsp2#cGF7+v=Z4{X*pXx8gg zU<)YIJSLaCwudw*D03~xEPBWFT>N9IU~nRecJUufz>R->pIA?m^ID4y7h zJ-YHO1~jITdObttrU~AR>;CA2y}3QW6yfTmM!i{=LQ=Avfw(9)H^Rfsq}IrK%}vgF z0%{UzDN*7Ku;dCOb%cE=`1vw+PP>U*-gpv&UUt>E)O$JdD;w1kNLk4Uk(KN@9$LR% zo{yYvdB}xxc0b1!Hc>UT}JpQnif|gKJ_WUjxn&s8MZPjw#&t443oITml z7qbU#jH(@|&H3Bsb>^bSsI_?5J9I|{HzTWNF(7bA&wE+K|DC+KDrj9q?OuQb;DjIR zts@X(cSZbaAB6~Hm+j5=YoA>)7(~$el>GqKatp2Cu~7&@oYBPSTEag-E+3s*i zzstN$ny}}ucPD=+$ZW~|lw6=uxDe;{Npk^_i5==q?D9cS#}WKkQ=nC0LY0wv=DssB z73_>)c9tt$R*_=(!uQ(c-;rh~qSC}rF`$pc{XCu4Cw0D3)&X9`j*8Ibw=!qT9&x8`<18#x+o&8&re`0Z(<`pROAslz=E%^ zlk30X$xra(x(;-y$oKjFqbw<(Heh`C=JlXvst*{N*hh(MEl`W8jfz7mEJ1ugyakOT zt^ehZLaU!)I9(><-;Nzemjd@c0^@dKYvp>raBpi5p*CE-za{~V@I*u+qD>mxN6N3A zYPld{J$W{t2OaBHp|sRAR$F{5si9Kbk zJF8^E>}-t9Jk)6Ov1Cky)Js_YUVfICHeBM@3j1DGxA0m0Y^9RUkSe?1XXQU+od>5F z@%>d%fe|FguzeNcS?J2{hSUX!xBV93PDp@u4q)`XnP+{+pimm)J`c z4m@?COP3f?f5}&WJRLmfI@EWVL9+bsNXkE6>i^(Vr)|C+x2^&zuMXI#xLgOl(M;JV zf=Z#7ix_Bx1!=bkM)5Q8#FyXP*tdf~Sv0en}&8*tJxMK1uA zc>-;lm%6!g%6684ls+6nA)jUerHUYG&v3N)cHZRvoi#LAvz8#1cM_zQ6m}ka_01eY)K+5H{1G5S73v z*cr)9P@OK%10*cDXLXY|opQryZ=P6POohyT;)8_^o(pjSLA(rHfl?vHbXwal?RMSj zyG;Mb#VMv`$rf@ItmyX#@xKyPpgbvA0O>GJz^~>6nvFWO(-`-4LP;s6#6@7_XG!4o zi0&B45G9#v4Gy zX9R?=45KfGJ^dka=f6ZAgSVUtCG7A|GdrQg?pNH1 zj_kZ=|L)oY(uLPb>j%9SKOcV8`6S$d(l!TbVEjv_gi4>?35NxV)yhtm{e$l+H|XDN z6Cas~rQG-lO3OW+((yBf8fE9>X47jyBlHCiq9PzGHQd>U&JSx5KztT(1#zv~*-BBs z8T48zOnpLQWP*GRl=iq*aIj>ysf}J({Q-Ocx}c=2n0LJKo#Y;rN`(3nef|bQ;!Yr< z7Gn^3?|YxP0ZQZBKFW9D;P;q2Hhzq)pF-^jv$$Eq(4yT!*y^{Wi$LpUhS6{0=u zBeJii=%~tpQoJ+tHEhpBgzJjD@Y*00CIAH{T(*gX_tDPSg4K#u@}De#ZXOmrSU7p> zhtNWg%ubY?&-x@VUyOi-nyy>pBkDP5b@Zm2MGmX2{UjZ`75pKHh$StaMs&u>z0EL~ z$YnjyvVo->;?U~iqnD(Q!FN){g%1r>{lR9godS`A=kK(6Z!;1OL6#2sHjhd2Hz?C7 z1MafLSS1CNSS2ZG{_`OTuAf+4cz_jhSoJ_^Amu_aRm(+_N^&N~r}t+w#;*+K{FviT zr|(=;nWSZ!Kw;Qy-;1hh0qFGsP0m1sG1Jb>cS!q8HF`*)%sd;u*}$SAkb6Iv*=}=G z%seItIu1GYi;wj=QC7TQsVu+5hL_q=+9q+fi)pW|RI9Kl)62tvw~t z(rV3kT~9wa7bIeji663HrypCVZ}GDnV}|*EKW}>6V?LG zfq6Hbun81H36e{(SQCsYoAxyCS!F^N4mdw->nHRdeJ4-NoEDi0JW1Y_jl$%Z-R|Zj z)(?I;d^)>__70FD?QhjYKc03jt5la)djvUl7CZ}l-j$luq>k2Mc$!Z&lT2Z?;B;Ub zu_IRufZA{@o4<0LBKI!JLEJ-7J2L8)%|?0vZ%z)_A%04do>3Bg2sh}f#(ma4PALzg zK_Rpemc{ING8D{Q2y2g}FCEyrUh075sv$0Cl?ubEd{8NCVUO9G2~$5@lEt;H*X6F! zJ21`q9t0kS%gX`KucsWHL4stq@co04oK2q|9^VzVDEFMV8j#5KZ1P!5Um3w|inh6XsKS>sLuUFamoLt}0BnKW_tnF$o6PQARG3Z%L!a7t%8Wv68aq~As2KN=~|IE!nBp34&60fRA*)CDJs$NwoSPs``#XUhxAS@71 z5$BioOonL)J2iJmHckfdkmkZj^m(Q{1vX>8S~h~FuU}qP%$=o#>0qXj+g8|1GlpSo z)H}1;eAhox%`w|p3D}gSN$wHlk+#qrMR^FZAv^}zQjjHKoh&*=EMHj+-e-E-cb z>s_TlB&Rx-Q>0m4QOU1lQ#o&C(*tc~-F>B@QTx4ByrkNMREPBN+qx>l+RDr)%l_Dt zNKKj%;bsNUer7mAemnQctT@dUeXo4*Ilt)M9E7t;3$;ikwp=tL?0b;ZS=wMkFgxwq z1O<}!dH&~O4R~xpGo4M$i5TAvv8IZ}&MHOfHOifq>&!MD{w|cZh&^F+sc?r>j3bG; z6Fi_Tv*m>XeeXJ1eDezj(}sHb5BWZ%czwvQ;xMz*E)?K_GBU(IZ@9AcvnE<&ZsC6? zmD9H)uMxC-g(U=@L%|X+r9@Irr+0G(rL=m6I4he(Ui6)#`XI%!g#T_*Z|=M4awDcIwGab8ja6v7+|0lDuS- z8Bl%A4R5rH3{;9m%D_JJ#1nh-(;kH&MoPo=A{VIhPUB75^lv_C+@`U~{Hfligi{d+ z=FOI0SJjL3Tp8(v!6|GqEBwCJ`6!josDEUNdnej)s=1O^G$p;VEJ|Lri#XxoS5syi zLpydD(tX3)$r5gXRjDCP!DiM7!m1H1T|ynIJ~3+7JkZ1K9(A!XDk;lWF;0Znjjzf2 zT%BJN`Rly&Yva#ON#__;4Cg{yq+O+I{Q`Qvf^;YL@T?NZX$f;>8q#*M-!RP3VQH;C zsY1=;o0HUQi@Hvo(z7rt*FxVLiReQNAo$YEwoqS&L21#zw1j$0tePyoVA{yrH8Z-# z&?!TXmQW*^=bejrmL`RBHW%$Ie27ehB>p_Qq6GJ}WckD^!-aNUltLRTfLLw zi+*8-S<`}@p>nipg;&i#*gK})nt39AHsQHnW5S-WIwpMS(U*2!5{lRO+Eu)M-^kwz zo8Z(Ji=dM+b)JhNesJ0ij=q4Ix1Gfyq-f&_0B@CjRTHl*;{R&T zW`aJ^-CCfwbzQ>hN`Lg@4&xi%hUA!j`R4r2D>|cljfdZQgK4M^@{raZvOXsJeV0q4m;gY7yu6I!S4hl~qT zY@_4q60^R9X{LUC6Je^%gyK58pp4GzHzcIoukI_m#1=L0P{4a#wu8BMOU8Y=!GR55q=C8a7 zVyjCC$w!q4zx52IkE+v=ioH|Gp$ z?!;BD(<)z$;SLHzsHa!PFKXo&Xml8waId-cOivXE5!=TPovU0CU`^XA%0i=b>whbQ zK)*6@3f~VOU8JF6yU|9FF70WbtxcOMB+@)rxq|7j6sfdjjVIwsh7&@!agnMK|Dt!XKibFl)4d<*)1@e6vR=T4APqzj|DF*UL+M7mCl{n-622u~(d%O7a!;)}&QC*h z6JNQ5^@oeY*_$6$_S~~R)lBU65?ia%NNIu-px*qM={fM!RBT^e_~vp<`ypO!mBj`b zKE!SEkz^&_D{gDGvA(;yr+5TNN{PB2tQ{ktq}~cp$P2;QpaY@;iH_mCkM>{o5a=AP zm=6P!Fg@W3A#}hx$~v#3ikxdf`{(RaZEZf{myF@5C*qYHe7a)jMTY_0AcK2IAS5ah zz0k^td|q15$wh2_sx4W`-MV2A*t@c4>qcMM*UX})eu6epy{l3Oi}}?HB$YaOaY@~` zD~Dpn1u?5lv!LH`-)g~g+3b!pqdbm17)I?zJD{48D(i6HyPKb8QZJy67Fgt|q|Uao*^b^Q*O0 zGtDf8Halg~JhwrhjP*S{5br%nAsDJbs{iU^y?4}J#osGNL6-=DH87N@9T-{@%r~Np z=caL$Wf7d>SQSh_MN$+UQlOcaLCKa3 z>&>>hG&jKL%FJ(sq_()b!R|qUO3dkE>~~&o#JcraUo0rE=ll$Ts-OekH6A5PWwse! zqqbYfob{i`_}g+l`yKn^9X`R#8vYwzxsAASZu?#Un@+|PJ9=D}j+Zk3I83pPL_5mn zUBY4tY`RAw%vf0PI+rq&^7rZQw5BfledkG{WY=(>{jZ2vDlF+u3Z9jWX&%WI1hz1u zwSnV3)$SWs6!T=pRc(KRy2AQe`;oFl?Trq5lWVFxyH?l4l%|!hMsGy21p%h$M@B2d zd3H?b7)`c)Ona5b4N;piKmUEXo!I%1nnJU3M^?PK;Yy{^+UjlTcHj1f@!N6!p|%|i zIdYgokzJ`NQVP1x3;lTyZU}t-pB3b6n%E$T_I#YpgifiqDy~WPqzJ6}=?(P{X`%1${qW15e?YNg(|twutMU1q+ZFH(Gx^Uw z_a@^tuhM3q$`>^by= zo+o*NVfMJs=F*->(7P11*6AEW(_2>0Y7@L6|Nh=9bNYN{wfQZkkcig`Rok`iDtdC{>rdGFAFs=4k3{Tf=@pr;x&K=vy6HPYu@?j4h1A%|php_bm(wqB z${lTwtac6jO}hE>OK*As9ySFH}9GyIpNo^TTk5dj)qB-h3S!1XR0?}RC;sucmdE)=yQ zHPJ<3|J%Ozw~xKea6*iP$;{nLsdw?*qhTSM!fX)Nur#kHT7-g*#tLP#U%5Np8~%OC zgi8NGUx*P8GC1vU2bY=hY7<$2MWvx%^3^NjPzDW-bx5b(i7_)sbK|NU-aGx@bm9#S)5TFB9O5G#n64VqPFO*A*+PZs1ufdZ!atJy$HB5i>5jp#ABjq3GonJdtXPEn?nJ{?~FuUQ%Vy!`5 zsQWjE#*Sh!43my)opx zqjsRR}K+-iMJkhc*LZng2(J@wZ# zp5>1)+7Npr8 zg>lv4q(GpAUW`5WS@xAXqzk#R*E0u_#9i)|o*3&4FDYbzOoQu)PF5 z#(&l8!NH}`3u@m7{8xfdeD0dS&DtN4UY`nl_$K|1OIcj#&pQBbIfmkWAK$-xOucvl zd_Q#hU9JC|9foYn&11Xzu{>4d_3T`1Dk5*Bhrc{i0(5Z%XKWU@+SOF+#=>(CH`3tp+rwmSVQ|fe=e!;mSIj3=k%^u83JvyRqdK2~sqwa{`O&iRg5r&2 zGUu6T3eeET3(9;o++KUV2TnN31qc1t9I}#iWR?nQz&;Tu`#05Dm=y>ibz~UwV46*X z7C#T;WR{CJ_;Gm!&`11%E-em^_-z8rU~`A$O>jDv?4-4prq9z8;CDtnvbqTDO>dxJ zkswHrw+@T>f@mM`y^TGAWO$-}NKi@v#gl4vp&a+6)8=f}1b)*eSv8A&)b4@CvLldA zYXNChBN_?^L)#f+xF>0CM}$F;3arwW3%)+#XN?ETWcRFTs&3a`hCy9Ax2toG1QzOm zxciPq`omST&-S9&Oc1@cuv=rx##eykXZ-kGnNiyO|16J`qAt$@76@&;-e{Pelay{Ih>A;d4MC*YpxBFIZwV>iJY*-Q9AYy4(`{e` zlxZg1O|Z-Kk)~ZxIGh;#!e+#}-=$&A)F6oX1?yZR2Yr;G9Ps<8S0ezrCb0=N+Wlcv z#m}Nn-uGAZc7ejdg!EU!72k2ZR#cHLn!$-#j&oK7@Ta=~`57 z#Y~<&e4opNsmCfgIr@O=|G^!Xi?7|;-JkIS*f(9k>eqQLd(5qyB~xp@Nqo=G@>Jfn zYWu=L|8Jh+9&n0iUH@f+R?ZWB^jKGnVKwugEn9H-&Ens$_zc=gUC%aoDWd*KD*SaUmmBaF2M(Tsh_U$0erKM>|I|i>hu#wnkErFCPO6thiYi_>>pw9|hQFiaBqKdsMy1c05`Y z+iV0{V3qa2EbIQd>*ylIvg#+x5%Sr9K%w}@E1djOZkX}6Mg0t}c=__N2!VSxeq*v0 zhUWDc4INpB)lzrfA|QH-DTGEzh>5cD*^Sh@5xozAkBPVdyUHb)qMhj=wx}9-GUnkc zkgwrvVm&Zt+%dTg%W(6XWxQ5w)e?i0hx`go<*#=cQsl|qKT&EhB>+Feo3>mWr__63 z*&TSP7PAdGJ<>w)aub9RzwmW03$R_&Wso;N0fPV{x}dQC>reBvYs1@sS@vX zuK#qW7iIae9PaD!=AnZ5Lm&YbDG7(u<-lfy7;O?>p5>8onXGYI3KR0Pngq_Jh?s7v z&o2J6FYW7CK|`jq**O*L-(~wsV*IJzqpNWlNpW`$9WqN42nLVN_Y}OZ|i(uh&Z@btx(jL!< z1Pg)|?>?qKT2;S-2M-EUuy6&#NwFv3YIf|HF+l9UvLqq~+${nKW?7-QzMG}bj2gwD z!0aXT&juH5U#!S&5b3mQjw1fx*g>`j7A1LIy^C5Ldw?|HRnuEFPMO%R$+V^-jGe`# z;lK(vs2dw?QyC^m2+!+nCSqlZ*h--w+-#c-zbKKXS{)8oW|^0v*?OAF)dLbzG;PsU zlp(|-YupO^-4&7O{Qxi~bhaIaiU+|;@Cei9FJ~3G?U3oAPW!E^$Z>Q5P^<@o=~>O8 z%z!-ej^LBR`#x6le$#OT#uC|IBG`hvHbB+@=O>fp-a_o-r*>boV?yJ_E8y;_o{q3& zAZ)83y+rgPuCeH{oeF`Pi!CYTY{_?}8|9y)e6AKcrBL|~j*$1NPi?c~x$(3XJfP*o z3)YLqOd*+_{2O3)>$m=V?68KO7A)%7VfLkedMQzYS_$SvsFIT_${yTA$`0=n4jzBq z6y|&sO*2yhH*tSJK76V`H1MRR(6HLom%)M5C)5glEL6yMBjE_` zS85G5v$5p!^Az-Wc4=Zic8Ap~QV$9-&^O21wA~%P7twVOd3I+;BG?|G; z1_(0P1zzcKDB>jdAjMU7&SFgzSIz}t&s^@5FWoA?AY3Sl8MlfED_;H4;kh^+LP35$ z@W(BCOqP4@Z9j}6xP>){x!$Guz#0YvcB_>L@+!x{h-$6G(+8&oIv`MtI8a@$gy}}q zY!phjA<-8tCsrT#V#e6WXE+Jj2|W}**1Q|!*{M=QM^Ed=deTH=u^eY3pl$WENU(6^ zc(V`Q56NUp?;U}R!y-n7R5i``q#a<_I%iM1QH7pD>#rXm-^x5sAk}Y)CdxODW08~T z%YWIcBfDCoexz8C$1x~0;6XsQIZHR| z+p`bBh?SWauB%;T6tQj-xc9}3eche;0gswKVZ8H8w`19 z4;&<}bEaWqAjv=noP0Q8I+w8iFoIW1S``6iPQf48C3~%Xft7oQp?3O zu1qZ+zum(gUa;HZ$;kf6bEx%(RPg1Ip3Yb*p+1_av{>=#9LLB+z9&irHWMq8h32En zx+U}NLbymbex+nY0CWl0}3BTgWYQ5n>RWJ5M48t*GmXnr2a6qc%BcoRY%Gouu$L6SpXV0Dpk z>ICL?MNYGDcqv;TB^_DF9%y4tE7Jpq!%t`O`zQ_Rf068Pe*u9aVv$!c&ppuVaS=jQ9I|{^AOgb>k&w@u@jw~ zRv}d$IsiPzw2bhkLn@*Ot=U+Rbo0hX%I=m@oUxL@b|(!rm$!CB4ERh^-w&O~c(rvrppMbGBtXtfW`2f&h4wCaE%OrHZB7r(4c zPQ-=SFg18m`xa(P)8wtMs<3Jk&gMD*2CJYs+El%3Y6VB6b|2u}@H_oqq`Ac~bashS z1+EA&FP(Ku~7o?TvZ)dB&s{Rm!qx3PCKPuR1`C(FF9Xxe!2 zW)DVB58T0ha`&I>s>06RTREa~l+4BSc1>eJkI~fxg(+duc?_oYd!PpzE^dln>W)-Rg1q ztn)%+yeuXd$0rf#PEy>f#4LUiRKh3z{0TaMFVf#2Ot?L8@7Cx6YD8v}r;2sBpx46d zRc`ikp;IUyU-wPKc?r-?yKd0N+z%x)boIH{LYz)YaLWa1jaHA%GrbbM*MsP5kKu1%Bgjytt7o9y;+F}|I8iq?ku@sSR#tR1z1 z^@CvDNJ?UE@d1bbuyOIxjd3}{L*|&LFxG)w*oU&)di9YcVGqJ8QW_=1%lMs5Qw*Pe z`1D1M`E70H?wuYGcJ-<6A<@EpeWH^k(<>}WCZxg?WkK6Ezm5BAT250GCkz$lImEy0 z%HA%E**bwdCF6vnZqJBqTblbxB)Zl}jkPXk@u~AVczvhlc=gHe!zX#c;@U@_^**swMn3MS6b>Z_>53!>gDVxiin{o?K%Qo4r8iOS z5VcG0@f@Pe&4DchsWsgKnMfi*dt1&vl(daul+<}7mW7X0En}z0^NsPfFmzsWpTp%w zOxg<#*}B@JojCr7i^TdBo0e3(yGRF%t-w@IUK#Za=$N2bs|j%ErO`R}3wei7pi zyo=6f+I9hr2HLm!J=47fKWqy0EC%b4s3zt37pl1TT`!rbF95eeIIG@Mpw!of4kqR+ zLIuPk)v~(@zk~*6P)13_Ws#R@vd@V}X*ZqETqKjUoxJtV- zd^+NBkjM&_V3brYPo#GX)kDR%ADq^PBs@Ml#<{CKgZZ9uohC%2wZ~Ftdd7(5{I$br z88$Xi&?Txja_bfd6N!RCKE)ZQg1{Et0Jw(g+ni%FA?CTleK{NR^J(LHP05{ zem?+eD2M^1WHeeR@B5F=h2m>dhGXxVOM4nO;3IEa(DC(Uenk1W8k?FOzf(G`ePXy6 z8QS~8Y^8nX_J2)3Tzc6AgFVZ}$mO;?J+y^{LtUY6x7IAgA6)rTIJ~7qyPLfC(SolU znesv?({H-_KmKEe}9r|cXpy`GjgoGGcCDp|aa-5j3I(lBq>>%nK-oTtWc1y|R3O%v4PNzI=gyub9Zt8d>_T{Tgy>0OY zu?smO>^cIyvJuz_nPcyG(NuiT$2=x!Ek<;}(T}&g>o?oY#v;0zs;8tb?yzC z|F*y?_Ims7d()=}SJ?2>>rPHtE@&1yRHB{NW4c|GP#h?<)k+wPdUH^oI1i3(*Y4;# z&@_jar!P_MaSY$-W4~?}$pU-KrQQe1Tt_EzWE~#WK&1j?lW=bX9dNam7beymnV#@n zvYahUl4zq3Z?Gc0*Sj@*(am_fK9s?_)ix6Cmx0slW0WU}FHWmtkCQ^duG;$uAlkRU zY1?EIM25)6lfnT?e*Y3X%e%m2(SYKUaRdpw53K03*3l|EDed@2DeXJ(cxD`WIDC(4 zC;p9;4lNVn_V-QZDSanXV9{fU=9m_vwe4$CN--pFgX4~>%079ThEpvFlYe}+>7Es- z(r5VfYj$00bk+2!&<$~+WLW{8b=|QG=5Ct@QIh%3(15~tc9Jce)p|8Wa^OuL8YPwO zVJ3@t?1Q;R@2lhd*KIpb%vRDiI5x)?#GO4>m1h8B%(kLeN_=-LXfeDZxXyyw6(VQO z+P&IyzIpoms%a2bhdc+Lf{c3Goj(#nM5Yo6x0mtRoSREB&>`-|+#3+ic^RXt>Rdbt zG3KUyf>L40596e>)||^dON3(q!6X3UT;m~M9=t-fEe z2^sBEXLO}bnf}xsVA+xSnr>6od^8?yQ~Zc@FcPZGV$NKI+oZ)dnnk2eKx9I;tEj+@ zL*Ozana^*H$5w!TSp2W1Db?5QdsG6Q|3Buw`mf2o|6dRUqz0&TOG=}}7$qeQ0+J#j zC5^;D0TF~DrP5tWcY_n9OF}?WkRBld8(n_i^W68j&pDs-2Yi3wu?N?-YuCG9uh%m| zH1O$ck3b4 zHwv1=1!k1#_%sl#+;ltB6$`b9VwRODPO24f45P+3epL95ZaMoy7SqVeRbe9ptpXKA zjw@g@yYY{Zb(kX%2&4DQ{CCSrZbs5Gi`r@!bwpfZzk1Q8mjKyM-uC$H5iPrjk2P4dSIZ)gd9>S$Ik~V%t(zI74hpZ1 z)(tupfi!lqX=+FR*;a{t?u3 z@ME}p)o87A1F$iy9>nqB+qL=j9o9FjKi$dpY^oa~)dd3W-*b@i%{E(W_Va+$+ss$`a}A)aE<;_M0lZy0 zb&;iL=F1x=W5I}6g|LKApW(Jk{vF}H@?4Bt5hXX5f;z6s@FC6}?%%^#2(h&P3bsmW z=bgZ61TR+m@HQ$<%WrC1#&VT*$H6MMO`VShCU}!Ual_zRKz?7ag%?0jhFQ<&oGnm) zoS+eVw~5K?H5@7NH2Xv{-iNlg`cBA$9!1m}N_Ifa4LvhFSU$-*FuB>Lags8j&;jQgT36ERo*(z@U8PSudwv!>7G_Z$!HX z{17~6OicC?SuZQ|Et4%HNd4;>5=A)V+R~RPW5e+18S2)V!`AWrhrQLzgO7K`x1ZT@ z8{O~hvyj2-h;TS&G(OvE+W*>j^jWO!v5oC^3=oG3*mJ+mWUffa&E08<(SDI|Nr{K=4 ztsNP{#wT1dDfv#@?6#k$E#njjDwdx5skM=B+AGcHeq zXu3_9t$TsgPV~>X1cz!BhSQ9vl}TgG}?b)1SiFxy#fw1SD(TvmZNplX_ zy7|dn>~8CydDXW4u8pV4`{mEP#IpwmALTOQA8;iYv#jG8pd?EO9}+3HtzYmL;uMM` zg6J^1ibYO50;J6`AsG|ql>c?tE*VZcgUrpghzH>clrNL+3Hx%WUSCk+GT%IQ)0~7+ zR3fw^fYayvIH(nF-)??%l(HGfO{nP47+f&fgMt2zs)DjrF9=HGl3-PHqZ;m$EY?cg zyg8V!?)={!SLh}YOI1nZN=gqP0Z$yJ@MTCMV3aQzQ=uJ}#j|hzqM30kdH%Muja#jjiR*#|Vv{i^2r22`dS=7-YB=4k4l86vxkWu9kGs zyivY$t$9epo_3hZDoBSF&P>4>e0sQHS1mU>X!pI#9C1P3CH!E7_nvO{=d^A-jjTmH z1aW9Rgq-b0Y0cd#P!~Qpg>OYG?2!HMNM`{df}F(L-Nd7_3Z?vYlmANmRlA7{S^TmU z1!%jo&BO$tXEC;OTXm^DV`CH+KVtV5WTUjAoVK(%kVQyH)j4*ttkam-IsLZ@{}S_a zLa5{_=7}G+b7GP1!3Aw$dPe3Q6J)G}3UDj)K*})4i)~IU>akTN9a6jvThAMRWeqWq zd_UXy-z@H5R$2`Uqu;dr(2q?dtaor@k)hqDeM_b7`Wd%BJNn*VrddCNBPMlZg9SeR zZ`Ax5Q|$@|8U3&{Z`GkwO%wPq@G-U>_qAitzq~Q&Qw56!MZvMdk1fc|*G6CU|Hk8g znE)V2rxC;BgOH}`tz8uU`-T4m@IjBjb%Vu~(*L#e-=1XxQXzNKN$VYX`@cQvC%htx z3OW6#)OwMB|8`4-Pq<$yB;#cu|Nip|lr5sM6|b;+(=}42XPC@i8UUG@QTF3Uzxa2J zb5KIK4)lpsL{5H;4HVrD1adyHU~~xjf*Sc7P$?>TBqm)wgJozg0Rv5SP$3H>jaXdc zG@6(J74#m=pll{z^WnnGH?NiEhGyZi9b2){fg>z&9tIHpJ)p!$eV)Q-n(PI@$lu>! z>*GCJpm4hcU<-_Kk0nM2lxUl^rc?3ks}FPgQ@{!2N%bpflOkT{FK+WQjtB3VO4+0h zNBNg)AZlI2-3f z>02+#`3VB7h-=j+T;l6kRqZMuL&pgUwT4jD!~T7&!)Iv*e*p)ED+rvO=ZGL&VNzbtWkcq{Gm97l9e~B_@-Qp;F-esPdC%KsYOY-u^VjKG z)kL-5*=72Ry}b{p_XI!Q8wOyj>+%da=jA|{B6vkQ(dX=oGFHfHo-ReiMon+*L#t;& zVy^KF^Lkevn!O7B9C2LRPN{BRng5Gb=fKNucBa(i%#DU3%3O?#)cq+e^= zfocI@E^v~#V`Y)w06nw%I&qA`9ln+s@ z3x3rjY=xDV+ODE6vHmPY4G%7iOp`T55$!6>sEp18{yJ-u#@T5VdnGqBp6*M&G`;o! z`=B<508Vr3I-{h=!zxh1C!OHiY8GYDB|oL%T`r-I-*BH%OwS}Dxjs~nOOb((z#|a{ zrPKSZsTZ@cP6Slv4Gt`9J96!AnPAVjuj-KxCyk?^fTqjTXNlg6oAEI>Ur%ePiu_vp zE9b-vfF|2~4g3ZKJEK;x;VEMPk`t?hVT|NZj-;U&7CRZ=Tnz8KJKkKJ*4MPp$Q-j$ zyb`q?_9=q(vSyM{_I&9J79n_iwbwHVB55L8M1iuzghr{s2nv~3|WJdxy=Y_v;H%h_oSo>;MNT^P2ZV) z`;LJff=rpWQhWBjqa{YyubXIepQ*XGEcVUfACLykO1`nK#mEK!kxOX9w^8BseW~SB zc?mq#`}b|=ou?y4To}nuT3)6-?zkRDt(9Ubb&y`wWh&-xHpaH>#PtjQzy`yUYJO7G zlwNl*`guc)^f`eEr zS_^L|*9l$=k;;$U>0H7V?=k?4fh7`<9+d~1DLQ(So{!%ZfRVZN&peFPOtIDV`~_?* z1lu4iG`$;K14&6DB&1IO3@80ZMH|^p(J+RDD@nrL$^*Dmyr}P7lJ@>Fp)A9|Y|fdM z3*_c1E7NF^cOYzcf>E^Mj|Td%v@k3xj#UEF;J)~XF$bYS?@o`kxcIaRu94|MZz5RR ztL2w?A9=p*4#ueZw-X9zF)^szS|Eg)g`7-F<7_A+yA$0Wk`7=N&U- z^i^lDLeQZ2HlRy5Z@jvg1D{I7kcf}CHJ3crVkiP$61OVyPqAh!7M-WSDQx+C zCa}54rc#XL75)O9BAuc$o2R8rwt>r@104v}hB1piNF7$GEZf}nT}+}bwU)$uNJSuj zKJ2$y!b5;F99kHf<+0#u+ne1h^Wmsb|8awHoX15F2!r&8G!WIajFy7Zb=GUUF^#dt zxwJ0=z1*UmN9?wVCDBc9H=>$ur=DSnJEKpml5|nN7>zS+(=XnstYjqa9OPxe6B6S> zocpxn_nfHB+~n=Mj0gNmR%I5Sz7%?t|Ff7MCDRCi8(r_oFg4QOz88qr#EZaSjhvFd z1d-df?Ki^7Z!@iJ+(xY`FdyqoggQPufgSkxhO~x0VMaQj5RzTe5#rlxG#2d~tQ7(G zru+Sy?LVN5^y{kj!bCg*1&&`w7oG4thAEXxr7NtHny0;t>D+4-z3sPZer>CvpZg@T z<5n;8c(IH?B^a+SSQpG3(+;p2|Gc&%4)G@-74EMkr6JkyfW78%;EFG2xx#}LGq)HZ zh&J6SI=A{I*G@7NtGzSd-0ALt^fGI+$?Ssq!Wi7lRC>P|&RvrA#StC!ZG}^4IQ5pC zXx$lFdN8?PuaO#8KW)3rH(ZBWrWX`U)lROHk_l5P!+5W|Kh?+($Dy*<$^v5sM&7!`(4Jrhb5)w@ZI)i< zBg`{(0(Ip8yjx@a5nB95=y;)X__GfB9K?=JpAc`HZzLQVmn!Qbg5|oc;$7?C0F_eL z>&p_5JMf`g_GBBI{TR6x;m4xgln5g-R0)~NShFerBF;-BM0!Fn-8K9&T(6>hHyBEu z0Z&rZ|I#Sma2rqYX0A2W6YCmYmDhW5F9-(cU6I0``V*JVM~M}QnYt;cUlIn4;v?-P zd}zAl(iiK`K?GBeQ<*pcD3EW1iEvZbh4$=+->O;~cMGAIb8c7><6c@wgFf|I>KA79 zw#4hqW6!iBypwr6n%}xdlivqL@hM!-YkRhn`Z#he<+AM5Y5wtBF}4A%<($PN8_xo- zAEQK5g(q7dWlz88+F!ky@wiu5Bz}jUU66K@h7nR}@c=Zzy6m3dweV5%D(THMbWU@v z2;<|YXC0`ZO#|iO-t_|YrFR>om6zsma-HXB2$!OUI$phP7$NwWvnkd}cQO})?%I3x zxC<-~Pr|3JL$B57jY`#Z_5Kib5?XwqtNcUOc3>;V>w&Z6N$;a_W37`Qg3QQ$%!{#5kyo^Ab3y&V>=SbP7_Vr1!(DV3Qa^@A@My;hZ?P$#l!HHzFJ|j&To}wN(lVF! zSMS)9I=#u>QRW3^t;`+Vmr(7KE8|4*9MYv#nN?j><2d`7UNePeu*_AzFCSh*iMIgj zTJ$NmoI^Fd7(3EmOmz3r(mGQKieNQbS@J&dYz1GLQXb(wP^o*jxgx2pkR%Z>qwOFn zaqdamn?Y>;*^O_f%Rt{HuVgV<&Z{BXvEehuC_g65TA$If`Lq9v&4Tw$u3ekSKOOjj zEuVh3l=Zz=cg8=>xx6V}o~AUtk9=B-bds@|c1dr`8JFy$uU9y|$2fkZKPe4PUp0i?Jbt_yYr$VT}FAmYX5kFy-UXGBmUK2s5rj7%JA9@!A zp+x)Dd43{tAwmlzE6YciJJ&$0^_ZbK_YK$bRlMyBAwqd%kC2*^XNyEEf`l%Y(BIg! zU8X!08?)bjKkYo>lM)oSb}@oK?_g{$L9Sl7OEy4iu8~I1Bp~r1joM4As;<semPp2Lmxo%Y(# z%o+5acfC&6PIu{}`ya2vbp$g2Q!m6-z(l1pB2lH zUCj!1`IK zL@dcsmC^QY`6*E)`HtI+Uv zPSEj-upddn)~&)-(t;8NBN`NCBbQ{63~-0rGkPE1Ne45R>_9*PE)Yg!ACuI1u z>XJWO?Vo(yR%tcLzu#ADHu~~se(%ne#-I9Hy)BKOtPv-~2REkJ)?ukkb~g1i z>ALlK&t%EBguaW4*=O?P@}@)}VL0JSu)w(=GNO#Vo1IQfRZ>SwBEguq^{pt=klC!6 zQ7Xr-=Ec@~epEt1$#^Coo70oMCqC~yd%kA*{%a5qqpNjQB!NLYK9lm!)LiG_MGErm zZw(J&EZN?ex!&ogXdC)(WfU~q?OE1+!FbACX#--)8OK+oTJh50bDo{O0G@AB;`c<%{_Q>@;;oeI#~*Kc(Hm$}s<-FsA5g#*~`kbC^1IoGLgiGM7Y+6wlLdAfJ8?X^(_U&_ApH>o#;_rkrS$;1TZ5geiJ zG1oPxwrSq#Y28~H7=n`pJjc!M6U)M1JzfwqnI>h|@ited-HE7o118^QDZny9Ufr#|Lh^U^YUwKAUu{#56wp$C>az zjI|Pro>bTcUfoE$xHf+q9|kbd(|sUQzs^3Ch)Y2Zpwt>C#$Y5rfUxxA4Y`gU+JGvKRyGTk8yxv*JDz_%4dZY%pG7OCuZQ6?^_b06F zjuenZBL(#R=hw@=Hfm4d!7f_ol8jy@5q#;bnJxu|Eiux6Tqer#-0_1tt?i4LKNx?? zwLaY49ig?X&$uFQa_`+t!cZ_*zQ%J%x|7DoZwz5!b)z2r!2dcwG{EM^l3~b@wO4^8Hz14BA_El*t7v0_JDYrjknmkZcqCfUZI78JP^18AH#{YYy@I_0eKH_sP!6ik$^hV+NkY*S-G? zT6Gs2B0a`C_CO?u#$8#H3pQV?LK-a1X38j4PGoucWf|n(?!)9ga`GMV1c45U0~7-F zzCUWXu{-uR7+&e(Y z95fm)XtevbXw5#~L3^)^bQK|MkoM_A-o|>%*?tLHxyx$Q<*r`$I~=By%Zmj%Y!Dmq z33f!U45pmRn9UW3##o0(8kP83)rqxblF*&T8%g=OJ+&bQlXhG*;p%>1s}Kv_Af<@) z4%>gdkNa9V&rj4pH0$$&jMrMY$-_%u1)=rGnH3IFg~X%9+5T51&^q6oI9a8jH}3oS7k@Wiew7s|cXzZGX1&&2Qk8#@?Je{H zIk_crpt7uxK2Q_^%XQycgrc+sZQCWvznPSIPHBthH-$#PVhYoEeVWq1f7bSF0_`dV zQdOkZifcZ4)u{T~{HMq5e)o8W&o^@yTuvS4AOPmMa#5a~s^5+6(W+)lUBt-9vun4) zg-b&imcx9=mAmB0DNU%gc@&BX1klheB&O9+dLm%}pIqJ34Q_7@^VE&)BJ_P|JO<+X zM8fu9Y1cxx`tfPrcEm1yYe`j{ab7BHd64qrB63$QXa405bVZW6@*ruf3zacoo{*GN zQ;KXvM?X0A%7}X~7H4VSKKlB-l&ibGAiOW4B32Bk>o#xqLuNWQbbv3gJ&U#M)$En7 zX|~S`QSC=>6%zJBYah+-1X%Pk=aAc|`&@QYiiu*u!0qlxM30l!`lg?FHr68Vq~CE( zJuhv_fsJg`nyn3Qyx)5t-E<|f*535NyO~gcyJ1o@(?-aeO)s&}hwvP*mI<%(pkD8y zT}rF8qf1?sh0EbGE`2+6CLljX?<{GflWs{*H0sHgS{qY3wF>|B4Yh6ZaFw|22~AEI z{4+7tIgLQycBU=LXirkW5lPN(*aT<9^rN-%ol!nt&I&*$`DOm@q?zOUyKrvu1Dnu# z+nFcEne}O2#@Iby#wsyBf2_l~lE(4ApA`7~?y<{hbou4^0j7mFmGTiV zm>Y}6a${?kZ(*#aT~OGxcjTh}XDX{WH@6E+ja0l^xbN4q4Sx0G zAjO{pJ2F$rsBFr4+~6oC3p6^;qD$w6=bS;Eywh;M$Uty?ksB)Ld9;POwA`K7^Z^X? zJOdGaZ^<4a%}z&J@1!YXkgE9svE&NI3-!%#R~sTlrwF@??iGg$*BAR7@YZcIAufY)m}~XS%mwfcdB>TN$>)(?zQ{h-E85U zn`P~5hXMLtnLlz(BWb*ER5AwCohS%tpq3q8RZC zLXkxMjmo`y=L&_hcJZ9n#o5bbZmEM}xy`d* zg)=cQUg8T65Di4ux*L@MsbwwNYbHr}2aYHJiqRiXX*GqSEd zYmoV|&rxAoJn1WJ3b^`t_w)wvXALM7R+DN()?=(bY6 zqTUb+0p?C@5wfkZhsDh{*B*A>9`5CU2a}4?fIXH}fnubyQullN%ROWd*ThEBul7Ej zjGel-LE{OgF0|2^ei!Qxr;1a2kj(uzXn51*IwZd|!PN+KU4Qjnrb~xk$ag~P>q|ir z#fupcHmb0e3E_znDeb!;*>h14(u3$M)c>e8T}cfN!QeN*03T{Cb!EV8?I zf;b%T7mSX|bH?N$)@n?f#O%W;g1rI4GL$g&t4qgGVcG}%twnTvdif%}Xf(KLnJ?CS zocLhS^S!od`4j8cu;OoE4_@A65L`4?_tI%AF?z6Uwt5u2&oV=XL0*xKZMPYE8vi)o zvEeIb!h#_t+EFOaVCp+`>MhdRIo~-3!C<>;hiOc_(&oeE zVUFGd+ND}Rp1A?1m2J8glbsA#Y4o1;MX_9S-_$MlnyF2#@9mBwFiuwqF+>VlB?cs6 zdTj9X1}M81P8WPXTve94F`^oUu@@`fV0Mi-O!){1dvpuxv-|M$4lgxlVlPI$nZD`f zZV#k!gTUGAqn4K^6)t-;`bm;`cZM@g0)=Sit`f@!Drk%@_%oW}4 zvxn$~HJS^-(W!~oMx~%(?gZ7nGQ~oa7+$YTZw=>q8KsP6A~nWkI7M57Oko=hD$#Kc zC|6%T_+Vs4MwD`D9av-fZR6{}YlZ&Ue7$Cb>(pj_l+eWX_ll-v)GB6ugw0jFsM`lb zQ}W4%>s^=d;Jm$2Oh*bCT-jucdJHKx-bp$5l9dc7yYr_hnc){9_%f4l1aCJ zU)|eLWxJQ;7#&lvE&N6#$8EM5`3>**wcG-xq7dHbgN)O<{YAR;w<3Qio=~>06d=m;G?vb4?W@aQY zgvm{BMLrRJg#_bZ!yq`#y0BfLXbi8&QV2h>PmUJAy>Oy9y1<$c=8c}SObtyY*sRG{ zkUBvHg^)Ev@%-c*ygsk(Ewwzz*y&jp$;=PChzippQRv_8eDhcYq`kLm8HgfxWG2aR zDHpDIJ(E48Abu(Sc>U8HtDKB)r)d*N8H#SJJfjL6j-eM95QTU`1w#$K!55>lW!Q{# z$=2z}Q$Ob`u_5bDt5P$Fl zHD5(!-Bx){Fi7vxz>2J04tiZ*F9OxWT9NB%N5Zzq_{a5qMUcdURCsJ=!E1O{o}Cn7 z!#j5yzG$aO2qQ>g--adM=H6+B{TNz{SnRZyUE?A5^0e}mSO^Ng1gwY1a|qR>5(He0 zl6GQJ=^3xmS)vytxNjh70+hO71I6iQkE2&fDwvs;A{boFmKqp#JbL?5;pnA)YjXnm zX-7I#%h5tiw1gRjY=bJ=P3%ne!CcQ&G^SJG`T`xfXQc&>>_8u&hJD>;K^zqNDneo3 z8*qx4+(|rJU%s@@Rf!?XAxo!d=VKD{6;*(-t<$mHGYeQb00|712DhIS_=k&@NsBAQ zb})wC1?SpG9*3QUMgHsQtYx>Lw{6hTn7$O$H!R6rp60ozxdP!`5j?#zNj#~L(g}SR z%|)+vq4_k5`Va}*G^ubu_hY!stU1P{yf6WCEt*WEl`;MD;awSI+Uf_T%iLs z#O(t*>2p)r(ZjP>Z!IS#GF>wJg-CtW1&?_7N^8d2KcTj_ep1YMdiYB#eXL{5q2XmZ z?Wv~qyJG7@aAQLjFla#i>C!=n3EQ zNHgPN;5s1BXfaBV8S^fm2WPYyD3e59jBvl$R(;*bO|5)zyYv9ZqGg-u??ujU_VWvS zvw8ZG%NNfLIWnu9eU2q|P4yTP^Gs$-nDkUAbR0J3odBkbTic?JHiNz6qqvF8ue$m< zc~xP_seuyJOK{mC+FUAC0$Pfk5n%U+@ULQ?6}k!zbqg~s!5&R|rVpPCZx21|Zf|p+ z6)MQ_eNDE-j()2F+_!FIR-J@VnQK=pwB+l#>R0zpInk1*qLUUTtaF*>?>~3j#@Zbb z>h2uFnO+AGNa3kLwGkTqKS1tFcROzZ=Yo5LKg-JY5D%#GOruH{>E_@sGgQ#oG9EIt zOnS94EAjb1k%bsSDH?JJ;T@%QNAn}2fm%XQC`Y;w9{D6+c@~%wZ*F5?% z;Ne^|ZSGw)`)i3ymdHk*zP!rE!(k!VHdH zBT(=%;*3_nF-oAmRo|>FZ|^-+Er3jQSuW>VZ46LU+QeTr7a+tAfQt5 z@a|I{{*BQZHK}_(#toNJ!ogwlUDYyk0LSrI1S#gK{0lgUEA@&UF3 zd>H?G8-wWluU_M&RgSWVwRWbK?aOUaS9D)fN0Gemn7-DB{zQBup$AP&Utt9n=|o3Bk1`8c$3NQ{#L}Y|r7vr3lK_X(woNemDh&{%=QZ(jAg-E1Od1=6 zF0*TkZs&iXu0P1jwe`Urevmh;Q57oYUNX=VWA`!roAOC|?dWHQ{7Y#b}&egu`k9za{edFH|QYud+0I4@EyhAx_ z-TX;34X<4>xNqoXmBbM&G%OSsr;CN|vR$EeAGbEcH~p~Z!Z$Aj*%!q5a0_|S{s)Sj z#fuY~E^Ib9#uSvj4;!2P;^&CPg{?Ax&#p6=qP`K}g{BNy#bn47#K|cBfrVsM__UxS zo&TsnI_B%+(SZ)^&5mQA5i}g2d=GR%3;&6o``-(>;Ai+bmo5+f?>~Qin_M7eh1_T+ zYDVQxK;>W2fEKuVXpnRTLVrMp%KfYESl3~Y+14q zc(_u@1d206EvAS$o1j5`h2*FWtI5*V=r{j@`VxcsEE*^L(YZUInR8+_pV0t=bY&p< zvHX1?0CaNMb!3YOll0Y~9E0BS`6(0dcN|^NPe@X=)cdv456InN^&SAQi2DpqlRZGG zj3UpM0|`)OvB1VtU=om=-^AZD;Sn_E@53r&RFq>yd5i(N#j&S4Q3Q)9djsG{HyQz? zEEbq=NG4;^ZiVo7y#Sn5e;q*d9=th1Wy%6G7A3mr6@J(21->f)KBgsr1!|Q6(1MqB z^CXEEUFFa8t*3TX06lh7eckI9>#MGkGQaf1kJ^9jf#0t^gRya0`NwlVG=N{>HG|RV ztrxV46+*%ao-G04-$3yJ75)>s3sM^;)vfMWrN(6IXvs%C^)ng?0Gsg&|9NlPWAFO) zJ>FRFWHueF2ozCQJ-|khbpHepAulxWVPpkBTdy|&!4>!k`l}%3lf~G^adfNXU)j@O znCwag{oEfuN%u~S{so-x?f^fnk1sy3KHGcAs$Wvo%ZtSdCVrNA;-ezI4{kO{y6_$A zL~sg}h%V1+xCbDVM}tKa1{XG^A4FnrVa4Ie`}eK_^?uE)_aAo0mT8B1W&vFO?y_L2 zd9vY*)^vWp4dzCw%5W2ffwRQsAuC1Lzr2>Oaeyk01SZtYDQEcET!)TrVzbdQ=4<6a8<*%(tBj$x+RYGO*LS1 zhz+;H!~u5fH@-6i01=m%e;uk+0mFw-|C0JyziJZvx8K@NEeTo7RNsRszy;3l%NJVS za>wRG(&M{6UI9NsGb8Z)=pmL6a?i-}g|Z970SLyg1#AIvA0pwxhi;AFb8Qt>;Z$6q z9ldtn8r-wBC{b%bnpqQ}9WnzTaFzwMm{>0D432%OHJa$o3Uy>ufnhsh3ZRZ`Ewz|@ zl^HF+rzL_`fL+Jl{tcKHGV^{}1MS|Ow00pPcd3~GJah?g#r!1_&BW8XfNc_Q+6bI} zrEhFzcYntf*2gWnmm_kp>zHRj)!%5pBT}t;`ynnkiQjDl^$MdWE9}-O$IkUL zV#WYTPPy+7jBi4s4*<;h8w5cHUCm;-rjZW4_3-gr5WxT?gl>xq?6y$ggF(&#zSRl> z*@m(T_70AhZog_>m1D>KB8(lGu~_?G`H&_6ReElBaRjU5)qd?Vz5Z#JVcItC6o6Pf zrzV~ii6f|dR@HGb*QZ<25`1|cF(57Mv#lmyq2SpTOX%3~mT&Q8n0TM9TURQ{&UG=Q z!$XH4!Y{bi)39KwKrEmO{r>w|Sqt|KXS{N@hujT>6}v0{5E2fK_?yy^f+(_u?}LX&40;FCWHhq~jH zpg#q|!x%)g8)>hpq+rEB7!1=`9SZtH$_S9%5CTi~;=4Yz&VFjiv?igtd0+*tevP*! ziA;jPDT<2-6}^^XB=9b-%usG_R@MygUi><(Mhejb}TkcB*boee)xM^?xWQ|K_|Nhl- zYml4q77BNB3>W3!G*PPaWIuocM)_TftD*{hyeYwb5am7(o#ambRmzLgkq_mjf^VIz6c z8WGf>L?X>jMyBuuQhI2eM6D2UbM!{K;g|0=b#_LSZ9fhv=__4V_1UwwyM|a6YPWBd zvE372WINHPYiF|3P(8JT(=tH57vzJLTKJTlm8e*c<*wvZa#6H+@w7>P&CdDDj00XT z@mXz{;QS32;)T7sc(x(NA=;rclmFzYWX0kE5_hXL4oH1+DLknPHNjydXCbYW zkGw3iJEIfh$0lRE^yBi)xVss-w8V)XeA0HS$+?odBeldTt@ zLIjBhh1N;TXuUbVyphm|i)m_sA`1yQXVmkc@rLzl)I+><9@tvLlQl4a?qW@I?G7@d%3N@5(O}j~vv4{tys4Dc!+n;|n$f|N zv3(ENK~MP1;|?TI1-Yl0i{z3LeA$1yVo-F10v8h2SL_%Hl`i)mb6yEGREcp z6>0MpbaaY87>H;HFTxS_^uwKbhwKjWA>V<(azw1y%CdheZfHR}F3l`8B_mB!=plQX z`ZAX6&1l^rXE_510tsp0eEFMJ0xedHA&AFSxiL{S=H+qOwfFHi_pV#W;t6I(2mebl zjMRm+vAmHRFqyB!{s!u(F#nsT<3%J;TPwqP(s7<#Eectphz+rig($wZJGt4mmhwG; z2J0@f0^9`8Zz!L2N~w3=YVp#%k5y!@Z5XYIKJBpvR2?!a3RKL-)ojg9FU?o`)-lpz zdY<>7f1~kYM}HP!P&0$oC7R}&p)AiKttixybvNbhXxy8z(PYOHmDb7W9awutp&G0W z=;a4fx3h+ZL|}x7KB{S~jb|DVTAjaPp|K;7(-zi!TP2Pxvu@lQfe#m&#}@$@K!_oM zHdAP`?rcf4?2bB(vPw#Ec{7y60Ly5JjS`#Ue%OcF)CQVcEM$y8Jl^vA4}~`8i405A zD&1UV4pe4f0Hi7!D91vmUU-ZYt|U=dH}OCyG6}S=K+zy{uq9mR+d-!amdqEviJlye zRK411rPmd=*dp=kX)W)5RW=jF9g_#qu@?FdKc>ZE3lZ%RP!K9mt#1I+(^jEcw&_Gw zh4j|NOY(>&eoLh`PFfG86z@`}8Rxo>?u(uHW$@v2Agx^;m%Jgmweh?k{j~{-?n*^u z13M1F+{6a~zE!}LCFK5)7V8CT!l}v%nrp|If8QIP5yi0#)iWsy(TQ4141;)ju>?X1Cyu|$dj#sS68{G75O-6?8`pTLk(={e#dm6v9-e}bc zt2+?B@Ii&6@lEW-HjLgm-u5w-l8TyT8*(>;30n(%N>$Wr4WPW`@p!9BO#mS zI>5h|+7$I}Bo8y{r#Z4Q@{!`6K|Um>K<`voTN)rLXKWjpQkF}-QXU1`Y?RgYrBL=o zI1*M$2#9*UgvtWZ1_-m-bY9;Z^9=I`hUcRW;r;EC3w_=+3)!Iuy5@i7G5-$wYZO2t zmx;UdqxH-H)6}eI%(2lR-xd3mA%W8qjq49<0Mq)UOeKL!^so7NcLaOp=?Xix`%aW?miW z74O=N`|t)1l;Vyj^2d(+Bf;bu_RKuX$IkWZ{&2m*=i!EJjZd-pMXU*N6dDi_NNg`n z`jd`6o+ic<2M*5owRS3kJg3HpgxW_f3pQV> ze{TMNP(2GYJ_#dub1|WRe)MlIhEhK1AmksC=ZvAg@ceU={eu!}VFd1VvqZa`eBj^7 zpeEP=DdqKl5|RH*zJ03T1gTJ?OmpV6e@?eQ-dY2$7u;gc{|`0wFB`${XxCB`%O#ch zAS3bb4`>gr>pCiHS^j(4t}O-R1TFpj{N4Wc+x8sD#Lfg>Ec=oWSni8>bXpf7IXo|7*uv3|RVm(h?ki@Z6SfkL};v zC`r|#9YG-)xZ9E@jX0jWeggTc0LK4%EsHj&8N;$Jj$bj^bMSx89DfYHXV0V&X5|0- z%wIXtz&YKBWgT;Qt_>iJ{%x!hE0$gS_kI{cfo|IK#owO)$FlO@xq{C&{_o#OwfYzg zkYgn7;r72j)hFE78^2fB|9;i~#^J6c7@Gd(I8P7%{a0B8b$k8w0sr#)I1Nk+neq!V zPyh8v!F*qW{yBsG{=#1iOvg;a3(U>``z3$0=GoBL^(cs&I?C{`-~PvIkYeT|S?>4AWtde7gdp%)R}4@K z%V+XdbGjxhblq(q|8EZi7ZgSe{1{~aZb5&TXt^&O30xg~Ppgb)wWEU3011_S#gASM zYu`;|zU6}%eL}mx^UoPAM^5=_uI-BMDoE~-jQr**0~!Ivim?EDdfgWhZOtq~9L21O zLuud`9`X^WINU%eo+cP6N^hvV?#9(MQ-`_!pIJKOj!WrxorLxB2atlzZj%8@KwPK& zfc)b>sk$Zcbc==X)o3F~GVvb)0b`1G)pdHS*C9CpSHk%qM+^VAnOEs>ptHwqdaDn9 z9UC5g0J6MXST6Z^?>yt4U=UyqiO#z=AFZfk&DUA3fZIHl=T5;YSwejw4pZbcmyV71 z+9*zF{_j}4^x_#TEQyV5{zK4sr$`^4wY$B}0K(@oPKfF{i`Q$Y>P+OuqmMdOa-)KF zQ(X@Kd{Ak*XVKz2p)|7!T?~V)sYJWDIKGv-;)S`C;eOdpGu*-WIW9W;GXLy1Feis5 zS7I6z+5;_T7j!ZWUoyd-u3z_xg{s{*_^{cMGiH9BSh1Gf>tA+1W4u)-(WfXtu(YcJ zrGtkYJ0esxp17m^yE@hKg8I~#!LCI7>I3mVXTYi*E}=pa9vgu6edPwflnrN{Gw|$M z1n5G+G3CUbe-7SIS{%}^maAl$mG#MTf>c@2kGS9B4VVkKGTLD5eontV#1<B z3U)Xb+yf}BueVh(Agj#@Bow+nKR=lJrgOuUoXyGjI$jPqr^}2^0yMbvXjB3{=?*k_QRcJpb>CmW+~-*kinGAPCMP z36d3*z5SX%*(yOz49FZm1W-P8EVVVhe|YU5!}2m@4&Rx}d;nAzim~xss@c<>jvlOg z4ie}W^k6j`9s(s7wK2ej9pW;!i8l~YhlAGS`mNvD1e^;blfjP@8QJS?)rvpBN`#}a z87to|soB@3gTGF-)w6$e(z}u%=h47CSQwk(^~2YlUe1cXYk1Oge2=ARCB=~v8E7*3 zMqMEJ>25BwrZQ?l>3_UC z^sF65xb8J5eQ-UD!Q&1V!L^k7{nVCIVMkrNkp`;_FHXl$nxCD|y0G=T?lrR&AU+Sf zddtWk)O)nBjDE6-B_On+-jHqPxAv|Fh}ET*fabV3b`tjYsBXj8|=FQ zF$&JiqtAA463zV_KCNJ7GANG&PR{_|K$T2-cjzGyIc9v-DQioKb!4DI#tJcT$?O$s z_nRdPtC*NKS!bSnLxlnz$^X~hbw4zfty{p*q$Si)goIuVy@|AhCS3-kNi_=65govQ zf)IjqkS4vOfK-(k5d=bzqErQJR25Mq6sdx5=gyt`M!fGoc>EMdayGl1v%meVZ>@Fg zMb+1nMA`WTwq64f!$f0>)P)KwizR!T>j99a#P^c{V^V^mc8(AV85|!5&A!#b#zP2XB<`fx&>* z6_S>Ly%TfcxmczzF^4uwImYp~Q|$m0>1J}9>v2n@n-x1Tv>Vz@9^Ku19l%ybxA^c4 z5G-KaS9iKZ1Nc!X>EQ9N<=~m5a$7=x|+S%DII-~h(3Xr1#X)OvbDeA?R zKux=dwq%i_U_x=Um>FTO!d`X$v^!-rx7y!H=Jps?y=6*8CHT$+qxak3JzwAyz!nXA zhangMGNk(bCZH;3iF^Q{sjrs--XsciKD<1e0j?*rLsDx`LugSHF#5nVCDRvBnv=eT zDZOE)RJu0YMq&xN!9o?at(i^rJgzte4UjQNr!WaoGN?nbO|@?EbBirtu9J@g`5M;F z7^!}WNjGplKgM<$DD2NxZ2n7z1ygOwkwB^n>VeRQBf zUNrt(TVws;1*J6QxOaS zFVkf3N#l;Qk3(Pt9C?MUVIs`5sQQ6hdG^K|d+^BE>$c+zjh)8ah9)x&!%T)kc8&h8 zr7Us26e}OgjrasX%iK$@%0n*Z6I+6qIt%8b$~+%|e*Lj@VLo3lPD#%KADy76*Kyi1 ziX~+#ouAwUyMQ~{(HbWxJRi{5!`&A}F9>h5g~~z2;P1v6yVv90{2*SB000{85!uBk zA*$@G82tcAgg&WD>uRZq>cb3|(-(rMZ6&5YUqhXDajj`HE>ocCEZc4~SC=UzV;P zVP)Vt;bKW!rq_jLyq~wM;-dPl{a)DdQ_J&UkFW>^+bAsgEzqK9b0hV+nji(sx&W0}YFDKvUT?;d;vYyvrYEBE52vrgQ>gao z7}fcG8}tZgUfsQgi@lva(N_M^^sEDkX%p;FjcID$@Y^K%U?63luR$|E8z;nyx;-`r zefZ&yb1um_d(CO9zx$Rd4{UuLQ# zMI4Bsqmxr4MOwsClBw(L?&RXoSx^BT^$|$HjHz?)df&K}U;J{`d?4R=1X$o4G2suMlFXAuVND#@q?KbdM@wiue$b&b* z1F@5u%v)WLyaCsl*#Kbybuo7iH87{?2ah`;eE54m32dReUmdZ*9;2`EL1~MU2UGNa z)4BjGhkO;W{kMwUIE?uktE(wuJDoCwjq24fLtkMBXKuN5K(G790maOX%qYKMnzgkP zUN`Yu(c`LXSrX9M?_9zdr;q3+%4YFm_k*y)*0x0$awyP z;=lsOb_EOY_tR|}Zs=EcF@)^b1w_bfTwBeWJA#4tQZ$5Ac8eiBSulGo)qGm$wR7GI z$?g&PM`~|A%vf9gcqcB0OKXo=sVkOc-X^{-vE&TIf)p5lRx!YIW}Uqvt zJO)RHy5Of(&kyDxDv|B8jF>;9l!D4Uv&#IUP>H(-l1d&(UE?BJeVs%nA6NZiqh~-R}dk56DPZ$9%hBhsxck`ciDX1lVw$ zN?}rb?p;?PWmyV?W?=tO5IcbGADeqT7k}#l(Qp?8j4->5cGe)mAX(ev>9J%wI86@z zTP&@{k2z0tYq~n8L{(938!E5Ml1vh$^zy`6wOQO$ zKP4HM>Y#PdXpFTf>omycS;#>Jy|0t8uO01s2}*_s{k*6u{+z#kW;~?i;mzkl3EHp3 z1B6-)`?qhx^~ySca?<5Rd_wo1|#|5Yh$#fw$q>vI+Bia#HO zqB@b#j+m4^pXDsWRhzLLuiqd@4dwB8GVhWOvUKKs`ZtNFSQvYgJ?98iwQlUz%p|jo zNSmR{-~`12x>G6Mj{$GKtC=)kse=#tLWyq&bmtqPB>J?b#BD=|u3UWXtV%_zkvarn zHWFa)q?gOcSha#AEk}5N3{Zn_WxX;WN#Ff? zEfICqJ@gr861u#^gSh2=el^@LBikiO;`8c7eYqh;;c?R^CMlNBP6-+HrVM0Qm<9%g z#rM1}AKQ8Ob7cHv21e-kQc=$}Va+$kiFL5+NZywwNv~qV;tRg@lI59^rXE=kVM8@p zP=@C4NVeq*K$pDB2lk+rB6>Z%?O)B!?3t1H*+7bBbua#D<^{ex-I5#{T+knkMhW&C zXjQC5|cSdywzZrVGcUvi81WyoQxmW}Y9BOZT{S}Mng9V;LhCJkxz z@KcLfrh0fD6FDAp(Ty+n!=N$3<8t%$=eui8`Pvc#)ZJfi$NQ{qmd`iipD|ZBsKGKN zWFp$`8HY8mkq@ua=j?9w+(X;&{Sx5_dKX!anm7PowAh^L3XxQWy1IO*Ia_z2EF23~ z%Pq7B{O+w-b35GsDB>JA>w3=km^a0Dr@@CNX zns*u{wcAPhhihV)HK_}Kr~w~HE7lmA-LuaUc{T`qpBpc1I}JL=I`QjeDv(?Sft4ghS`3{kC%pn#Mp^k0n^Q_^#?N5S z3fu zZH_;%d<~9OT+ew1c-zHGqzj&R97;TZjY2|uHQq3kRU26!Y5nIp0-CwwbnPTa%5V#+ zq&=6}cbcQ?ytBiMdo`&R{S1J&8Nh*C&91qi?dhX|C)^=jS(2L|pt|r?-^{*m;!(4Jh`RW(OR|!UEU!7jMZa2B?%{SL0PIeW zlI?0SmKSAn3ZscYL9I)?F&n>k((=R%Roi^}9aOTB8l>8tNwHEqB)jUM6(IAqKaCNT zRC^m@Ly+tM0()Z09<9gbN~=y4WC|6PPgvDcP7$p~T|!0-I?tvuF)G|pEQ$2|!eQX- z>Cn7}2}IsKS<37mM=2D57ODF)uZZvsl#<#vR1_XiB)wlj<0pB#vhb70R^l8N`VWeH zB&4DUpPt}~zgii6UgUbj#}m_xJ9B+K4O$S@-$IzrVD3zQ_s?3P2f`xw;+{}jX9epr z*tA=em3W46#dP66y>US1cY~WcCKevmccscFvy3bP z%L0baK~}}Ca3|SKNr>^EE49;(DeL+Icg3C$t@ki|cm1u_!#fTu?F@sd+~!@9t8M#xTcX=}Nn)eaMV$c# z5A!l(bo2-!(8WvM!NRjkNEm*!Ezz@dB*BwbWq`+mdHFTC{m?tDCi2Rx6T_`-1axCg z#eUeupA)ykg4AKpTCxWQ>tL}ApQ2jz7ZWqi$0W|61{T$Zjcj~JYn8Ft3Z>DJ{QAS7 zoZ$9%3Ss5iCmd8+d#BHm)xHhH%dzcHRL_p^ZEXo5iPDr?Url>H&xh@*P_#lbKtwE- z2z7@rMq9r{*E8ISMjzQ<(AxX{QKtxcgAgeY(b8H}>E~utWR?tbe11!Dtd|%#?gl^W zOR1d<44h%|vVYraTzGlHcI^xHvlE+e3| z>O)xQPaU_}jM_R{i5tc0o4=3t-c+dv4zps_+ zG)5`%Xq8JB91_dSzLX;Kfs$IZJA6pID4II4;(dWElEv>&@qe_uwgmi&*-#7f?*~Pq5#NV_Smd8O{b$}3s6OO-7Dihi=cTZdxcIp<`tzL zQ3Lq5UHC+Q(RD)vtFAc+?yudcoc;s?Ek!rlGps;KbLH}fshY57s%4(vuherV2V18N zgtu&TtT@LnZ~!g{Nt=mOEH;-on_6 z18@sxeg(dx&ihuaL2+iT4Gue;4x3>0<8w?}ZRYf1sW7~GyjV8|S0&FuY3hEmvi@1| z)ceGWw&j}(riHDd>-m`Lcs*>De}I%w z88V+v;VKkAd8R-RU3Y2=lx@UCGJRU~=yrtUQnoP9h2!Uw*Gl8@ats8H!nkIlX= zq^*^7q|0r)_lN@d&!pGk2u zOB(7IxeIN3^-E22cjk4?*KM_=3`sz#4?KBAXku98Nj3iq2lKdh*0*FDX-k9KPF09g z@pg`GdrSc>;dHW?(t*dk=~)O~Fj+bXmrqeL{25?k7@n#uIv4fyTS+z}l-%YTgDjwD z!U&HU1>0pE1(Q}Qu&-wVHh zL%9&L;O$G#HPEu-!bqf|(yBhkA`P{N3DQFv5z2E#WARa7>XM!g?eU-I8_Yd(01f2M zhZOrdoF1I5v@?-Ixy^fTaS5T@aH%~XKTlsO{tmMo%E=OocoDIeyQe9gxc+mWC|&e2 z*DdgAQy}-dZL_*YJ^&yNJHlW7+8W~Y{&G*#O9yc#%;nJ#mqSwrel=$#8^~y1J^=Q_ zDxlI_T;3ic7_l(|J&OPHmF4-U>Z0clQeOO=)_4M;i>GpdheAg_ou6byKc_GV^rVx1 z_o11BRA-EvFG*g`)O`JXxarH|jSr`pFptAroPQylz^_0=*$GQV6*p}Z-B5hPq)RXXCDmi2i_UcW<9hLIN&r8X#65>V0$01nnc;O$hz>C&A=Qx z#zGT~BhNJC>b`|gxG-du$CLzg7+^$oeUD~34sUw}jbpGdKUU$tnE@pDtD2WRfu7(` za}p(pKv)WEizG8(;_{!oz-cT2S6NpZbk6|!gbeA4N zR1#7F4aT(z0G@GD-Th13<@->5rQ&LC0^JOm?dR)*=A!(ex^R86bzDB{iGpvm-1dmD z`6M+y8W)8jfiA47(cm`2Pv^v_9z%-apjvbIv*vOmymrZDgnzdBSi$Hw{0 z0R90|9YDaS(%-rAUr_W#7|qw}t*7yU3cKmou}$q-%i3PQbnainnPXD?kyiN4W387V z9~jsFYhlilVPSbryv1glu5ydTAEy7X{rTrgS7K!5s>&|pA;52F{^s^vYrpXSs8kX4 z;zJ|bU#3C{gmVG^$7f0msm!}+tXVFw5CHCWo<&3B7xeY7rLcIMTA76jqat7AbMFsX z=!+*K4*z`8|L1i`5tSgui~q6zNgbjYP1-#4TexlT&QFQNzmtj{G+w-bA%~|eg`av6 zrcJ8`piOZKOYMbhTftBJ$p2owfXb2XHeTnFE5|NO#1InowDYbH&64kby}srtDwZQr z4CbDa_Cst6M}#}`53XJPz4?_YoR(U-=iADj&fS6}1*tQ|qkd04K3q!C^P0^$p{Z7* zmlg2vb?^M@wiSCFIY3N=ng+rEMd)6q`qzu?YqXV*TBlc>vKzaO+`V99%8B9Fz-eD| zuAgABH&GUL`R9khw{-KKgm7QFG4N(l@hnNYe!2!yke@w(-Fx>=x5%Pu*?#saPKI~j z)W5I(cQZIwH$mf*u2OlP_k+FeZBBgr_;F$jbz(36aZ;W{ln`rNNAH^l_jwHWH1ftn ze)|6qx-$ikO;?Aa^$m{t37->|Mha}@g%?=s*6uf5+iE(e*W&s_c76xv-z$sN`LG}1 zzBjGO@prR-cQaDDVLDY&Y&cRNrFXgN%hpz;rpW3}1yd;`C-+eQsG@%7u>XL3u|l^J z{*Ph$>xyzTM@zHJpXwdz9h8AllAuo}u^(>N5yFX)%S*lS*HHiOyHIv3XzWaq*x!@z z_d6{Nk_1OpB>m4*gwREZ79);!@|zxR*9&F^z3;2fdT7M1>l*9oaYn0U9d376xEOJ9 zDn{U!KBr!fmV^Yz9&WeTKv!?PiBaR1fk0BzS~I+}|E1l8>(SL%j^Mw?{XgH+2LlIf zYy72MZg8Ce@rA$p|DW&0fPq7XdNTg!5C4Ag{}b@A<@L{D!~q%`)%<+eKSZ9;>jQehv7~BP)I7`)X=f*MMt0ED$y=)DlO=9O65h6kyIUW=`Y&(TbFm zN*DCRf}MjlsjA)jDd*!S9m&bmYFT;IRD@Z9S(iZf^k~c`XxypI{LQHktdQpVmrlP> z`i0i`4!G~6dhe{c%Q8I;{0{# zt72B&P*6>g^shgXDS@t_>cIc+Z;{17Uyy4uuYVzET37iW&-?Z0i>yWe`%Pzm8C(IL z`27T@`1@aD`0p~H%l2PS!~ZIYeKAX>5i7spA5$RHWZt9w_vF-}0eJ%gSZ@XXH5g!O zm;Qg7npq&;U+=m^UUl?2B|EVq!VM;X zI`}9anJ}(B-Sz*9ZY9YTcY0e;VOX*d&cl(k6-LgPqtl3PA!z?HAZsdhyjj*hq9J=X zzjnK(-D{)ZPJYApb+)_Z@r}!L6us&2b5$K21tBg~A57XBB{~(=AKV(ae#hA9rcMAV zk{Gg@k$Cj%Kc#WK5I}m!{9N3@WVh)8$rU_UibI2^kl)F-ulMDgTxNOf*e1F}?Iu3E?S^|!iyf~X(3p6I=VxE_2P#)8h#5o%+g&M`eB5W{SoYUqU$=ETweKeLC$@pfNlJ<=Re%pFF5k z5_!2fG}2QKJibRSZ-{7pi928DuejLh2F?N233|7=b2MI<^gb7Ti#XZsJiZY4F8-G; zWaQ#SPKX>LJ~I=~iw1juTfihbN1RHZr}R1bIvsgEWA5bk?YTYAJNmTP7KtDdo<-Upl5Q_1(T)91Bx)r|zwrv4~(lRh~$X#lG7VH>@n z-_f$|0+?di{B6R`=iu0`pt^2{b;IS6M~%z23Z1^cMYsfF^!++?V(YY$=0Vv;d%1PW zy5QZ|EmP0x1iIF~ZXBJ4S+_rf9^?Z-_`yPYQX>tl>%kK+t1tVv69z^~)lHU^XL=qc zIvC)4)&0`lLwx4%81;Ug)duS=IFTxCzC?FLNO(g@qcAq9^{;0t<6JOxabsTLeBpB) zAJTg}v6F%J;aIg2+yn(*s$YNemRR&H_8Q-8=`J1Ch`=wYkaGCv5(|VWw15M~j2!Ak zS|8S*eDs+0J$zM#8j$_z!-wDc9!;I9J3~b(}Wrn#HIa3i*y2QoHby9|u!Z zE}wgq1+o_4Qr?2>X&?2z%-`F5yjjxuFpKoKsBW)YBERBZ=@Y^G;v;_+2Uu zD?GqkmZTwVEoJRyF{$5kC%W+W0{&3L3dnmDK_$TN@Wz)UI%_4}yRf2Q=rZG|7pZ90 zqWkZ4s0o2~T7q{4DHhRR%q?0A9GB$M`xrk=_Ra3SQ|Wg7C8ARn!7JFInKpmTSQLlh zka=AcI;Qx`{REV-6=Uoku=6)i6wEkU!nW&KHzO{kJ^;3Y^2oF>>|>qhmxsshfMgd6 zYqx3VK1N!@y&lcCgj_&QCOr^)Yqb>qpF~;DBsyr3F9T~q=wBwum>-jz8IFA*)kY*P z5LM>WBR^ny_1yhst6&YJkKQr$ZPTa$=1Ez0zmJyrJM&jFP+I>*)&*j<6tRM2NtgS( zn`HxI-4#>zwXY-;#HSr$c635l77cPLhA4iEgMBar1d(Zbw488<-@@3Bb3-M4sXqVy zQDQs%GPpHh$s3t z?^kyP=W0pz`Cm9;{m=BHpq&@9M91{A=j0x~e|lA!U4EJ-O8-h{3A?}^`;L`pPh~zx z+T@p=O#$uEyhlqY%r)v5{CGcQIptEm!@JSj{U&1mDAV5>wR#^TZuFpm$0xVA zj5q?=krxlMr{NfBXjhpIBRsK4kD$u**F#M~DDgJeujW!N<-+9*B$-~q8Ke4=4}Nl6+= z$EGC;ilF@D+{1U_zEwTO^vj;ilU!_m>r#|$cy4juIhE6FX z1<%#z`Eee-{b(C8M(2jF{ z@Z4Jx;r|(c%gA-x$nM=&^He`+bJSNU-J8&d$OOBRc1ku@VkiFIz515s#$USS$Jr#@ zndUL`Oi(bN(C9&$TljPt%B~Lm9OCv+$aajkfy1&x7)Lvf(Ne4ouzv5vvT4Zg1+T`F zefzz!0RgiFIH}A!*DrJk2ZngBzs?O0W%8?Xh!nR`<>}G)%_)uc|ImiN zzEoU#f3(b}Z?#I}m&!1K5RA-)gJ0K1bV59B+C5Aj?ULEPO1OPXC`|vs1Wme}k$tt5 z8uc}u8o(Zolnt18Z|Qk|=FZ)%{m&p!;$*XeZ(4cAv}`?J79Nn&;Wep~*jnhLa~Mk& z3a)&VmR!KwFB$)8neH;*$R9oYvvJr5c|Zz!%DazQ#n36#P zkcrpYtA%zdQ{F^ggQqe+Oyc&&0=fko>tN%~XeLwiGeL9tl4<7#jkOwNDfF+GFU%@*3i+FvTiT?t1<^|Sr8oY&-`Bw67i z@$CS@`yP$O2*WsD6F&on$N5G~0Z|9~0D>ZDV^#Zmk?R#OayI<%9Z*7PIjo{gjEm-6 zALdDb<`Fa#JN&P^;Y}Hr>pwgnyNN@#ijRYM!$s`opffC19{ZoG0hY&v6%evYfz4gu zx|^K&GuY&sp$B=!6KkIT`EE=oa>sDk>+IeBnN}V2>!&#{pM2e0KXoq+{!1=fz`{Iy zk-&{vwMZ^(DecsPM{rUP5f|jMwVNNbUf%!f^@2!1IXb`%xfs2QAr~xEQ_rT|s4x(n ze6Ri2hAiip3&taMl!VobEP$w^9P# zbRi;-YHC2{c=Ll~n(Sl066>KX3&7~T@k*#IJVCjV$euXv0ub)QWR|hg z1$z1Q2b0U6{C2c(@L=Pnk9})!t7UW5(a%I$@|k&X8lCXC$Dh0Ww#M9p_sX z79s@r!GU1PgO3khVDAP`Fjio&<&<6JT_3d`gR=V+fNQ^F_~M3#yqYXZyJ8$@0MrpH(}$;~`+Z+q(hE!u)qlCqAJED4 zxbC)OFsMC~HcHD#?%qn}li3k<5}pp>81LlSj~KN0VSOp=Y!7F6-~q-`-N=v}vy^NK z8N9^HoGmNxmqIWx%4n4Z#xl4Lpog3Q>KES-%nO~2^ zEc)ryV%Lfu>~t7PR*~KbPDWh_1lk`y8q^`2n!P3z=_Ra@Ce)9;WgcpjqupXeDQ}$?< zX2t^ukjJmjd&Ha9w3!>--i^1#8KNrc2m9`JvCn$R`xbx;0YPJw`4(U?Zb473omuuG zvJ4%s4w?ra+HfKC4v&wgA_^)duU(z=TuF_7wet%=g~3$Yhf-f7Td&jGO?AJ|q1^fA*bbg8umoHV9y`9$s_hS;$+QNTl0 zcg|+C*;}VzwHWaevox^Gxex0^w>$zs*ZY(>Z^Fe&@)AS{#cie8A8hA-%KE0+$pe`$ z&j#MzTJk${!_=NQp16;f2F7M+&>VP~NcH92PNo+)QyoKRtkFhcEl2bPh#UZ}5KbSo z6e$p3(d!GuTMftCuSZ&h6q$Tocb|{farC{R=o}Pk?*Wmp5)R-8-uB3LQ*nF%2{LbE zw&~;v5NnSMuDXg~3_ip@DlQ^_!KK4%poocJM%@Z4q|Kokwa6sWEbt43M3N`PL6a!$2NjEI z+5_1y$*b9J@#+^d<6j{RYQ~d~s)YIZx-A&jC*r2dqhP$PW)-^~be-cBiD;5${|mHX zE(oG!dA8Opa30#dq+n8Bnu~P&XAp^u{uJ?I?AP?gO0s}&2U89jEAXWkr6QDCx%CMT zDVza63FwzsxW62h*Bi-#GfUY|HfQIGxGMkTTL8*yT*Q~teXo{*@z%60Ug@uhIfDV%yIY#qpPU(N4s^|UeW6&} z()W!b4X1kww4J@rgmsFI`(aJf7f7TWUut|DITC(^idwLK?3@Q$?6b^dmUy_2-=?Z7 z$tDFw(TJwb4T18AEtS~qhPu$|7Sz_;?>ZfY@6+AudL|}^RtV;m_c@ z^3!MsjLU;E?Qd&CIvauC+bR>^*eu3wk+%!w;lpPZockekdj{f)B&D-Z(vc~@RC0?> z6)QRpL&i}GpswKA0#}R0+ZW(zbw9HCF15%=l2ZUf8wHqVEIc+;MTT z184vngjGx))gkwRn^zbVzR4D8>p#2TKG`s`EO-uM^C$rz`0!oQs|3ujIY7jcOEHGL zSCBk&%H=>*Kgve{(<-c4f5Xmo(`-yh2{)_?hVg%oXv^!qEkx){le>fSX^Zgf5TN8f zE-GAPQV%+M!LTW)CCbK|&^!S*{4*C^51BwDj#S;2hWl+*Tnyy501KzIqp#zvEh@L= z2Z6-KpObghhk{2H;%Ks1o!S?~Cyt9fjjHdQshbJQ99Xyrbhg>8mHdfW7{+?|YacbC zpGx_=`U%d+&;C3;j3D5z$`})Re>kEnxfm%N4#YBLV{5&KW$N_tf{;oejP%5CorLH- z)DQj?@+Otgc@E2?A)P!=x^|O5nDB^xFKdT8eF5kYw|hnGTP{QXBq99OG@LJOVN$ zYcn|hG{B#}zkr&cSSFS+k>1BCxE?wGK}SPPhgWfaRexB>B9B~{wcU{dlI$8BE%`ZF z$R(meO-PaL>tIZbhCNYdB3=6x1t*c6@CJyVcdGll0$dA~h z7@N(W692s)Tlp?K*Y61vHrwQt#m zBDJPB&1@7EHII#ini9*K$aI`XmEQsH0R%{Sx;UvD^3A|r1VG&_fNVcGfD!n&meNcLvQ$Dpl4quB4lnbr^KXxf< zyzRN)bLxZmXnir4zpUK_EWj!yCgs3S4cnYhs z-02x$fx08FGswHV>gV|h?_vH;`?g$NDescKEQ`4F|%TK zGWnpJ+X2Y=;=Y=ta%cpxD8md-QJr=GSL{~}J^Z;*CqRlXnYTR|a#WuUm)O?wN)g>* z&hi1#D^}G5*pR@&C`3qvc$dH^%I#>QJHI-{J;_Z_=;Es}gP?mGS7ihI7^Q<$ZQ6*yvUr_ftRj53|#sgX6 zzTO$rFU94QyGP7Iu+wt9U0ZZJ9&okg(BMfR^01xy?sV~UwP2_Oj90uukvC&#A#j|k zG3BHI2`vX;H!|zQI-%#w8JvM00;#L@nTnEo7}s+R##|T5CFm4xXpqh!TQTaiodgvC zqs#`hsE~ecx0=GFT!s?udal>;?)4_MDYNDf&1jleuTo5Xw+nlq$8WZhe*04kjzE?> z{K%fWX!(pZ2pD3U6FJQ8oD{}rPab_hUG_*m8k3H(^N^{_st___i2^h96rW5?+;;eA zZ?Mj=A%^ct9uhcG_3S94rx03*35&gR5~r|v3!FIZ7fm|9oNUyZ!9ye ziIkg)UzT6?THI=nf&RkonjCao};s8nxNcG01i~jm>jQRbc?hmbI>6jaB!Zor=&>L3053H<% z7Ug5=h8A80P*J-&K(;P0>>xUsqjC1?=K=||X-`!FCurZ9;ELgfg*>*WpX=-0uwJ`t z^?@Bov7q2>yYuPzI}(sR#AiVx>SLsB8?cD)vvpc~D0%f`{OI=dAt*+aVcJ(sTKoFW z?^UITgP0VUssV)OQ)#&s=Q`Y*MS+Zfn@~gJVphidIf03xVqzT6H(FQ|sUm%BVDje~ zqD|&1Zs9O<2k)t=;p;zLzRibH%^uGjKFtU^Z1HG3{$!D2SD}pk+B5FS11;6cUMB!M z+Lw%*crP2gbc0l&l&W#z>@5TqKqiJCL%3t;(uAu`F}xtkz}umrxVvHCF!yPvPV)gs zBChf!b>W>x_j#dvPq??gB%9+g-PkJEk()d66Q3l)=6kfa^uGOmiXEkCr|Wysigrq) zm+qHInFO@%=Wlux6$#l$gh%%Tf!zmZ?2P8Nk`^g2CSiNn3Xa7)H2yVVOA7bbe--MI#s^7Q8t!OX!i zZ7KFr{0KS+$5FYiX-#vSY4K@XRMGsi*2p`E!FlE2l^IRGXxX+O+B-6Ix6<1PQ>48Q zu!`8XA9t@f4%|^98|RFi9h^tOPCE9EyGu^C&7JNtQ}21B@?b`S`Qpfx8Gr*;HhU?p z6a+zazzAg)Lk`WsRN_+gKn%?Jc)+TFVVzP|6}&`haU@FR}6p(~ng} z+ZMt!eMr%MB=P$&&h%sm$Nm9%v?UN+tJu)Gsbb=@$xOn#M-h$RA{O$pqi&^N4u^?~ z6n+I`=g4=Gb5c2@6yf-!wFlpKk8QB@TGBY6}} z)X*3zhy^{br-0=WoQ)#r^xP9UVT?<+B>gPZuwt`Ux)jj1D0SpFe#W(BG~%zyeVE;H z*uwm`{HA}@L&6ss-oj+$Hr+jS() zBYf+h>v-E%n~l$q7QO85LB@26R^-bKg*yA4Q?pPH4B@wZP;!=G>PW?M$O2MrCx(&- zvyXq`M2Fs1P`nNp*jp((v+h%NI-$u`ymbt+Kz2C-qx_y^<5vgcK)3bHLzbQeaa{@I zHDSkl<@$MQSM|NL0ocVwF5|8>ie&mRp-EfF^)CGzR-qu<>`yc<)FDXNM+zbu03|Ygg3=1p338Df7!q zT4VqYg)=PlEx}q>+sJj0$!2|oq+u-g&0BXFTe3j{Ii(VCLc#kay7ZT8E+ieHo2FB& z&EAy!q=b!f;mcFbr2PsCm;u`1B7V}@Km<`(3~9h7F0A+gKsx$_c{~IcgJTo+yD$C8 zLfIAD;UKRxEE7h;#YkwomT6#n(js=dOd_E`kF>mVEVstEHABE_`a?W9JafDNC0kYI z=;%b;e0!g^IqBZZq3vV`coNkyhyN)k*guWid^akn*pl~8IR6d#d)=9P@%dum~d^rnY z$vp|?=DJV|dKm8;u)THbUH6Yytw?M&V$jg=mT)8JX8 znX0tX;Lv+kpQX})gf`&&Q??!!oDDe`i?uJDr!YD2Ga1QhypB3I#`+j`Uf5mT>k$y( zum3uHh3%pS6C~by>)jnW#hi4O%kv3vEaN(cLLkk$0Ms@xfxmqR{1{Ka)R*4L`8ZCE z!h%}u7g%YY3!`R;K=44$RHSpr%?Dx)wfP$UYrUjl7z1S(h| zlbS4Pz!Ds38neoh&ibNQOT>BFt27qyx&{L*C%LURywWY-feJt1G2=0R z0J7s)L#UlOkO;xWPQcSj6y^O3dj^nOk zM*1CS&z7@Na--}y`dE5b#X#ptGQa2$aESppFGMIS( zz?`+>Qdt6^;#M{&TF=jE*Y7Al04yb8H><1Zg_$nVDZg z4cP2G(dNjj3DS|1(kTm?wJ+CmVeuh9fj|luHM_49y?qLvD@Q_W=OZ#@P@%6*XG(ZLLdS_|hBeNf(~@Gm(h8>TmR8I+aZ~}`yZ zAPGjzify5}J9LImF??r}BR*mJCCe&F&(yOeQZnNX5N|x{Ymd6IEn)qQ#t~h?@_Eqj zc#D(gj6yiW{1|8zfhWi{4&4EsyIwE$P66}EYjSg6M>GWder%Xh z3DkepzS=#t6;iiVF%?edP*;Kg5Mrp_hfYTC{A5wv+q-S#8s%;$nBcd+60y5bF}!kS zix)5~E~d=O$HlZj_Hf#zy_G~(YV&~hGVkQqL?e4{Owz)Y+RlBa{?Kd33QWbTuQ?55 z6fSP<0WYU2doZBh!>eqV2GpC?Y=^pRHDD^+0OnqE8d#O(M@dJ=Kv|#hG!PiJV{#Nr zwwM}>HEO>b>%Lud*(du4ofTGv!8B)LXi6qe7W{m*_{`3+4|;&oefHkt$XNpX1`jY4 zbnWHf?A6}Sj+lgv#qn0%y(-cpz_ONd%Al0 z-n*SMqz{xbjWv|XuQ&k7t=Ef+oyo-4rx?9fGcAD14xZbFHm}N~?SPT`Y)w2!_sqfM zSddru9H(1C2qO3@r%Mw}1&Rg(6yiOBck9$wpBi)m6v0!8k!lC4+Rajxhtap!x{oDg z$Ga}`_K1PC~b{n0xD z4%^*3UseV*#-+w@9For3lmST;C#YvNRkdICDc;R;tD^>5$3dn>M-msQtwEb?J1%j% zC+Mp894Y0#mqAABQ(b1oK+$9QqeFnBDAJ<*7#sU7iHNg1yI$w!-JNwjL<<5~v4Hl|>t0_8FIC5L*dzBtYJB^*5g z>V*MJL6fdkJH;KvyKf-jB% zl?)brqT~5iayP_lkvY5+YR4T* z0kc!3EI3-Zf^GTW>mNJzaZ~ahyv$a0vTGMRN0^#w00sSL z8ES@QFlu#r(BSlIu58-DP_xXJUf~fh+0)FC=N=0|*lBl=#6#w}iw*Ngx*Xz7fc{Q} z)BqdPi7bYCEO4%L%+R(y(?(fQ2y%Etyw|>(Jj`MY6q{T|#t#v@@^WhkTjHtBlst;3 z6eztf=KK9C4$G_c3ZRUS-6^hS&@=SNzIH2dGPowh5$l1JYqxtRg9kfHQC6XwUD0r3e3-t?=hz_swp@lF8|uR=>46228*dFD-9`grfY-xj zG(}J*>{u3F1$9j4si3&hI9gvyaCx|@U?H{;dD0@PENE)ug4 zFT5Y_Zux?;1u6{-^R7NP2J!_lGB=?f^uX%f82X{Kox`pzASt&~|=l=3pm1H^_C{fe2jU2TVC z4-%CeQH~cmorG|;1F$Uj5ymrjmjmX9SR!@nNq*Xd()9R|=m#!=$}_(J6$Rpdy|Pcd z?=Y;~E%FT5m$DZ>@+AOMjq4^u%3GZ2nstpR`$@~9Cq$!! zOYzH%qDC3dmxIj%(xgfeLr3gOa~l54uzi1J{gp4C=YH@zg+ThPA!5!bPKSxlYhU)OasnTqf~_*y z4&r1A-=i7lrAqjQ-HxAnEyx9TvZm`+i|bWyPO}-;(zimgNbF(={OMsPJ}p&Ll1Oj6 zZf%XmQcb$G$-Be$PP2~nZ4Mk+UXM5hCU5+lx%SwPZO@yLrIHt_9a@Tr=K8cQo;h0m z>AXPw6Q(Ta#$>ZL#viQm;1K#|eQ8NqrOv^@g4`gD%bUhGmvyKjC0T8|c|u-sSi_Rn zUR+dHNN5mv(S!&E!;^NMLto0Y3@r|RfNjF;dQF*bSCy6NV2HAMNV)e$HzLB3Un+{4 zw%zOPy!le7_@y-K?%)-;J_^sZuyxgJVGAy~5oQou7_Jgr1NO{fu*|!v3QKKe%l|m| z()m-X(aXB}leB|cfNOqAMIBH$B~*AdKNi|HSk}dZOm-SbY=7$BFM8VsTwSGD4DP9l z?zZ!f37dE&^B^by3iApa9hETkfv`(Z9Ci8XTi`yg1hJ2 z-A4%mohzQ7*GS`>&3TfSZ1h)72MVTqtFFUC3ofgfp_IMWV*(1i>0ZdZmKf4R*I}?ZHL-$Xv^u>!NgX)Z z;`?_@1$~>S6y0jDh0sfsS+}c~5G0*m1$wtGu&Z`jQWc->ghd0=gsr+unJ3Ej zd0D`zfAjepep2g}9*%Xe+*?veHIreO1-j?PPOQd_p4HWHIvKqk#IVq2%z<1vQ4vQ! zlhxcLYHl@FfRWQ!hkBWBAm;Xtv1K)pxoB`P7jBluM>^zpau=@3DpP9hPTUgy?G&j` zsjqOj55b4*jh1Nh#47j56K4|Us1+E^?iIN8LQPAYBfzFI#$Vl^YhZ0KqCR}=H{0%> zI3E`8z*7{*38ND|LxihpI7Kqg_V545PdI~kuG|4&=`0szA<~FdS4>^x1<*wZbjNqc zn*d_u8nu3LZ)^CWd%3>rWDk(eYdd1T)JztgIXyvTa?MrUWq=3rhOp@U6LS+EK`Bl& zdy8-&F?Uwehu1o|;uyE|kSwCDF8i~Yby}+tf?9iTQ9)NNyzu88^`WV!L-n29YB}cmmM}}+Y zXG7XgON?6rXUVHH#|7(rzAbg^D|tn_L&1S87HY-4HtvxULjh z4EH>*k(uV7mnW7lLIw*wWs;LfHdl|KSI62-R=6QLjSw0nN*0jn#!kyjfiYbY) zBjjrx31(5hGc2R^ zgQ=;e(4WoISZonQgF+kw?ntp^3+z_J>J^vue* z16g!%3!Rc3Ni;nm6DV*=F!`Y>9yxuGqH;fDKu&`A<#GJ2PBNz;@49DH)GGraOj4Z^ z9Bne)0W7Ha(B$RjVN>PN`p4A=c|5FRsjuXR?QuhD5r><&zcSSaaO zFlza<&Dnt?`17}@40@X{C&uT@#5w@l-;%Qb_)dgapfl?x2xU=)~qHB}*H zFcaRJ;Sz}+S*M+^o9AvVF~%k2yoXScc^QfjK)hkcA-I&(jkO-uqagoyC9D>Zb9rD*Bu6C4q{YDLFNj{#cwDo1w5Q+`%HXavL+qM9gGP#ffYhPL9;7-clmJ=E z91+(R31h#71CY7jG7%f(I|+=e(x?BMz9cqAaV&ovn%sRO97&D2i~ z0#d%zg^HbY(N`*j*uh#@0eBsoiq__{@vjzC*-HGsFWh`eLcyv|4BH1Ubi+cv5$k}m z?q?C1a3b=UZ;O1noS6m|b7p%iV{RqU82 zq(KtC?46hY>IMNhlHWyyc_P>|V@xi+LhY4}Kelv9+WaH3GBa4_L1=*46H&)E3 zDJ&GrSfsSZuJMxEfla{P=#BuY)}DK;*$0qp2*(29C4MR$@P~EJ*>>GM z1b0G+DM$?I`&q%$S?k(?lSxHuJ?XLaTZx>HPmhI=sW)q=?I~yPo)at%n6SFwpj-?S zR@Xeont`EUb~>5DUZpdLXe6 z&-1ta*b~&xcd{F0rbJ}(ZO{*e>&Y3XE=<{)h*E3uVr05FgjJM?mXMoUfJv`Lvc)Wo zV_RHeg$odx%YmDME^84A@F6DwD@WRnX2g%>+tUAz+d=KKM=Xi+P}1M|)RMbiYZ-eEn zbrP$c3h{~~B;Ydi5mo+%i=LLJ8>566>3{1{BMiWTfPFj4Teb||FjZiC2Ej$@mcJJsuIF=sUtTXfkU_Hdb=I`(KJPZwLDI!6uQf$mx~))gUHcD7nc?TW-B%6(3CujYV|x|Ds%_u}L;VGV zg^VNnHS5LKH>1@#HeBIr!4HeTO-5`?ULy4Tj-9sH*x4p^WNkQAi(A6Rts_{w*iPWq zh@4a=6S!+RX)NkSn;aaN(MdWXp-Ss1pOjyTn;RId&gClzJJV_oe<(ckDMFaK z1&e6`Y(B)TQ2y=Hj$aa_+Eo+NuxODcI6}k7(fbIQHx!NRBr?%;UEjUld>j-dEle~d z)CmYYSq$ZXu$V8=i>6uwsPP%cDkYh>A2kg|oN|0nuzbsMH)#RdNroNDZdFyfM7&#g z%06CN_qse;*W+(|Vwo(CJZ6o`RI}uo!8ApYhgisNg>znBAbEkpZ};2FB0^)$H!PaK zt(OdKM9TEanYN&kyXx}R;V0<(ts4>EDb8E9I}M@^B?_K7LABpV-0q=lrCV+JrL5&8 z-X_^|O|cegOt(IXy7_^}l*e;&BGg)+(tXd>7dIzbu1A2mp4{3g2JT_CfMp<$pT@$ zc5Wg<)WK^cWth+);et>vj<7La36>Cc;*d?}+wgwRZL2^o|3i{B{CiI{VV!a1pxT?v zctasp`6Ol5LBy~t-HU^c>TsFv(O&RNFg0LYkYPTki$UK8-y$bs23!-y<0=;8=w|c? zrCY&-)f!*~IApH#urx28sS)S_%drjG2r~DL)kx2>gQzef>fham$VocMr{=0!_?UrG=o_{!R|!OL0&A$%#7O6CRak8 zHAs;>2@hUB?BH>5xgSpV#))XY1kik`!K$S7Wmt7Lj$FA$YTNT!uId;YIuJ#pUG{Kz-996ab& z9IQz58nhtrCU7R#Po+{@A zeKkgbH%`H=C4yszUHq1K1?blB@3R05rxaoNfdpyF!4<~F)VCPeqE|N)qpf5aOdzkf zQZLB)lbDb!i&=TU?YBQV*>7;EF9&>Gr~8K3gjw2LS-#uL`WxI$u}ov{rIOx7ZP9Dg z33pH5^1+wm9Cq?u*c$64kN#oY>ZBTvTST`l8M)YVsQHra$<=XoJMjERquCaJ^YL#KGTueoj6S^QiJ87`JX9QU zMfb|5WQr*VzLW_NzhTG6=7w1y=$u7*m=*%Z({#3CcD3L zPqanhS8gE8%`m0nx}dGw zWY5#TT1G@LHD0OF-k1SKUc=4_*?wn`qChdnl@=-XX5q?Ac5vs&*(1+W54te`V7y4z z#|jvLdKIkb_&wLIdZVfJu}HCD@4T9=2k*}o`rH9t0tP|b+s22{_l|fhSkhN@nh_3b zFXzdQOn)`fYMsC49|+>mE$*c>zzlwXJQ-kU_cSpj_q#xX`OwGrt!6XzZ84{Xwrf|$ zum3fCB{L<^*FPB)YH1wA>-_TJTV8J?8TXJ6!YefCAELS#&Nke6+J=G?$mF!s4K83N zpyn?j4pa$RV0s@Z1RB0sl-zs&w0v{R&_i(x90K%gyZ}KKiQE)Z^e>-r1em2~4u}QP zG9u28`p(AMiN~xdxT*T9-SW4j1j&F77BMp2zQ3k;E)&f9>Y@N4hS2t8{XLQ2e-4NR zy17LD@2meG#ncom>GD}OuZmLdKPCGuVhlnV0d-*7Zv;FbI={~s_`P`Nib9r09N+{b z{I!|isCye||F{0Xb|QxGaKhP&;5YX|0(eK_WS>~SIOV#O$WU41zN`< zs7w$nzL*|cR+*O;FaGO={9_e>?W=4CB&E4)mlKT&Q3GvZ_~hC_kxa5Od^VBS#Jj9T>sJ0dFz1gQGDCnIZ_NkEoH;(iCl)|u zsrvUev@>IE_BBqhG$zfjt@Re;j=ytVdh)4#@!3BN79s>Ia0qA%dm)24!=(f`4Cl^e zwO7bwK9jmC1ds=hc=N_7q2@Yo{y)r_9eGp0MJ-h3eF7?eRYnD#{9^g3&S9>sx2Dl6xrGOhT(i`t@y9OsXPt%pRe$$@AOgcn z&lM>0$sQ75zYddpDWMyp()~Zs#4iKxj|@l*$kDpE;T3>l)_~*SzWL{RyphM^yP$T# zE_ z?Pd6W7oihfb1J5Fv@KipJYV`};m$|ek&XpS2uIJO9+won&rEwvFnL~y^J|y>nvbCC z*#luKVyk$}w2!U9l;ZzWnrDyI#6G)=!kR1M*Wo^Ac;}BJQ3!|XrJB_Ep=flP^)7x^ zx~uy6A6^FPhA+|Q>o(Q@^ejgFp95hIXkZ!KXN3EXkloh(mlKL41)Q4@&W_DL)GP*C z2~;YqCngrt|1U9|?Gdv`z-vAsMQHqEPyksM(iCjZjXE#(|1~8*&b=q4K#j{+PoUZ` z99Y0i#BMuPM3RXY_tgiF8%zX9&JSyXI*uw4W?%;>Dv6%eBr}e1eh*Kvo`!-XxsIl%+&Kv-MBkCQW{kJ#>p~q9BS8q zLLTXmEbR?0aDxB0zu=MCBRsU3?Bmmug|*xl>KCC`7Wf31KrDY+6~F?QENG5BpEV7Z z3WC_AbdJzCn93PU2lMSmKqp*-@i#X1i&<5M|D9C=#1u5xGn+GyfBsm@Obhj4oV;__ zO^<1^Gymp!i^&XYQv~wcY^hmu;MePcRAg;cm?qh9gAYccj9zx%O_;wACpG9A1I2yq zowBF93-5}2?d}Ew$NF#sWf27nwx-91PGbfSD44zvhK7@?OJv$ldeLF}oE88M+phku zotu2#sN$yUj)xO)PSBVdIi2ion8p?12mQRKr#n9zFpaO4E=E0%PdXp-ZUbbB?jfd=zQD4o z+58<)z(Hwxx|cKs>45*%&`3V)woKsEEAi@|i=yTIkd2q-_biRB4V-W1dL?EP`TVMd z@b>Izndc^|XjyBZm43rJHgD%5>x{1Sx4EFT7F5IwGnDZCUM}Fomb74SBBqY01Ssmc zf)ea~y8G7j$D1m^BP_&}O92G|BN83hbV+`aKTp0y=%`D+s;l1Gk>YyC29BNA(U4Bq z^9Ag2%4K-f$b5&IFY;lu>?uOXVW#f+v3RG8iA3TSaAt~fIPVHjmN3;_8y8qRq(DbB zN&J}F5~Yfs0-D%H`g+t->FihD7BzTH!hP*))^e^?GHWc>{I>0%l>Jvo(>uXoDEtzj z5Qegj{`cWZY|8#D)<7wOpWKTCmIo&SEuL9Yx_0b6gLa;W^-$d+IlrIBvs5pBo~+o` zqFno{;M0RG=y1uMx-Sd1h7CYnZ4nQc0Z7EQm)ne7dTF;pkVRl3!e%ZN={AhXIxYZZ zW)8{Al@0p^ff^q%q|QPL(A(_ahv)e7=*7=x74z$zPrjE#`|^gtF%9^;4Ttp(P_Md7 zFbb2mcs>`>6>3Ix5J2LSF<}`kz3^7$w+*ie#u5re2(E-AFK8x-gtue%;vgpo!P5LQ zJ()V-r*fpi+8rI{HzRv^S0;ciPR)~L(^IPw%xOsChA)n%SBBkB>lPVyxPV^T#;-qs z6?=%Ovioq*ei>F$)r7Q#8+IbHDO<}lg&R-zi>QFZV%(-aUf;YHD4j^MpkSsDGkx2@ zDj{b^(Q{It6XyiTA}`w7g;lkxvQOWOJmjUr(fo)h?&I7kz?7hUJ3VYPtvjsW;lp$e zEku}}STq+`U>d#|TF_;P44@5&E&@2VV6>D~TY5H|ueIz=V$+{090zH_zBhH-Co#q&dIC$U+5Lx1x#k*liXiD3H#Yh=PlJvJt;)i z+-dcpM$L*ndkplQ=VGJQ2}zbi?@#tr5=XK%#SN7JR5Fy1uGON74Cew>^K;um0~hWo zpyV$CNO#w6yUD4hq9VR*IXtc{gnk7Uk3%i@j(Cum*ysO;x%UpF`u+d7!-*s79NGJr z$sQS*S*ZxA%#M{EGUAZ!;MmE?%B;vpC{ZLFnxuzPfd^;-FWRad*yp%-{%Qr;7^;4w`6SoX0Q3bw7! za|V0@fBaLu04LSg&gJ#hKOqCvuzFX3VZa@MJ^WFouCOy(>HE&Q%F#Lw+{1-gR5R=N zy^k{c>)^xl5yeU;KO`f=X&@*c-d(QkIJsD;^25<@m}{K28Xza=a(;Zb??0>dB`=aA zE;+cWyPvKW}f!~y%SxhsXL4An00DvJ**irH7vC|aY%-5 zz)Y9~X39+XuRC48$$aU{17f&DNZ?L0C$*~2d-*v!{~dwNDViX;B^zNJph;v8cBx}w z7hu}r>7E~q%UJTd%yFN<@j!6nf1L=3Nb2A?K#%Mu^w z_zrXk^NY;(Sq3`kl6+-H%_;}#Awz~rguX~8H-aQK`%!6AVxBG2(F`zE?{T07veXB~ z!X2TVG|D=$(+a44fMhlV5kNmKh(B9gog=ya@bd>02|e;0iVqPRFsCr_^s(*n+3g=I zK|wC8NF=ZuF7EG$$Ofuwf78j{4a2#RT&+@&-m{>qz0Pu`Hs;kx{my-g3XHwmvv8p1 z5UQ_eQ*b)PUoiOM-&dk?{3vFp?6FwK$`!H0;|W z@R&@!4~((uCc@4NRVhqOE&Ph9Kx)>XIezuvbIuPF5qkBt&WglCUN)yIng!is+IlV%_O+0$@2r_Z+Z-_{? zE31vG($SGwrlF68Q2g+MNQ%YWFzQa9PlL6`(r05$-egdz^Q2NYPjtEmGL1(xMWv;% z9vOp1feBvk8-S`q@?I}1=lR&;scI_wsBhTFpoLlqWsYVAp{?*Y5>51-w~~nxDexFR z#q`Fd$}UkMOcEL-nfDuM0`INy4#K7#rfAhaG4;l)LX3!GlW2(h7LnVpGYYSs9aj0* zpI|1+X8u;QRvG!=8eu6Mi%nw++uj;y$o_(2_-YalBVJ02H8s4S7~2Vn@GMVR|2DgF zpoa@O*>L`#TBecX}#mRQ4@Cj`#A+Xy+nEg>!$n%_%ee0%b_plndOj=pZ3S z@cVk6HCuP7GgNR>7)u*bmqHbSd5wLsT9IS<(HJ9I5g>O-gBdH5{0VSbg7g`(Vg!wu z6*w1!a8yK3m_3oAB+ptvoM6=%nP6HjCm7ODNr9Sh!UGRHZk*M@LK2$twWGeujH} zVJlDEZ?ovoB<4tW!(_i_gKN(w<|LM>g6K?2o(&v(Rur?Q@|1AS%ajU!L?5G_AfN2s z_->`6kN_GCif~*fXCHLGeFH+WX%k-9-R7EFUKKry< z#m<6f!f%vEi7lfiG-61ft@r-0hHZ@FvdV6U9>1R8R?vcB&S}xNJGkUSf^zOQG@qK= z#P!i^m@2wvtIOh0a*2QIWJMw@qsfqBr3g^_6ghwO9)hs*8#QrVdm|1}r*QF;(&Kl{ zC-NR~#`zf@tyTG?hsWN$`j&rf1$kaRZf|i(vN*`g%`NUo7vE6)%30yLcYqLV%Dw68 zyabg34rW7z*Z=I+@Z+OBS21@_D*Y(a!ZnU%`uznFd@wlzD#rK3pMa(5@1{HYF>!Q5 z0liuIW>cL;>FTHj(&~EoC9(P!!!EDBbiVoMQ|A_VFwHsiC6Yo6scXw)N5ffnSB;B1 z{v&vs_wBT7swHBlE5Pw5(8Ac18FhA+!4Y~(#hX~uxq-WaxFExW@`ZX9nP&zTCp4f664F8+xI8A~ z)PyK1UT&*ww~!%O5-l=WS^72Y@U{)QE?PY6rhdG<&A=HRSoablf0wK11k5 zsJ^eM9jW@p3H3(+KH~Y&2>pO*fnk5$7NbrGpg?VrhAu&IP5Z3^xoi$-PVe8qf0x^x zNs7^$ZCeyj3?jXGWiOPGL47@qol1`fl0O8TQy@{kWsn7cMuI`aCP3*i{xUBZbx|T& zEaFijAFj{6joqm0N6^uBP(P*PORC>2B3^Mj2=1{#Ll1So= z1LZfB-{_sj67{ETQ2_%jz5Sq=@%&E&+& zhwiP+QS)XKdxNGd8oa;xa3RawFq)|^*NQ8|sKkEkrBH1?+x}QJc3RT!jD}{quK$C? zSe$9mt6SU15}#Y;Zi_<`79%gkj^}UJ%_()LhjYFE8RYtp_~de>PF}#SibPz8n+ZH| zum{mu&e6otevn|Whde24fA!t&xAQ6)8VHTpwQ#KuCBUH&sVouke0+a%C;nIyS)Q$& zHC#aTc_TqicU5!&W2qs(7Ca)@$9cqM834RMy~by+#{CwtBmi_vaJ*T(y|M7~bOh&a zr02P`VuGdLM&J1dG|U_92?7vqV$V5-<`E24%Up5Evy1cE>SO%GMmAV3(C<@brqn3P zx0dibI<&bMJSS9oa7+40_(FbCbD_m&{goQ!j5ATO$lA!8S95}g3<5-V2fdkR+cvuV z4i2l=%?6xxz*&8EGMFTzng<8VA#@P=mcg3m?gH6<@KY^=nWHVYBbg|5$c4i<4f}71 ze7SQEdL|Bff?b+-o8&uNZt{`}lzQi(q^#vy}Ji_NB zQK=qK^lvC(rL8$*#wy}$M##Oj&;DN90AC?Qq*xKO{bu4Z z0;%YunHJVJ)40wqCi3RfYW!S!>YS(IS6Sds7>GH^id)mNTUV}K$?7AGt+q`4>d|mq zuFly0iM5&+fnF8<#ZsE&M^<8`tL4>atak*2FWe5M2+%^Jq|`zm%oR9(X?KubT&Z0+ z$yB5`px6J(EFA>vAf{&V3qRjQSQOGa3*d_fuykS*rskBlYeVNJyISMGx<2xMXZOVK zJeOMH%^gE12MMjnkI;J>-5lRRtZCP;`ccq;^=DZ0DEKq4?I{zEB(fC&2#1~e{x&da z(&i-wpIII*fI#Be5;vE0bGHYA;#_o|Wo?SRvkj+v24)Bgs~^@o<{oie@{8c8=Tu~# z1Ij62HH=foq)mDovZ4&q>FfRf(&kkkz%Yys_|)o4(VvD|CYhr zjP%i7`H|_qm5V}>74Oa0J4ZVSN80DPtJwfsohUK46@4=>9tls-@1ERkYlMZU-(9;z#!2zH5$hPq<{KY{eo39T!WO3hvKf#Q2wV-~e?5R$q`4<0n> ztuy4Yd?9ZcF|fPOD;0QU#br-5op95$-nlBxa8WEQcYJEDZcJ5CQ ze*!t+;yZHm7I^L9^V?8?`8J~~+;ALBeMLMw2%k)&mk@X|ifLXPB!RKkAg2?gq*hK|HHv&fSWVGGxA^wu4jP&;&wK zi(*FP6~(dDMhuPS7kBqK??=d>y~3aj`=x*LpS0i1h*5Xv06tP3>vywb3sn&*02e#V zP&&VL8taFHRqa`0H0z2e6CH#jGUH@?7TCn1ruoDsWB~+In!+8md%hN@5Rk~^$Izyk zfkb{Bno({lQi&(q4Zu-c5W@^?L)fjizUm{9E*#yA9xv25qS+W-uZQ#_12#UT$szhk7p9Xg|e6GTx zRG3mWCNH~HH@Jnoxr+SVvn%|2s zYoUe+wSJ3G;~dQtdD<&jn05cY_{<46KE=FtGI;R~9!|8_n0@mOey%Mm;NZD#M<`xG zC)V-XZsF(d$@tLsG#s1@tt#njJEL~KtEmwc@5|z+a-_#~vy$>`2XbuGW}LmLw|_?z z;+4RiJ~`e95&+$~tZF@z^<8ft3sd72Bj)r`18vNxYN|wxi`Q3pl$KvLA5!#d@0>xl zzx>BoR5f2WQLB`*3EM@5!<|U0bD*rCJ%RelMx~cuaT9wd#4@dIpu_h?)n!VKXY$x5 ziIo*M>F#i=`otRN1r0fHbM4i|-Xw3fagz?@9JQjeU3g_nnUDo|=WWH8bI#@n7uzuV zh!?!w;8Nplw;7mGgvWNC%Ub`jImm3NkZk#BbIo{gx+5tFISVqLQpMx)tS&q`=4{;T z-0W@_7MN*H&XPs@%}>59B?iQ2W`!Kx#?ZDiD(lk@B42 zzI}(owV>|dA{9OfPV{3kF7>Xq1nyCy#0dAR)$d)eYDGi(2-~X9A$aqTO5-;fVx1n2 zhk9ohbD2)Xl-oi+qz^8#rZQrG3w_zd~X79so9HCI}_f$9=E_GT)R$3 z-Lje8l{|>^vs%08<1CtX(%7)>MJoAuYP$8SLVR*dT3?3n)NHGs|Hb(|<-?|dl}F7tzxn>`anQUy#U$?6;vv0=C0 zAU*w}K4KLUWo3)8Sc+D62Rn#gzY$s(tWDQ^l{>EMkX!ZEbvx1KRZ;a^J_gZkSe|%| zGR&fDToa>}0ci~d@Isq&Up)W26UZ`|{DXlK=;A#QIw=Wn*T*%8kBX>wL3Lt{3`$V< z3d~>HDzi%n0n(9FlaRQtZuj`502`1pB9%}m$PWIzmFwJzCJK9~c6mWR}^ zaG=ZuW|=b)>X-z=+y(y02&U1TfznN5Anjqfw3qz{y1hUi z%HA{8&1p;;nAmzbjf(5|TgPb>L*GbAS0OD|Qm&gml>>oBGyxanO()?B+=1gE;|DD) z6!)|LY~?HB4Z*Z&)MfdjXo$73x16p?5c}}thfiJ55dVsl(x|9i)C_UoX71Gbo;R3z0LBV08POc7PsokR&|<%pjM^hA1*7Fsik47ijf&xy$%=2 zDRTh)diAsKqu=4$udE{|+6NE2_bP2a+LL_4s!mOg;;2f4mlTf++Yxl?C!#N#^DEi{ zoq07KMa&A@M2S{xasOq7GQx147(!J50OD41i<{{BL?{#Q=b?VCu&T7U<9B-h!65!_ zGyn`kOx{Vu=X&XqkNKAs0lw}j|7_YN#NnhB8XrJlA}n^}J}s}-d#N5jngnAY(D2D0 zAd|{^mBOyqX4r&@Xa;;TdVmjUjjo#`uuT8+0!d-U&n?dxy}ze*J_xv!VTuZBJ52gf z@I%SeSWF4_mKi1eo5Q`u7$C;3w?tiqvd!ihpqj@3`9`pZAkdDud;`#pqF{~b@`1d! zgqdeILm1UY2<$35jeIcsi4k?xAShy?MaP#n4b-qN=Fldr=D1o(H+_=cM-Ub?w|LTj zwL}QB5pWyX7Bhj1pt$q zmnLDOwHDiv4ztI|>C{e%U@33+Dz57g$kTIgPOLEtvN~(|)>Eq-tm0y&-WU1OBA$fj zrWV?u#z9p#-HoMh)74r!pPpUo>w#O-9{wna*kC^NT%XpLalq+_0jTn_7q5Pl*H|D? zQ{j%Kg0goDV}tYw1^Wsxzk@OJR^^4l%B2$jopS)ZUKR7yu#ka_17$gS$E}UZ8;}@u zi1w_b2mI34tAm4b%VH%yD{|qhogUoq7DSPR^nhVaT+ca&?3E3*3JwSY#tpq)z-nVj zMsunhT1|iw`Mk&nt4Q|LC+zIW&tHV>eF0pM$7N*=v{`OI$M5*?Hsp%95c&2tVS3H(8X+2C&g{e1Jkx@!S;S!}D7d8&SHC&DnXIl`+svF5dF`E0&G7A%kAAATT0MVl9JmWg4OjP zqyv_iM}}BkYY=y4(my7$zP({%?pif3zH5+PHK3XJnaFx8*n6Fy{hG#At}_&Oby*=@ky-_;Mo%o%DZosc_j*hX2T461cbZQZvx=DJL>5h;Rrx5_0b6-{_F$hvM@jfDAZ$TIE`Gp zuY|PATqR5*SvaAgbJx^OXG?!wKq>$8oAJ$O=OS?@boE;f4x46o57v7*ldH!`AuT-d za{1$;zq(xB!N^yIn~pe@FbiQ}K$xF5zNAQ;@#CcT^014OC8Em_M_rj zCD!jvEO&s4h*j82LSDz54pYf6+jES4{n~Yt$AoDFRZ8k+v!T%yFTHTRV31n1)FheA z%Db;*9~o-KeHwLdStVmPu<}U))SGdf z$ppj+p>K_fsEKCW5wSPmEqr5&O6T*EH|P$zWOGk;^j6gftKb?nKI%ua1onw%uaYc# z=N;@OL%&lc9DYA?OZO%OwT?wZpG51Ck;gD&bZymZaAi6QtuJFN zF*iWx;!AbVXp`{hj-qqgHH2*F!jDNZm3G;e_0&A(F?ygw!;5mcV=A!u-?ay9_i6umEw=ey<)e)YyYWyd%V zS0nw|WUpttXy`$NvM-e)V?Afi7VmjPh=*Z5aW+%L$?!-&OJOsunbP`ZFNA|3Y#XIK z$hiCvUWlfb&E>K`tmfqj3-s(mRa9K7Px+WL=)|D5YCY^& z_%J%sbVN%PVMImJdkB@>WZ4Hey<{8jAwd5ZCLRz1d~diutWf(!oY4TG{fTU-jCWiM zy4agp(lFP^RfjIFbFAS&OEqN{kC~Ipb7wuxp1NbWb#V4@XSyobKyePlw0A`+r1#-` zfgtZlAG0xH_xi(W9%TCS^VuS|z@hQf_rwiwmhE1e0nW+wOXl&i=FppXqP9gGV~^*1 zd4ai!M%J?SDR2yR`ra#6%*Hf>_KKb=qcKqK@B2Vf;?F*&H_q5Sqi?HdhT6WyJmt%@ z{S2;-*7x8KZ_K(^n-8~cYdZ5_#ik-_^w(bZfwoxBi$nua<-q0JVvQrG-Rbk5; zUi#dXH%~wG#b{Vor0ZLdBjv&ua`{9pk3MfI&hJEq-W!a@cTetK@3m|XaJ#ty_&>!S zn&WH&yWZWWh|kyEs|MwX10DR~%x7#nGefqP@A-L}-f^%5%|;e~^=*9<3)$g}Se;ln z+CHrQd|_^?CuaNmZGL4}MrCNEf5LJXXp_+D#q=8RoIO$+6fSt{%o4!2Gl6b7D(>?M z-z+a$JkAYC1+4*6C)L=ZS2I+#+G(s+EyAqocvg!h<7Cnz1hngUhx*F%VVWv1R%iih zkp%2~mi31?S-L891Pq?9o@S6@1S7ePCAE7Ly(+RtW^d=++`JG>LR~~PGS4%9HY;*9 zM1a}?mLdR&pnJ8kd zovvkJHKk){G5vDP3{ZL*`;f+nKu!LIrQ_b(0cf7Xq|itRYK`+8o9?k1}8P#Z;Am7Y)^5 z$0idz@6^o4Dl`fg3Yn#oI3FiQs+Zg$BwlKj?R1OQzH;3ZxPQV>*4L5L`St3m^f~Ax zCuNxVi2C=&ejDJPJ-aK9QAZuZn!+rbg46X+*=g`(C1^AcT$6qaMU=u0&wm-?T(D{t z8BwQmh`h%|n?UH-X?eCi%)jfIg7+t+uCGNpAzT#Cx6tIn*sR_sZ!mGQkE@{$(Xioe zM#1-wl{NS&pA48WIIU*qcLV-iR#(c=WC%Tn%dAaZHb{fK1|v;8&7i!R1p!zVjfWm@ z45*0&AmY_A+nDHN+b``cy@yxN5LUUDGIi5 zFOhE{%o|Jc0-VT?2+gyDq}Ej961Fz?x+kUU9z?e>uih^VKPO%lIc2xsY4lc-La4<< zy`G)e04*w^Z_nBb)*B1C2JZ*{Zec97M{^x$Ues$im=&v%Wk8;SlH01I8mGBqB~fk! zI_GDSr|gjYZ>-rjdU#8;ct3-l;OQ5gXL?Dli(#Y16(Q7$;jmGZC=A1m_b|Z-%7#vo zsNXLjQ&(&>Ui~U~LI)Fz$U>_cmeNS@Df&YT)Hm(|qkrSg5dK`J)M~s>T>;7|&~?EV zyLrkO)kx;G%hohuDgh}GH_ig%i{@S2?waX#YYvAah~eAGbM!IWq8FMnB^Dy^$;b0g zbGV_}*a5%LHwO&YgouC!YA*_+L!(tCGCkwNIcXlK7e!{x<+W35N+~PnT8{eN{a=)k zpP4f$+9tX3X|uB^`eJW@Gdc{DLy{1tj)y6f za$Ew#LAe~wU?o7A(N%RNQOpj^aqVm|Nir+UvI#%XlF;<87$2sF^CR0>&OzOepCd)^ zL(p={1I~3fnAq0C<9J)G5U{D9rb*dABG9eC?5b1ZX6YI}MK*&1_91`Y3$r3;npvM*a7BdMl#j1n1^`Qffu18I`6T zOFqTt4)BlH#)gJuI6v0O?mI(`XIybg@on_I{7u6kHL727-(m=Nc3#TLpJ~N{Qmn=< z_N~WJ&59!Hz=b#8hkgd=lR=Q3(wTLM!^mn$_1PmQ<}YUcVrs&To)31qWef$pYg$lg9uXi#G0703*z6g!#AjFdMlLbwB`1WZIoE-Q=MaCOJo z-9z%3-!45(E^*Y}1oJ`#cTU-&S;J#YlWlu+bkI3$ULVf2e0jOVgpMpEMMyR~BFv}i z>B43lIHw|=xerP@DKH+C_ezAfrjHxS+~+q65S;AkXqwDk-&y+*i_pK<{Nxtgu=CC} z_5_;nHQ(7G_(iR6ta(=G)|PT4p^0YgB^wAyXZufEtba!gOx9o7th)sY$R@*6T~-I3 zjq8!B*s3cG7?>&y9qsGWUeO{PfRVP<uF*Kwi-c_^0v z)Ks@j2NQwO(sX-m8p0r3v;t;o=p|HHA{jjI(xIk~uehPPm>Tb`Rti$o-JR^QSKXiw zfzog@)Q#52Kzh=y#d~H*K-3^|iixO0OVt~jcCOA(E_V?s)=XuPog4;YcuvEY9nHG( zyIn_OZ?{*QVMm5LQ;5K<`Ngrza_?zQ`LE#KFwb-BonS71&Ch(zV7^vQOUd_Y{e1)a zl;zR3VMJ?afl0EPm;IZ|Tus*=gdmQB2_PO`J7Nk#ukT&o=dg%Y%Y;UiTKnkh2Ljhu zE>Y#5Pr>&-^o*9eel2CunUiFT>5-v~$H$F3$b#vEs`z_sSTuJ5&i6J*@zH;#>x~jS zbB%+3^ZnF;)*St!n49dH@j1*>TAR*0SL?qvlZ4R3I9suAx@Ykr&mWa6SaVv_ghH&f z&}ZnoBv~1!VwgB=q3pbgUt`Yn+SUdt55po?{SouBO=g%ljQ-}@RrqNW>A*gb6B+|y zr;ZzGC@jo1C(W_pY49gPqF`k(+$s}~hHK>UJjX)nx( z^2BaXbXzr2=BY7t>Sr3_)${D)gkeA-wjJEN zfKx01QTh$V>(RC`qhnQRBf!A8m4fMj@)Sad*K`RMm0VA6TZ{{eb01yqE3UJRm&0Mq z(I0BTyznC9y6U+0uzTotq59||m{e3n&h7Smg!MhL(@$*6JCAE}2xPlK($l^wFHABK zzrP^xFnI-^Ill3vE`GWcu7#Jx{kS6iHjVaj)B{STF)z*EEP&qgeXGe3Ci6@NtA4r_gTq}zmV5{m!vT>0iX$pu2d(_;}<0E`1k9bxWZndqLF5OnyQSS8N3cNSfM)6i` z^(ck&><%dK3Is~uGAcMa(2HH^_m7+;<0G}~IsDc?^s=K!OzlW_ZqjogEveVQM!8`3 zR8FBps-w)&H;0+NJH&8V_)18hC29rDcxl%UjCGl{38-(Cg7R2=xU8^r)r8F*bMCAy z&%ApDSC}*Dtbcn1{z=S-C#$?sHl+s6aAapF*{+Rv)5DC-8B~umGO0m4oGVcXHKped zpNhHo$sIzc*3xNZ)^c`7?Iey=-Wm3sFnN1q^P~NO1xO67Ioe888XL_NdJ^X8j?*e4-Z7KxDq)YqM<)GcrC?DEw@4K|G%*PKyKKSQMtX~%MU{i)XMwVihj~*s_svCd{>dPi4MN$Xq?P)M+9MDm^?7*m+;k8xkFU3D1R@S!xBDt zP1x*Z57`ExIi4fZ`t&Y`f)JAHg?mCVrI@fVZO?gT#n(-!>${OtmVB5R0!>TE9JDpf){h`Z0W74#=SMW(w5qr*qa zomo1QCN(+X=R3Jgs+Mpe*xHu#mW79159Xxa%kjJ-w|R2+JgRxN?vvwy<4K9-EYJ5H z-2I^M??L8cP*wj>5{e=v(5@TL$Xg#XCq<} z$kuLOMK|6_{-h!PWm*3R#N!Kg0^>FfBd~(snW4hT7wWiEbJmRB9pLd9{ z!CoUT9F?f?syM1E^m_vZmPul2mX4Ju4r#+`CedqmL~%qUfmkYRNTn)z4u%w^tPVpU zuA<#EcdphV$9CJv(QU9MvIu?6G#cbZ%CDmx+Q@jhuuqo;Z+v$fi@`-wkE>$rQM@gi zcww#O{QPYS9COt~qF4H=m{B)skzRKu_hGlCAN=fZ^hB8>4&n?%H}3BXchw z(s-=OIw)qSc?;~*hf23$2CG^)B%#4V3Cowgc|DfRK;pR4iu||${_H~3bH!}2ky$@e ziDxv`Gi=;maGKL=Y*Rfg<%0-oLOt3<#fngoap|U;+_HM^ zDoD~e2sb)jYx6q0I?VB^eZ`M8nb6}^*YUTf`$C|>oAi?On`~gPLB;1e7DR3~Li_u3 z%fZuV_PwwVhGGJjE2z=;)fsqcv>vI#8l|2jsv0Qz$h{)ZQve}|XVOgRQ%1?Q2V+1R zaXN+?s$y(#Gnk6=qNbb3Fui((T%=e#L9Vhjj5`2D3Rx*orErnTA;8KWf;@xop05W5 zqEviS5qBcsoe;EnT97ujUo_(U2SUd|eQsJkMK6n5k1_O2vqEv=HbX$bdgeryZ|=MG zn3Z(DO|;`|61C4FWvjkEAjT@8x$HzWW)x?W4LwjKVQ{rY zSnKmwb+aPA3@Xnk4$0WP^p}jKTuiin>R2-b-uW;p>h5yBd*EG{yesvuu+IbV-XP$1QrCXSdbUQVZZ@-C+n_fZVM^;xJw7r$gx&gs;$h0R@G)YO?T zN04d+W)y`fw+72@Q4M9BljGJa89deDQF4dsy6@XGoJ$j) zxYkiJcMJ_@vSZVF(~h{B1_;{k>ajxMwl=2T=F4#LrcsOxENVD`k>*F!WTX)lh0El8 zn1KpXQjrGLP_g>Gjf@+qO#mLhz8k_wqXs9Y#5r1!CINYa62+A<&pj5TC0jF3oOq)h zrI-{t^Li|eZ(Rv+CWSSOmj#+wSD0hXHO$ow+8_101uU9*%JJ3KX3(2v&PUq*+Pbnt zhn)BV(jzJS#aStqJWTkJz(K-{XI6NxdnY5KQ|ysry|d4+R?%23VtljgA}+KUqC#CI zE_%WMN*Y_ZJF-C07 zy>vXo8H}(Nlo90SKJicq_%#8%3_iJ@9?tm| z4B zpMOaEjRo*dZg`f|>;Ky#@?XEMuM2)RBjzXE=yyx*@5`z~1TlGr*;4p_cRBncnCA*{ zB5+j9$NsJ*j{u~Up)n`1ze?Dz0#ZuxR~+(hH}v5QsO`W0di`$R|2*Tb4Uz$V_YC8& zVIF_ft_e&<1OPRic{M`uXI1;37x4-FZgFkRuUVPDU(P>ObuTKQsee}c30d^FH(10E zFf~0b^?$t~XHfpU(^Iyk_Js^Dp`ffQtoPHA^HUr&`C<#tN6G=&hdx`esHSf{+~C)t~RN1w5#U_x&`_PTJl$I6e2J5 z{eZHL*7~D$jL7v@WBMGbk1`Z|mUE>mK3)`h3`R*Q_M}S*=w`}@^yH`nz1w_N*?BtZ zc-SWb{=#H#xWcACKSnge*k&~ds6#&j7RKqWH=u!oDG+?t=H4-eKc7d2H`o%5qLl$+ zin?7epnHoJLx4Y}0vyMwvo{w(-@CXH5LAaFZe~u;?hoz*m@>b#eaVMXP4grYQ#i zyJDkNK0d{4NFA>Bvjf{Nh5b7_nDOXhJWCL#G|#{+z8Ug>MCaC9n z1Ws;>)~z;Cb+tC~r5baWfpJUt%4#jj40%txr8BG;cc!Vkx5Y-eV}$PmJZ9~hs|Dym zP}k1I3$(*zZhiXWW`ZFRNyoC~2o--e1(97*e5_rY-OjB+c$Uw|Y0%q)RrX^^B5uTf64jV6v8-R{m>5MqFf%bL@dwZgVkQ(KOGl^=JYMxK zX7^GDU)=+Ib@ljR(mysvh&Na8P8Dbkn0+?+DO9c!DBVA;tNaj)OKBwbxz9Gl2Lwl`hRz`_Od0%9ihg2*Cu*UcUSAk9V)CPD~it#~^rW1Bj4k zE3aKhhNq15z;0;M#tm12sSEsemd=CGQBN|J3RPxT$3UO&o3*E!?iltN-zL*}ALT|M z5qy_R7$+m0e4Q7#e!oo_$YpX9p(!9InmUZz6HXGtLp5i*1T9#{rKgAtm1V}t)13}M zmv%4GfJSLUpCM|PQ9hbwlT5GJFVC+3iLSGkQ@M?fg`NYlFLZ*C@=gj)>fQsziXIMA z!zVf&n&dvs8M>H<6#HO;6Vp}jro*E9JO6ysm$`%IdO&$PRmsTOuFU#K{(A4~po!0y zJD$qNZ3a1M$4(*_&0_$mll9W6! z&X9yENrVSvopi3NfZZctAT~g^ztRf7f5Po5x$`vnxwEFk&3dXK4m;_i`G1%^@lePr zk~|?L2!IA^1$cGhKflff8lmq~m0$wx=Bh&@328!>ZK?@>PBZ?wwg-DTS>>|Z4PI}p z=cZQS^jEssT7_MpQepw~mlpj_PW6R37)mp7!4Trd8RM(U6IJQ%aCxL888=f`M4t&j zE8R(?W0Q!{e`U$KNeJd55(v#z71ezI{b{r@QMr+F}^NE^dx1*M&7j{3lVpU=W~ zHf2}5%CRNz{&gOh2oT}<3uFzy>a!M6~DYAEs`C|pXgThtW z)X=KQ?_w|?N?{@2Xc6`^45l6(e0RSmz)sb*=daetxajq zSZ@OFgMjcDs7r@^gU&Y_pB&U`x&8H_;hm5M@Z0Y3N! zIB#8DldAD-xHGYih5&FFkptR7|L@@m=MQEwka==D2mn(&a5LH$<8Ft5Fj^edJh`bx zy!dhm`Q|+BqQ{MgYoMZWE@J*`t#WLgCTfQZR-uz7j-Zf754%$)@N8XU&NgB~*z&d^ zU2A761!(2t9Tve_?;1BrJ}2$haS3^UapZP0Rh0u)2}}_?OaxfZu%3do`pQ>=1E9Kegu} z$Ni0AF|Zbb?b2v+Me_}c+vo;ZfhiZrTU(|)PY{x2K);pJQT}w!R_v|`!y?pK{rN}W zLr(FC+l82p`>W2cZk7zvI^D65q8d2y2J!C>{b{yyIUz6w*s0Z6$5!p)*c$1NS`8A| zWSGz1!b#kvjfT&j#S6UTXU5euL%dW8%_P5ji%PD?=0QO6!PSE?p&(Qmivj#X?0tD;!$i?gr>-LX3ol6CRiI-El z6_@^Y)lv>zr4n!%{p*-F7i|ztP`O3l`0Lf?3*f|&a~w_m zt4x=PUBqB<%u@UWXI3tK_^-dgBu(tVy5tJ}@}+|6{$H?R!H@;`H@dvAWx&r2=q`U&B!Td^KQ$XB?*%m3y6Q>lGchk{tiBH=M!r zRNE4f9}XMkJCBxod)fMy#UCfeuYKt7@EG}1azUGUX?IKsyOi@_>28_DR(m$wi7owI zTO6yA+3nF2ji&~AMh(q=j>7nJ`&vrFF(!j11$x~;7h2wTN?Tj^ecokqA))aH@pKp* zaSbz2e|J#G@-f7>QN>g~`^z2Nd#6f{RzJb5FAR8f?S`M*DnhCyXy<%h05I*lTYomf z|MS$bGQ{jjkNjSQZ2SG?t>0691EOuG2MSgyf4d4?58$^VR%QMgZjXe6u=vdw`Tk$O zy%vD!_CZ5lnf&2a{c|3E5Ctd0$n51|n!jBYR|8kIpmjZe%>Z~40&Y>Crog`}Wv&o7 zpu0+|O}Y>U$o@~L4kkfU02Ab0dvXf|AOH7iEDT(Q6T4OY<(hWx4RBn%?0Wc|^KVxz z(FDf|hRXbF5@1OU7^oY|=pX<0{{+v#qX7&Q`rYW?D2%iYjKDxGc?f)u{HuXN5d0g+ zxZU5@%Kx`d`)J}#C@PC_yGgP)RC}b4JcV^BimB{@(Omuh#&<7O@?ivCc_0_ z85T}Sv(sB|eXr_$0)|BL-zjDpGSG{7_?M5C>qsp3oa+SKNyer%_Pf=K4{8)=0uHqq zzo+qjngWDM>5mDqOaG-B`27U>3CDb+7;bp=eV+_X?Gqe?0~%BSI)#Yo!@pdW|6V^1 zM#K(`ay7ZOxsG0;chDnhYEB;1g4B1Kcx*ZU@<6K1s$Hu6-yqI+LomF0H~-)GmuG*P zUD6S)H)f+08U(9i_*ucse-~j0H_?k&V95mtM^8KqLn-3C-PV5ir=R}w zU+HTSj+3HjotS;?uhOn#H4MF#HGf@~zTkj*cn@&Ll`z3YWPR-_AGZE*`+wa;$RK&H zBiL2%%Z8X$rH}N*gqQ#K=70&!C4mzD>dw2k%0C06f3~+}5!5TX|I%n=ngqRh;XAhC z$PT~1yt{Aa#FsrMYPa8K|K+n0(s&=XI6%fypzg03%m1I6q*%8H@qe|UTCzVAeLx4o z^%GVI#0z=^O-hE8{blPT%i)9Fb>~mdmB2TY2JrS5VCHM#<5bhrWzU;V_*yj#CIQQ? z$MN<)GgyG|lb5b|**4%R(QfoK6p5un-G zo{Qds$6mJx*n9zUKeJ2c-VVfI$@wtaRj24ZVt5=k!s@%pd@&<1z2B~2pP`TKo8RohOZ_P=E8Y(iM)CQ zB0~k27kyeL!V7Nu1@m3NSJ!3l&v2f{c^vQe z>rJdvdU{}9HuYl4r5xaR$!>I`r9y6w1ZWzubUKjnoxFr)00f2_&xuAKz%#nIzJE%}K(=;V_}vVDG~13Iq>dv49v zhR@CTyqW;wWuiS?nk*o@40O#&NC3B9A7Glr)B@_+ThQ0Ye1HAw@2w%Qo+v1s*|Y%@ z(na&rLw%|ccxZT_rgQZmqo$Y{D)N#(01uV|*d!Z>r;6NUooQbfj^onq1CWT9M?;+l z$6VV3+8T54k0;5JCiAXkguM_at{J0T1TnqdnIA9dFG9`T!-H=BRGGcv=yxCsmdh8C z{_;JLigN_yJv!aq1}*4SAwJg>x1k%L)oOVNTy^oXuy(gNb0%Wxl800NT?loMik!rh-Vp)8KsFMK~wF~7|`JwNeupHO8P$s zkLxebSOFT|Zs8&jG2e#jB0Mb^Dv!`|;7bJ}VBtlfkG0XmvO%MqrzVQv^ku)tTm|m? zHXVNk0x!7Qh`Hfi6k(ae=9}Wz*VchAidis#Z_xR2^W{H$QOFGtX_h zGAYdO)f?dzH~$N=Ft*B=!TZDXHAXi~a_*xUYrrkZmx@%OZhyq9wE(pTl1i1N7Uf6< zpEcR(wv_P$vM7-SY3&r9q3#|O*&SXB)QLuRO9St9n~f$zIVjB@(llHS_he<=q_^ZK z2Ns(w;7da~Lao35UQs`3!Ye@AxF2#gJth{kK~X#qc7IS#hE>4CX?dm8k>2voKjw!UED<4*4S7{dIfiL`!%7W8I@30!l(g%wYaO5lF=UGy1Nv5; z7dA2#4d7TWu>@nEs=Hs#Y|6p?Cgyc59L_K(!#f1b4SNoOj3A+Dg2 zfg#Adb2r*$0}Avxz|i~hq$zEI*upOIJXm;0%D7Kl{jC)xQmM`uFMpibT z74Hm#I4cjGy&3~1!L0f2^Y=rII$i5R)*ILwHQlYm;=S}BIeTVwwqG`?Dw32 zI7IFOZ`;X=2qrtIN;R9Zp%)N*6PQxL)65?(R?`xEl_Z$5_q=?TY|A-@ zc`7EGf{1qxm@i1S)zbdki#+BBHxHb?4(o=!yRk(O`eC!Bp(E3QjoZFuqjO5(UHe7g zA8rbqr)*GXRj_6+E!1taUj*>96*s!!4xtSY+sjL=gw~vj+H2Y$ht$F3jOfW* zka<^o{;3X9v3M(K4(P$vA-1WvTbSV_Y?OySklt~vHWFjA%WHF^8gvO=5KXT#sM%^R zD)g}O_d$)>cv&#dlP^bCa+8(TJ9Jn5e&4$|KG8%c)Wbt_aG{4h7v)0)FU-8FZ(lZ2 zN@6J4c}>uY_3FByJO4Dq*OUkDyp0w|1a3@od2Kzz9hk0r>A&8m9u{2@dVO%K?z!&v zlTbMfry_RjVE(l%8y@1|KKym~9yBYcW1DiG)eE0ti}zc#H>cM%7 za>-p^>LVuuqCNDZelP4?q|bGZt@fu?#x*Mbu;Hjn4v}8h#F=8S(Bb2|p9KqD^#pF>KeCCpBqO zHP|FLhF;D(pa<&3pTFw=p-lfUu914&6vuEuX6Scgw6Mw(rk}Lk!9{l{FyZ502KVaj zM5es=NU(lMJJ9ptv~1(sRfe-pcz1bk7$N+4*V&gJ1i;qG&u<{(1Yqn>iwkF)pqUjw z`s9BgB;S02&KBsA1;qjPCCXv%Hz#`^>*g3lA$I@87bs>+cYZ1J^W#CY{Qd`OrU6Kb zK1*4I@e+}u8gH?p9wuVWie8i^P&wmm&qmJuDOwEAw3^x0bF*6!fe54vx^ly!1O#pF zWk2Uy7sN!T2}`|&JGZGfNxwTqXIrqnXD`ivm3RFx@Y7ESR@++9A~WRkGluO5jM3UH zEouzc!{tTiBbR}HvYOcFnQTD|i}+Kwg&W}~pM&WlI~ zg^Q1wP$BI{oiES6c2?%cA+ODWPHv}_8$r5Wo15(@G_#(65!S=Tcg{J$L@*| zc_QRzRsBpa3i1peLBL{MZtJo#(ndpiSBFoH@rsm7I~5+Ig<&US=#&IznD|DTpb43R zE!~3v?4FbCJ$7+?1=J-3Jv}W8-1mwneaQWb{XAQ@z5*THXa*MF4jo4tAH#dOn|InX zLHa4}!WtN{^6C})59Jw-<}QK(;p*I=h>36M<=@&GZ+wXa&Jzc~_ zI0)VNg*1u0*WAfI6d%(g(ZtZsdP`89DU}n;c#Ob8Irdjxa9=^9Z>sM+w*p$(AK9A&s!1iZjkLSG?1dIPGlaE?t+^wa@hk{)w%_+_|YP^ce2 z1%REp|1s~HqyN!(vcFvq{YTXAmmz~lfRC8x0;+jcd~$`ZID~*K9XnvVH*pe|K{_^V zWb47~yRLD>>Un#ZNHIGBl)b|x*$RA@U1|ixIV{|Af&LEX3l*d9u9lCmcn1lyUhM61 z^xICFU`Qu591+C%z_BoqYEWPqxxZGpixndwdkni;xg~SLtQQmd!>!QURGa1E;M7^A zlXRPhpgy(Bc?5`a15-c)Sny~UMn$v#%n#1m?z)c_Gxa-|J21fD=BO2ZW-X))M#|~T zDqqY6MYw5GBNnFwVc*vn`&P+(7Zl3oi${7ObQG^2d4!s>|NpSp{^<(HC7fxv5O|Z< z+2;)l@jUAg#r@@{RfOJAvGTAxL8czn!^4pJ7~{m;aIZat$57}7`m;z7R$ExgFLC#< ze);@5^@_Tqmv%m#<>f{C>9b}G`Qdt4$xtRgbOBi!7rDkIa;nLzWi2=9kq^EPl1n6D z*CVgrY~EHwrEl=b_aL=|Sy}-o(}X8H6fxy7E5s77nUSnm7E9%TDIy?liuDRnA>ZEw z#Xb5qdDKTzp-y09`5fH4=QIK|s7Cggw9)V2Sk=jpeXSRp^-aZqFJHB^@oa^)DZl;I zd&jSC!(YrCK_P9di}*hnoc~k|rnk?8k~kC*q-P81%aHiDA2WrpkEe$L+s#sp%Co!5 zS+$W)c^~8kh9#P3O68w2kefXOjQwVXMc}#m97u>qh}Qv%?V%a8zZtVFazNOs(}c1_ zfAftWQ*-IKFKw-e|EkYR^JS^Gz zdy`&nAO4-t35PvD-f%9=+0}ezAfWtm$&1*njPbmq0yKPG;L-hjx_+$68f{aYra?w3 zIEV@obHfzbyx6q_(j3kSDrP~Fz3R_e8o0`Ex99pHq6sNcqa#H&n2YVCdxUbBS4e|j z;3a++b`u-j26yp zRus92zMke-*gRoi19jcc=4A69P!(1vcAxDdy92RS4!`7&S5|$1e|cJ{BA6-6n@X=; z-S1ECX@z;`>>I^vbC=kHWb-XeRmlF3$goL&#)XD+M4LyAOefFopPBjY-%CS-#u&35j=OB0vRV|?gmq3>gqMwqRazAS~%MKg<7 z%UdpSN7y%QtFNbVqt^a=Taxp*#LV%?8E3*EME%kDT~j4`N8pEp`_P8oICkq7R>L^v zo7kR5<6+&|z^_j4)J<5!QfTw`%LGzdd#PNH%H50dktN}9Pz6FQnY_?2G*q|PVI^W~PNbw$Fo{mNi$3y~ z7!+G?ucn1*CouaNl%<0nDw+hX&4|#@dr9{LYJ+Kp98~XW!BJ8Q$z1dYuYuv(Cy(Yr zNYNxeoo2ei7RtGYTdLMf{7}=6qxGQ^g>KYH{mNPmXMTX z-4Pm=p0K9CQo%WfFR~DN-c!x%B)QuRPn}kmhaoqwg}p}HIJ`DV!}jiJy-b>1q2F%q z(eyBJUth}3aDi#p#3q>L^pa>hdXJB9?Sclf(jvsgWLeiVN9op- zYdRVhZ8)LCIoVAvIx@;IhaW8uem;Q(v*71GSlU;fe_lf546}+_Z~@E|Qcm|$@fn8Q z>BYtxl{kwFUbqZ_X>G(sz{j5YRaGcklu;MTyWs_isrtWxA`?f^-PlhOlQ&7?dh5n% z>OPL6+9?%yGY=Y{Gw8vRo93p&l4s=x`2(t^oV=1aOUkb zi(3%Xa=>Rbz-3R@Z*)@3Njzi?h1du3Ht}E$& zG{}7M5EBMdql*;da&*sIm_x}ns)bq!cF>K}OgUhMsLg$x{lJ9;s>gYy! z&!26Fdky8NDtc12h@cPPZuR000;KmNpsubrnKNnrp0mKwwL5NZ@8lG>=3kw!fu*vw zsxbMw?#*Uz4avL8MKa6t3+VJfq2Q(-NEV28A-I@z7^lEN*4&n7lrFEzqiyc^}MYek`S@T7Grd?sS$#_-j_@ zPWvQ$CUs<$3Z$cDdIEc8pI5BRLL@QK2sK)Fpc4yIgzujd(>Nl5u6xNY0MWS%56Z!h z&+(l*oEEWSNk{(Ne9bfxb{OWY9k;li$cgZCxR2`^$&$Lg>R6NFZl(_?J@Z|WW>JyJ zvb!W&(*Td*TG+^@#O_Kgxi?~EpWe(@z49EYLJc)3OJ;qNOP=9m)%>giR2$x{2442I(3zk;HJDa}bUDKH0`= zi&a#b;u_00dhOIeq+4w~8M;rcHVO-9>>y_4f|}a;64eH?&!^9or#g50ftSYGopaHr zVpBfiS+n3vn<#HQ6%@VJG2*&~x#N!#o8_|P-`2q%X?&;bPl7?kOtO|jqE?Z2PNu4t z2-lS3vd_g}W@zO~iJ##l-t4-+8}K2I~{GpmNW zl_OTdpbeiU8dR054}v+SK=D>Nm5A|usOgNvv%uc%&1Ws9bvKo=JPfb6Xk)Vl0~Z-T4^W zFEm_wswxcz&rNd0MVT)O?nTOoYHRRVzk*|(JtULxv?RY>J`&9#dNvbkxM>+uofC1T z8)4y6A#w2q7o3#RaQXM)Dzn_yJZKQIN|Ip2btpnW&jd~!gB5v7@OI;_PczxNV$8F4 z{QKvnf&~09t@UT;0|2Iuqlqrv(8=f15obet$W4ZHvI~luG;iEK5cZzo|Jq-TIaBf} z%`8@ph=6{b%jM#xd>KgH+JiOHUtmL%#+0Uz^|wt4Q>+?*Na+7+W%ktIW7*~3gwb}j zr)%a;lT5=yI1r3V0~)IIW=eUUU;Ns8snYDg<|NO6QbgSf*I~;q|F)E(!~Ecdfs!Xd z`UTFle^19;na|?W+bpn)^#ZJNN;3oQ32MV`_qjM35(W}shLxOQS};czsgSnuo3#@# z4*CZcoCR5z@Y4%!-MK8*_D8SvpSEkG${9yS{KSAR8NC%j7A@E2Y&am8Wq)cG7z7@PFV;|J|=#aKck64qb(Zw8!i6>dX9Cj-$Z(T=?&c@V~AH4JAHB zz@a~V>)xqO_rG;E(2h<+3P~~=O?Up|_WQ3r9{R^mXCN)znbfSm3O0J>fWSd`La9*x z_i4m`=C>qIfKKGhch8*PZqNVzWTg02ddyw`y4MF7Yw=L#Macdpn2=~dJkLbYhenMq z%|ZSL9p-%?%P-kBqa6$}ivO9~dV@=(J`kK|wl7WyMiN!702`JGvZ!zt zmEvtX{!lx&UsvChvYM^{a`$zv_Np*FG$>g54s31uWs=grHX0Y>v~S-4s%ZVW&65?N zo#-WI|;NMF{SM>*H9|fus_O?Sw`p%g%+b z(bHi~5V|fCh)(y!TO#6dvsY^NSkpoMcG+$c=ULNUa&Bq?6t)@>ydPA@?go)^cj&AF zE{bHy#&*?k4Ik4`b?^xZYeWnkAp$#4M^GI~V**j%bB$%g z`sTiF(W3T*hs99j+}_(EYw~SkL;-0m}wugnN2u7q&*y_Ulx71;g_Tz(l3J z5ljl*3y&c%;ut71?T_Yx?gXOfKqJ|IX5FCYrC~Q}9j^{U!RRfpH?d5B=A}jzJpyjm z{QQ=~I#9=ooG-EY(#~KGnMSV=9HO?4xm+Vor!zLPS z9h9XTgds}3kzSsewoI8y3UUfNIoFjXUQK-sHuyhLCI7Q@&v&2_r1_=@HGH7 zb={~Z{Mdx!gz2QvOe>%lHQ8}~jKKA&L#1Q}N(-@7!4E);zsns#NHHk{;9G`94#mx0 zuJ2cFC>YSem zz1g-?WjYfom*W(qDr9ADVpgh}NW^idkhT#;g6__o#=zMuXZXtUxKjquV z1KXgI$>#!!u%QF8wB)i#=~9{dY+|T2Xc}&6Imy3yqp|W&lNxp(#o3@1nyd)1^1di6 zyyN$^gEZ0GE=pENHm z+zjA!`KeQ{bf(@6^iQ)OX`}ZiiVhHbGfbMwkFgC#kh_9$?(D|1q` zx2a(2mNni|2hagL7=ydC4JxV*fmL9J5vegti^^+I6#%2)x@vtI%3&hvCT4NP+ixMA z$-HYKa96Q(k~tF=a*wbU^_pq1<+>Ggou9nLtHgRk3=hF512b8DaE?^SVR-q2`JXqq z#}K-~D6HIf&T(jHIL7C4#QZdl)FO+U{}z^Mb~}u=wryNNx-(lzCPjeU(h8=EK4ZbA zQ*aGuZ6JVp4wr#wtVUXjyi%?W*@Wva%GZ z`@?^!Sj;jus3u0ep$-v49COiIJm8Ir6c9HkePALc)IoVQH7&;~F5-SR`ssflb^r5{ zLuOQ#9&a64ovUS9H5iMj!8^jw2}+omnG2Xxzo!KKL{xgiso*MbQMh~fK`07?HT;;J zB*5Q9mFdWTK@rK0Tq9}P=t6epa;9T`D$0&9O)6urVqPe5QXM1b$>(F&LuebIun`+F zPRwgnKlMpu`wG~YDs!>o0&?P(vMg{sy%(=q1Tm~Xon=1PBYwauH-5?sqp;j`orXoP zHuKQ9Nl_L9woJa4NRq3l-VqI~(F-}uMsTYZE3sCVRzi-|1jj2~!wf6kIr>%BMUhk^ z>}^N~@8`a&`9QJL>dzYrhwi7P^3|8z3bvn;uGm~xFpzKigu-u-BoLeiH`s%P454gk z%ZvZ&mt{Q@K2;MHeil5sq5oa7;`J^&n<#8h1?a%V>rnA`S12>GZV|+g5%F}T24g>w zTbRNOOvLs;f(0^k4AKG-@u$EZAAyedt7x&qbNpjd3?L=XCXn9q<;J7H%MCgMhItX& z;SKu0Qw@8Gn4~(oA^J7)Nq;;cJS`Y4LP>glR(Pp!3PIU@W8iEkG&dhL&hd%7#*yrV zy1jOJ?#PH0|M@+J)CrYTQe5rEz#X=wh}72Kjuw!rjfa3m>SlOJ_$G}2Rt8969y4Cf zSsi6dNAw^7zIwxC>xxK2&#ymRy=y*Jn^MHdz3#N6xH^Wx&E{AKl05?ttM%*xizfK(qXFNZF zCB;N5nK3?9H-!6zeAl9)hHJp(VOO~6!wNdwI|Wa@xqd_#v zl3M&o$C&wSgMH11{ljUZ1<^+>+n=su+P5Cd3>+8v{kcjn+`>pDsC54xOo~f?HA+$(JUfi#1P!L653TT3Q>S`@u(zE+WFQK^qbyl} zruz-lu6`^IL@C^{VlxEIA});mU#&1HNT+%W(9ml;0tTuMSntDjJS2})`Br@&6V3u& zH+|wn)0^|^ezxmPzc4wKKxYTgR=G?Sz=<#B!j}T7rpncymjaQ2RtGfJEKU6F`(?^^ zkANoXd8`GipEDUE!r>ApLEGCtO_gK<$oO_J)Fgp;A!1)Z5td{I z-mhT{64k&W=vrBeHr+YTVzCQN7OM)3hj`gVE-hU4yL=m(r z(sRxmA(3IUsy~}IoyfB3UinnHwcVN?(1SqE?OuB~$3ZB-=OIM?d=mawg^Q94qs@h* zSn#n4c`!mAx=9qT5)Zqr4B*`xZ7Ey4hkT?A)P>0b*py`hD-j}$Aet4Q4_CpG$G`NTG}9mEF)PmUc?XmOy}9BnW)cUk zg(zZoDDGRg`lyT_oNGUE>opXE{4J$Ad-Tz=IscfhRvNu1z2|m{H<-!F9l35QrH5kjIMDa z#Y)fFe-1YLsbFr&wKjja!F(M^i(FimmCaDi+wJ4%NG0=**0%Y9@L)njx2nVd&l>6E z%{i-5tv$;J?@XK1^lT0PWjkQ*8N|~%K*&|dQav!?KTG{P6I3saB6;Nf3Tq-&^Z z*q}o_o0dYwg?}zG-%cE!wL~)@f80vBUiKqRr!#@V4HSIhiY!Y|y7cf3<5`vl?{LwR z!{Is`1Th)iq1x;33h8TJz8v^Z-#MTO5nio=P33?j{WerikI#Hvq~Y?g3{8j!_4#vu z46{L<-P46%WyFlykuQbC5UgJUt(qnlswQM4yimVpNms)EVya;0?GUui-p|j$YW^E& zD|a zXbZyN{4T;u3$#azk_QPJD@YwcO<_=svJq^&6t;YUbyTD>UO+0=04B&(eZUV13*-J?j&()5_ep+yy-g*0!44i&KnA`=U$*7nN8MCm${r!wDDg0Uk^ zj_ge$|NfFdg$%wVOKF}hIvPF(=F;Q$)~U`PU{%?&*e$jpZaYJtk}8dv5LVKcRwAIy zP*I%Hk{qRmp>D2(3Bj&+N`(wxM5^dcpLYvpR(ixl&a~|XY{55xal0c`Y$`#~#ST>< z{`4m~aUem67{zY0$Q`OQm=%2t_5+Ol7OMu9*7Bn7eWSzd&@C+OoV}%gFRYt&{o*vW z6j6XxyBDeo-UFYp91>p2jLPvs1hf@moOXkle!bDJhD401F;~fL#ggtOvUT%*p+jN~ zq&2djL+gQf$++`FU)uf9>s%dlf(UxmY(w*hUMBxKK!I}1h}rT7kIOLgBy$D)5CaPo zsJB1A3c(Yv@!*`>R4~JuFd3E0ZaOSG+*?Eh1JiWf%u5YN=MmXvw~%(yImtV|43=r36;8=G9qCF~*M3JNX(C0l-N8^?-6Ah?q3Kmnaw2b}g@|n+)vvmE2Ji44QSoQ+fK*g|ec(*<&7$=QonQb9j zr^ktlKaGd8C`mXArW06_q~EA%YKGD9mdY>C%)=Q6+crdzJK@(e%(jLB+$Wwi@N6J_ z9A=Cj`@V{K2ogFBCmOaxizFI80t4wwRFk~P1q~*fLljBI?Si@m(qL?`0aXL_F=|0> zhk1v0Nm(_+&p@QTMByu%MUa&~L~bX5%!k>Vt5?6F+hD_FdMula=La6@(}lt}K1KCR zYcmiEf=7O2QZCD9-VccHnkS6$i(&G4BIS33=Fi)}6p@^i(IsC5@$0{b4AXSe-m3IN zi(HuE8J_=BL;$*da=bg@!b!VZrQS%tCxyVOvBwm-bezgsoT8oe$Jm2`#(LYC}4~r z4W|F|`0{`2e<=t2Kfu6rV|%{4&~1eD&x;GSO?qzN_lS;t9W!Ho;PK>d{-Co&1FSt< zxA`F}>}5$kvUD$vgx@D6|8%O!ZSC=Ze-m=Tep6tU0XC4;uHL`7h?oP+@Oy;8)ZN0) zmk>VxC29CDEcNde!dGV!L-2Z;CxCmE9W)#IuZBPp;u;*{{;V)DgNc)?e8&O> z{Wq@5?_NVxo~PsIcACO2ZTAs5`=m6D)c&Or-*f#e`JGF5TKep!;rfy`K(e@PT(=S< z%{m}{Eg+g+tf2gRIc1<$o=SOE`M-w=&pz3*z)SK*?Y6 zFLS}037mw0>mio<(M?{4XnHDZ(?8F7(9#(YKn@F~P<&&+e(TkC;358X_8Ysl=hk=sa@Cmw{(m#*|G8>3|L>VW|G($+4cJ`mi#ge`I{9^A z*Oe^c=?VJ!+PnkJg8we{z#2lr9w2DdGP?xq`AL=u0v0ICFsq=^NvA;(=ry^V^_CeQ z3|54`q)3WIG0Dn&d*(dTGAAOI5$o9I^c6iG^PjTdzaP*y{`gKeE#&|6vVx16dbQ)n7vgoUSd0jTl@nFheed_9fC{gU{i33`;*SVWk zfcHur^71X|h!kX`l1To=oGsUn?{rt5f@Q#=C6acu(KaoZ>cQc!7`_p~8##@SRh@0$ zGSQCz#iRa)@$44))vAT`(RzZb8QOWQ_$-!(FrIS)Z~lFHh4zjD{2o3hC;5L&smrjk zV9WrJxiBnYRN7_!vI0E`33wTh*-JFEhljY4+J=8o=dX9e^};{P?mr|1YAm#>WPR`` z|KRQ)xAOY`JnjFpkSgKX(n2dawDnv@!fB0$)}mwnvgxb735T{5Ey@I{{g_U>i3;bd zsn*Va+Y$w~zy@_4spU5A4L_?zmLB8(W;Nh1zd8SsTt9Hnrx+IsJ+VDW1IW<7Y-4hS zaOiI>J)yUz{#!}$N%KE&2r@w{?uy$15;|nDyMMWMJ(c*$KWZPZr%3wA0SAP-tAq#+U&G(Gf~a8`jL z-uof=+!}~g0qtyh>*DO&yQXE{op_Y1{wMkm(Tf$y5SXfQvSi(7UUCr_LDd0k+Rf6t zh-AOgd##cKjPg#~$ymwQ9I=qROQd<{A3Y@yV)X-9;444y@5R3E04r<)NGu+n-GVf6 zMqm+HN(D)K!zcFx zn)EfC+;<&?f7n1>`)q~_VPQFymi-}0Y*HNLL_4;J!ecar>4~It-HM`R_N^g;-yw*| zdS$x8=?w)u0|f2^O&#qK3lm5G7062W`ezfz;o}F5kN@!jgB13R51jyM^vVDlnPvee zQgNDG6CqKK(4?~1H=oDpb-0$F0;9lhdS2ZjcO_5qk9ENH%vt>8|F*C$ATN^YVACF< zTH#o;gLeH)fcMJ3!ZOPOjk8Po&v#LZKN5I336Q(n6$S6wm9;Dg(!-NH4;QmIFbs^- zz&)FhuK2Cm?AJ7P7EqGcus6UpXp#CbS?~v6)H@TaD3L9%3RDt2XoH63gj~N0P*mQ6 zV=_<8cFKAuqwLQ6RNuA7uR9#qyjn+3flY3yYQzIjO20JzEh`o(9N_*+X@Ccgqv zMwu64RzT*{MVX)9$NWTPwiCMVQ7hp}mWa$@K9CjA$R@$sD|wmtkA?QD>XU5Bme9y% z7>5Eyc+rP|4LJsuygLr=R*J>S z@PXg7Az(pbwvJ{1Y@6_;qqRK>js=X^D!%GOSOh(*#2mp*R~^ZG%WfRW+nG6$=8(yToTAUEFGa z2XBGX0RcWu=<_U*CS;3)H8T5G6{XWJbRAecgi{=|6RLg1;* zZEa0{pffcP4h3R`^%Q!A%#%23Dn=K55&MSIpN7`7Z=UV-oI$OZED`Cttkpp2UqVPG z;R!QrWL?IxK5fgol`18%-OOVPs`d@HED%O|7kMI__1A^y*TZ_QB_qss zbM!^{Cb~0Vijb-Nb`UCctX?=)hm8FTvcNu=Qm@TIKVSHg8L#gA#;2F}&$CMGX!sxY zGg$6{Ww^H04qXX_D2<*Ay=M%&j&p_^3P=TS3~<#Yz$@u!TZy79m4)$>p`=Byap}5g zKp8-fZ5bw2m9*ZY@foMY(uSHrJqzmkn71%6>4D-C9ov0!YsQ~(6qt`nilLw!;iU{w*Sz) z1w3N^Wbl%crllPX7xcUO)FNU$-^G%*<)=IFy62~kxq8~nWS;Ua7uNaJhGc3m5)f2h zZ*t&VXUbuplmpZNgM3;`0l}syo+550bwm) zq$f=nzDs>5w30m`5!lZkhpL$=5)CAs{mrM*UBL(gOZb*r{mRR=<$LN6Q|l< zsFde`5s-!i+ESDAKyr|!& z!SLbazh18kub)HlZ6aDjU2s@}wWTErqXKO

P5QT)}{q|FY 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