From 8f139c9e65e054c9ac9c9630c963abc8f9201779 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:49:05 -0400 Subject: [PATCH] fix(crowdin): keep code examples out of translation --- src/js/crowdin.js | 15 +++++++++++++++ tests/crowdin.test.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/js/crowdin.js b/src/js/crowdin.js index 901ffad..1d989e9 100644 --- a/src/js/crowdin.js +++ b/src/js/crowdin.js @@ -9,6 +9,7 @@ const loadScript = require('./load-script'); * @type {string} */ const CROWDIN_DIST_MIRROR = 'https://cdn.jsdelivr.net/gh/LizardByte/i18n@dist'; +const CROWDIN_CODE_BLOCK_CLASS = 'crowdin-ignore-code'; const CROWDIN_PLATFORM_STYLING_MAX_ATTEMPTS = 100; const CROWDIN_PLATFORM_STYLING_RETRY_DELAY_MS = 50; const CROWDIN_INLINE_ELEMENT_SELECTOR = [ @@ -199,6 +200,18 @@ function _createCrowdinTranslationCallback(boundaries) { return restoreAndObserve; } +/** + * Keeps preformatted examples out of Crowdin's text-node rewriting. Syntax + * highlighters can replace their children after translation starts, leaving + * the whitespace boundary snapshots pointing at detached nodes. + */ +function _excludeCrowdinCodeBlocks() { + document.querySelectorAll('pre, .fragment').forEach((block) => { + block.classList.add(CROWDIN_CODE_BLOCK_CLASS); + block.setAttribute('translate', 'no'); + }); +} + /** * Monkey-patches globalThis.fetch to redirect Crowdin distribution requests to * the self-hosted GitHub Pages mirror. @@ -341,11 +354,13 @@ function initCrowdIn(project = 'LizardByte', platform = 'jekyll') { let currentBaseUrl = globalThis.location.origin; // Initialize Crowdin translator + _excludeCrowdinCodeBlocks(); const whitespaceBoundaries = _captureCrowdinWhitespaceBoundaries(); globalThis.proxyTranslator.init({ baseUrl: currentBaseUrl, callback: _createCrowdinTranslationCallback(whitespaceBoundaries), + crawlerIgnoreClasses: [CROWDIN_CODE_BLOCK_CLASS], distribution: projectSettings[project].distribution, defaultLanguage: "en", languageTitles: languageTitles, diff --git a/tests/crowdin.test.js b/tests/crowdin.test.js index 0be2f4d..173757d 100644 --- a/tests/crowdin.test.js +++ b/tests/crowdin.test.js @@ -128,6 +128,7 @@ describe('initCrowdIn', () => { expect(Object.keys(options).sort()).toEqual([ 'baseUrl', 'callback', + 'crawlerIgnoreClasses', 'defaultLanguage', 'distribution', 'languageRoutingMethod', @@ -139,6 +140,37 @@ describe('initCrowdIn', () => { ]); }); + it('should exclude code blocks before Crowdin scans highlighted content', () => { + globalThis.document.body.innerHTML = [ + '
Run sudo modprobe uhid first.
sudo usermod -aG input $USER', + ].join(''); + + globalThis.proxyTranslator.init.mockImplementation(() => { + const fragment = document.querySelector('.fragment'); + const pre = document.querySelector('pre'); + + expect(fragment.classList.contains('crowdin-ignore-code')).toBe(true); + expect(pre.classList.contains('crowdin-ignore-code')).toBe(true); + expect(fragment.getAttribute('translate')).toBe('no'); + expect(pre.getAttribute('translate')).toBe('no'); + expect(document.querySelector('p code').hasAttribute('translate')).toBe(false); + expect(document.querySelector('p code').classList.contains('crowdin-ignore-code')).toBe(false); + + fragment.innerHTML = '
sudo modprobe uhid';
+ expect(fragment.querySelector('code').closest('.crowdin-ignore-code')).toBe(fragment);
+ });
+
+ initCrowdIn();
+ jest.runAllTimers();
+
+ expect(globalThis.proxyTranslator.init).toHaveBeenCalledTimes(1);
+ expect(globalThis.proxyTranslator.init.mock.calls[0][0].crawlerIgnoreClasses).toEqual([
+ 'crowdin-ignore-code',
+ ]);
+ });
+
it('should restore whitespace around translated inline elements', () => {
globalThis.document.body.innerHTML = `