fix: translation jumping on auto-translations (#50)
Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
@@ -769,7 +769,7 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
}
|
}
|
||||||
}, [title, content, isDetectingLanguage, tr]);
|
}, [title, content, isDetectingLanguage, tr]);
|
||||||
|
|
||||||
const handleTranslatePost = useCallback(async (targetLanguage: string) => {
|
const handleTranslatePost = useCallback(async (targetLanguage: string, options?: { switchToTarget?: boolean }) => {
|
||||||
if (!targetLanguage || isTranslatingPost) return;
|
if (!targetLanguage || isTranslatingPost) return;
|
||||||
setIsTranslatingPost(true);
|
setIsTranslatingPost(true);
|
||||||
try {
|
try {
|
||||||
@@ -782,8 +782,13 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
updatePost(postId, refreshedPost as Partial<PostData>);
|
updatePost(postId, refreshedPost as Partial<PostData>);
|
||||||
setPost(prev => prev ? { ...prev, ...refreshedPost as Partial<PostData> } : prev);
|
setPost(prev => prev ? { ...prev, ...refreshedPost as Partial<PostData> } : prev);
|
||||||
}
|
}
|
||||||
|
// 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);
|
const refreshedMap = mapTranslationsByLanguage(loadedTranslations);
|
||||||
applyDisplayedDraft(targetLanguage, canonicalDraft, refreshedMap);
|
applyDisplayedDraft(targetLanguage, canonicalDraft, refreshedMap);
|
||||||
|
}
|
||||||
showToast.success(tr('editor.translations.translateSuccess', { language: getLanguageLabel(targetLanguage) }));
|
showToast.success(tr('editor.translations.translateSuccess', { language: getLanguageLabel(targetLanguage) }));
|
||||||
} else {
|
} else {
|
||||||
showToast.error(result?.error || tr('editor.translations.translateFailed'));
|
showToast.error(result?.error || tr('editor.translations.translateFailed'));
|
||||||
@@ -813,7 +818,7 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
const handleConfirmTranslation = useCallback(() => {
|
const handleConfirmTranslation = useCallback(() => {
|
||||||
if (!translationTargetLanguage) return;
|
if (!translationTargetLanguage) return;
|
||||||
setShowTranslationModal(false);
|
setShowTranslationModal(false);
|
||||||
void handleTranslatePost(translationTargetLanguage);
|
void handleTranslatePost(translationTargetLanguage, { switchToTarget: true });
|
||||||
}, [handleTranslatePost, translationTargetLanguage]);
|
}, [handleTranslatePost, translationTargetLanguage]);
|
||||||
|
|
||||||
// Load project language for AI post analysis
|
// Load project language for AI post analysis
|
||||||
|
|||||||
@@ -329,6 +329,58 @@ describe('Editor AI post suggestions', () => {
|
|||||||
expect((window as any).electronAPI.chat.translatePost).toHaveBeenCalledWith('post-1', 'fr');
|
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 () => {
|
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.get = vi.fn().mockResolvedValue(createPost({ title: '' }));
|
||||||
(window as any).electronAPI.posts.getTranslations = vi.fn().mockResolvedValue([
|
(window as any).electronAPI.posts.getTranslations = vi.fn().mockResolvedValue([
|
||||||
|
|||||||
Reference in New Issue
Block a user