搭建个人知识付费应用系统-(4)Remix i18n、主题切换

视频地址:https://www.bilibili.com/vide...

Remix i18n

注意一下 languages 的结构调整:

export const languages = {
  zh: {
    flag: '',
    name: '简体中文'
  },
  en: {
    flag: '',
    name: 'English'
  }
};

语言切换组件

与 DaisyUI 相同

import { useI18n } from 'remix-i18n';
import { useEffect, useState } from 'react';
import { useLocation } from '@remix-run/react';
import { getLocale, languages } from '~/i18n';

export function ToggleLocale() {
  const { t } = useI18n();
  const location = useLocation();
  const [path, setPath] = useState(
    location.pathname.replace(`/${getLocale(location.pathname)}`, '')
  );
  useEffect(() => {
    setPath(location.pathname.replace(`/${getLocale(location.pathname)}`, ''));
  }, [location]);

  return (
    
); }

Remix 主题切换

切换主题接口

/api/theme
import {
  type LoaderFunction,
  redirect,
  type ActionFunction,
  json
} from '@remix-run/node';
import { themes } from '~/components/atom/use-theme';
import { getSession } from '~/services/session.server';

export const action: ActionFunction = async ({ request }) => {
  switch (request.method) {
    case 'PUT':
    case 'POST': {
      const session = await getSession(request.headers.get('Cookie'));
      const data: { theme: string } = await request.json();
      const theme = themes.map((x) => x.id).includes(data.theme)
        ? data.theme
        : 'retro';
      session.set('theme', theme);
      return json(
        { success: true },
        {
          headers: {
            'Set-Cookie': await sessionStore.commitSession(session)
          }
        }
      );
    }
    default: {
      return json(
        {
          success: false
        },
        {
          status: 403
        }
      );
    }
  }
};

export const loader: LoaderFunction = () => {
  throw redirect('/');
};

Theme Provider

import { createContext } from 'react';

export const themes = [
  {
    name: '  light',
    id: 'light'
  },
  {
    name: '  dark',
    id: 'dark'
  },
  {
    name: '  cupcake',
    id: 'cupcake'
  },
  {
    name: '  bumblebee',
    id: 'bumblebee'
  },
  {
    name: '✳️  Emerald',
    id: 'emerald'
  },
  {
    name: '  Corporate',
    id: 'corporate'
  },
  {
    name: '  synthwave',
    id: 'synthwave'
  },
  {
    name: '  retro',
    id: 'retro'
  },
  {
    name: '  cyberpunk',
    id: 'cyberpunk'
  },
  {
    name: '  valentine',
    id: 'valentine'
  },
  {
    name: '  halloween',
    id: 'halloween'
  },
  {
    name: '  garden',
    id: 'garden'
  },
  {
    name: '  forest',
    id: 'forest'
  },
  {
    name: '  aqua',
    id: 'aqua'
  },
  {
    name: '  lofi',
    id: 'lofi'
  },
  {
    name: '  pastel',
    id: 'pastel'
  },
  {
    name: '‍♀️  fantasy',
    id: 'fantasy'
  },
  {
    name: '  Wireframe',
    id: 'wireframe'
  },
  {
    name: '  black',
    id: 'black'
  },
  {
    name: '  luxury',
    id: 'luxury'
  },
  {
    name: '‍♂️  dracula',
    id: 'dracula'
  },
  {
    name: '  CMYK',
    id: 'cmyk'
  },
  {
    name: '  Autumn',
    id: 'autumn'
  },
  {
    name: '  Business',
    id: 'business'
  },
  {
    name: '  Acid',
    id: 'acid'
  },
  {
    name: '  Lemonade',
    id: 'lemonade'
  },
  {
    name: '  Night',
    id: 'night'
  },
  {
    name: '☕️  Coffee',
    id: 'coffee'
  },
  {
    name: '❄️  Winter',
    id: 'winter'
  }
];

type ThemeContextType = [
  string | null,
  React.Dispatch>
];

const ThemeContext = createContext(undefined);

ThemeContext.displayName = 'ThemeContext';

const prefersLightMQ = '(prefers-color-scheme: light)';

export function ThemeProvider({
  children,
  themeAction = '/api/theme',
  specifiedTheme
}: {
  children: ReactNode;
  themeAction: string;
  specifiedTheme: string | null;
}) {
  const [theme, setTheme] = useState(() => {
    if (specifiedTheme) {
      return THEMES.includes(specifiedTheme) ? specifiedTheme : null;
    }

    if (typeof window !== 'object') return null;
    return window.matchMedia(prefersLightMQ).matches ? 'valentine' : 'retro';
  });

  const mountRun = React.useRef(false);

  useEffect(() => {
    if (!mountRun.current) {
      mountRun.current = true;
      return;
    }
    if (!theme) return;

    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    fetch(`${themeAction}`, {
      method: 'POST',
      body: JSON.stringify({ theme })
    });
  }, [theme]);

  useEffect(() => {
    const mediaQuery = window.matchMedia(prefersLightMQ);
    const handleChange = () => {
      setTheme(mediaQuery.matches ? 'valentine' : 'retro');
    };
    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);

  return (
    
      {children}
    
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

Use Theme


export const loader: LoaderFunction = async ({ request }) => {
  const session = await getSession(request.headers.get('Cookie'));
  const theme = (session.get('theme') as string) || 'retro';
  return json({ theme });
};

export default function App() {
  const { theme } = useLoaderData();

  return (
    
      
    
  );
}

切换主题组件

import { useI18n } from 'remix-i18n';
import { themes } from './use-theme';

export function ToggleTheme() {
  const { t } = useI18n();

  return (
    
{/* {$t('change-theme-btn')} */}
{themes.map((theme) => (
{theme.id}
))}
); }

组件加切换事件

const [currentTheme, setTheme] = useTheme();
const onThemeClicked = (theme: string) => {
    setTheme(theme);
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    fetch('/api/theme', {
      method: 'PUT',
      body: JSON.stringify({ theme })
    });
    localStorage.setItem('theme', theme);
};

你可能感兴趣的:(搭建个人知识付费应用系统-(4)Remix i18n、主题切换)