~cytrogen/masto-fe

9e66c07be70fa71467cfcdf0d4ebff0147bf187a — Renaud Chaput 2 years ago 734e186
[Glitch] Fix `/share` and cleanup and reorganize frontend locale loading

Port b0780cfeeda641645ea65da257a72ec507e71647 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
M app/javascript/flavours/glitch/actions/streaming.js => app/javascript/flavours/glitch/actions/streaming.js +6 -5
@@ 1,6 1,6 @@
// @ts-check

import { getLocale } from 'mastodon/locales';
import { getLocale } from 'flavours/glitch/locales';

import { connectStream } from '../stream';



@@ 25,8 25,6 @@ import {
  fillListTimelineGaps,
} from './timelines';

const { messages } = getLocale();

/**
 * @param {number} max
 * @returns {number}


@@ 44,8 42,10 @@ const randomUpTo = max =>
 * @param {function(object): boolean} [options.accept]
 * @returns {function(): void}
 */
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
  connectStream(channelName, params, (dispatch, getState) => {
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
  const { messages } = getLocale();

  return connectStream(channelName, params, (dispatch, getState) => {
    const locale = getState().getIn(['meta', 'locale']);

    // @ts-expect-error


@@ 122,6 122,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
      },
    };
  });
};

/**
 * @param {Function} dispatch

M app/javascript/flavours/glitch/containers/admin_component.jsx => app/javascript/flavours/glitch/containers/admin_component.jsx +3 -8
@@ 1,24 1,19 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { IntlProvider } from 'react-intl';

import { getLocale, onProviderError } from 'mastodon/locales';

const { messages } = getLocale();
import { IntlProvider } from 'flavours/glitch/locales';

export default class AdminComponent extends PureComponent {

  static propTypes = {
    locale: PropTypes.string.isRequired,
    children: PropTypes.node.isRequired,
  };

  render () {
    const { locale, children } = this.props;
    const { children } = this.props;

    return (
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
      <IntlProvider>
        {children}
      </IntlProvider>
    );

M app/javascript/flavours/glitch/containers/compose_container.jsx => app/javascript/flavours/glitch/containers/compose_container.jsx +3 -15
@@ 1,37 1,25 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { IntlProvider } from 'react-intl';

import { Provider } from 'react-redux';

import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
import { hydrateStore } from 'flavours/glitch/actions/store';
import Compose from 'flavours/glitch/features/standalone/compose';
import initialState from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store';

import { getLocale, onProviderError } from 'mastodon/locales';

const { messages } = getLocale();

if (initialState) {
  store.dispatch(hydrateStore(initialState));
}

store.dispatch(fetchCustomEmojis());

export default class TimelineContainer extends PureComponent {

  static propTypes = {
    locale: PropTypes.string.isRequired,
  };
export default class ComposeContainer extends PureComponent {

  render () {
    const { locale } = this.props;

    return (
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
      <IntlProvider>
        <Provider store={store}>
          <Compose />
        </Provider>

M app/javascript/flavours/glitch/containers/mastodon.jsx => app/javascript/flavours/glitch/containers/mastodon.jsx +2 -12
@@ 1,8 1,6 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { IntlProvider } from 'react-intl';

import { Helmet } from 'react-helmet';
import { BrowserRouter, Route } from 'react-router-dom';



@@ 17,10 15,8 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import UI from 'flavours/glitch/features/ui';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store';
import { getLocale, onProviderError } from 'locales';

const { messages } = getLocale();

const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;



@@ 44,10 40,6 @@ const createIdentityContext = state => ({

export default class Mastodon extends PureComponent {

  static propTypes = {
    locale: PropTypes.string.isRequired,
  };

  static childContextTypes = {
    identity: PropTypes.shape({
      signedIn: PropTypes.bool.isRequired,


@@ 83,10 75,8 @@ export default class Mastodon extends PureComponent {
  }

  render () {
    const { locale } = this.props;

    return (
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
      <IntlProvider>
        <ReduxProvider store={store}>
          <ErrorBoundary>
            <BrowserRouter>

M app/javascript/flavours/glitch/containers/media_container.jsx => app/javascript/flavours/glitch/containers/media_container.jsx +3 -9
@@ 2,8 2,6 @@ import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';

import { IntlProvider } from 'react-intl';

import { fromJS } from 'immutable';

import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';


@@ 14,18 12,14 @@ import Audio from 'flavours/glitch/features/audio';
import Card from 'flavours/glitch/features/status/components/card';
import MediaModal from 'flavours/glitch/features/ui/components/media_modal';
import Video from 'flavours/glitch/features/video';
import { IntlProvider } from 'flavours/glitch/locales';
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';

import { getLocale, onProviderError } from 'mastodon/locales';

const { messages } = getLocale();

const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };

export default class MediaContainer extends PureComponent {

  static propTypes = {
    locale: PropTypes.string.isRequired,
    components: PropTypes.object.isRequired,
  };



@@ 74,7 68,7 @@ export default class MediaContainer extends PureComponent {
  };

  render () {
    const { locale, components } = this.props;
    const { components } = this.props;

    let handleOpenVideo;



@@ 84,7 78,7 @@ export default class MediaContainer extends PureComponent {
    }

    return (
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
      <IntlProvider>
        <>
          {[].map.call(components, (component, i) => {
            const componentName = component.getAttribute('data-component');

A app/javascript/flavours/glitch/locales/global_locale.ts => app/javascript/flavours/glitch/locales/global_locale.ts +22 -0
@@ 0,0 1,22 @@
export interface LocaleData {
  locale: string;
  messages: Record<string, string>;
}

let loadedLocale: LocaleData;

export function setLocale(locale: LocaleData) {
  loadedLocale = locale;
}

export function getLocale() {
  if (!loadedLocale && process.env.NODE_ENV === 'development') {
    throw new Error('getLocale() called before any locale has been set');
  }

  return loadedLocale;
}

export function isLocaleLoaded() {
  return !!loadedLocale;
}

A app/javascript/flavours/glitch/locales/index.ts => app/javascript/flavours/glitch/locales/index.ts +5 -0
@@ 0,0 1,5 @@
export type { LocaleData } from './global_locale';
export { setLocale, getLocale, isLocaleLoaded } from './global_locale';
export { loadLocale } from './load_locale';

export { IntlProvider } from './intl_provider';

A app/javascript/flavours/glitch/locales/intl_provider.tsx => app/javascript/flavours/glitch/locales/intl_provider.tsx +56 -0
@@ 0,0 1,56 @@
import { useEffect, useState } from 'react';

import { IntlProvider as BaseIntlProvider } from 'react-intl';

import { getLocale, isLocaleLoaded } from './global_locale';
import { loadLocale } from './load_locale';

function onProviderError(error: unknown) {
  // Silent the error, like upstream does
  if (process.env.NODE_ENV === 'production') return;

  // This browser does not advertise Intl support for this locale, we only print a warning
  // As-per the spec, the browser should select the best matching locale
  if (
    error &&
    typeof error === 'object' &&
    error instanceof Error &&
    error.message.match('MISSING_DATA')
  ) {
    console.warn(error.message);
  }

  console.error(error);
}

export const IntlProvider: React.FC<
  Omit<React.ComponentProps<typeof BaseIntlProvider>, 'locale' | 'messages'>
> = ({ children, ...props }) => {
  const [localeLoaded, setLocaleLoaded] = useState(false);

  useEffect(() => {
    async function loadLocaleData() {
      if (!isLocaleLoaded()) {
        await loadLocale();
      }

      setLocaleLoaded(true);
    }
    void loadLocaleData();
  }, []);

  if (!localeLoaded) return null;

  const { locale, messages } = getLocale();

  return (
    <BaseIntlProvider
      locale={locale}
      messages={messages}
      onError={onProviderError}
      {...props}
    >
      {children}
    </BaseIntlProvider>
  );
};

R app/javascript/flavours/glitch/load_locale.js => app/javascript/flavours/glitch/locales/load_locale.ts +31 -15
@@ 1,21 1,37 @@
import { setLocale } from 'locales';
import { Semaphore } from 'async-mutex';

import type { LocaleData } from './global_locale';
import { isLocaleLoaded, setLocale } from './global_locale';

const localeLoadingSemaphore = new Semaphore(1);

export async function loadLocale() {
  const locale = document.querySelector('html').lang || 'en';
  const locale = document.querySelector<HTMLElement>('html')?.lang || 'en';

  // We use a Semaphore here so only one thing can try to load the locales at
  // the same time. If one tries to do it while its in progress, it will wait
  // for the initial load to finish before it is resumed (and will see that locale
  // data is already loaded)
  await localeLoadingSemaphore.runExclusive(async () => {
    // if the locale is already set, then do nothing
    if (isLocaleLoaded()) return;

  const upstreamLocaleData = await import(
    /* webpackMode: "lazy" */
    /* webpackChunkName: "locales/vanilla/[request]" */
    /* webpackInclude: /\.json$/ */
    /* webpackPreload: true */
    `mastodon/locales/${locale}.json`);
    const upstreamLocaleData = await import(
      /* webpackMode: "lazy" */
      /* webpackChunkName: "locales/vanilla/[request]" */
      /* webpackInclude: /\.json$/ */
      /* webpackPreload: true */
      `mastodon/locales/${locale}.json`
    ) as LocaleData['messages'];

  const localeData = await import(
    /* webpackMode: "lazy" */
    /* webpackChunkName: "locales/glitch/[request]" */
    /* webpackInclude: /\.json$/ */
    /* webpackPreload: true */
    `flavours/glitch/locales/${locale}.json`);
    const localeData = await import(
      /* webpackMode: "lazy" */
      /* webpackChunkName: "locales/glitch/[request]" */
      /* webpackInclude: /\.json$/ */
      /* webpackPreload: true */
      `flavours/glitch/locales/${locale}.json`
    ) as LocaleData['messages'];

  setLocale({ messages: {...upstreamLocaleData, ...localeData} });
    setLocale({ messages: { ...upstreamLocaleData, ...localeData }, locale });
  });
}

