/** * Simulates the DOM mutations Chrome / Google Translate performs when * translating a page, so tests can reproduce the well-known class of React * crashes and stale-text bugs without a real browser translation session. * * Mutation shape, cross-verified from facebook/react#11538 (incl. the verbatim * Chromium bug 872770 analysis in comment 59) and * https://martijnhols.nl/blog/everything-about-google-translate-crashing-react: * * 1. Adjacent Text nodes are merged first (`normalize()`), so several * renderer-owned text nodes can collapse into one run. * 2. Each run is split into segments (numbers get isolated into their own * segment), and every segment becomes a nested double * `` wrapper around a NEW text * node with the translated content. * 3. The wrappers are inserted before the original text node, then the * original is REMOVED — it stays alive in memory (renderers still hold * references) but has parentNode === null. * 4. Translation can also delete text nodes outright and move inline * elements to match target-language word order (Chromium bug 872770). * 5. Comment nodes are never touched. * 6. A MutationObserver keeps watching the page and translates content that * appears or changes later. * 7. BEFORE any text is touched, the document is marked: a `translated-ltr` * class and a `lang` flip on (class first, then lang — verified * empirically against real Chrome, where both land ~275-500ms ahead of * the first displacement mutation). */ declare function pseudoTranslate(text: string): string; type TranslateFn = (text: string) => string; interface TranslateOptions { /** Text nodes the "translation" deletes outright (no replacement). */ deleteTextNodes?: Text[]; /** Inline elements moved to the end of their parent (word-order change). */ moveToParentEnd?: Element[]; } /** One-shot translation pass over a subtree, like the initial page translate. */ declare function translateSubtree(root: Node, translate?: TranslateFn, options?: TranslateOptions): void; /** * Models Google Translate's ongoing observation of the page: newly inserted * text gets translated (including a fresh normalize pass over its parent, * merging adjacent restored text nodes); text that changes inside an existing * wrapper is re-translated in place. * * Returns a stop function. */ declare function startTranslateObserver(root: Node, translate?: TranslateFn): () => void; /** Which browser's wrapper markup to emit. */ type TranslatorSignature = 'google' | 'edge'; /** * Displaces one text node the way a real translator does — wrapper in, original * out — using only the captured natives, so the shim's patched methods never * see the operation. This is the faithful reproduction of a browser * translator; `translateSubtree` is the same mutation shape performed in-realm. * * Returns the wrapper now standing in for the original. */ declare function displaceFromIsolatedWorld(textNode: Text, translatedText?: string, signature?: TranslatorSignature): HTMLElement; /** * Firefox's full-page translator (translations-document.sys.mjs) sends an * element's content to its engine as one piece — adjacent Text nodes * serialise into a single run of text — and applies the result with its * `merge()`: every child is detached, first to last; then the translated * nodes are appended in order, reusing the element's live Text nodes BY * POSITION (their data overwritten) and its live elements by identity, each * merged recursively before it is re-appended. A run of several renderer Text * nodes comes back as one translated text, so the first node of the run * carries the whole translation and the rest are never re-appended: they stay * detached while the renderer still holds them. * * Like the other out-of-world helpers, this uses DOM natives captured at * import: Firefox applies translations through Xray wrappers, which never see * a page's prototype patches. * * One deliberate difference in ordering: Firefox overwrites a reused Text * node, and merges a child element, while it is detached, and browsers report * those mutations to page observers through the DOM's transient registered * observers. jsdom does not implement transient observers, so it would never * report them. Here each node is overwritten or merged just after it is * re-appended instead: every parent's own record sequence and the final DOM * are the same, and jsdom sees every mutation a browser would report. */ declare function mergeLikeFirefox(element: Element, translate?: TranslateFn): void; /** * The whole of a Firefox page translation of `element`, in the order a real * one happens: `` is set to the target language from outside the * page's JavaScript world as translation starts, the elements inside the * block are tagged as it is sent to the engine, and the translation arrives * later, from the engine running in another process — so the merge happens in * a later task. */ declare function translateLikeFirefox(element: Element, translate?: TranslateFn, targetLanguage?: string): Promise; export { type TranslateFn, type TranslateOptions, type TranslatorSignature, displaceFromIsolatedWorld, mergeLikeFirefox, pseudoTranslate, startTranslateObserver, translateLikeFirefox, translateSubtree };