From 3e11a6fc0bf60f8d9b3f49ac3c15c415dba08b4f Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Sun, 18 Oct 2026 00:17:04 -0400 Subject: [PATCH] fix(translate): say why Translate did nothing (#287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a message was already in the target language, or its language could not be detected, Translate switched itself back off with no chip, no "Translating…" and no error. Show a short toast instead: "This message is already in English." or "Couldn't tell what language this message is in, so it wasn't translated." Fixes #287 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- src/app/hooks/useMessageTranslation.ts | 16 ++++++++++++++-- src/app/utils/translation/langUtils.ts | 6 ++++++ src/app/utils/translation/translateSkip.test.ts | 8 ++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/app/utils/translation/translateSkip.test.ts diff --git a/src/app/hooks/useMessageTranslation.ts b/src/app/hooks/useMessageTranslation.ts index 7f9eb91ad..ce4bd6237 100644 --- a/src/app/hooks/useMessageTranslation.ts +++ b/src/app/hooks/useMessageTranslation.ts @@ -12,7 +12,8 @@ import { markAutoTranslateTried, } from '../state/translation'; import { chromeTranslationEngine as engine } from '../utils/translation/chromeEngine'; -import { sameLanguage } from '../utils/translation/langUtils'; +import { sameLanguage, translateSkipMessage } from '../utils/translation/langUtils'; +import { toastQueueAtom } from '../state/toast'; export type MsgTranslationStatus = | 'idle' // not translating (original shown) @@ -52,6 +53,7 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla // Write-only: reading the whole cache array here (via useAtom) would // resubscribe this message to every other message's writes. [Gitea #39] const setCache = useSetAtom(translationCacheAtom); + const setToast = useSetAtom(toastQueueAtom); // The key includes a fingerprint of `text`, so an edit (same event id, new // body) misses the old entry and re-translates instead of showing stale @@ -124,6 +126,16 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla if (!from || sameLanguage(from, targetLang)) { setStatus('skipped'); setActive(false); + // [Gitea #287] Say why nothing happened instead of silently undoing + // the click (no chip shows once translation is switched back off). + setToast({ + id: `translate-skipped-${eventId}-${Date.now()}`, + displayName: 'Translate', + body: translateSkipMessage(from, targetLang), + roomName: '', + roomId: '', + onClick: () => undefined, + }); return; } setFromLang(from); @@ -154,7 +166,7 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla // Invalidate this run's writes if inputs change / unmount. runIdRef.current += 1; }; - }, [active, cached, supported, text, targetLang, key, setActive, setCache]); + }, [active, cached, supported, text, targetLang, key, setActive, setCache, setToast, eventId]); // When switched off, drop back to idle (keep the cached translation around so // toggling back on is instant). diff --git a/src/app/utils/translation/langUtils.ts b/src/app/utils/translation/langUtils.ts index c0738e109..9ae545f37 100644 --- a/src/app/utils/translation/langUtils.ts +++ b/src/app/utils/translation/langUtils.ts @@ -85,3 +85,9 @@ export function languageName(code: string, uiLocale = 'en'): string { export function isSupportedTargetLang(code: string): boolean { return CURATED_NAME.has(normalizeLang(code)); } + +/** Pure: why a manual Translate did nothing (#287). */ +export const translateSkipMessage = (from: string | undefined, targetLang: string): string => + from + ? `This message is already in ${languageName(targetLang) || targetLang}.` + : "Couldn't tell what language this message is in, so it wasn't translated."; diff --git a/src/app/utils/translation/translateSkip.test.ts b/src/app/utils/translation/translateSkip.test.ts new file mode 100644 index 000000000..2caf652bf --- /dev/null +++ b/src/app/utils/translation/translateSkip.test.ts @@ -0,0 +1,8 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { translateSkipMessage } from './langUtils'; + +test('translateSkipMessage explains a Translate that did nothing (#287)', () => { + assert.equal(translateSkipMessage('en', 'en'), 'This message is already in English.'); + assert.match(translateSkipMessage(undefined, 'en'), /^Couldn't tell what language/); +});