fix(translate): align RTL translated text to the start (#4844)
Inline translation wrappers set lang but never dir, so RTL target languages (Arabic, Hebrew, Persian, etc.) inherited the source document's LTR base direction. Justified text then pushed its last line to the LTR start (left) instead of the RTL start (right). Derive the wrapper's dir from the target language via getDirFromLanguage so justified RTL translations align to the start. Extract the node construction into createTranslationTargetNode to make the behavior unit-testable. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,44 @@ import { eventDispatcher } from '@/utils/event';
|
||||
import { walkTextNodes } from '@/utils/walk';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { getLocale } from '@/utils/misc';
|
||||
import { getDirFromLanguage } from '@/utils/rtl';
|
||||
|
||||
export const createTranslationTargetNode = ({
|
||||
translatedText,
|
||||
lang,
|
||||
targetBlockClassName,
|
||||
hidden,
|
||||
widthLineBreak,
|
||||
}: {
|
||||
translatedText: string;
|
||||
lang: string;
|
||||
targetBlockClassName: string;
|
||||
hidden: boolean;
|
||||
widthLineBreak: boolean;
|
||||
}) => {
|
||||
const wrapper = document.createElement('font');
|
||||
wrapper.className = `translation-target ${hidden ? 'hidden' : ''}`;
|
||||
wrapper.setAttribute('translation-element-mark', '1');
|
||||
wrapper.setAttribute('lang', lang);
|
||||
// Set the base direction from the target language so justified RTL text
|
||||
// (e.g. Arabic) aligns to the start (right) instead of inheriting the
|
||||
// source document's LTR direction.
|
||||
wrapper.setAttribute('dir', getDirFromLanguage(lang));
|
||||
if (widthLineBreak) {
|
||||
wrapper.appendChild(document.createElement('br'));
|
||||
}
|
||||
|
||||
const blockWrapper = document.createElement('font');
|
||||
blockWrapper.className = `translation-target ${targetBlockClassName}`;
|
||||
|
||||
const inner = document.createElement('font');
|
||||
inner.className = 'translation-target target-inner target-inner-theme-none';
|
||||
inner.textContent = translatedText;
|
||||
|
||||
blockWrapper.appendChild(inner);
|
||||
wrapper.appendChild(blockWrapper);
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
export function useTextTranslation(
|
||||
bookKey: string,
|
||||
@@ -260,23 +298,13 @@ export function useTextTranslation(
|
||||
const translatedText = translated[0];
|
||||
if (!translatedText || text === translatedText) return;
|
||||
|
||||
const wrapper = document.createElement('font');
|
||||
wrapper.className = `translation-target ${!enabled.current ? 'hidden' : ''}`;
|
||||
wrapper.setAttribute('translation-element-mark', '1');
|
||||
wrapper.setAttribute('lang', targetLang || getLocale());
|
||||
if (widthLineBreak) {
|
||||
wrapper.appendChild(document.createElement('br'));
|
||||
}
|
||||
|
||||
const blockWrapper = document.createElement('font');
|
||||
blockWrapper.className = `translation-target ${targetBlockClassName}`;
|
||||
|
||||
const inner = document.createElement('font');
|
||||
inner.className = 'translation-target target-inner target-inner-theme-none';
|
||||
inner.textContent = translatedText;
|
||||
|
||||
blockWrapper.appendChild(inner);
|
||||
wrapper.appendChild(blockWrapper);
|
||||
const wrapper = createTranslationTargetNode({
|
||||
translatedText,
|
||||
lang: targetLang || getLocale(),
|
||||
targetBlockClassName,
|
||||
hidden: !enabled.current,
|
||||
widthLineBreak,
|
||||
});
|
||||
|
||||
if (el.querySelector('.translation-target')) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user