feat: swap translation languages with Return on empty input

This commit is contained in:
2026-10-18 10:08:33 +02:00
parent 9001411880
commit 9613cbb994
3 changed files with 31 additions and 4 deletions
+2 -2
View File
@@ -136,9 +136,9 @@ Notes autosave locally as RTFD packages, including embedded images, under `~/Lib
## Translation ## Translation
Search for **Conjure Translate**, identified by its centered 🌏 icon, and open it. Choose a provider in the pill-shaped header selector, enter text in the search bar, then press Return or click **Translate**. Typing alone never sends text. When the translation is available, the action changes to **Copy**: press Return or click it to copy the full translation and close Conjure. Command-C still copies selected output. Editing the input restores **Translate**. Search for **Conjure Translate**, identified by its centered 🌏 icon, and open it. With empty or whitespace-only input, Return or **Swap languages** reverses the source and target without submitting a request. Enter text to change the action to **Translate**; press Return or click it to submit. Typing alone never sends text. When the translation is available, the action changes to **Copy**: press Return or click it to copy the full translation and close Conjure. Command-C still copies selected output. Editing the input clears the result and restores **Translate**, or **Swap languages** if empty. The action is disabled while translating.
The footer places **Translate/Copy** and its Return keycap before **Actions**, whose Command and K keys use separate keycaps, matching the launcher. The footer places the current primary action and its Return keycap before **Actions**, whose Command and K keys use separate keycaps, matching the launcher.
Press Command-P or click the provider selector to expand a searchable action-style popup from its top edge downwards, hiding the indicator until the popup closes. A checkmark marks the current provider. Use Up/Down and Return or click a provider to select it; Escape, Back, or clicking outside closes the popup. Changing providers clears the previous result without submitting a request. Press Command-P or click the provider selector to expand a searchable action-style popup from its top edge downwards, hiding the indicator until the popup closes. A checkmark marks the current provider. Use Up/Down and Return or click a provider to select it; Escape, Back, or clicking outside closes the popup. Changing providers clears the previous result without submitting a request.
+9 -2
View File
@@ -239,8 +239,11 @@ final class TranslationController: NSViewController {
func inputChanged() { func inputChanged() {
cancelRequest() cancelRequest()
output.string = "" output.string = ""
translateButton.title = "Translate" let isEmpty = (input?() ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
translateButton.toolTip = "Translate the input (Return). Escape dismisses Conjure." translateButton.title = isEmpty ? "Swap languages" : "Translate"
translateButton.toolTip = isEmpty
? "Swap source and target languages (Return)."
: "Translate the input (Return). Escape dismisses Conjure."
settingsButton.isHidden = true settingsButton.isHidden = true
languageButton.title = "\(languageName(sourceLanguage)) → \(languageName(targetLanguage))" languageButton.title = "\(languageName(sourceLanguage)) → \(languageName(targetLanguage))"
output.setAccessibilityLabel("\(languageName(targetLanguage)) translation") output.setAccessibilityLabel("\(languageName(targetLanguage)) translation")
@@ -269,6 +272,10 @@ final class TranslationController: NSViewController {
@objc func performPrimaryAction() { @objc func performPrimaryAction() {
guard task == nil else { return } guard task == nil else { return }
if (input?() ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
swapLanguages()
return
}
guard !output.string.isEmpty else { translate(); return } guard !output.string.isEmpty else { translate(); return }
pasteboard.clearContents() pasteboard.clearContents()
if pasteboard.setString(output.string, forType: .string) { if pasteboard.setString(output.string, forType: .string) {
@@ -58,6 +58,26 @@ private func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.fl
@Suite(.serialized) @Suite(.serialized)
struct TranslationExecutionTests { struct TranslationExecutionTests {
@Test @MainActor
func emptyPrimaryActionSwapsLanguagesWithoutSubmitting() {
_ = NSApplication.shared
let suite = "TranslateEmpty.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defer { defaults.removePersistentDomain(forName: suite) }
let tool = TranslationController(defaults: defaults,
myMemory: { _, _, _ in Issue.record("Empty input must not submit"); return "unexpected" })
tool.input = { " \n\t" }
_ = tool.view
let button = descendants(tool.view).compactMap { $0 as? NSButton }
.first { $0.action == NSSelectorFromString("performPrimaryAction") }!
button.performClick(nil)
#expect(tool.sourceLanguage == "ja")
#expect(tool.targetLanguage == "en")
tool.performPrimaryAction()
#expect(tool.sourceLanguage == "en")
#expect(tool.targetLanguage == "ja")
}
@Test @MainActor @Test @MainActor
func languageActionsInvalidateOldResultsAndApplyTheSelectedPair() async throws { func languageActionsInvalidateOldResultsAndApplyTheSelectedPair() async throws {
_ = NSApplication.shared _ = NSApplication.shared