0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google翻訳をダークテーマに自動切り替えする方法

0
Posted at

Google翻訳を長時間使うと、明るい画面がまぶしく感じることがあります。そこで便利なのが、パソコンやブラウザの表示設定に合わせて、Google翻訳を自動的にダークテーマへ切り替えるユーザースクリプトです。

このスクリプトを導入すると、端末がダークモードのときだけGoogle翻訳の背景や入力欄、翻訳結果、メニューなどが暗い配色になります。ライトモードに戻すと、Google翻訳も通常の表示に戻ります。

主な目的

このスクリプトの目的は、Google翻訳を見やすい暗色表示に変更することです。

特に、夜間や暗い部屋でGoogle翻訳を使う場合に、画面のまぶしさを抑えられます。また、OSやブラウザのテーマ設定と連動するため、毎回手動で表示を切り替える必要がありません。

必要なもの

使用するには、ユーザースクリプトを実行できるブラウザ拡張機能が必要です。

代表的なものには、TampermonkeyやViolentmonkeyがあります。これらをChrome、Edge、Firefoxなどに追加して使用します。

導入方法

1. ユーザースクリプト拡張機能を追加する

ブラウザにTampermonkeyまたはViolentmonkeyをインストールします。

2. 新しいスクリプトを作成する

拡張機能の管理画面を開き、「新しいスクリプトを作成」などの項目を選びます。

3. スクリプトを貼り付ける

初期状態で入力されている内容を削除し、Google翻訳用の自動ダークテーマスクリプトを貼り付けます。

4. 保存する

スクリプトを保存し、有効な状態にします。

5. Google翻訳を開く

Google翻訳のページを開きます。すでに開いている場合は、ページを再読み込みしてください。

使い方

基本的に、導入後の操作は必要ありません。

パソコンやブラウザがダークモードの場合、Google翻訳も自動的に暗い表示になります。ライトモードに切り替えると、Google翻訳は通常の明るい表示に戻ります。

このスクリプトは、次のような部分を暗色表示に調整します。

  • ページ全体の背景
  • 翻訳文の入力欄
  • 翻訳結果の表示領域
  • 言語選択メニュー
  • ボタンやタブ
  • ポップアップメニュー
  • スクロールバー

Google翻訳の画面内に新しく追加された要素も自動的に確認されるため、ページを操作した後もダークテーマが維持されます。

注意点

Google翻訳の画面構成が変更されると、一部の場所が正しく暗くならないことがあります。その場合は、スクリプトの更新が必要です。

また、ほかのGoogle翻訳用ユーザースクリプトや外観変更系の拡張機能を同時に使用すると、表示が崩れる可能性があります。

問題が発生した場合は、ほかのテーマ変更機能を一時的に無効にして確認してください。

まとめ

Google翻訳の自動ダークテーマスクリプトを使うと、OSやブラウザの表示設定に合わせて、Google翻訳の外観を自動的に切り替えられます。

一度導入すれば毎回設定する必要がなく、夜間でも見やすい環境で翻訳作業を行えます。

