Переглянути джерело

Fix `/share` and cleanup and reorganize frontend locale loading (#25240)

Renaud Chaput 1 рік тому
батько
коміт
b0780cfeed

+ 1 - 1
.prettierignore

@@ -61,7 +61,7 @@ docker-compose.override.yml
 /app/javascript/mastodon/features/emoji/emoji_map.json
 
 # Ignore locale files
-/app/javascript/mastodon/locales
+/app/javascript/mastodon/locales/*.json
 /config/locales
 
 # Ignore vendored CSS reset

+ 1 - 1
app/helpers/react_component_helper.rb

@@ -11,7 +11,7 @@ module ReactComponentHelper
   end
 
   def react_admin_component(name, props = {})
-    data = { 'admin-component': name.to_s.camelcase, props: Oj.dump({ locale: I18n.locale }.merge(props)) }
+    data = { 'admin-component': name.to_s.camelcase, props: Oj.dump(props) }
     div_tag_with_data(data)
   end
 

+ 5 - 4
app/javascript/mastodon/actions/streaming.js

@@ -24,8 +24,6 @@ import {
   fillListTimelineGaps,
 } from './timelines';
 
-const { messages } = getLocale();
-
 /**
  * @param {number} max
  * @returns {number}
@@ -43,8 +41,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
@@ -121,6 +121,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
       },
     };
   });
+};
 
 /**
  * @param {Function} dispatch

+ 3 - 8
app/javascript/mastodon/containers/admin_component.jsx

@@ -1,24 +1,19 @@
 import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { IntlProvider } from 'react-intl';
-
-import { getLocale, onProviderError } from '../locales';
-
-const { messages } = getLocale();
+import { IntlProvider } from 'mastodon/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>
     );

+ 3 - 13
app/javascript/mastodon/containers/compose_container.jsx

@@ -1,18 +1,14 @@
-import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { IntlProvider } from 'react-intl';
-
 import { Provider } from 'react-redux';
 
 import { fetchCustomEmojis } from '../actions/custom_emojis';
 import { hydrateStore } from '../actions/store';
 import Compose from '../features/standalone/compose';
 import initialState from '../initial_state';
-import { getLocale, onProviderError } from '../locales';
+import { IntlProvider } from '../locales';
 import { store } from '../store';
 
-const { messages } = getLocale();
 
 if (initialState) {
   store.dispatch(hydrateStore(initialState));
@@ -20,17 +16,11 @@ if (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>

+ 2 - 12
app/javascript/mastodon/containers/mastodon.jsx

@@ -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';
 
@@ -16,11 +14,9 @@ import { connectUserStream } from 'mastodon/actions/streaming';
 import ErrorBoundary from 'mastodon/components/error_boundary';
 import UI from 'mastodon/features/ui';
 import initialState, { title as siteTitle } from 'mastodon/initial_state';
-import { getLocale, onProviderError } from 'mastodon/locales';
+import { IntlProvider } from 'mastodon/locales';
 import { store } from 'mastodon/store';
 
-const { messages } = getLocale();
-
 const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
 
 const hydrateAction = hydrateStore(initialState);
@@ -40,10 +36,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,
@@ -79,10 +71,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>

+ 3 - 8
app/javascript/mastodon/containers/media_container.jsx

@@ -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 'mastodon/components/hashtag';
@@ -14,17 +12,14 @@ import Audio from 'mastodon/features/audio';
 import Card from 'mastodon/features/status/components/card';
 import MediaModal from 'mastodon/features/ui/components/media_modal';
 import Video from 'mastodon/features/video';
-import { getLocale, onProviderError } from 'mastodon/locales';
+import { IntlProvider } from 'mastodon/locales';
 import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
 
-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,
   };
 
@@ -73,7 +68,7 @@ export default class MediaContainer extends PureComponent {
   };
 
   render () {
-    const { locale, components } = this.props;
+    const { components } = this.props;
 
     let handleOpenVideo;
 
@@ -83,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');

+ 0 - 14
app/javascript/mastodon/load_locale.js

@@ -1,14 +0,0 @@
-import { setLocale } from "./locales";
-
-export async function loadLocale() {
-  const locale = document.querySelector('html').lang || 'en';
-
-  const localeData = await import(
-    /* webpackMode: "lazy" */
-    /* webpackChunkName: "locale/[request]" */
-    /* webpackInclude: /\.json$/ */
-    /* webpackPreload: true */
-    `mastodon/locales/${locale}.json`);
-
-  setLocale({ messages: localeData });
-}

