From 6345e7d58531c0817f0a23cf9a72c0c017ac3316 Mon Sep 17 00:00:00 2001 From: Dmytro Storozhuk Date: Wed, 28 Jan 2026 18:40:55 -0600 Subject: [PATCH 1/6] DP-44594: Enhance Google Translate integration for improved accessibility and behavior Implemented a fully accessible custom Google Translate interface with reset and apply functionality, improved cookie management, and robust language attribute handling. Added support for `.google-translate.css`. Signed-off-by: Dmytro Storozhuk --- .../mass_theme/mass_theme.libraries.yml | 2 + .../templates/layout/html.html.twig | 377 +++++++++++++++++- 2 files changed, 368 insertions(+), 11 deletions(-) diff --git a/docroot/themes/custom/mass_theme/mass_theme.libraries.yml b/docroot/themes/custom/mass_theme/mass_theme.libraries.yml index 48af911ed9..a7d928d439 100644 --- a/docroot/themes/custom/mass_theme/mass_theme.libraries.yml +++ b/docroot/themes/custom/mass_theme/mass_theme.libraries.yml @@ -24,6 +24,7 @@ global-styling: overrides/css/sections.css: {} overrides/css/news-page.css: {} overrides/css/language-bar.css: {} + overrides/css/google-translate.css: {} overrides/css/view-location-listing.css: {} overrides/css/rich-text.css: {} overrides/css/info-details.css: {} @@ -63,6 +64,7 @@ global-styling-lp: overrides/css/sections.css: {} overrides/css/news-page.css: {} overrides/css/language-bar.css: {} + overrides/css/google-translate.css: {} overrides/css/view-location-listing.css: {} overrides/css/rich-text.css: {} overrides/css/info-details.css: {} diff --git a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig index 757ca996db..4d3ec0dcdf 100644 --- a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig +++ b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig @@ -136,18 +136,373 @@ {% endif %} {# Google Translate JS #} +{# {{ dump(languages) }}#} + + {# Google Translate Script #} + + + + {# Accessible Interface Script #} From 2825226ff8e8d0539648061ffc5b2b47a4f2240c Mon Sep 17 00:00:00 2001 From: Dmytro Storozhuk Date: Wed, 28 Jan 2026 20:06:32 -0600 Subject: [PATCH 2/6] DP-44594: Add accessible reskin styles and browser language detection to Google Translate widget (work on mobile, bad on desctop). Integrated `google-translate.css` for accessible styling and compact utility navigation design. Added browser language detection to dynamically update the "Select language" label. Signed-off-by: Dmytro Storozhuk --- .../overrides/css/google-translate.css | 331 ++++++++++++++++++ .../templates/layout/html.html.twig | 66 ++++ 2 files changed, 397 insertions(+) create mode 100644 docroot/themes/custom/mass_theme/overrides/css/google-translate.css diff --git a/docroot/themes/custom/mass_theme/overrides/css/google-translate.css b/docroot/themes/custom/mass_theme/overrides/css/google-translate.css new file mode 100644 index 0000000000..0a7acceea1 --- /dev/null +++ b/docroot/themes/custom/mass_theme/overrides/css/google-translate.css @@ -0,0 +1,331 @@ +/* Accessible Google Translate Widget Styles - Compact for Utility Nav */ + +/* Fix Google Translate spacing issue with notranslate elements */ +.notranslate { + padding-left: 0; + padding-right: 0; +} + +html.translated-ltr .notranslate, +html.translated-rtl .notranslate { + padding-left: 0.25em; + padding-right: 0.25em; +} + +/* Hide default Google Translate widget */ +.ads-goog-te-wrapper { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +/* Hide Google Translate top banner */ +.goog-te-banner-frame.skiptranslate { + display: none !important; +} + +.skiptranslate iframe { + display: none !important; +} + +body > .skiptranslate { + display: none !important; +} + +.goog-te-menu-frame { + display: none !important; +} + +body { + top: 0 !important; +} + +/* Compact styling for utility nav integration */ +.ma__utility-nav__translate .ads-translate-container { + display: inline-block; + margin: 0; +} + +/* Hide fieldset border and make it inline */ +.ma__utility-nav__translate .ads-translate-fieldset { + border: none; + padding: 0; + margin: 0; + display: inline-block; +} + +/* Hide the legend visually but keep for screen readers */ +.ma__utility-nav__translate .ads-translate-legend { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +/* Make the label visible and inline */ +.ma__utility-nav__translate .ads-translate-label { + display: inline-block !important; + color: #fff !important; + font-size: 14px !important; + font-weight: 400 !important; + margin: 0 8px 0 0 !important; + vertical-align: middle !important; + position: static !important; + width: auto !important; + height: auto !important; + overflow: visible !important; + left: auto !important; +} + +/* Make select wrapper inline */ +.ma__utility-nav__translate .ads-translate-select-wrapper { + display: inline-block; + position: relative; + margin: 0; +} + +/* Style the select to fit in utility nav */ +.ma__utility-nav__translate .ads-translate-select { + padding: 4px 8px; + font-size: 14px; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 2px; + background-color: transparent; + color: #fff; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + min-width: 120px; +} + +.ma__utility-nav__translate .ads-translate-select:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.5); +} + +.ma__utility-nav__translate .ads-translate-select:focus { + outline: 2px solid #fff; + outline-offset: 2px; +} + +/* Hide the dropdown icon from example */ +.ma__utility-nav__translate .ads-translate-icon { + display: none; +} + +/* Hide help text visually but keep for screen readers */ +.ma__utility-nav__translate .ads-translate-help, +.ads-sr-only { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +/* Style Apply and English (Reset) buttons to fit inline in utility nav */ +.ma__utility-nav__translate .ads-translate-button { + display: inline-block; + padding: 4px 12px; + font-size: 14px; + font-weight: 500; + color: #fff; + background-color: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 2px; + cursor: pointer; + margin-left: 8px; + margin-right: 0; + vertical-align: middle; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.ma__utility-nav__translate .ads-translate-button:hover { + background-color: rgba(255, 255, 255, 0.3); + border-color: rgba(255, 255, 255, 0.5); +} + +.ma__utility-nav__translate .ads-translate-button:focus { + outline: 2px solid #fff; + outline-offset: 2px; +} + +.ma__utility-nav__translate .ads-translate-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* English (Reset) button - slightly different styling */ +.ma__utility-nav__translate .ads-translate-reset-button { + background-color: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.25); +} + +.ma__utility-nav__translate .ads-translate-reset-button:hover { + background-color: rgba(255, 255, 255, 0.25); + border-color: rgba(255, 255, 255, 0.4); +} + +/* Screen reader status messages */ +.ads-status-message { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + + + +/* Responsive styles for mobile/tablet */ +@media (max-width: 768px) { + + /* Hamburger menu specific styles */ + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-container { + display: block; + width: 100%; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-fieldset { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + padding: 16px 20px; + border: none; + background-color: transparent; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-label { + display: block !important; + color: #141414 !important; + font-size: 16px !important; + font-weight: 500 !important; + margin: 0 0 12px 0 !important; + position: static !important; + width: 100% !important; + height: auto !important; + overflow: visible !important; + left: auto !important; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-select-wrapper { + display: block; + width: 100%; + margin-bottom: 12px; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-select { + width: 100%; + padding: 8px 12px; + font-size: 16px; + border: 2px solid #707070; + border-radius: 0; + background-color: #fff; + color: #141414; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + min-width: auto; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-select:hover { + background-color: #f2f2f2; + border-color: #535353; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-select:focus { + outline: 2px solid #3e94cf; + outline-offset: 2px; + border-color: #707070; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-button { + display: block; + width: 100%; + padding: 10px 16px; + font-size: 16px; + font-weight: 500; + color: #fff; + background-color: #14558f; + border: none; + border-radius: 0; + cursor: pointer; + margin: 0 0 12px 0; + text-align: center; + transition: background-color 0.2s ease; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-button:hover { + background-color: #104472; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-button:focus { + outline: 2px solid #3e94cf; + outline-offset: 2px; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-reset-button { + background-color: #fff; + color: #141414; + border: 2px solid #707070; + margin-bottom: 0; + } + + .ma__header__hamburger__utility-nav .ma__utility-nav__translate .ads-translate-reset-button:hover { + background-color: #f2f2f2; + border-color: #535353; + } + + /* Stack elements vertically on mobile */ + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-fieldset { + display: flex !important; + flex-direction: column !important; + align-items: flex-start !important; + width: 100% !important; + padding: 12px !important; + border: none !important; + } + + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-label { + display: block !important; + margin-bottom: 8px !important; + margin-right: 0 !important; + font-size: 14px !important; + width: 100% !important; + } + + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-select-wrapper { + display: block !important; + width: 100% !important; + margin-bottom: 8px !important; + } + + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-select { + width: 100% !important; + min-width: auto !important; + } + + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-button { + display: block !important; + width: 100% !important; + margin-left: 0 !important; + margin-right: 0 !important; + margin-bottom: 8px !important; + text-align: center !important; + } + + .ma__utility-nav .ma__utility-nav__items .ma__utility-nav__item .ma__utility-nav__translate .ads-translate-reset-button { + margin-bottom: 0 !important; + } +} diff --git a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig index 4d3ec0dcdf..00cc358d45 100644 --- a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig +++ b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig @@ -172,6 +172,72 @@ const applyButton = document.getElementById('ads-translate-apply'); const resetButton = document.getElementById('ads-translate-reset'); const statusMessage = document.getElementById('ads-translate-status'); + const selectLanguageLabel = document.getElementById('ads-translate-label'); + + // Translations for "Select language" in different languages + const selectLanguageTranslations = { + 'en': 'Select language', + 'af': 'Kies taal', + 'sq': 'Zgjidhni gjuhën', + 'am': 'ቋንቋ ይምረጡ', + 'ar': 'اختر اللغة', + 'hy': 'Ընտրեք լեզուն', + 'zh-CN': '选择语言', + 'zh-TW': '選擇語言', + 'tl': 'Pumili ng wika', + 'fr': 'Sélectionner la langue', + 'de': 'Sprache wählen', + 'el': 'Επιλέξτε γλώσσα', + 'gu': 'ભાષા પસંદ કરો', + 'ht': 'Chwazi lang', + 'iw': 'בחר שפה', + 'he': 'בחר שפה', + 'hi': 'भाषा चुनें', + 'hmn': 'Xaiv lus', + 'it': 'Seleziona la lingua', + 'ja': '言語を選択', + 'km': 'ជ្រើសរើសភាសា', + 'rw': 'Hitamo ururimi', + 'ko': '언어 선택', + 'lo': 'ເລືອກພາສາ', + 'ml': 'ഭാഷ തിരഞ്ഞെടുക്കുക', + 'my': 'ဘာသာစကားကို ရွေးချယ်ပါ', + 'ne': 'भाषा चयन गर्नुहोस्', + 'ps': 'ژبه غوره کړئ', + 'fa': 'زبان را انتخاب کنید', + 'pl': 'Wybierz język', + 'pt': 'Selecionar idioma', + 'ro': 'Selectați limba', + 'ru': 'Выберите язык', + 'so': 'Dooro luqadda', + 'es': 'Seleccionar idioma', + 'sw': 'Chagua lugha', + 'ta': 'மொழியைத் தேர்ந்தெடுக்கவும்', + 'te': 'భాషను ఎంచుకోండి', + 'th': 'เลือกภาษา', + 'ti': 'ቋንቋ ምረጽ', + 'tr': 'Dil seçin', + 'uk': 'Виберіть мову', + 'ur': 'زبان منتخب کریں', + 'vi': 'Chọn ngôn ngữ' + }; + + // Detect browser language and update label + function updateSelectLanguageLabel() { + const browserLang = navigator.language || navigator.userLanguage; + const langCode = browserLang.split('-')[0]; // Get just the language code (e.g., 'en' from 'en-US') + + // Update label text if we have a translation for this language + if (selectLanguageLabel && selectLanguageTranslations[langCode]) { + selectLanguageLabel.textContent = selectLanguageTranslations[langCode]; + } else if (selectLanguageLabel && selectLanguageTranslations[browserLang]) { + // Try full locale code (e.g., 'zh-CN') + selectLanguageLabel.textContent = selectLanguageTranslations[browserLang]; + } + } + + // Update label on page load + updateSelectLanguageLabel(); // Store the original page language in a cookie const ORIGINAL_LANG_COOKIE = 'original_page_lang'; From 273842e4d382b7e305c23d9e50845caa3ad5b1ae Mon Sep 17 00:00:00 2001 From: Dmytro Storozhuk Date: Wed, 28 Jan 2026 21:13:01 -0600 Subject: [PATCH 3/6] DP-44594: Add Google Translate settings for native language translations and sanitize language codes display Integrated a new textarea setting for specifying native language translations in "Select language." Sanitized language codes display using `XssAlias::filter` to improve security. Removed redundant language entry from Twig. Signed-off-by: Dmytro Storozhuk --- .../themes/custom/mass_theme/mass_theme.theme | 21 +++++++++++++++++++ .../templates/layout/html.html.twig | 4 ---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docroot/themes/custom/mass_theme/mass_theme.theme b/docroot/themes/custom/mass_theme/mass_theme.theme index 53f6de7154..f62453a38b 100644 --- a/docroot/themes/custom/mass_theme/mass_theme.theme +++ b/docroot/themes/custom/mass_theme/mass_theme.theme @@ -8,6 +8,7 @@ use Drupal\block\Entity\Block; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\UrlHelper; +use Drupal\Component\Utility\Xss as XssAlias; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\ContentEntityInterface; @@ -16,6 +17,7 @@ use Drupal\Core\Link; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\ByteSizeMarkup; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\image\Entity\ImageStyle; @@ -6731,6 +6733,25 @@ function mass_theme_form_system_theme_settings_alter(&$form, $form_state) { '#title' => t('Choose google translate languages?'), '#default_value' => theme_get_setting('languages', 'mass_theme'), ]; + + $codes = array_map(function($language, $code) { + return "
  • $language:
    $code
  • "; + }, $languages, array_keys($languages)); + $codes_html = '
      ' . implode('', $codes) . '
    '; + + $form['mass_theme_settings']['select_language_translations'] = [ + '#type' => 'textarea', + '#title' => t('"Select language" in native languages'), + '#default_value' => theme_get_setting('select_language_translations', 'mass_theme'), + '#description' => t('Insert pairs in format "code|translation_in_native_language". List of language codes:'), + ]; + $form['mass_theme_settings']['info'] = [ + '#type' => 'details', + '#title' => t('Codes'), + '#open' => FALSE, + ]; + + $form['mass_theme_settings']['info']['#markup'] = XssAlias::filter($codes_html, ['ul', 'li', 'pre', 'code']); } /** diff --git a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig index 00cc358d45..b403f04e8e 100644 --- a/docroot/themes/custom/mass_theme/templates/layout/html.html.twig +++ b/docroot/themes/custom/mass_theme/templates/layout/html.html.twig @@ -135,9 +135,6 @@ {% endif %} - {# Google Translate JS #} -{# {{ dump(languages) }}#} - {# Google Translate Script #}