diff --git a/pontoon/base/static/js/theme-switcher.js b/pontoon/base/static/js/theme-switcher.js index 5cd793e6c4..69c5556cad 100644 --- a/pontoon/base/static/js/theme-switcher.js +++ b/pontoon/base/static/js/theme-switcher.js @@ -13,26 +13,37 @@ $(function () { function applyTheme(newTheme) { if (newTheme === 'system') { newTheme = getSystemTheme(); + storeSystemTheme(newTheme); } $('body') .removeClass('dark-theme light-theme system-theme') .addClass(`${newTheme}-theme`); } + /* + * Storing system theme setting in a cookie makes the setting available to the server. + * That allows us to set the theme class already in the Django template, which (unlike + * setting it on the client) prevents FOUC. + */ + function storeSystemTheme(systemTheme) { + document.cookie = `system_theme=${systemTheme}; path=/; max-age=${ + 60 * 60 * 24 * 365 + }; Secure`; + } + window .matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', function (e) { + .addEventListener('change', function () { // Check the 'data-theme' attribute on the body element let userThemeSetting = $('body').data('theme'); if (userThemeSetting === 'system') { - applyTheme(e.matches ? 'dark' : 'light'); + applyTheme(userThemeSetting); } }); - if ($('body').hasClass('system-theme')) { - let systemTheme = getSystemTheme(); - $('body').removeClass('system-theme').addClass(`${systemTheme}-theme`); + if ($('body').data('theme') === 'system') { + applyTheme('system'); } $('.appearance .toggle-button button').click(function (e) { diff --git a/pontoon/base/templates/base.html b/pontoon/base/templates/base.html index 117fa90058..e89144c445 100644 --- a/pontoon/base/templates/base.html +++ b/pontoon/base/templates/base.html @@ -33,9 +33,9 @@
{% block content %} diff --git a/pontoon/base/templatetags/helpers.py b/pontoon/base/templatetags/helpers.py index 4507d91e5c..a1e1cca062 100644 --- a/pontoon/base/templatetags/helpers.py +++ b/pontoon/base/templatetags/helpers.py @@ -37,13 +37,28 @@ def return_url(request): @library.global_function -def theme(user): +def user_theme(user): """Get user's theme or return 'dark' if user is not authenticated.""" if user.is_authenticated: return user.profile.theme return "dark" +@library.global_function +def theme_class(request): + """Get theme class name based on user preferences and system settings.""" + theme = "dark" + user = request.user + + if user.is_authenticated: + theme = user.profile.theme + + if theme == "system": + theme = request.COOKIES.get("system_theme", "system") + + return f"{theme}-theme" + + @library.global_function def static(path): return staticfiles_storage.url(path) diff --git a/translate/public/translate.html b/translate/public/translate.html index 689962dba9..c4891e6a4b 100644 --- a/translate/public/translate.html +++ b/translate/public/translate.html @@ -24,7 +24,7 @@ {% include "tracker.html" %} - + diff --git a/translate/src/context/Theme.tsx b/translate/src/context/Theme.tsx index 6166cd3a60..a61d4169df 100644 --- a/translate/src/context/Theme.tsx +++ b/translate/src/context/Theme.tsx @@ -15,17 +15,19 @@ export function ThemeProvider({ children }: { children: React.ReactElement }) { useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - function handleThemeChange(e: MediaQueryListEvent) { - let userThemeSetting = document.body.getAttribute('data-theme') || 'dark'; + function handleThemeChange() { + let userThemeSetting = document.body.getAttribute('data-theme'); if (userThemeSetting === 'system') { - applyTheme(e.matches ? 'dark' : 'light'); + applyTheme(userThemeSetting); } } mediaQuery.addEventListener('change', handleThemeChange); - applyTheme(theme); + if (theme === 'system') { + applyTheme(theme); + } return () => { mediaQuery.removeEventListener('change', handleThemeChange); diff --git a/translate/src/hooks/useTheme.ts b/translate/src/hooks/useTheme.ts index bb9d252684..a4464c7934 100644 --- a/translate/src/hooks/useTheme.ts +++ b/translate/src/hooks/useTheme.ts @@ -1,4 +1,15 @@ export function useTheme() { + /* + * Storing system theme setting in a cookie makes the setting available to the server. + * That allows us to set the theme class already in the Django template, which (unlike + * setting it on the client) prevents FOUC. + */ + function storeSystemTheme(systemTheme: string) { + document.cookie = `system_theme=${systemTheme}; path=/; max-age=${ + 60 * 60 * 24 * 365 + }; Secure`; + } + function getSystemTheme(): string { if ( window.matchMedia && @@ -10,9 +21,10 @@ export function useTheme() { } } - return function (newTheme: string) { + return function useTheme(newTheme: string) { if (newTheme === 'system') { newTheme = getSystemTheme(); + storeSystemTheme(newTheme); } document.body.classList.remove('dark-theme', 'light-theme', 'system-theme'); document.body.classList.add(`${newTheme}-theme`);