+ 22 - 0
app/javascript/mastodon/locales/global_locale.ts

@@ -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;
+}

+ 0 - 22
app/javascript/mastodon/locales/index.js

@@ -1,22 +0,0 @@
-let theLocale;
-
-export function setLocale(locale) {
-  theLocale = locale;
-}
-
-export function getLocale() {
-  return theLocale;
-}
-
-export function onProviderError(error) {
-  // 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(typeof error === "object" && error.message.match("MISSING_DATA")) {
-    console.warn(error.message);
-  }
-
-  console.error(error);
-}

+ 5 - 0
app/javascript/mastodon/locales/index.ts

@@ -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';

+ 56 - 0
app/javascript/mastodon/locales/intl_provider.tsx

@@ -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>
+  );
+};

+ 29 - 0
app/javascript/mastodon/locales/load_locale.ts

@@ -0,0 +1,29 @@
+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<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 localeData = (await import(
+      /* webpackMode: "lazy" */
+      /* webpackChunkName: "locale/[request]" */
+      /* webpackInclude: /\.json$/ */
+      /* webpackPreload: true */
+      `mastodon/locales/${locale}.json`
+    )) as LocaleData['messages'];
+
+    setLocale({ messages: localeData, locale });
+  });
+}

+ 0 - 221
app/javascript/mastodon/locales/locale-data/README.md

