diff --git a/src/renderer/components/Editor/PostEditor.tsx b/src/renderer/components/Editor/PostEditor.tsx index bc58038..cca44f6 100644 --- a/src/renderer/components/Editor/PostEditor.tsx +++ b/src/renderer/components/Editor/PostEditor.tsx @@ -769,7 +769,7 @@ export const PostEditor: React.FC = ({ postId }) => { } }, [title, content, isDetectingLanguage, tr]); - const handleTranslatePost = useCallback(async (targetLanguage: string) => { + const handleTranslatePost = useCallback(async (targetLanguage: string, options?: { switchToTarget?: boolean }) => { if (!targetLanguage || isTranslatingPost) return; setIsTranslatingPost(true); try { @@ -782,8 +782,13 @@ export const PostEditor: React.FC = ({ postId }) => { updatePost(postId, refreshedPost as Partial); setPost(prev => prev ? { ...prev, ...refreshedPost as Partial } : prev); } - const refreshedMap = mapTranslationsByLanguage(loadedTranslations); - applyDisplayedDraft(targetLanguage, canonicalDraft, refreshedMap); + // Only switch the editing language when the user explicitly requested + // it via the quick action modal. Background/auto translations must + // never move the editor away from the language the user is working on. + if (options?.switchToTarget) { + const refreshedMap = mapTranslationsByLanguage(loadedTranslations); + applyDisplayedDraft(targetLanguage, canonicalDraft, refreshedMap); + } showToast.success(tr('editor.translations.translateSuccess', { language: getLanguageLabel(targetLanguage) })); } else { showToast.error(result?.error || tr('editor.translations.translateFailed')); @@ -813,7 +818,7 @@ export const PostEditor: React.FC = ({ postId }) => { const handleConfirmTranslation = useCallback(() => { if (!translationTargetLanguage) return; setShowTranslationModal(false); - void handleTranslatePost(translationTargetLanguage); + void handleTranslatePost(translationTargetLanguage, { switchToTarget: true }); }, [handleTranslatePost, translationTargetLanguage]); // Load project language for AI post analysis diff --git a/tests/renderer/components/EditorPostAISuggestions.test.tsx b/tests/renderer/components/EditorPostAISuggestions.test.tsx index f3dbef1..b9d9ece 100644 --- a/tests/renderer/components/EditorPostAISuggestions.test.tsx +++ b/tests/renderer/components/EditorPostAISuggestions.test.tsx @@ -329,6 +329,58 @@ describe('Editor AI post suggestions', () => { expect((window as any).electronAPI.chat.translatePost).toHaveBeenCalledWith('post-1', 'fr'); }); + it('switches editing language to translated language when confirmed via quick action modal', async () => { + const frTranslation = createTranslation({ language: 'fr', title: 'Bonjour le monde', excerpt: 'Resume', content: 'Contenu traduit' }); + (window as any).electronAPI.posts.get = vi.fn().mockResolvedValue(createPost({ + title: 'Hello world', + language: 'en', + content: 'Original content', + })); + (window as any).electronAPI.posts.requestAutoTranslation = vi.fn().mockResolvedValue(undefined); + // First call (initial load) returns empty, subsequent calls return the french translation + (window as any).electronAPI.posts.getTranslations = vi.fn() + .mockResolvedValueOnce([]) + .mockResolvedValue([frTranslation]); + (window as any).electronAPI.chat.translatePost = vi.fn().mockResolvedValue({ success: true }); + + const view = render(); + const ui = within(view.container); + + // Wait for post data to load and editor to initialize + await act(async () => { + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + }); + + // Expand metadata section so the title input is visible + await act(async () => { + fireEvent.click(ui.getByRole('button', { name: /Metadata/i })); + }); + + // Verify canonical content is shown initially + const titleInput = view.container.querySelector('#post-editor-post-1-title') as HTMLInputElement; + expect(titleInput?.value).toBe('Hello world'); + + // Open quick actions → translate modal + await act(async () => { fireEvent.click(ui.getByRole('button', { name: '⚡ Quick Actions' })); }); + await act(async () => { fireEvent.click(ui.getByRole('button', { name: /Translate to\.\.\./i })); }); + await act(async () => { fireEvent.change(ui.getByLabelText('Select target language'), { target: { value: 'fr' } }); }); + + // Confirm translation from the quick action modal + await act(async () => { + fireEvent.click(ui.getByRole('button', { name: 'Translate to...' })); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + }); + + // After quick-action translation, editor should switch to the translated content + expect(titleInput?.value).toBe('Bonjour le monde'); + }); + it('renders available translations as compact flag indicators in metadata', async () => { (window as any).electronAPI.posts.get = vi.fn().mockResolvedValue(createPost({ title: '' })); (window as any).electronAPI.posts.getTranslations = vi.fn().mockResolvedValue([