Fix /share and cleanup and reorganize frontend locale loading (#25240)
				
					
				
			This commit is contained in:
		@@ -61,7 +61,7 @@ docker-compose.override.yml
 | 
				
			|||||||
/app/javascript/mastodon/features/emoji/emoji_map.json
 | 
					/app/javascript/mastodon/features/emoji/emoji_map.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Ignore locale files
 | 
					# Ignore locale files
 | 
				
			||||||
/app/javascript/mastodon/locales
 | 
					/app/javascript/mastodon/locales/*.json
 | 
				
			||||||
/config/locales
 | 
					/config/locales
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Ignore vendored CSS reset
 | 
					# Ignore vendored CSS reset
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ module ReactComponentHelper
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def react_admin_component(name, props = {})
 | 
					  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)
 | 
					    div_tag_with_data(data)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,6 @@ import {
 | 
				
			|||||||
  fillListTimelineGaps,
 | 
					  fillListTimelineGaps,
 | 
				
			||||||
} from './timelines';
 | 
					} from './timelines';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { messages } = getLocale();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {number} max
 | 
					 * @param {number} max
 | 
				
			||||||
 * @returns {number}
 | 
					 * @returns {number}
 | 
				
			||||||
@@ -43,8 +41,10 @@ const randomUpTo = max =>
 | 
				
			|||||||
 * @param {function(object): boolean} [options.accept]
 | 
					 * @param {function(object): boolean} [options.accept]
 | 
				
			||||||
 * @returns {function(): void}
 | 
					 * @returns {function(): void}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
 | 
					export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
 | 
				
			||||||
  connectStream(channelName, params, (dispatch, getState) => {
 | 
					  const { messages } = getLocale();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return connectStream(channelName, params, (dispatch, getState) => {
 | 
				
			||||||
    const locale = getState().getIn(['meta', 'locale']);
 | 
					    const locale = getState().getIn(['meta', 'locale']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // @ts-expect-error
 | 
					    // @ts-expect-error
 | 
				
			||||||
@@ -121,6 +121,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {Function} dispatch
 | 
					 * @param {Function} dispatch
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,19 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { PureComponent } from 'react';
 | 
					import { PureComponent } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IntlProvider } from 'react-intl';
 | 
					import { IntlProvider } from 'mastodon/locales';
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { getLocale, onProviderError } from '../locales';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { messages } = getLocale();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class AdminComponent extends PureComponent {
 | 
					export default class AdminComponent extends PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    children: PropTypes.node.isRequired,
 | 
					    children: PropTypes.node.isRequired,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { locale, children } = this.props;
 | 
					    const { children } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
 | 
					      <IntlProvider>
 | 
				
			||||||
        {children}
 | 
					        {children}
 | 
				
			||||||
      </IntlProvider>
 | 
					      </IntlProvider>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,14 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { PureComponent } from 'react';
 | 
					import { PureComponent } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IntlProvider } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Provider } from 'react-redux';
 | 
					import { Provider } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fetchCustomEmojis } from '../actions/custom_emojis';
 | 
					import { fetchCustomEmojis } from '../actions/custom_emojis';
 | 
				
			||||||
import { hydrateStore } from '../actions/store';
 | 
					import { hydrateStore } from '../actions/store';
 | 
				
			||||||
import Compose from '../features/standalone/compose';
 | 
					import Compose from '../features/standalone/compose';
 | 
				
			||||||
import initialState from '../initial_state';
 | 
					import initialState from '../initial_state';
 | 
				
			||||||
import { getLocale, onProviderError } from '../locales';
 | 
					import { IntlProvider } from '../locales';
 | 
				
			||||||
import { store } from '../store';
 | 
					import { store } from '../store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { messages } = getLocale();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (initialState) {
 | 
					if (initialState) {
 | 
				
			||||||
  store.dispatch(hydrateStore(initialState));
 | 
					  store.dispatch(hydrateStore(initialState));
 | 
				
			||||||
@@ -20,17 +16,11 @@ if (initialState) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
store.dispatch(fetchCustomEmojis());
 | 
					store.dispatch(fetchCustomEmojis());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class TimelineContainer extends PureComponent {
 | 
					export default class ComposeContainer extends PureComponent {
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { locale } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
 | 
					      <IntlProvider>
 | 
				
			||||||
        <Provider store={store}>
 | 
					        <Provider store={store}>
 | 
				
			||||||
          <Compose />
 | 
					          <Compose />
 | 
				
			||||||
        </Provider>
 | 
					        </Provider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { PureComponent } from 'react';
 | 
					import { PureComponent } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IntlProvider } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Helmet } from 'react-helmet';
 | 
					import { Helmet } from 'react-helmet';
 | 
				
			||||||
import { BrowserRouter, Route } from 'react-router-dom';
 | 
					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 ErrorBoundary from 'mastodon/components/error_boundary';
 | 
				
			||||||
import UI from 'mastodon/features/ui';
 | 
					import UI from 'mastodon/features/ui';
 | 
				
			||||||
import initialState, { title as siteTitle } from 'mastodon/initial_state';
 | 
					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';
 | 
					import { store } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { messages } = getLocale();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
 | 
					const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const hydrateAction = hydrateStore(initialState);
 | 
					const hydrateAction = hydrateStore(initialState);
 | 
				
			||||||
@@ -40,10 +36,6 @@ const createIdentityContext = state => ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default class Mastodon extends PureComponent {
 | 
					export default class Mastodon extends PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static childContextTypes = {
 | 
					  static childContextTypes = {
 | 
				
			||||||
    identity: PropTypes.shape({
 | 
					    identity: PropTypes.shape({
 | 
				
			||||||
      signedIn: PropTypes.bool.isRequired,
 | 
					      signedIn: PropTypes.bool.isRequired,
 | 
				
			||||||
@@ -79,10 +71,8 @@ export default class Mastodon extends PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { locale } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
 | 
					      <IntlProvider>
 | 
				
			||||||
        <ReduxProvider store={store}>
 | 
					        <ReduxProvider store={store}>
 | 
				
			||||||
          <ErrorBoundary>
 | 
					          <ErrorBoundary>
 | 
				
			||||||
            <BrowserRouter>
 | 
					            <BrowserRouter>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,6 @@ import PropTypes from 'prop-types';
 | 
				
			|||||||
import { PureComponent } from 'react';
 | 
					import { PureComponent } from 'react';
 | 
				
			||||||
import { createPortal } from 'react-dom';
 | 
					import { createPortal } from 'react-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IntlProvider } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { fromJS } from 'immutable';
 | 
					import { fromJS } from 'immutable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
 | 
					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 Card from 'mastodon/features/status/components/card';
 | 
				
			||||||
import MediaModal from 'mastodon/features/ui/components/media_modal';
 | 
					import MediaModal from 'mastodon/features/ui/components/media_modal';
 | 
				
			||||||
import Video from 'mastodon/features/video';
 | 
					import Video from 'mastodon/features/video';
 | 
				
			||||||
import { getLocale, onProviderError } from 'mastodon/locales';
 | 
					import { IntlProvider } from 'mastodon/locales';
 | 
				
			||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
 | 
					import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { messages } = getLocale();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
 | 
					const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class MediaContainer extends PureComponent {
 | 
					export default class MediaContainer extends PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    components: PropTypes.object.isRequired,
 | 
					    components: PropTypes.object.isRequired,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,7 +68,7 @@ export default class MediaContainer extends PureComponent {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { locale, components } = this.props;
 | 
					    const { components } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let handleOpenVideo;
 | 
					    let handleOpenVideo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,7 +78,7 @@ export default class MediaContainer extends PureComponent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages} onError={onProviderError}>
 | 
					      <IntlProvider>
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
          {[].map.call(components, (component, i) => {
 | 
					          {[].map.call(components, (component, i) => {
 | 
				
			||||||
            const componentName = component.getAttribute('data-component');
 | 
					            const componentName = component.getAttribute('data-component');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
									
								
								app/javascript/mastodon/locales/global_locale.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/javascript/mastodon/locales/global_locale.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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
									
								
								app/javascript/mastodon/locales/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/javascript/mastodon/locales/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
									
								
								app/javascript/mastodon/locales/intl_provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/javascript/mastodon/locales/intl_provider.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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
									
								
								app/javascript/mastodon/locales/load_locale.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/javascript/mastodon/locales/load_locale.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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 });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
@@ -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;
 | 
					 | 
				
			||||||
@@ -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;
 | 
					 | 
				
			||||||
@@ -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;
 | 
					 | 
				
			||||||
@@ -229,14 +229,14 @@ ready(() => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
 | 
					  [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
 | 
				
			||||||
    const componentName  = element.getAttribute('data-admin-component');
 | 
					    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 }) => {
 | 
					    import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => {
 | 
				
			||||||
      return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => {
 | 
					      return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => {
 | 
				
			||||||
        const root = createRoot(element);
 | 
					        const root = createRoot(element);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        root.render (
 | 
					        root.render (
 | 
				
			||||||
          <AdminComponent locale={locale}>
 | 
					          <AdminComponent>
 | 
				
			||||||
            <Component {...componentProps} />
 | 
					            <Component {...componentProps} />
 | 
				
			||||||
          </AdminComponent>,
 | 
					          </AdminComponent>,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,15 @@
 | 
				
			|||||||
import './public-path';
 | 
					import './public-path';
 | 
				
			||||||
 | 
					import main from "mastodon/main"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { start } from '../mastodon/common';
 | 
					import { start } from '../mastodon/common';
 | 
				
			||||||
import { loadLocale } from '../mastodon/load_locale';
 | 
					import { loadLocale } from '../mastodon/locales';
 | 
				
			||||||
import { loadPolyfills } from '../mastodon/polyfills';
 | 
					import { loadPolyfills } from '../mastodon/polyfills';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
start();
 | 
					start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
loadPolyfills().then(loadLocale).then(async () => {
 | 
					loadPolyfills()
 | 
				
			||||||
  const { default: main } = await import('mastodon/main');
 | 
					  .then(loadLocale)
 | 
				
			||||||
 | 
					  .then(main)
 | 
				
			||||||
  return main();
 | 
					  .catch(e => {
 | 
				
			||||||
}).catch(e => {
 | 
					 | 
				
			||||||
    console.error(e);
 | 
					    console.error(e);
 | 
				
			||||||
});
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,7 @@ import { start } from '../mastodon/common';
 | 
				
			|||||||
import { timeAgoString }  from '../mastodon/components/relative_timestamp';
 | 
					import { timeAgoString }  from '../mastodon/components/relative_timestamp';
 | 
				
			||||||
import emojify  from '../mastodon/features/emoji/emoji';
 | 
					import emojify  from '../mastodon/features/emoji/emoji';
 | 
				
			||||||
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
 | 
					import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
 | 
				
			||||||
import { loadLocale } from '../mastodon/load_locale';
 | 
					import { loadLocale, getLocale } from '../mastodon/locales';
 | 
				
			||||||
import { getLocale }  from '../mastodon/locales';
 | 
					 | 
				
			||||||
import { loadPolyfills } from '../mastodon/polyfills';
 | 
					import { loadPolyfills } from '../mastodon/polyfills';
 | 
				
			||||||
import ready from '../mastodon/ready';
 | 
					import ready from '../mastodon/ready';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { start } from '../mastodon/common';
 | 
					import { start } from '../mastodon/common';
 | 
				
			||||||
import ComposeContainer  from '../mastodon/containers/compose_container';
 | 
					import ComposeContainer  from '../mastodon/containers/compose_container';
 | 
				
			||||||
import { loadLocale } from '../mastodon/load_locale';
 | 
					 | 
				
			||||||
import { loadPolyfills } from '../mastodon/polyfills';
 | 
					import { loadPolyfills } from '../mastodon/polyfills';
 | 
				
			||||||
import ready from '../mastodon/ready';
 | 
					import ready from '../mastodon/ready';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,6 +25,6 @@ function main() {
 | 
				
			|||||||
  ready(loaded);
 | 
					  ready(loaded);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
loadPolyfills().then(loadLocale).then(main).catch(error => {
 | 
					loadPolyfills().then(main).catch(error => {
 | 
				
			||||||
  console.error(error);
 | 
					  console.error(error);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ const config = {
 | 
				
			|||||||
  collectCoverageFrom: [
 | 
					  collectCoverageFrom: [
 | 
				
			||||||
    'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
 | 
					    'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
 | 
				
			||||||
    '!app/javascript/mastodon/features/emoji/emoji_compressed.js',
 | 
					    '!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/service_worker/entry.js',
 | 
				
			||||||
    '!app/javascript/mastodon/test_setup.js',
 | 
					    '!app/javascript/mastodon/test_setup.js',
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,7 @@
 | 
				
			|||||||
    "@reduxjs/toolkit": "^1.9.5",
 | 
					    "@reduxjs/toolkit": "^1.9.5",
 | 
				
			||||||
    "abortcontroller-polyfill": "^1.7.5",
 | 
					    "abortcontroller-polyfill": "^1.7.5",
 | 
				
			||||||
    "arrow-key-navigation": "^1.2.0",
 | 
					    "arrow-key-navigation": "^1.2.0",
 | 
				
			||||||
 | 
					    "async-mutex": "^0.4.0",
 | 
				
			||||||
    "autoprefixer": "^10.4.14",
 | 
					    "autoprefixer": "^10.4.14",
 | 
				
			||||||
    "axios": "^1.4.0",
 | 
					    "axios": "^1.4.0",
 | 
				
			||||||
    "babel-loader": "^8.3.0",
 | 
					    "babel-loader": "^8.3.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ describe ReactComponentHelper do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    it 'returns a tag with data attributes' do
 | 
					    it 'returns a tag with data attributes' do
 | 
				
			||||||
      expect(parsed_html.div['data-admin-component']).to eq('Name')
 | 
					      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
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3273,6 +3273,13 @@ async-limiter@~1.0.0:
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
 | 
					  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
 | 
				
			||||||
  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 | 
					  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:
 | 
					async@^2.6.2:
 | 
				
			||||||
  version "2.6.4"
 | 
					  version "2.6.4"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
 | 
					  resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user