mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
fix(i18n): review translations and fix i18n library misuse (#2750)
This commit is contained in:
1 parent
78f783f0c4
commit
8cb097f954
575 files changed
+192671
-160540
No files matched your search
@@ -1,7 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import styles from '@app/features/guild/components/UploadSlotInfo.module.css';
|
||||
import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import {useLingui} from '@lingui/react';
|
||||
import {UploadIcon} from '@phosphor-icons/react';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
@@ -18,7 +20,9 @@ interface UploadSlotInfoProps {
|
||||
|
||||
export const UploadSlotInfo: React.FC<UploadSlotInfoProps> = observer(
|
||||
({title, currentCount, maxCount, description, uploadButtonText, onUploadClick, additionalSlots}) => {
|
||||
const maxSlotCount = maxCount === Number.POSITIVE_INFINITY ? '∞' : maxCount;
|
||||
const {i18n} = useLingui();
|
||||
const numberFormat = getCachedNumberFormat(i18n.locale);
|
||||
const maxSlotCount = maxCount === Number.POSITIVE_INFINITY ? '∞' : numberFormat.format(maxCount);
|
||||
return (
|
||||
<div className={styles.container} data-flx="guild.upload-slot-info.container">
|
||||
<div className={styles.header} data-flx="guild.upload-slot-info.header">
|
||||
@@ -29,7 +33,7 @@ export const UploadSlotInfo: React.FC<UploadSlotInfoProps> = observer(
|
||||
<div className={styles.stats} data-flx="guild.upload-slot-info.stats">
|
||||
{additionalSlots || (
|
||||
<span data-flx="guild.upload-slot-info.span">
|
||||
{currentCount} / {maxSlotCount}
|
||||
{numberFormat.format(currentCount)} / {maxSlotCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
MUTE_COMMUNITY_DESCRIPTOR,
|
||||
UNMUTE_COMMUNITY_DESCRIPTOR,
|
||||
} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import {useMuteSheet} from '@app/features/notification/hooks/useMuteSheet';
|
||||
import {useGuildMenuData} from '@app/features/ui/action_menu/items/GuildMenuData';
|
||||
import {
|
||||
@@ -80,6 +81,7 @@ export const GuildHeaderBottomSheet: React.FC<GuildHeaderBottomSheetProps> = obs
|
||||
const guildCounts = GuildCount.getCounts(guild.id);
|
||||
const presenceCount = guildCounts?.onlineCount ?? 0;
|
||||
const memberCount = guildCounts?.memberCount ?? 0;
|
||||
const formattedPresenceCount = getCachedNumberFormat(i18n.locale).format(presenceCount);
|
||||
const headerContent = (
|
||||
<div className={headerStyles.header} data-flx="guild.guild-header-bottom-sheet.div">
|
||||
<div className={headerStyles.avatarWrapper} data-flx="guild.guild-header-bottom-sheet.div--2">
|
||||
@@ -104,7 +106,7 @@ export const GuildHeaderBottomSheet: React.FC<GuildHeaderBottomSheetProps> = obs
|
||||
data-flx="guild.guild-header-bottom-sheet.div--6"
|
||||
/>
|
||||
<span className={headerStyles.statText} data-flx="guild.guild-header-bottom-sheet.span--2">
|
||||
{i18n._(ONLINE_DESCRIPTOR, {presenceCount})}
|
||||
{i18n._(ONLINE_DESCRIPTOR, {presenceCount: formattedPresenceCount})}
|
||||
</span>
|
||||
</div>
|
||||
<div className={headerStyles.stat} data-flx="guild.guild-header-bottom-sheet.div--7">
|
||||
|
||||
@@ -10,7 +10,7 @@ import {focusChannelTextareaAfterNavigation} from '@app/features/messaging/utils
|
||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
||||
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
||||
import {formatShortRelativeTime} from '@fluxer/date_utils/src/DateDuration';
|
||||
import {formatShortRelativeTime} from '@app/features/ui/utils/ShortRelativeTimeLabels';
|
||||
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
@@ -54,7 +54,10 @@ export const DuplicateGroupConfirmModal = observer(({channels, onConfirm}: Dupli
|
||||
<div className={styles.channelList} data-flx="guild.duplicate-group-confirm-modal.description.channel-list">
|
||||
{channels.map((channel) => {
|
||||
const lastActivitySnowflake = channel.lastMessageId ?? channel.id;
|
||||
const lastActiveText = formatShortRelativeTime(SnowflakeUtils.extractTimestamp(lastActivitySnowflake));
|
||||
const lastActiveText = formatShortRelativeTime(
|
||||
i18n,
|
||||
SnowflakeUtils.extractTimestamp(lastActivitySnowflake),
|
||||
);
|
||||
const lastActiveLabel = lastActiveText || i18n._(NO_ACTIVITY_YET_DESCRIPTOR);
|
||||
return (
|
||||
<FocusRing
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ShieldIcon,
|
||||
StarIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
import {formatListWithConfig} from '@pkgs/list_utils/src/ListFormatting';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import type {ReactNode} from 'react';
|
||||
import {useCallback, useMemo, useState} from 'react';
|
||||
@@ -138,8 +139,8 @@ export const GuildFolderSettingsModal = observer(({folderId}: GuildFolderSetting
|
||||
.slice(0, 3)
|
||||
.map((guildId) => Guilds.getGuild(guildId)?.name)
|
||||
.filter((name): name is string => name != null);
|
||||
return guildNames.join(', ');
|
||||
}, [folder]);
|
||||
return formatListWithConfig(guildNames, {locale: i18n.locale, style: 'narrow', type: 'conjunction'});
|
||||
}, [folder, i18n.locale]);
|
||||
const [name, setName] = useState(folder?.name ?? '');
|
||||
const nameRef = useCursorAtEnd<HTMLInputElement>();
|
||||
const [color, setColor] = useState(folder?.color ?? 0);
|
||||
|
||||
@@ -80,7 +80,7 @@ const UNREAD_BADGES_SECTION_DESCRIPTION_DESCRIPTOR = msg({
|
||||
'Helper text under the unread badges section in the community notification settings modal. The quoted "Default" should match the matching option label.',
|
||||
});
|
||||
const COMMUNITY_UNREAD_BADGES_LEVEL_ARIA_DESCRIPTOR = msg({
|
||||
message: 'Community unread badges level',
|
||||
message: 'Community unread badge level',
|
||||
comment: 'Accessible label for the unread badges level radio group in the community notification settings modal.',
|
||||
});
|
||||
const SUPPRESS_EVERYONE_AND_HERE_SWITCH_DESCRIPTOR = msg({
|
||||
@@ -100,7 +100,7 @@ const REPLY_MENTION_PREFERENCE_SECTION_DESCRIPTOR = msg({
|
||||
});
|
||||
const REPLY_MENTION_PREFERENCE_SECTION_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Override how replies to your messages handle the @ mention in this community. "Inherit from account" uses your account-wide setting.',
|
||||
'Override how replies to your messages handle the @mention in this community. "Inherit from account" uses your account-wide setting.',
|
||||
comment:
|
||||
'Helper text under the reply mention preference section in the community notification settings modal. The quoted "Inherit from account" should match the matching option label.',
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as Modal from '@app/features/app/components/dialogs/Modal';
|
||||
import {ExternalLink} from '@app/features/app/components/shared/ExternalLink';
|
||||
import {
|
||||
AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
PRODUCT_NAME,
|
||||
STATIC_IMAGE_FORMATS,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
VERIFY_EMAIL_DESCRIPTOR,
|
||||
} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
@@ -98,7 +99,7 @@ export const GuildCreateForm = observer(() => {
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
showIconUploadErrorModal(
|
||||
i18n._(ICON_FILE_IS_TOO_LARGE_PLEASE_CHOOSE_A_DESCRIPTOR, {
|
||||
imageMaxSizeLabel: IMAGE_MAX_SIZE_LABEL,
|
||||
imageMaxSizeLabel: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -184,7 +185,7 @@ export const GuildCreateForm = observer(() => {
|
||||
title: i18n._(CHANGE_ICON_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadRecommendedHint(i18n, {
|
||||
formats: STATIC_IMAGE_FORMATS,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
recommendedSize: AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
}),
|
||||
onPickUpload: handleIconUpload,
|
||||
|
||||
+6
-4
@@ -5,7 +5,7 @@ import * as Modal from '@app/features/app/components/dialogs/Modal';
|
||||
import {ExternalLink} from '@app/features/app/components/shared/ExternalLink';
|
||||
import {
|
||||
AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
PRODUCT_NAME,
|
||||
STATIC_IMAGE_FORMATS,
|
||||
THE_OTHER_PLATFORM_TEMPLATE_EXAMPLE_URL,
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
|
||||
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
@@ -146,7 +147,8 @@ type TemplateImportStep = 'url' | 'json' | 'create';
|
||||
|
||||
const TEMPLATE_IMPORT_STEP_ORDER: ReadonlyArray<TemplateImportStep> = ['url', 'json', 'create'];
|
||||
const TEMPLATE_STATS_DESCRIPTOR = msg({
|
||||
message: '{textChannelCount} text, {voiceChannelCount} voice, {categoryCount} categories, {roleCount} roles',
|
||||
message:
|
||||
'{textChannelCount, plural, one {# text channel} other {# text channels}}, {voiceChannelCount, plural, one {# voice channel} other {# voice channels}}, {categoryCount, plural, one {# category} other {# categories}}, {roleCount, plural, one {# role} other {# roles}}',
|
||||
comment:
|
||||
'Template preview summary listing how many text channels, voice channels, categories, and roles will be imported.',
|
||||
});
|
||||
@@ -278,7 +280,7 @@ export const TemplateImportForm = observer(() => {
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
showIconUploadErrorModal(
|
||||
i18n._(ICON_FILE_IS_TOO_LARGE_PLEASE_CHOOSE_A_DESCRIPTOR, {
|
||||
imageMaxSizeLabel: IMAGE_MAX_SIZE_LABEL,
|
||||
imageMaxSizeLabel: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -329,7 +331,7 @@ export const TemplateImportForm = observer(() => {
|
||||
title: i18n._(CHANGE_ICON_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadRecommendedHint(i18n, {
|
||||
formats: STATIC_IMAGE_FORMATS,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
recommendedSize: AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
}),
|
||||
onPickUpload: handleIconUpload,
|
||||
|
||||
+32
-20
@@ -37,6 +37,7 @@ import type {GuildAuditLogEntryResponse} from '@fluxer/schema/src/domains/guild/
|
||||
import type {I18n} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Plural, Trans} from '@lingui/react/macro';
|
||||
import {formatListWithConfig} from '@pkgs/list_utils/src/ListFormatting';
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
const SAFE_DESCRIPTOR = msg({
|
||||
@@ -67,7 +68,8 @@ export type ChangeRenderer = (
|
||||
|
||||
const renderInline = (value: unknown, i18nInstance: I18n, guildId?: string): ReactNode =>
|
||||
renderValueInline(value, guildId, i18nInstance);
|
||||
const joinLabels = (labels: Array<string>): string => labels.join(', ');
|
||||
const joinLabels = (labels: Array<string>, i18nInstance: I18n = i18n): string =>
|
||||
formatListWithConfig(labels, {locale: i18nInstance.locale, style: 'long', type: 'conjunction'});
|
||||
const normalizeStringArray = (value: unknown): Array<string> =>
|
||||
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
||||
const getRoleNameResolver = (guildId: string): ((roleId: string) => string) => {
|
||||
@@ -88,10 +90,12 @@ const getRoleDiff = (oldValue: unknown, newValue: unknown): {added: Array<string
|
||||
const mapFeatureLabels = (features: Array<string>, i18nInstance: I18n): Array<string> =>
|
||||
features.map((feature) => getFeatureLabel(feature, i18nInstance) ?? feature);
|
||||
const formatPermissionList = (flags: Array<bigint>, i18nInstance: I18n = i18n): string =>
|
||||
flags
|
||||
.map((flag) => formatPermissionLabel(i18nInstance, flag, false))
|
||||
.filter((label): label is string => label !== null)
|
||||
.join(', ');
|
||||
joinLabels(
|
||||
flags
|
||||
.map((flag) => formatPermissionLabel(i18nInstance, flag, false))
|
||||
.filter((label): label is string => label !== null),
|
||||
i18nInstance,
|
||||
);
|
||||
const whenOldValueMissing =
|
||||
(hasNoOld: ChangeRenderer, hasOld: ChangeRenderer): ChangeRenderer =>
|
||||
(change, ctx) =>
|
||||
@@ -233,10 +237,10 @@ const GUILD_CHANGE_RENDERERS: Record<string, ChangeRenderer> = {
|
||||
<Trans>Set the AFK channel to {renderInline(change.newValue, i18n, guildId)}.</Trans>
|
||||
),
|
||||
afk_timeout: (change, {i18n}) => {
|
||||
const raw = safeScalarString(change.newValue, i18n);
|
||||
const minutes = raw != null ? Number(raw) : Number.NaN;
|
||||
if (Number.isNaN(minutes))
|
||||
return <Trans>Set the AFK timeout to {renderInline(change.newValue, i18n)} minutes.</Trans>;
|
||||
const seconds = change.newValue;
|
||||
if (typeof seconds !== 'number' || !Number.isFinite(seconds))
|
||||
return <Trans>Set the AFK timeout to {renderInline(change.newValue, i18n)} seconds.</Trans>;
|
||||
const minutes = seconds / 60;
|
||||
return (
|
||||
<Plural
|
||||
value={minutes}
|
||||
@@ -658,26 +662,34 @@ const ROLE_CHANGE_RENDERERS: Record<string, ChangeRenderer> = {
|
||||
},
|
||||
hoist: (change) =>
|
||||
change.newValue === true ? (
|
||||
<Trans>Display role members separately.</Trans>
|
||||
<Trans>Enabled separate display of members with this role.</Trans>
|
||||
) : (
|
||||
<Trans>Don't display separately.</Trans>
|
||||
<Trans>Disabled separate display of members with this role.</Trans>
|
||||
),
|
||||
mentionable: (change) =>
|
||||
change.newValue === true ? <Trans>Allow @mention.</Trans> : <Trans>Disallow @mention.</Trans>,
|
||||
change.newValue === true ? (
|
||||
<Trans>Allowed everyone to mention this role.</Trans>
|
||||
) : (
|
||||
<Trans>Restricted mentions of this role to members with permission.</Trans>
|
||||
),
|
||||
icon_hash: () => <Trans>Updated the role icon.</Trans>,
|
||||
unicode_emoji: whenNewValueMissing(
|
||||
() => <Trans>Removed the unicode emoji.</Trans>,
|
||||
(change, {i18n}) => <Trans>Set the unicode emoji to {renderInline(change.newValue, i18n)}.</Trans>,
|
||||
() => <Trans>Removed the Unicode emoji.</Trans>,
|
||||
(change, {i18n}) => <Trans>Set the Unicode emoji to {renderInline(change.newValue, i18n)}.</Trans>,
|
||||
),
|
||||
};
|
||||
const INVITE_CHANGE_RENDERERS: Record<string, ChangeRenderer> = {
|
||||
code: (change, {i18n}) => <Trans>Invite code is {renderInline(change.newValue, i18n)}.</Trans>,
|
||||
max_uses: (change, {i18n}) =>
|
||||
change.newValue === 0 ? (
|
||||
<Trans>This invite has unlimited uses.</Trans>
|
||||
) : (
|
||||
<Trans>This invite expires after {renderInline(change.newValue, i18n)} uses.</Trans>
|
||||
),
|
||||
max_uses: (change) => {
|
||||
if (change.newValue === 0) return <Trans>This invite has unlimited uses.</Trans>;
|
||||
if (typeof change.newValue !== 'number') return null;
|
||||
const count = change.newValue;
|
||||
return (
|
||||
<Trans>
|
||||
This invite expires after <Plural value={count} one="# use" other="# uses" />.
|
||||
</Trans>
|
||||
);
|
||||
},
|
||||
max_age: (change) => {
|
||||
if (change.newValue === 0) return <Trans>This invite never expires.</Trans>;
|
||||
if (typeof change.newValue !== 'number') return null;
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ import {
|
||||
shouldHideChangeKey,
|
||||
toChangeShape,
|
||||
} from '@app/features/guild/utils/guild_tabs/GuildAuditLogTabUtils';
|
||||
import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache';
|
||||
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||
import type {User} from '@app/features/user/models/User';
|
||||
import {getFormattedDateTime} from '@app/features/user/utils/DateFormatting';
|
||||
@@ -144,7 +145,7 @@ export const renderValueInline = (value: unknown, guildId: string | undefined, i
|
||||
if (isEmptyString(value)) return renderBoldValue(i18n._(NOTHING_DESCRIPTOR));
|
||||
const scalar = safeScalarString(value, i18n);
|
||||
if (scalar !== null) {
|
||||
if (typeof value === 'number') return renderBoldValue(scalar);
|
||||
if (typeof value === 'number') return renderBoldValue(getCachedNumberFormat(i18n.locale).format(value));
|
||||
if (typeof value === 'boolean') return renderBoldValue(scalar);
|
||||
if (typeof value === 'string' && looksLikeSnowflake(value)) {
|
||||
if (guildId) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import type {
|
||||
DiscoveryStatusResponse,
|
||||
} from '@fluxer/schema/src/domains/guild/GuildDiscoverySchemas';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro';
|
||||
import {InfoIcon, WarningIcon} from '@phosphor-icons/react';
|
||||
import {clsx} from 'clsx';
|
||||
import type React from 'react';
|
||||
@@ -346,8 +346,15 @@ const GuildDiscoveryTab: React.FC<{guildId: string}> = ({guildId}) => {
|
||||
<Trans>Not enough members</Trans>
|
||||
</p>
|
||||
<p className={styles.warningText} data-flx="guild.guild-tabs.guild-discovery-tab.warning-text">
|
||||
<Trans>
|
||||
Your community needs at least {minMemberCount} members before it can be listed in Discovery.
|
||||
<Trans comment="Warning in the community Discovery settings tab when the community is below the minimum size. The plural count is the minimum number of members required to be listed.">
|
||||
Your community needs at least{' '}
|
||||
<Plural
|
||||
value={minMemberCount}
|
||||
one="# member"
|
||||
other="# members"
|
||||
data-flx="guild.guild-tabs.guild-discovery-tab.min-member-count.plural"
|
||||
/>{' '}
|
||||
before it can be listed in Discovery.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -621,7 +628,7 @@ const GuildDiscoveryTab: React.FC<{guildId: string}> = ({guildId}) => {
|
||||
disabled={!eligible && canApply}
|
||||
data-flx="guild.guild-tabs.guild-discovery-tab.button.submit"
|
||||
>
|
||||
{hasActiveApplication ? <Trans>Save</Trans> : <Trans>Apply</Trans>}
|
||||
{hasActiveApplication ? <Trans>Save</Trans> : <Trans>Submit application</Trans>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -179,7 +179,7 @@ const GuildEmojiTab: React.FC<{guildId: string}> = observer(function GuildEmojiT
|
||||
}, [filteredEmojis]);
|
||||
const maxEmojis = guild?.maxEmojis ?? MAX_GUILD_EMOJIS;
|
||||
const currentEmojiCount = emojis.length;
|
||||
const emojiMaxSizeLabel = formatFileSize(GlobalLimits.getEmojiMaxSize());
|
||||
const emojiMaxSizeLabel = formatFileSize(i18n.locale, GlobalLimits.getEmojiMaxSize());
|
||||
const canModifyEmoji = useCallback(
|
||||
(emoji: GuildEmojiWithUser): boolean => {
|
||||
if (canManageExpressions) return true;
|
||||
@@ -292,7 +292,7 @@ const GuildEmojiTab: React.FC<{guildId: string}> = observer(function GuildEmojiT
|
||||
const preparedEmojis: Array<{name: string; image: string; file: File}> = [];
|
||||
const preparationFailures: Array<EmojiUploadFailure> = [];
|
||||
const maxEmojiSize = GlobalLimits.getEmojiMaxSize();
|
||||
const maxEmojiSizeLabel = formatFileSize(maxEmojiSize);
|
||||
const maxEmojiSizeLabel = formatFileSize(i18n.locale, maxEmojiSize);
|
||||
for (const file of filesWithinSlots) {
|
||||
try {
|
||||
const base64Image = await ImageCropUtils.optimizeEmojiImage(file, maxEmojiSize, 128);
|
||||
@@ -307,7 +307,7 @@ const GuildEmojiTab: React.FC<{guildId: string}> = observer(function GuildEmojiT
|
||||
? EMOJI_PROCESSED_FILE_TOO_LARGE_DESCRIPTOR
|
||||
: EMOJI_SOURCE_FILE_TOO_LARGE_DESCRIPTOR,
|
||||
{
|
||||
fileSize: formatFileSize(error.actualSizeBytes),
|
||||
fileSize: formatFileSize(i18n.locale, error.actualSizeBytes),
|
||||
maxSize: maxEmojiSizeLabel,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -87,6 +87,7 @@ import {useState} from 'react';
|
||||
|
||||
const MESSAGE_DESCRIPTOR = msg({
|
||||
message: 'Message',
|
||||
context: 'message-action',
|
||||
comment:
|
||||
'Member actions sheet menu item. Sends a direct message to the target user. Standalone verb used as a menu label.',
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ const VERIFICATION_LEVEL_MEDIUM_NAME_DESCRIPTOR = msg({
|
||||
'Member verification level option in the community moderation settings tab. Short standalone severity label.',
|
||||
});
|
||||
const VERIFICATION_LEVEL_MEDIUM_DESCRIPTION_DESCRIPTOR = msg({
|
||||
message: "Requires a verified email address, and an account that's at least 5 minutes old.",
|
||||
message: "Requires a verified email address and an account that's at least 5 minutes old.",
|
||||
comment: 'Helper text for the "Medium" member verification level option in the community moderation settings tab.',
|
||||
});
|
||||
const VERIFICATION_LEVEL_HIGH_NAME_DESCRIPTOR = msg({
|
||||
@@ -313,7 +313,7 @@ const GuildModerationTab: React.FC<{guildId: string}> = observer(({guildId}) =>
|
||||
{isDiscoverable && (
|
||||
<p data-flx="guild.guild-tabs.guild-moderation-tab.p--3">
|
||||
<Trans>
|
||||
Communities listed in Discovery require at least verified email. None cannot be selected while
|
||||
Communities listed in Discovery require at least email verification. None cannot be selected while
|
||||
Discovery is enabled.
|
||||
</Trans>
|
||||
</p>
|
||||
@@ -354,8 +354,8 @@ const GuildModerationTab: React.FC<{guildId: string}> = observer(({guildId}) =>
|
||||
data-flx="guild.guild-tabs.guild-moderation-tab.section-description--2"
|
||||
>
|
||||
<Trans>
|
||||
Communities listed in Discovery are required to scan all members. This setting cannot be changed while
|
||||
Discovery is enabled.
|
||||
Communities listed in Discovery are required to scan messages from all members. This setting cannot be
|
||||
changed while Discovery is enabled.
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
@@ -464,7 +464,7 @@ const GuildModerationTab: React.FC<{guildId: string}> = observer(({guildId}) =>
|
||||
data-flx="guild.guild-tabs.guild-moderation-tab.section-description--4"
|
||||
>
|
||||
<Trans>
|
||||
Require two-factor authentication for moderators before they can ban, kick, timeout, or remove
|
||||
Require two-factor authentication for moderators before they can ban, kick, time out, or remove
|
||||
messages.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
@@ -78,7 +78,7 @@ const YOU_CANNOT_GRANT_A_PERMISSION_YOU_DON_T_DESCRIPTOR = msg({
|
||||
'Tooltip on a permission toggle in the role editor that the current user cannot grant because they do not hold that permission themselves.',
|
||||
});
|
||||
const YOU_CANNOT_REMOVE_THIS_PERMISSION_BECAUSE_IT_WOULD_DESCRIPTOR = msg({
|
||||
message: 'You cannot remove this permission because it would remove it from yourself',
|
||||
message: 'You cannot remove this permission because it would remove it from you',
|
||||
comment:
|
||||
'Tooltip on a permission toggle in the role editor when revoking that permission would leave the current user without it.',
|
||||
});
|
||||
|
||||
@@ -227,7 +227,7 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
|
||||
},
|
||||
[canManageExpressions, canCreateExpressions, currentUserId],
|
||||
);
|
||||
const stickerMaxSizeLabel = formatFileSize(GlobalLimits.getStickerMaxSize());
|
||||
const stickerMaxSizeLabel = formatFileSize(i18n.locale, GlobalLimits.getStickerMaxSize());
|
||||
return (
|
||||
<div className={styles.container} data-flx="guild.guild-tabs.guild-stickers-tab.container">
|
||||
<CloneAllowedToggle
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ import {ImagePreviewField} from '@app/features/app/components/shared/ImagePrevie
|
||||
import {
|
||||
ANIMATED_BANNER_FEATURE,
|
||||
ANIMATED_IMAGE_FORMATS,
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
STATIC_IMAGE_FORMATS,
|
||||
WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
@@ -16,6 +16,7 @@ import styles from '@app/features/guild/components/modals/guild_tabs/guild_overv
|
||||
import type {GuildLike} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/GuildOverviewTypes';
|
||||
import {useGuildImageAssetField} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/hooks/useGuildImageAssetField';
|
||||
import type {FormInputs} from '@app/features/guild/utils/guild_tabs/GuildOverviewTabUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
|
||||
import {GuildFeatures} from '@fluxer/constants/src/GuildConstants';
|
||||
@@ -96,7 +97,7 @@ export const GuildBannerUploadField: React.FC<{
|
||||
sourceModalTitle: i18n._(CHANGE_BANNER_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadMinimumHint(i18n, {
|
||||
formats: bannerFormats,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
minimumSize: bannerMinimumSize,
|
||||
aspectRatio: WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
}),
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
STATIC_IMAGE_WITH_AVIF_FORMATS,
|
||||
WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
@@ -13,6 +13,7 @@ import styles from '@app/features/guild/components/modals/guild_tabs/guild_overv
|
||||
import type {GuildLike} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/GuildOverviewTypes';
|
||||
import {useGuildImageAssetField} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/hooks/useGuildImageAssetField';
|
||||
import type {FormInputs} from '@app/features/guild/utils/guild_tabs/GuildOverviewTabUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
import {Trans, useLingui} from '@lingui/react/macro';
|
||||
@@ -75,7 +76,7 @@ export const GuildEmbedSplashUploadField: React.FC<{
|
||||
sourceModalTitle: i18n._(CHANGE_CHAT_EMBED_BACKGROUND_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadMinimumHintWithNote(i18n, {
|
||||
formats: STATIC_IMAGE_WITH_AVIF_FORMATS,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
minimumSize: embedSplashMinimumSize,
|
||||
aspectRatio: WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
note: i18n._(SHOWN_IN_INVITE_EMBEDS_IN_CHAT_DESCRIPTOR),
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ import {
|
||||
ANIMATED_ICON_FEATURE,
|
||||
ANIMATED_IMAGE_FORMATS,
|
||||
AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
STATIC_IMAGE_FORMATS,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
import {AssetType} from '@app/features/expressions/components/modals/AssetCropModal';
|
||||
@@ -15,6 +15,7 @@ import type {GuildLike} from '@app/features/guild/components/modals/guild_tabs/g
|
||||
import {useGuildImageAssetField} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/hooks/useGuildImageAssetField';
|
||||
import {GuildIcon} from '@app/features/guild/components/popouts/GuildIcon';
|
||||
import type {FormInputs} from '@app/features/guild/utils/guild_tabs/GuildOverviewTabUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import {GuildFeatures} from '@fluxer/constants/src/GuildConstants';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -74,7 +75,7 @@ export const GuildIconUploadField: React.FC<{
|
||||
sourceModalTitle: i18n._(CHANGE_ICON_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadRecommendedHint(i18n, {
|
||||
formats: iconFormats,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
recommendedSize: AVATAR_RECOMMENDED_SIZE_LABEL,
|
||||
}),
|
||||
});
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
import {ImagePreviewField} from '@app/features/app/components/shared/ImagePreviewField';
|
||||
import {
|
||||
IMAGE_MAX_SIZE_LABEL,
|
||||
IMAGE_MAX_SIZE_BYTES,
|
||||
STATIC_IMAGE_WITH_AVIF_FORMATS,
|
||||
WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
} from '@app/features/app/config/I18nDisplayConstants';
|
||||
@@ -14,6 +14,7 @@ import styles from '@app/features/guild/components/modals/guild_tabs/guild_overv
|
||||
import type {GuildLike} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/GuildOverviewTypes';
|
||||
import {useGuildImageAssetField} from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/hooks/useGuildImageAssetField';
|
||||
import type {FormInputs} from '@app/features/guild/utils/guild_tabs/GuildOverviewTabUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import {Button} from '@app/features/ui/button/Button';
|
||||
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -81,7 +82,7 @@ export const GuildInviteSplashUploadField: React.FC<{
|
||||
sourceModalTitle: i18n._(CHANGE_INVITE_BACKGROUND_DESCRIPTOR),
|
||||
uploadHint: formatImageUploadMinimumHint(i18n, {
|
||||
formats: STATIC_IMAGE_WITH_AVIF_FORMATS,
|
||||
maxSize: IMAGE_MAX_SIZE_LABEL,
|
||||
maxSize: formatFileSize(i18n.locale, IMAGE_MAX_SIZE_BYTES),
|
||||
minimumSize: splashMinimumSize,
|
||||
aspectRatio: WIDE_IMAGE_ASPECT_RATIO_LABEL,
|
||||
}),
|
||||
|
||||
+3
-4
@@ -30,6 +30,7 @@ import {
|
||||
INVALID_IMAGE_TRY_ANOTHER_DESCRIPTOR,
|
||||
} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
|
||||
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
|
||||
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||
import {modal} from '@app/features/ui/commands/ModalCommands';
|
||||
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
|
||||
@@ -218,8 +219,7 @@ export function useGuildImageAssetField({
|
||||
if (!canManage) return;
|
||||
const requestId = ++requestIdRef.current;
|
||||
if (file.size > maxBytes) {
|
||||
const maxMB = Math.round(maxBytes / (1024 * 1024));
|
||||
const maxSizeLabel = `${maxMB}MB`;
|
||||
const maxSizeLabel = formatFileSize(i18n.locale, maxBytes);
|
||||
showErrorModal(i18n._(FILE_IS_TOO_LARGE_PLEASE_CHOOSE_A_FILE_DESCRIPTOR, {label, maxSizeLabel}));
|
||||
return;
|
||||
}
|
||||
@@ -300,8 +300,7 @@ export function useGuildImageAssetField({
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
if (croppedBlob.size > maxBytes) {
|
||||
const maxMB = Math.round(maxBytes / (1024 * 1024));
|
||||
const maxSizeLabel = `${maxMB}MB`;
|
||||
const maxSizeLabel = formatFileSize(i18n.locale, maxBytes);
|
||||
showErrorModal(i18n._(CROPPED_IMAGE_IS_TOO_LARGE_PLEASE_CHOOSE_A_DESCRIPTOR, {maxSizeLabel}));
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ const AFK_TIMEOUT_DESCRIPTOR = msg({
|
||||
comment: 'Short label in the idle settings section. Keep it concise.',
|
||||
});
|
||||
const SECONDS_DESCRIPTOR = msg({
|
||||
message: '{seconds} seconds',
|
||||
message: '{seconds, plural, one {# second} other {# seconds}}',
|
||||
comment: 'AFK timeout label for a stored custom duration shown in the idle settings section.',
|
||||
});
|
||||
export const IdleSettingsSection: React.FC<{
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ const ENABLE_MESSAGE_HISTORY_THRESHOLD_DESCRIPTOR = msg({
|
||||
});
|
||||
const ALLOW_MEMBERS_WITHOUT_THE_PERMISSION_TO_SEE_MESSAGES_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Allow members without the {readMessageHistoryPermissionLabel} permission to see messages after a specific date.',
|
||||
'Allow members without the {readMessageHistoryPermissionLabel} permission to see messages sent after a specific date.',
|
||||
comment: 'Label in the message history threshold content. Keep the tone plain and specific.',
|
||||
});
|
||||
const THRESHOLD_DATE_DESCRIPTOR = msg({
|
||||
@@ -74,7 +74,7 @@ export const MessageHistoryThresholdDescription: React.FC = () => {
|
||||
</p>
|
||||
<p data-flx="guild.guild-tabs.guild-overview-tab.message-history-threshold-content.message-history-threshold-description.p--2">
|
||||
<Trans>
|
||||
If you'd like these members to access historical messages after a specific date, enable this feature and
|
||||
If you'd like these members to access historical messages sent after a specific date, enable this feature and
|
||||
choose a threshold below.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import {useDrag, useDrop} from 'react-dnd';
|
||||
import {getEmptyImage} from 'react-dnd-html5-backend';
|
||||
|
||||
const YOU_CANNOT_EDIT_THIS_ROLE_BECAUSE_IT_IS_DESCRIPTOR = msg({
|
||||
message: 'You cannot edit this role because it is your highest role or above you',
|
||||
message: 'You cannot edit this role because it is at or above your highest role',
|
||||
comment:
|
||||
'Tooltip on a disabled role row in the community roles settings tab. Explains the role hierarchy rule: you cannot edit roles at or above your own highest role.',
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user