diff --git a/.prettierignore b/.prettierignore index 2ea40753333..91029f665da 100644 --- a/.prettierignore +++ b/.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 diff --git a/app/helpers/react_component_helper.rb b/app/helpers/react_component_helper.rb index fc08de13ddc..ce616e83062 100644 --- a/app/helpers/react_component_helper.rb +++ b/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 diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 562e72655c8..9daeb3c60fc 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/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 diff --git a/app/javascript/mastodon/containers/admin_component.jsx b/app/javascript/mastodon/containers/admin_component.jsx index 562151fe24a..7400111293f 100644 --- a/app/javascript/mastodon/containers/admin_component.jsx +++ b/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 ( - + {children} ); diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx index 751015d18d5..f76550678ed 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/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 ( - + diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx index c4d4611a2dd..4538db050d1 100644 --- a/app/javascript/mastodon/containers/mastodon.jsx +++ b/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 ( - + diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx index 84eab1cae11..fba3c5df78b 100644 --- a/app/javascript/mastodon/containers/media_container.jsx +++ b/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 ( - + <> {[].map.call(components, (component, i) => { const componentName = component.getAttribute('data-component'); diff --git a/app/javascript/mastodon/load_locale.js b/app/javascript/mastodon/load_locale.js deleted file mode 100644 index cb14acd6227..00000000000 --- a/app/javascript/mastodon/load_locale.js +++ /dev/null @@ -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 }); -} diff --git a/app/javascript/mastodon/locales/global_locale.ts b/app/javascript/mastodon/locales/global_locale.ts new file mode 100644 index 00000000000..01133ca2392 --- /dev/null +++ b/app/javascript/mastodon/locales/global_locale.ts @@ -0,0 +1,22 @@ +export interface LocaleData { + locale: string; + messages: Record; +} + +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; +} diff --git a/app/javascript/mastodon/locales/index.js b/app/javascript/mastodon/locales/index.js deleted file mode 100644 index 6e57e3ddc43..00000000000 --- a/app/javascript/mastodon/locales/index.js +++ /dev/null @@ -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); -} diff --git a/app/javascript/mastodon/locales/index.ts b/app/javascript/mastodon/locales/index.ts new file mode 100644 index 00000000000..63f45c3047f --- /dev/null +++ b/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'; diff --git a/app/javascript/mastodon/locales/intl_provider.tsx b/app/javascript/mastodon/locales/intl_provider.tsx new file mode 100644 index 00000000000..1ea77c798e1 --- /dev/null +++ b/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, '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 ( + + {children} + + ); +}; diff --git a/app/javascript/mastodon/locales/load_locale.ts b/app/javascript/mastodon/locales/load_locale.ts new file mode 100644 index 00000000000..8a69123174a --- /dev/null +++ b/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('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 }); + }); +} diff --git a/app/javascript/mastodon/locales/locale-data/README.md b/app/javascript/mastodon/locales/locale-data/README.md deleted file mode 100644 index 83368fae7df..00000000000 --- a/app/javascript/mastodon/locales/locale-data/README.md +++ /dev/null @@ -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"; -} -``` diff --git a/app/javascript/mastodon/locales/locale-data/co.js b/app/javascript/mastodon/locales/locale-data/co.js deleted file mode 100644 index dff8a54dacb..00000000000 --- a/app/javascript/mastodon/locales/locale-data/co.js +++ /dev/null @@ -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; diff --git a/app/javascript/mastodon/locales/locale-data/oc.js b/app/javascript/mastodon/locales/locale-data/oc.js deleted file mode 100644 index 6ab306b8cf7..00000000000 --- a/app/javascript/mastodon/locales/locale-data/oc.js +++ /dev/null @@ -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; diff --git a/app/javascript/mastodon/locales/locale-data/sa.js b/app/javascript/mastodon/locales/locale-data/sa.js deleted file mode 100644 index 65e09e97f25..00000000000 --- a/app/javascript/mastodon/locales/locale-data/sa.js +++ /dev/null @@ -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; diff --git a/app/javascript/packs/admin.jsx b/app/javascript/packs/admin.jsx index 9bb4d4dbf32..ebcc6903f8c 100644 --- a/app/javascript/packs/admin.jsx +++ b/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 ( - + , ); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 01ab8f8f4b2..f26321c41aa 100644 --- a/app/javascript/packs/application.js +++ b/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); + }); diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index 22e6b01a1f1..da43bba7d68 100644 --- a/app/javascript/packs/public.jsx +++ b/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'; diff --git a/app/javascript/packs/share.jsx b/app/javascript/packs/share.jsx index f9fc7856184..0f3b84549dc 100644 --- a/app/javascript/packs/share.jsx +++ b/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); }); diff --git a/jest.config.js b/jest.config.js index 42c2b415224..f611812ef65 100644 --- a/jest.config.js +++ b/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', ], diff --git a/package.json b/package.json index 31f2454fee8..a08b485fd75 100644 --- a/package.json +++ b/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", diff --git a/spec/helpers/react_component_helper_spec.rb b/spec/helpers/react_component_helper_spec.rb index 3f133bff9a8..28208b619ba 100644 --- a/spec/helpers/react_component_helper_spec.rb +++ b/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 diff --git a/yarn.lock b/yarn.lock index 3ae63436357..fd751c04192 100644 --- a/yarn.lock +++ b/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"