156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
import type { ChangeEventHandler, FC } from 'react';
|
|
|
|
import type { IntlShape } from 'react-intl';
|
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
|
|
import { createSelector } from '@reduxjs/toolkit';
|
|
import type { List as ImmutableList } from 'immutable';
|
|
|
|
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
|
import type { RootState } from '@/mastodon/store';
|
|
import { useAppSelector } from '@/mastodon/store';
|
|
|
|
import { Section } from './section';
|
|
|
|
const messages = defineMessages({
|
|
rules: { id: 'about.rules', defaultMessage: 'Server rules' },
|
|
defaultLocale: { id: 'about.default_locale', defaultMessage: 'Default' },
|
|
});
|
|
|
|
interface RulesSectionProps {
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
interface BaseRule {
|
|
text: string;
|
|
hint: string;
|
|
}
|
|
|
|
interface Rule extends BaseRule {
|
|
id: string;
|
|
translations: Record<string, BaseRule>;
|
|
}
|
|
|
|
export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
|
|
const intl = useIntl();
|
|
const [locale, setLocale] = useState(intl.locale);
|
|
const rules = useAppSelector((state) => rulesSelector(state, locale));
|
|
const localeOptions = useAppSelector((state) =>
|
|
localeOptionsSelector(state, intl),
|
|
);
|
|
const handleLocaleChange: ChangeEventHandler<HTMLSelectElement> = useCallback(
|
|
(e) => {
|
|
setLocale(e.currentTarget.value);
|
|
},
|
|
[],
|
|
);
|
|
|
|
if (isLoading) {
|
|
return <Section title={intl.formatMessage(messages.rules)} />;
|
|
}
|
|
|
|
if (rules.length === 0) {
|
|
return (
|
|
<Section title={intl.formatMessage(messages.rules)}>
|
|
<p>
|
|
<FormattedMessage
|
|
id='about.not_available'
|
|
defaultMessage='This information has not been made available on this server.'
|
|
/>
|
|
</p>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Section title={intl.formatMessage(messages.rules)}>
|
|
<ol className='rules-list'>
|
|
{rules.map((rule) => (
|
|
<li key={rule.id}>
|
|
<div className='rules-list__text'>{rule.text}</div>
|
|
{!!rule.hint && <div className='rules-list__hint'>{rule.hint}</div>}
|
|
</li>
|
|
))}
|
|
</ol>
|
|
|
|
<div className='rules-languages'>
|
|
<label htmlFor='language-select'>
|
|
<FormattedMessage
|
|
id='about.language_label'
|
|
defaultMessage='Language'
|
|
/>
|
|
</label>
|
|
<select onChange={handleLocaleChange} id='language-select'>
|
|
{localeOptions.map((option) => (
|
|
<option
|
|
key={option.value}
|
|
value={option.value}
|
|
selected={option.value === locale}
|
|
>
|
|
{option.text}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const selectRules = (state: RootState) => {
|
|
const rules = state.server.getIn([
|
|
'server',
|
|
'rules',
|
|
]) as ImmutableList<Rule> | null;
|
|
if (!rules) {
|
|
return [];
|
|
}
|
|
return rules.toJS() as Rule[];
|
|
};
|
|
|
|
const rulesSelector = createSelector(
|
|
[selectRules, (_state, locale: string) => locale],
|
|
(rules, locale): Rule[] => {
|
|
return rules.map((rule) => {
|
|
const translations = rule.translations;
|
|
if (translations[locale]) {
|
|
rule.text = translations[locale].text;
|
|
rule.hint = translations[locale].hint;
|
|
}
|
|
const partialLocale = locale.split('-')[0];
|
|
if (partialLocale && translations[partialLocale]) {
|
|
rule.text = translations[partialLocale].text;
|
|
rule.hint = translations[partialLocale].hint;
|
|
}
|
|
return rule;
|
|
});
|
|
},
|
|
);
|
|
|
|
const localeOptionsSelector = createSelector(
|
|
[selectRules, (_state, intl: IntlShape) => intl],
|
|
(rules, intl): SelectItem[] => {
|
|
const langs: Record<string, SelectItem> = {
|
|
default: {
|
|
value: 'default',
|
|
text: intl.formatMessage(messages.defaultLocale),
|
|
},
|
|
};
|
|
// Use the default locale as a target to translate language names.
|
|
const intlLocale = new Intl.DisplayNames(intl.locale, {
|
|
type: 'language',
|
|
});
|
|
for (const { translations } of rules) {
|
|
for (const locale in translations) {
|
|
if (langs[locale]) {
|
|
continue; // Skip if already added
|
|
}
|
|
langs[locale] = {
|
|
value: locale,
|
|
text: intlLocale.of(locale) ?? locale,
|
|
};
|
|
}
|
|
}
|
|
return Object.values(langs);
|
|
},
|
|
);
|