M app/javascript/flavours/glitch/packs/admin.jsx => app/javascript/flavours/glitch/packs/admin.jsx +2 -2
@@ 6,14 6,14 @@ import ready from 'flavours/glitch/ready';
ready(() => {
  [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
    const componentName  = element.getAttribute('data-admin-component');
    const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props'));
    const { ...componentProps } = JSON.parse(element.getAttribute('data-props'));

    import('flavours/glitch/containers/admin_component').then(({ default: AdminComponent }) => {
      return import('flavours/glitch/components/admin/' + componentName).then(({ default: Component }) => {
        const root = createRoot(element);

        root.render (
          <AdminComponent locale={locale}>
          <AdminComponent>
            <Component {...componentProps} />
          </AdminComponent>,
        );

M app/javascript/flavours/glitch/packs/home.js => app/javascript/flavours/glitch/packs/home.js +8 -8
@@ 1,11 1,11 @@
import 'packs/public-path';
import { loadLocale } from 'flavours/glitch/load_locale';
import { loadLocale } from 'flavours/glitch/locales';
import main from "flavours/glitch/main";
import { loadPolyfills } from 'flavours/glitch/polyfills';

loadPolyfills().then(loadLocale).then(async () => {
  const { default: main } = await import('flavours/glitch/main');

  return main();
}).catch(e => {
  console.error(e);
});
loadPolyfills()
  .then(loadLocale)
  .then(main)
  .catch(e => {
    console.error(e);
  });

M app/javascript/flavours/glitch/packs/public.jsx => app/javascript/flavours/glitch/packs/public.jsx +3 -4
@@ 1,7 1,7 @@
import 'packs/public-path';
import { createRoot }  from 'react-dom/client';

import * as IntlMessageFormat  from 'intl-messageformat';
import { IntlMessageFormat } from 'intl-messageformat';
import { defineMessages } from 'react-intl';

import { delegate }  from '@rails/ujs';


@@ 12,10 12,9 @@ import { throttle } from 'lodash';
import { timeAgoString }  from 'flavours/glitch/components/relative_timestamp';
import emojify  from 'flavours/glitch/features/emoji/emoji';
import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions';
import { loadLocale } from 'flavours/glitch/load_locale';
import { loadLocale, getLocale } from 'flavours/glitch/locales';
import { loadPolyfills } from 'flavours/glitch/polyfills';
import ready from 'flavours/glitch/ready';
import { getLocale }  from 'locales';

const messages = defineMessages({
  usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' },


@@ 24,7 23,7 @@ const messages = defineMessages({
});

function main() {
  const { localeData } = getLocale();
  const { messages: localeData } = getLocale();

  const scrollToDetailedStatus = () => {
    const history = createBrowserHistory();

M app/javascript/flavours/glitch/packs/share.jsx => app/javascript/flavours/glitch/packs/share.jsx +1 -2
@@ 2,7 2,6 @@ import 'packs/public-path';
import { createRoot } from 'react-dom/client';

import ComposeContainer from 'flavours/glitch/containers/compose_container';
import { loadLocale } from 'flavours/glitch/load_locale';
import { loadPolyfills } from 'flavours/glitch/polyfills';
import ready from 'flavours/glitch/ready';



@@ 23,6 22,6 @@ function main() {
  ready(loaded);
}

loadPolyfills().then(loadLocale).then(main).catch(error => {
loadPolyfills().then(main).catch(error => {
  console.error(error);
});