fix: translation jumping on auto-translations (#50)

Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
Georg Bauer
2026-03-13 09:07:10 +01:00
committed by GitHub
parent 6a8d38d5a2
commit 101036e58e
2 changed files with 61 additions and 4 deletions

View File

@@ -769,7 +769,7 @@ export const PostEditor: React.FC<PostEditorProps> = ({ 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<PostEditorProps> = ({ postId }) => {
updatePost(postId, refreshedPost as Partial<PostData>);
setPost(prev => prev ? { ...prev, ...refreshedPost as Partial<PostData> } : 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<PostEditorProps> = ({ 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

View File

@@ -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(<PostEditor postId="post-1" />);
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([