@@ -1,221 +0,0 @@
-# Custom Locale Data
-
-This folder is used to store custom locale data. These custom locale data are
-not yet provided by [Unicode Common Locale Data Repository](http://cldr.unicode.org/development/new-cldr-developers)
-and hence not provided in [react-intl/locale-data/*](https://github.com/yahoo/react-intl).
-
-The locale data should support [Locale Data APIs](https://github.com/yahoo/react-intl/wiki/API#locale-data-apis)
-of the react-intl library.
-
-It is recommended to start your custom locale data from this sample English
-locale data ([*](#plural-rules)):
-
-```javascript
-/*eslint eqeqeq: "off"*/
-/*eslint no-nested-ternary: "off"*/
-
-export default [
-  {
-    locale: "en",
-    pluralRuleFunction: function(e, a) {
-      var n = String(e).split("."),
-        l = !n[1],
-        o = Number(n[0]) == e,
-        t = o && n[0].slice(-1),
-        r = o && n[0].slice(-2);
-      return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other"
-    },
-    fields: {
-      year: {
-        displayName: "year",
-        relative: {
-          0: "this year",
-          1: "next year",
-          "-1": "last year"
-        },
-        relativeTime: {
-          future: {
-            one: "in {0} year",
-            other: "in {0} years"
-          },
-          past: {
-            one: "{0} year ago",
-            other: "{0} years ago"
-          }
-        }
-      },
-      month: {
-        displayName: "month",
-        relative: {
-          0: "this month",
-          1: "next month",
-          "-1": "last month"
-        },
-        relativeTime: {
-          future: {
-            one: "in {0} month",
-            other: "in {0} months"
-          },
-          past: {
-            one: "{0} month ago",
-            other: "{0} months ago"
-          }
-        }
-      },
-      day: {
-        displayName: "day",
-        relative: {
-          0: "today",
-          1: "tomorrow",
-          "-1": "yesterday"
-        },
-        relativeTime: {
-          future: {
-            one: "in {0} day",
-            other: "in {0} days"
-          },
-          past: {
-            one: "{0} day ago",
-            other: "{0} days ago"
-          }
-        }
-      },
-      hour: {
-        displayName: "hour",
-        relativeTime: {
-          future: {
-            one: "in {0} hour",
-            other: "in {0} hours"
-          },
-          past: {
-            one: "{0} hour ago",
-            other: "{0} hours ago"
-          }
-        }
-      },
-      minute: {
-        displayName: "minute",
-        relativeTime: {
-          future: {
-            one: "in {0} minute",
-            other: "in {0} minutes"
-          },
-          past: {
-            one: "{0} minute ago",
-            other: "{0} minutes ago"
-          }
-        }
-      },
-      second: {
-        displayName: "second",
-        relative: {
-          0: "now"
-        },
-        relativeTime: {
-          future: {
-            one: "in {0} second",
-            other: "in {0} seconds"
-          },
-          past: {
-            one: "{0} second ago",
-            other: "{0} seconds ago"
-          }
-        }
-      }
-    }
-  }
-]
-
-```
-
-## Notes
-
-### Plural Rules
-
-The function `pluralRuleFunction()` should return the key to proper string of
-a plural form(s). The purpose of the function is to provide key of translate
-strings of correct plural form according. The different forms are described in
-[CLDR's Plural Rules][cldr-plural-rules],
-
-[cldr-plural-rules]: http://cldr.unicode.org/index/cldr-spec/plural-rules
-
-#### Quick Overview on CLDR Rules
-
-Let's take English as an example.
-
-When you describe a number, you can be either describe it as:
-* Cardinals: 1st, 2nd, 3rd ... 11th, 12th ... 21st, 22nd, 23nd ....
-* Ordinals: 1, 2, 3 ...
-
-In any of these cases, the nouns will reflect the number with singular or plural
-form. For example:
-* in 0 days
-* in 1 day
-* in 2 days
-
-The `pluralRuleFunction` receives 2 parameters:
-* `e`: a string representation of the number. Such as, "`1`", "`2`", "`2.1`".
-* `a`: `true` if this is "cardinal" type of description. `false` for ordinal and other case.
-
-#### How you should write `pluralRuleFunction`
-
-The first rule to write pluralRuleFunction is never translate the output string
-into your language. [Plural Rules][cldr-plural-rules] specified you should use
-these as the return values:
-
-  * "`zero`"
-  * "`one`" (singular)
-  * "`two`" (dual)
-  * "`few`" (paucal)
-  * "`many`" (also used for fractions if they have a separate class)
-  * "`other`" (required—general plural form—also used if the language only has a single form)
-
-Again, we'll use English as the example here.
-
-Let's read the `return` statement in the pluralRuleFunction above:
-```javascript
-  return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other"
-```
-
-This nested ternary is hard to read. It basically means:
-```javascript
-// e: the number variable to examine
-// a: "true" if cardinals
-// l: "true" if the variable e has nothin after decimal mark (e.g. "1.0" would be false)
-// o: "true" if the variable e is an integer
-// t: the "ones" of the number. e.g. "3" for number "9123"
-// r: the "ones" and "tens" of the number. e.g. "23" for number "9123"
-if (a == true) {
-  if (t == 1 && r != 11) {
-    return "one"; // i.e. 1st, 21st, 101st, 121st ...
-  } else if (t == 2 && r != 12) {
-    return "two"; // i.e. 2nd, 22nd, 102nd, 122nd ...
-  } else if (t == 3 && r != 13) {
-    return "few"; // i.e. 3rd, 23rd, 103rd, 123rd ...
-  } else {
-    return "other"; // i.e. 4th, 11th, 12th, 24th ...
-  }
-} else {
-  if (e == 1 && l) {
-    return "one"; // i.e. 1 day
-  } else {
-    return "other"; // i.e. 0 days, 2 days, 3 days
-  }
-}
-```
-
-If your language, like French, do not have complicated cardinal rules, you may
-use the French's version of it:
-```javascript
-function (e, a) {
-  return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
-}
-```
-
-If your language, like Chinese, do not have any pluralization rule at all you
-may use the Chinese's version of it:
-```javascript
-function (e, a) {
-  return "other";
-}
-```

+ 0 - 110
app/javascript/mastodon/locales/locale-data/co.js

@@ -1,110 +0,0 @@
-/*eslint eqeqeq: "off"*/
-/*eslint no-nested-ternary: "off"*/
-/*eslint quotes: "off"*/
-
-const rules = [{
-  locale: "co",
-  pluralRuleFunction: function (e, a) {
-    return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
-  },
-  fields: {
-    year: {
-      displayName: "annu",
-      relative: {
-        0: "quist'annu",
-        1: "l'annu chì vene",
-        "-1": "l'annu passatu",
-      },
-      relativeTime: {
-        future: {
-          one: "in {0} annu",
-          other: "in {0} anni",
-        },
-        past: {
-          one: "{0} annu fà",
-          other: "{0} anni fà",
-        },
-      },
-    },
-    month: {
-      displayName: "mese",
-      relative: {
-        0: "Questu mese",
-        1: "u mese chì vene",
-        "-1": "u mese passatu",
-      },
-      relativeTime: {
-        future: {
-          one: "in {0} mese",
-          other: "in {0} mesi",
-        },
-        past: {
-          one: "{0} mese fà",
-          other: "{0} mesi fà",
-        },
-      },
-    },
-    day: {
-      displayName: "ghjornu",
-      relative: {
-        0: "oghje",
-        1: "dumane",
-        "-1": "eri",
-      },
-      relativeTime: {
-        future: {
-          one: "in {0} ghjornu",
-          other: "in {0} ghjornu",
-        },
-        past: {
-          one: "{0} ghjornu fà",
-          other: "{0} ghjorni fà",
-        },
-      },
-    },
-    hour: {
-      displayName: "ora",
-      relativeTime: {
-        future: {
-          one: "in {0} ora",
-          other: "in {0} ore",
-        },
-        past: {
-          one: "{0} ora fà",
-          other: "{0} ore fà",
-        },
-      },
-    },
-    minute: {
-      displayName: "minuta",
-      relativeTime: {
-        future: {
-          one: "in {0} minuta",
-          other: "in {0} minute",
-        },
-        past: {
-          one: "{0} minuta fà",
-          other: "{0} minute fà",
-        },
-      },
-    },
-    second: {
-      displayName: "siconda",
-      relative: {
-        0: "avà",
-      },
-      relativeTime: {
-        future: {
-          one: "in {0} siconda",
-          other: "in {0} siconde",
-        },
-        past: {
-          one: "{0} siconda fà",
-          other: "{0} siconde fà",
-        },
-      },
-    },
-  },
-}];
-
-export default rules;

+ 0 - 110
app/javascript/mastodon/locales/locale-data/oc.js

@@ -1,110 +0,0 @@
-/*eslint eqeqeq: "off"*/
-/*eslint no-nested-ternary: "off"*/
-/*eslint quotes: "off"*/
-
-const rules = [{
-  locale: "oc",
-  pluralRuleFunction: function (e, a) {
-    return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
-  },
-  fields: {
-    year: {
-      displayName: "an",
-      relative: {
-        0: "ongan",
-        1: "l'an que ven",
-        "-1": "l'an passat",
-      },
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} an",
-          other: "d’aquí {0} ans",
-        },
-        past: {
-          one: "fa {0} an",
-          other: "fa {0} ans",
-        },
-      },
-    },
-    month: {
-      displayName: "mes",
-      relative: {
-        0: "aqueste mes",
-        1: "lo mes que ven",
-        "-1": "lo mes passat",
-      },
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} mes",
-          other: "d’aquí {0} meses",
-        },
-        past: {
-          one: "fa {0} mes",
-          other: "fa {0} meses",
-        },
-      },
-    },
-    day: {
-      displayName: "jorn",
-      relative: {
-        0: "uèi",
-        1: "deman",
-        "-1": "ièr",
-      },
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} jorn",
-          other: "d’aquí {0} jorns",
-        },
-        past: {
-          one: "fa {0} jorn",
-          other: "fa {0} jorns",
-        },
-      },
-    },
-    hour: {
-      displayName: "ora",
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} ora",
-          other: "d’aquí {0} oras",
-        },
-        past: {
-          one: "fa {0} ora",
-          other: "fa {0} oras",
-        },
-      },
-    },
-    minute: {
-      displayName: "minuta",
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} minuta",
-          other: "d’aquí {0} minutas",
-        },
-        past: {
-          one: "fa {0} minuta",
-          other: "fa {0} minutas",
-        },
-      },
-    },
-    second: {
-      displayName: "segonda",
-      relative: {
-        0: "ara",
-      },
-      relativeTime: {
-        future: {
-          one: "d’aquí {0} segonda",
-          other: "d’aquí {0} segondas",
-        },
-        past: {
-          one: "fa {0} segonda",
-          other: "fa {0} segondas",
-        },
-      },
-    },
-  },
-}];
-
-export default rules;

+ 0 - 98
app/javascript/mastodon/locales/locale-data/sa.js

@@ -1,98 +0,0 @@
-/*eslint eqeqeq: "off"*/
-/*eslint no-nested-ternary: "off"*/
-/*eslint quotes: "off"*/
-/*eslint comma-dangle: "off"*/
-
-const rules = [
-  {
-    locale: "sa",
-    fields: {
-      year: {
-        displayName: "year",
-        relative: {
-          0: "this year",
-          1: "next year",
-          "-1": "last year"
-        },
-        relativeTime: {
-          future: {
-            other: "+{0} y"
-          },
-          past: {
-            other: "-{0} y"
-          }
-        }
-      },
-      month: {
-        displayName: "month",
-        relative: {
-          0: "this month",
-          1: "next month",
-          "-1": "last month"
-        },
-        relativeTime: {
-          future: {
-            other: "+{0} m"
-          },
-          past: {
-            other: "-{0} m"
-          }
-        }
-      },
-      day: {
-        displayName: "day",
-        relative: {
-          0: "अद्य",
-          1: "श्वः",
-          "-1": "गतदिनम्"
-        },
-        relativeTime: {
-          future: {
-            other: "+{0} d"
-          },
-          past: {
-            other: "-{0} d"
-          }
-        }
-      },
-      hour: {
-        displayName: "hour",
-        relativeTime: {
-          future: {
-            other: "+{0} h"
-          },
-          past: {
-            other: "-{0} h"
-          }
-        }
-      },
-      minute: {
-        displayName: "minute",
-        relativeTime: {
-          future: {
-            other: "+{0} min"
-          },
-          past: {
-            other: "-{0} min"
-          }
-        }
-      },
-      second: {
-        displayName: "second",
-        relative: {
-          0: "now"
-        },
-        relativeTime: {
-          future: {
-            other: "+{0} s"
-          },
-          past: {
-            other: "-{0} s"
-          }
-        }
-      }
-    }
-  }
-];
-
-export default rules;

+ 2 - 2
app/javascript/packs/admin.jsx

@@ -229,14 +229,14 @@ 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('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => {
       return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => {
         const root = createRoot(element);
 
         root.render (
-          <AdminComponent locale={locale}>
+          <AdminComponent>
             <Component {...componentProps} />
           </AdminComponent>,
         );

+ 9 - 8
app/javascript/packs/application.js

@@ -1,14 +1,15 @@
 import './public-path';
+import main from "mastodon/main"
+
 import { start } from '../mastodon/common';
-import { loadLocale } from '../mastodon/load_locale';
+import { loadLocale } from '../mastodon/locales';
 import { loadPolyfills } from '../mastodon/polyfills';
 
 start();
 
-loadPolyfills().then(loadLocale).then(async () => {
-  const { default: main } = await import('mastodon/main');
-
-  return main();
-}).catch(e => {
-  console.error(e);
-});
+loadPolyfills()
+  .then(loadLocale)
+  .then(main)
+  .catch(e => {
+    console.error(e);
+  });

+ 1 - 2
app/javascript/packs/public.jsx

@@ -15,8 +15,7 @@ import { start } from '../mastodon/common';
 import { timeAgoString }  from '../mastodon/components/relative_timestamp';
 import emojify  from '../mastodon/features/emoji/emoji';
 import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
-import { loadLocale } from '../mastodon/load_locale';
-import { getLocale }  from '../mastodon/locales';
+import { loadLocale, getLocale } from '../mastodon/locales';
 import { loadPolyfills } from '../mastodon/polyfills';
 import ready from '../mastodon/ready';
 

+ 1 - 2
app/javascript/packs/share.jsx

@@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client';
 
 import { start } from '../mastodon/common';
 import ComposeContainer  from '../mastodon/containers/compose_container';
-import { loadLocale } from '../mastodon/load_locale';
 import { loadPolyfills } from '../mastodon/polyfills';
 import ready from '../mastodon/ready';
 
@@ -26,6 +25,6 @@ function main() {
   ready(loaded);
 }
 
-loadPolyfills().then(loadLocale).then(main).catch(error => {
+loadPolyfills().then(main).catch(error => {
   console.error(error);
 });

+ 0 - 1
jest.config.js

@@ -13,7 +13,6 @@ const config = {
   collectCoverageFrom: [
     'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
     '!app/javascript/mastodon/features/emoji/emoji_compressed.js',
-    '!app/javascript/mastodon/locales/locale-data/*.js',
     '!app/javascript/mastodon/service_worker/entry.js',
     '!app/javascript/mastodon/test_setup.js',
   ],

+ 1 - 0
package.json

@@ -49,6 +49,7 @@
     "@reduxjs/toolkit": "^1.9.5",
     "abortcontroller-polyfill": "^1.7.5",
     "arrow-key-navigation": "^1.2.0",
+    "async-mutex": "^0.4.0",
     "autoprefixer": "^10.4.14",
     "axios": "^1.4.0",
     "babel-loader": "^8.3.0",

+ 1 - 1
spec/helpers/react_component_helper_spec.rb

@@ -33,7 +33,7 @@ describe ReactComponentHelper do
 
     it 'returns a tag with data attributes' do
       expect(parsed_html.div['data-admin-component']).to eq('Name')
-      expect(parsed_html.div['data-props']).to eq('{"locale":"en","one":"two"}')
+      expect(parsed_html.div['data-props']).to eq('{"one":"two"}')
     end
   end
 

+ 7 - 0
yarn.lock

@@ -3273,6 +3273,13 @@ async-limiter@~1.0.0:
   resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
   integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 
+async-mutex@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f"
+  integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==
+  dependencies:
+    tslib "^2.4.0"
+
 async@^2.6.2:
   version "2.6.4"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"