dataesr / react-dsfr

Non-official React components of the official french Système de Design de l'État.
https://dataesr.github.io/react-dsfr/
MIT License
44 stars 22 forks source link

Contribution poposal: i18n support #297

Open garronej opened 1 year ago

garronej commented 1 year ago

Hello,

Would you be interested in a PR that enables to provide translations for the default strings in different languages?
For example

image image

By default we would provide translation for French, English and Spanish (we must provide at least three, it's either only French or at least three languages, legally speaking).
Users would be able to provide extra translations for specific languages on a per component basis.

By default the language selected would be the one that best match the navigator.language but the user would be able to use a provider to enforce a specific language to be used.

image

Let me know if it's something that you'd be interested in.
If it's not, I'll be happy to contribute in some other way.
Maybe there are some component missing that I can implement?

Best regards,

garronej commented 1 year ago

To clarify, this is how it would look like in SwitchTheme.js:

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ReactComponent as Light } from '@gouvfr/dsfr/dist/artwork/light.svg';
import { ReactComponent as Dark } from '@gouvfr/dsfr/dist/artwork/dark.svg';
import { ReactComponent as System } from '@gouvfr/dsfr/dist/artwork/system.svg';
import { Modal, ModalTitle, ModalContent } from '../Modal';
import { RadioGroup, Radio } from '../Radio';
import { createComponentI18nApi } from '../i18n';

import '@gouvfr/dsfr/dist/scheme/scheme.css';

const DARK = 'dark';
const LIGHT = 'light';
const SYSTEM = 'system';

const SwitchTheme = ({
  isOpen,
  setIsOpen,
  title,
  legend,
  darkLabel,
  lightLabel,
  systemLabel,
  systemHint,
}) => {
  const [storedValue, setStoredValue] = useState('');

  const { t } = useTranslation();

  const themes = [
    { label: lightLabel ?? t('light theme'), value: LIGHT, svg: <Light /> },
    { label: darkLabel ?? t('dark theme'), value: DARK, svg: <Dark /> },
    {
      label: systemLabel ?? t('system theme'), 
      hint: systemHint ?? t('system theme hint'), 
      value: SYSTEM, svg: <System />,
    },
  ];

  useEffect(() => {
    const initialTheme = window.localStorage.getItem('prefers-color-scheme');
    setStoredValue(initialTheme || SYSTEM);
  }, [isOpen]);

  useEffect(() => {
    let tempTheme = storedValue;
    if (!storedValue || storedValue === SYSTEM) {
      const preferedTheme = window.matchMedia('(prefers-color-scheme: dark)');
      tempTheme = (preferedTheme && preferedTheme.matches) ? DARK : LIGHT;
    }

    window.localStorage.setItem('prefers-color-scheme', storedValue);
    document.documentElement.setAttribute('data-fr-theme', tempTheme);
  }, [storedValue]);

  return (
    <Modal
      id='fr-theme-modal'
      isOpen={isOpen}
      hide={() => setIsOpen(false)}
      aria-labelledby='fr-theme-modal-title'
    >
      <ModalTitle>{title ?? t('display settings')}</ModalTitle>
      <ModalContent className='fr-form-group'>
        <RadioGroup
          legend={legend ?? t("pick a theme")}
          value={window.localStorage.getItem('prefers-color-scheme')}
          onChange={(value) => setStoredValue(value)}
        >
          {themes.map((item) => (
            <Radio
              key={item.value}
              label={item.label}
              value={item.value}
              isExtended
              svg={item.svg}
              hint={item.hint}
            />
          ))}
        </RadioGroup>
      </ModalContent>
    </Modal>
  );
};

SwitchTheme.defaultProps = {
  darkLabel: 'Thème sombre',
  lightLabel: 'Thème clair',
  systemLabel: 'Système',
  systemHint: 'Utilise les paramètres système.',
};

const [ useTranslation, addSwitchThemeTranslations ] = createComponentI18nApi({
  'display settings': 'Paramètres d\'affichage',
  'close': 'Fermer',
  'pick a theme': `Choisissez un thème pour personnaliser l'apparence du site.`,
  'light theme': `Thème clair`,
  'dark theme': `Thème sombre`,
  'system theme': `Système.`,
  'system theme hint': `Utilise les paramètres système.`
});

addSwitchThemeTranslations({
  lang: 'en',
  messages: {
    'display settings': 'Display settings',
    'close': 'Close',
    'pick a theme': `Pick a theme to customize the website's look.`,
    'light theme': `Light theme`,
    'dark theme': 'Dark theme',
    'system theme': `System.`,
    'system theme hint': 'Use system preference'
  }
});

addSwitchThemeTranslations({
  lang: 'es',
  messages: {
    'display settings': 'Parámetro de visualización',
    'close': 'Cerrar',
    'pick a theme': `Elija un tema para personalizar el aspecto del sitio.`
  }
});

export { addSwitchThemeTranslations };

SwitchTheme.propTypes = {
  title: PropTypes.string,
  legend: PropTypes.string,
  darkLabel: PropTypes.string,
  lightLabel: PropTypes.string,
  systemLabel: PropTypes.string,
  systemHint: PropTypes.string,
  isOpen: PropTypes.bool.isRequired,
  setIsOpen: PropTypes.func.isRequired,
};

export default SwitchTheme;