// ==UserScript==
// @name         Google Translate Auto Dark Theme
// @namespace    https://translate.google.com/
// @version      3.0.0
// @description  Follows prefers-color-scheme. Uses the default theme in light mode and applies a custom theme in dark mode.
// @match        https://translate.google.com/*
// @match        https://translate.google.co.jp/*
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(() => {
    'use strict';

    const STYLE_ID = 'tm-google-translate-auto-theme-v3';

    const css = String.raw`
        /*
         * Apply the custom theme only when the OS or browser
         * is in dark mode.
         *
         * In light mode, no styles are overridden, so
         * Google Translate's default theme remains unchanged.
         */
        @media (prefers-color-scheme: dark) {
            :root {
                color-scheme: dark !important;

                --tm-page: #0f1115;
                --tm-surface: #171a20;
                --tm-surface-2: #1d222b;
                --tm-surface-blue: #1d2735;
                --tm-hover: #282e38;
                --tm-border: #3b424d;
                --tm-text: #e8eaed;
                --tm-text-2: #bdc1c6;
                --tm-muted: #9aa0a6;
                --tm-accent: #8ab4f8;
                --tm-selection: #3d5f91;
                --tm-scrollbar: #565e69;
                --tm-scrollbar-hover: #707985;

                /*
                 * Google Material Design color variables.
                 */
                --gm3-sys-color-surface: var(--tm-surface) !important;
                --gm3-sys-color-surface-container: var(--tm-surface-2) !important;
                --gm3-sys-color-surface-container-low: var(--tm-surface) !important;
                --gm3-sys-color-surface-container-lowest: var(--tm-page) !important;
                --gm3-sys-color-surface-container-high: var(--tm-surface-2) !important;
                --gm3-sys-color-surface-container-highest: var(--tm-hover) !important;
                --gm3-sys-color-surface-variant: var(--tm-border) !important;
                --gm3-sys-color-on-surface: var(--tm-text) !important;
                --gm3-sys-color-on-surface-variant: var(--tm-text-2) !important;
                --gm3-sys-color-outline: var(--tm-border) !important;
                --gm3-sys-color-outline-variant: var(--tm-border) !important;
            }

            html,
            body,
            #yDmH0d {
                background-color: var(--tm-page) !important;
                color: var(--tm-text) !important;
            }

            /*
             * Primary containers that often remain white.
             */
            .ccvoYb,
            .gb_fd,
            .gb_cd,
            .gb_jd {
                background-color: var(--tm-surface) !important;
                border-color: var(--tm-border) !important;
            }

            /*
             * Pale blue translation result panel.
             */
            .QcsUad,
            .QcsUad.sMVRZe,
            .QcsUad.hCXDsb,
            .QcsUad.wneUed {
                background-color: var(--tm-surface-blue) !important;
                color: var(--tm-text) !important;
            }

            /*
             * Rounded buttons such as History and Saved.
             */
            .ySES5 {
                background-color: var(--tm-surface-2) !important;
                color: var(--tm-accent) !important;
                border-color: var(--tm-border) !important;
            }

            /*
             * White gradient at the right end of the language tabs.
             */
            .X4DQ0::after {
                background: linear-gradient(
                    to right,
                    rgba(23, 26, 32, 0),
                    var(--tm-surface)
                ) !important;
            }

            /*
             * Similar pseudo-elements used in horizontal scroll areas.
             */
            [class*="X4DQ0"]::after {
                background-image: linear-gradient(
                    to right,
                    rgba(23, 26, 32, 0),
                    var(--tm-surface)
                ) !important;
            }

            header,
            nav,
            footer,
            main,
            [role="banner"],
            [role="navigation"] {
                border-color: var(--tm-border) !important;
            }

            textarea,
            input,
            select,
            [role="textbox"],
            [contenteditable="true"] {
                background-color: transparent !important;
                color: var(--tm-text) !important;
                caret-color: var(--tm-accent) !important;
                border-color: var(--tm-border) !important;
            }

            textarea::placeholder,
            input::placeholder {
                color: var(--tm-muted) !important;
                opacity: 1 !important;
            }

            button,
            [role="button"],
            [role="tab"],
            [role="option"] {
                border-color: var(--tm-border) !important;
            }

            button:hover,
            [role="button"]:hover,
            [role="tab"]:hover,
            [role="option"]:hover {
                background-color: var(--tm-hover) !important;
            }

            [role="tab"][aria-selected="true"],
            [aria-checked="true"],
            [aria-current="true"] {
                color: var(--tm-accent) !important;
            }

            [role="dialog"],
            [role="menu"],
            [role="listbox"],
            [role="tooltip"],
            [aria-modal="true"],
            .goog-menu,
            .goog-menuitem,
            .goog-tooltip,
            .ita-kd-menu,
            .ita-kd-dropdown-menu,
            .ita-kd-statusbar {
                background-color: var(--tm-surface-2) !important;
                color: var(--tm-text) !important;
                border-color: var(--tm-border) !important;
                box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55) !important;
            }

            [role="option"][aria-selected="true"],
            .goog-menuitem-highlight,
            .goog-menuitem:hover,
            .ita-kd-menuitem:hover {
                background-color: var(--tm-hover) !important;
            }

            hr,
            [role="separator"] {
                background-color: var(--tm-border) !important;
                border-color: var(--tm-border) !important;
            }

            a,
            a:visited {
                color: var(--tm-accent) !important;
            }

            /*
             * Bright backgrounds detected by JavaScript.
             */
            .tm-gt-auto-bg {
                background-color: var(--tm-surface) !important;
                border-color: var(--tm-border) !important;
            }

            .tm-gt-auto-bg-blue {
                background-color: var(--tm-surface-blue) !important;
                border-color: var(--tm-border) !important;
            }

            /*
             * Bright gradients detected by JavaScript.
             */
            .tm-gt-auto-gradient {
                background-image: linear-gradient(
                    to right,
                    rgba(23, 26, 32, 0),
                    var(--tm-surface)
                ) !important;
            }

            /*
             * Dark text detected by JavaScript.
             */
            .tm-gt-auto-text {
                color: var(--tm-text) !important;
            }

            button svg,
            [role="button"] svg,
            [role="tab"] svg,
            [role="option"] svg,
            .TYVfy svg {
                color: inherit !important;
            }

            button svg path:not([fill="none"]),
            [role="button"] svg path:not([fill="none"]),
            [role="tab"] svg path:not([fill="none"]),
            [role="option"] svg path:not([fill="none"]),
            .TYVfy svg path:not([fill="none"]) {
                fill: currentColor !important;
            }

            ::selection {
                background-color: var(--tm-selection) !important;
                color: #ffffff !important;
            }

            * {
                scrollbar-color:
                    var(--tm-scrollbar)
                    var(--tm-page) !important;
            }

            *::-webkit-scrollbar {
                width: 12px;
                height: 12px;
            }

            *::-webkit-scrollbar-track {
                background: var(--tm-page) !important;
            }

            *::-webkit-scrollbar-thumb {
                background: var(--tm-scrollbar) !important;
                border: 3px solid var(--tm-page) !important;
                border-radius: 10px;
            }

            *::-webkit-scrollbar-thumb:hover {
                background: var(--tm-scrollbar-hover) !important;
            }
        }
    `;

    function installStyle() {
        if (document.getElementById(STYLE_ID)) {
            return;
        }

        if (typeof GM_addStyle === 'function') {
            const style = GM_addStyle(css);

            if (style) {
                style.id = STYLE_ID;
            }

            return;
        }

        const style = document.createElement('style');

        style.id = STYLE_ID;
        style.textContent = css;

        (document.head || document.documentElement).appendChild(style);
    }

    function parseRgb(value) {
        const match = value.match(
            /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+))?\s*\)$/i
        );

        if (!match) {
            return null;
        }

        return {
            r: Number(match[1]),
            g: Number(match[2]),
            b: Number(match[3]),
            a: match[4] === undefined
                ? 1
                : Number(match[4])
        };
    }

    function luminance({ r, g, b }) {
        return (
            0.2126 * r +
            0.7152 * g +
            0.0722 * b
        ) / 255;
    }

    function chroma({ r, g, b }) {
        return (
            Math.max(r, g, b) -
            Math.min(r, g, b)
        ) / 255;
    }

    function isBlueTint({ r, g, b }) {
        return b >= r + 10 && b >= g + 4;
    }

    function shouldSkip(element) {
        return element.matches([
            'img',
            'picture',
            'video',
            'canvas',
            'iframe',
            'object',
            'embed',
            'svg defs',
            'svg linearGradient',
            'svg radialGradient',
            'svg stop'
        ].join(','));
    }

    function hasLightGradient(backgroundImage) {
        if (
            !backgroundImage ||
            backgroundImage === 'none' ||
            !backgroundImage.includes('gradient')
        ) {
            return false;
        }

        return (
            /rgba?\(\s*25[0-5]\s*[,\s]+\s*25[0-5]\s*[,\s]+\s*25[0-5]/i.test(
                backgroundImage
            ) ||
            /rgba?\(\s*24\d\s*[,\s]+\s*24\d\s*[,\s]+\s*24\d/i.test(
                backgroundImage
            ) ||
            /#fff(?:fff)?\b/i.test(backgroundImage) ||
            /\bwhite\b/i.test(backgroundImage)
        );
    }

    function darkenElement(element) {
        if (
            !(element instanceof Element) ||
            shouldSkip(element)
        ) {
            return;
        }

        const style = getComputedStyle(element);

        /*
         * Detect bright backgrounds and assign classes.
         *
         * These classes may also be assigned in light mode,
         * but the corresponding CSS exists only inside
         * prefers-color-scheme: dark, so they have no effect
         * while light mode is active.
         */
        if (
            !element.classList.contains('tm-gt-auto-bg') &&
            !element.classList.contains('tm-gt-auto-bg-blue') &&
            style.backgroundImage === 'none'
        ) {
            const background = parseRgb(style.backgroundColor);

            if (
                background &&
                background.a >= 0.2 &&
                luminance(background) >= 0.68 &&
                chroma(background) <= 0.30
            ) {
                element.classList.add(
                    isBlueTint(background)
                        ? 'tm-gt-auto-bg-blue'
                        : 'tm-gt-auto-bg'
                );
            }
        }

        /*
         * Detect bright gradients.
         */
        if (
            !element.classList.contains('tm-gt-auto-gradient') &&
            hasLightGradient(style.backgroundImage)
        ) {
            element.classList.add('tm-gt-auto-gradient');
        }

        /*
         * Detect dark neutral-colored text.
         */
        if (!element.classList.contains('tm-gt-auto-text')) {
            const foreground = parseRgb(style.color);

            if (
                foreground &&
                foreground.a >= 0.5 &&
                luminance(foreground) <= 0.48 &&
                chroma(foreground) <= 0.18
            ) {
                element.classList.add('tm-gt-auto-text');
            }
        }
    }

    let scanScheduled = false;

    function scan(root = document) {
        installStyle();

        if (root instanceof Element) {
            darkenElement(root);
        }

        const elements = root.querySelectorAll
            ? root.querySelectorAll('*')
            : [];

        for (const element of elements) {
            darkenElement(element);
        }
    }

    function scheduleScan(root = document) {
        if (scanScheduled) {
            return;
        }

        scanScheduled = true;

        requestAnimationFrame(() => {
            scanScheduled = false;
            scan(root);
        });
    }

    installStyle();

    const start = () => {
        scan(document);

        const observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    for (const node of mutation.addedNodes) {
                        if (node instanceof Element) {
                            scheduleScan(node);
                        }
                    }
                } else if (mutation.target instanceof Element) {
                    scheduleScan(mutation.target);
                }
            }
        });

        observer.observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: [
                'style',
                'class',
                'hidden',
                'aria-hidden',
                'aria-selected',
                'aria-checked',
                'aria-expanded'
            ]
        });

        /*
         * Periodically recheck elements that are reused
         * by visibility toggling rather than being recreated.
         */
        window.setInterval(() => {
            scheduleScan(document);
        }, 2500);
    };

    if (document.readyState === 'loading') {
        document.addEventListener(
            'DOMContentLoaded',
            start,
            { once: true }
        );
    } else {
        start();
    }
})();
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?