Compare commits

..
Author SHA1 Message Date
HampusandGitHub 47b5c3d4f0 fix(app): expose the guild id on guild list items again (#1778) 2026-08-20 14:39:24 +02:00
HampusandGitHub d9bfca66d6 fix(app): keep search results in server order instead of per channel (#1777) 2026-08-20 13:10:47 +02:00
HampusandGitHub ded51b65d3 fix(app): restore the mobile voice message recorder (#1776) 2026-08-20 04:33:24 +02:00
HampusandGitHub ddc8837d4f fix(app): scale embed and attachment width caps with zoom (#1775) 2026-08-20 04:33:17 +02:00
HampusandGitHub df16957c95 fix(app): scale tooltip max-width with app zoom (#1774) 2026-08-20 04:33:10 +02:00
HampusandGitHub f38b19d4bd fix(app): let the caret move past emoji and mentions in the composer (#1768) 2026-08-19 23:41:05 +02:00
HampusandGitHub f7cd4f2c74 docs(operator): explain how to reclaim space after upgrades (#1767) 2026-08-19 23:31:15 +02:00
a078392888 chore(i18n): update public marketing catalogs (#1719)
Co-authored-by: hampus-fluxer <[email protected]>
2026-08-19 22:46:33 +02:00
HampusandGitHub 3060f23f8c chore(marketing): bump the marketing submodule pointer (#1766) 2026-08-19 22:29:10 +02:00
HampusandGitHub 6a5f0d4d29 fix(i18n): simplify the marketing translation and saved media strings (#1764) 2026-08-19 22:25:01 +02:00
HampusandGitHub 91d989dc7c fix(app): point the language notice at Weblate instead of email (#1763) 2026-08-19 22:05:39 +02:00
HampusandGitHub 7c82804df6 fix(app): stop sensitive content options overlapping in long locales (#1762) 2026-08-19 19:53:46 +02:00
102 changed files with 743 additions and 1320 deletions
@@ -332,6 +332,7 @@ export const GuildListItemPresentation = forwardRef<
role="button"
tabIndex={0}
data-guild-list-focus-item="true"
data-guild-id={guild.id}
onLongPress={onLongPress}
disabled={false}
data-flx="app.sidebar-nav.guild-list-item-presentation.guild-list-item.click"
@@ -5,7 +5,7 @@ import FocusRing from '@app/features/ui/focus_ring/FocusRing';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {clsx} from 'clsx';
import type {CSSProperties, FC, ReactNode} from 'react';
import type {FC, ReactNode} from 'react';
const REVEAL_SPOILER_DESCRIPTOR = msg({
message: 'Reveal spoiler',
@@ -23,24 +23,14 @@ interface SpoilerOverlayProps {
label?: string;
inline?: boolean;
className?: string;
style?: CSSProperties;
}
export const SpoilerOverlay: FC<SpoilerOverlayProps> = ({
hidden,
onReveal,
children,
label,
inline,
className,
style,
}) => {
export const SpoilerOverlay: FC<SpoilerOverlayProps> = ({hidden, onReveal, children, label, inline, className}) => {
const {i18n} = useLingui();
const ariaLabel = label ?? i18n._(REVEAL_SPOILER_DESCRIPTOR);
return (
<div
className={clsx(styles.container, inline && styles.inline, hidden && styles.hidden, className)}
style={style}
data-flx="app.spoiler-overlay.container"
>
<div className={styles.content} aria-hidden={hidden} data-flx="app.spoiler-overlay.content">
@@ -28,6 +28,8 @@ export const SUPPORT_EMAIL = '[email protected]';
export const SUPPORT_EMAIL_MAILTO = `mailto:${SUPPORT_EMAIL}`;
export const I18N_EMAIL = '[email protected]';
export const I18N_EMAIL_MAILTO = `mailto:${I18N_EMAIL}`;
export const I18N_WEBLATE_DOMAIN = 'weblate.fluxer.tools';
export const I18N_WEBLATE_URL = `https://${I18N_WEBLATE_DOMAIN}`;
export const EXAMPLE_DOMAIN = 'example.com';
export const EXAMPLE_URL = `https://${EXAMPLE_DOMAIN}`;
export const EXAMPLE_CALLBACK_URL = `${EXAMPLE_URL}/callback`;
@@ -28,6 +28,10 @@ import {
SearchIndexingState,
} from '@app/features/channel/components/channel_search_results/SearchResultsStateViews';
import type {MessageGroupRenderWrapperProps} from '@app/features/channel/components/MessageGroup';
import {
buildSearchResultGroups,
buildSearchResultGroupsByMessageId,
} from '@app/features/channel/components/SearchResultGrouping';
import {SearchResultMessageList} from '@app/features/channel/components/SearchResultMessageList';
import {areSegmentsEqual} from '@app/features/channel/components/SearchResultsUtils';
import {DEFAULT_SCOPE_VALUE, getScopeOptionsForChannel} from '@app/features/channel/components/SearchScopeOptions';
@@ -134,35 +138,27 @@ export const ChannelSearchResults = observer(
() => new Map(successChannels.map((searchChannel) => [searchChannel.id, searchChannel])),
[successChannels],
);
const messagesByChannel = useMemo(() => {
const grouped = new Map<string, Array<Message>>();
for (const message of successResults) {
if (!grouped.has(message.channelId)) {
grouped.set(message.channelId, []);
}
grouped.get(message.channelId)!.push(message);
}
return grouped;
}, [successResults]);
const resultGroups = useMemo(() => buildSearchResultGroups(successResults), [successResults]);
const resultGroupsByMessageId = useMemo(() => buildSearchResultGroupsByMessageId(resultGroups), [resultGroups]);
const onCopySelectedMessages = useMessageSelectionCopyForMessages<HTMLDivElement>(successResults);
const spammerOverrideVersion = LocalUserSpamOverride.version;
const collapsedMessageVisibility = useMemo(
() => ({
isMessageRevealed: (message: Message) => {
const channelMessages = messagesByChannel.get(message.channelId);
if (!channelMessages) return false;
const resultGroup = resultGroupsByMessageId.get(message.id);
if (!resultGroup) return false;
const messageChannel = searchChannelsById.get(message.channelId) ?? Channels.getChannel(message.channelId);
if (!messageChannel) return false;
const groupKey = getCollapsedMessageGroupKey({
channel: messageChannel,
messages: channelMessages,
messages: resultGroup.messages,
messageId: message.id,
treatSpam: true,
});
return groupKey != null && revealedGroupKeys.has(groupKey);
},
}),
[messagesByChannel, revealedGroupKeys, searchChannelsById, spammerOverrideVersion],
[resultGroupsByMessageId, revealedGroupKeys, searchChannelsById, spammerOverrideVersion],
);
const handleCollapsedGroupRevealChange = useCallback((groupKey: string, revealed: boolean) => {
setRevealedGroupKeys((current) => {
@@ -606,9 +602,9 @@ export const ChannelSearchResults = observer(
data-message-selection-root="true"
data-flx="channel.channel-search-results.render-content.results-scroller"
>
{Array.from(messagesByChannel.entries()).map(([resultChannelId, messages]) => {
{resultGroups.map((resultGroup) => {
const renderData = getSearchResultChannelRenderData(
resultChannelId,
resultGroup.channelId,
searchChannelsById,
(activeScope ?? DEFAULT_SCOPE_VALUE) as MessageSearchScope,
);
@@ -649,7 +645,7 @@ export const ChannelSearchResults = observer(
</div>
);
return (
<React.Fragment key={resultChannelId}>
<React.Fragment key={resultGroup.key}>
<MessageContextPrefix
channel={messageChannel}
showGuildMeta={showGuildMeta}
@@ -660,7 +656,7 @@ export const ChannelSearchResults = observer(
/>
<SearchResultMessageList
channel={messageChannel}
messages={messages}
messages={resultGroup.messages}
revealedGroupKeys={revealedGroupKeys}
onGroupRevealChange={handleCollapsedGroupRevealChange}
collapsedGroupClassName={styles.collapsedMessageGroup}
@@ -725,7 +721,7 @@ export const ChannelSearchResults = observer(
handlePaginationJump,
onCopySelectedMessages,
collapsedMessageVisibility,
messagesByChannel,
resultGroups,
searchChannelsById,
revealedGroupKeys,
handleCollapsedGroupRevealChange,
@@ -1375,7 +1375,10 @@ export const LexicalChannelTextareaContent = observer(
showStickersButton={showStickersButton}
showEmojiButton={showEmojiButton}
showMessageSendButton={showMessageSendButton}
showVoiceMessageButton={false}
canRecordVoice={canAttachFilesInChannel(channel)}
isEditingMessage={isEditingMessageInComposer || editingMessageId != null}
hasPendingSticker={hasPendingSticker}
voiceTooltipAnchorRef={contentAreaRef}
expressionPickerOpen={expressionPickerOpen}
selectedTab={selectedTab}
isMobile={mobileLayout.enabled}
@@ -2,6 +2,8 @@
import type {MessageAttachment} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
export const ATTACHMENT_CARD_WIDTH = 400;
const IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp', 'image/avif'];
const VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime'];
@@ -48,7 +48,7 @@
margin-top: var(--message-container-gap, 0.25rem);
}
.urlEmbedSpoiler {
div.urlEmbedSpoiler {
width: 100%;
max-width: 22.5rem;
}
@@ -109,7 +109,6 @@ const SpoileredUrlEmbed = observer(function SpoileredUrlEmbed({
hidden={hidden}
onReveal={reveal}
className={styles.urlEmbedSpoiler}
style={{width: '100%', maxWidth: 360}}
data-flx="channel.message-attachments.spoilered-url-embed"
>
{children}
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {ATTACHMENT_CARD_WIDTH} from '@app/features/channel/components/MessageAttachmentUtils';
import styles from '@app/features/channel/components/MessageUploadProgress.module.css';
import * as MessageCommands from '@app/features/messaging/commands/MessageCommands';
import {useMessageUpload} from '@app/features/messaging/hooks/useCloudUpload';
@@ -77,8 +78,8 @@ export const MessageUploadProgress = observer(({attachment, message}: MessageUpl
}
: {
display: 'grid',
width: '400px',
maxWidth: '400px',
width: remFromPx(ATTACHMENT_CARD_WIDTH),
maxWidth: remFromPx(ATTACHMENT_CARD_WIDTH),
};
return (
<div style={containerStyles} data-flx="channel.message-upload-progress.div">
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {Message} from '@app/features/messaging/models/MessagingMessage';
import {describe, expect, it} from 'vitest';
import {
buildSearchResultGroups,
buildSearchResultGroupsByMessageId,
countSearchResultChannels,
} from './SearchResultGrouping';
const message = (id: string, channelId: string): Message => ({id, channelId}) as Message;
describe('SearchResultGrouping', () => {
it('keeps the order the server returned instead of bucketing per channel', () => {
const groups = buildSearchResultGroups([
message('500', 'general'),
message('400', 'random'),
message('300', 'general'),
]);
expect(groups.map((group) => group.messages.map((result) => result.id))).toEqual([['500'], ['400'], ['300']]);
expect(groups.map((group) => group.channelId)).toEqual(['general', 'random', 'general']);
});
it('merges consecutive results from the same channel into one group', () => {
const groups = buildSearchResultGroups([
message('500', 'general'),
message('490', 'general'),
message('400', 'random'),
]);
expect(groups).toHaveLength(2);
expect(groups[0].messages.map((result) => result.id)).toEqual(['500', '490']);
expect(groups[1].messages.map((result) => result.id)).toEqual(['400']);
});
it('gives every group a unique key even when a channel appears more than once', () => {
const groups = buildSearchResultGroups([
message('500', 'general'),
message('400', 'random'),
message('300', 'general'),
]);
expect(new Set(groups.map((group) => group.key)).size).toBe(3);
});
it('indexes each message to the group it renders in', () => {
const groups = buildSearchResultGroups([
message('500', 'general'),
message('400', 'random'),
message('300', 'general'),
]);
const groupsByMessageId = buildSearchResultGroupsByMessageId(groups);
expect(groupsByMessageId.get('500')).toBe(groups[0]);
expect(groupsByMessageId.get('400')).toBe(groups[1]);
expect(groupsByMessageId.get('300')).toBe(groups[2]);
});
it('counts distinct channels rather than groups', () => {
const groups = buildSearchResultGroups([
message('500', 'general'),
message('400', 'random'),
message('300', 'general'),
]);
expect(countSearchResultChannels(groups)).toBe(2);
expect(countSearchResultChannels(buildSearchResultGroups([]))).toBe(0);
});
});
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {Message} from '@app/features/messaging/models/MessagingMessage';
export interface SearchResultGroup {
key: string;
channelId: string;
messages: Array<Message>;
}
export const buildSearchResultGroups = (messages: Array<Message>): Array<SearchResultGroup> => {
const groups: Array<SearchResultGroup> = [];
let currentGroup: SearchResultGroup | null = null;
for (const message of messages) {
if (!currentGroup || currentGroup.channelId !== message.channelId) {
currentGroup = {key: `${message.channelId}-${message.id}`, channelId: message.channelId, messages: []};
groups.push(currentGroup);
}
currentGroup.messages.push(message);
}
return groups;
};
export const buildSearchResultGroupsByMessageId = (
groups: Array<SearchResultGroup>,
): Map<string, SearchResultGroup> => {
const groupsByMessageId = new Map<string, SearchResultGroup>();
for (const group of groups) {
for (const message of group.messages) {
groupsByMessageId.set(message.id, group);
}
}
return groupsByMessageId;
};
export const countSearchResultChannels = (groups: Array<SearchResultGroup>): number => {
return new Set(groups.map((group) => group.channelId)).size;
};
@@ -7,6 +7,34 @@
justify-content: center;
}
.voiceButton {
display: flex;
align-items: center;
justify-content: center;
min-width: var(--textarea-button-height);
height: var(--textarea-button-height);
padding: 0;
color: var(--text-primary-muted);
border-radius: var(--radius-md);
background: transparent;
border: none;
outline: none;
cursor: pointer;
flex-shrink: 0;
touch-action: none;
}
.voiceButton:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.voiceButtonIcon {
width: var(--textarea-button-icon-size);
height: var(--textarea-button-icon-size);
flex-shrink: 0;
}
.holdTooltip {
background-color: var(--brand-primary);
color: var(--text-on-brand-primary);
@@ -33,6 +61,10 @@
}
.overlayInner {
display: flex;
align-items: flex-end;
gap: 0.375rem;
padding: 0.5rem;
width: 100%;
min-width: 0;
box-sizing: border-box;
@@ -133,6 +165,7 @@
flex-shrink: 0;
display: flex;
align-items: center;
width: 2.25rem;
height: 2.25rem;
}
@@ -2,7 +2,6 @@
import Accessibility from '@app/features/accessibility/state/Accessibility';
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
import layoutStyles from '@app/features/channel/components/textarea/MobileTextareaLayout.module.css';
import styles from '@app/features/channel/components/VoiceMessageRecorder.module.css';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {usePortalHost} from '@app/features/ui/overlay/PortalHostContext';
@@ -529,7 +528,7 @@ export default function VoiceMessageRecorder({channelId, disabled, tooltipAnchor
>
<motion.button
type="button"
className={layoutStyles.mobileVoiceButton}
className={styles.voiceButton}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerMove={handlePointerMove}
@@ -541,7 +540,7 @@ export default function VoiceMessageRecorder({channelId, disabled, tooltipAnchor
data-flx="channel.voice-message-recorder.button.pointer-down"
>
<MicrophoneIcon
className={layoutStyles.mobileRightButtonIcon}
className={styles.voiceButtonIcon}
data-flx="channel.voice-message-recorder.microphone-icon"
/>
</motion.button>
@@ -578,10 +577,7 @@ export default function VoiceMessageRecorder({channelId, disabled, tooltipAnchor
transition={resolvedOverlayMotion.transition}
data-flx="channel.voice-message-recorder.recording-overlay"
>
<div
className={clsx(styles.overlayInner, layoutStyles.mobileTextareaWrapper)}
data-flx="channel.voice-message-recorder.overlay-inner"
>
<div className={styles.overlayInner} data-flx="channel.voice-message-recorder.overlay-inner">
<button
type="button"
className={clsx(styles.iconButton, styles.trashButton)}
@@ -610,7 +606,7 @@ export default function VoiceMessageRecorder({channelId, disabled, tooltipAnchor
</div>
</div>
<div
className={clsx(styles.lockIndicatorWrapper, layoutStyles.mobileRightButtonContainer)}
className={styles.lockIndicatorWrapper}
data-flx="channel.voice-message-recorder.lock-indicator-wrapper"
>
<button
@@ -8,6 +8,11 @@ import {Message as MessageComponent} from '@app/features/channel/components/Chan
import {CollapsedMessageVisibilityProvider} from '@app/features/channel/components/CollapsedMessageVisibilityContext';
import {MessageActionBottomSheet} from '@app/features/channel/components/MessageActionBottomSheet';
import type {MessageGroupRenderWrapperProps} from '@app/features/channel/components/MessageGroup';
import {
buildSearchResultGroups,
buildSearchResultGroupsByMessageId,
countSearchResultChannels,
} from '@app/features/channel/components/SearchResultGrouping';
import {SearchResultMessageList} from '@app/features/channel/components/SearchResultMessageList';
import {type ChannelSearchFilters, useChannelSearch} from '@app/features/channel/hooks/useChannelSearch';
import type {Channel} from '@app/features/channel/models/Channel';
@@ -216,22 +221,17 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
() => new Map(successChannels.map((searchChannel) => [searchChannel.id, searchChannel])),
[successChannels],
);
const messagesByChannel = React.useMemo(() => {
const grouped = new Map<string, Array<Message>>();
for (const message of successResults) {
if (!grouped.has(message.channelId)) {
grouped.set(message.channelId, []);
}
grouped.get(message.channelId)!.push(message);
}
return grouped;
}, [successResults]);
const resultGroups = React.useMemo(() => buildSearchResultGroups(successResults), [successResults]);
const resultGroupsByMessageId = React.useMemo(
() => buildSearchResultGroupsByMessageId(resultGroups),
[resultGroups],
);
const spammerOverrideVersion = LocalUserSpamOverride.version;
const collapsedMessageVisibility = React.useMemo(
() => ({
isMessageRevealed: (message: Message) => {
const channelMessages = messagesByChannel.get(message.channelId);
if (!channelMessages) {
const resultGroup = resultGroupsByMessageId.get(message.id);
if (!resultGroup) {
return false;
}
const messageChannel = searchChannelsById.get(message.channelId) ?? Channels.getChannel(message.channelId);
@@ -240,14 +240,14 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
}
const groupKey = getCollapsedMessageGroupKey({
channel: messageChannel,
messages: channelMessages,
messages: resultGroup.messages,
messageId: message.id,
treatSpam: true,
});
return groupKey != null && revealedGroupKeys.has(groupKey);
},
}),
[messagesByChannel, revealedGroupKeys, searchChannelsById, spammerOverrideVersion],
[resultGroupsByMessageId, revealedGroupKeys, searchChannelsById, spammerOverrideVersion],
);
const handleCollapsedGroupRevealChange = useCallback((groupKey: string, revealed: boolean) => {
setRevealedGroupKeys((current) => {
@@ -598,7 +598,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
);
}
const totalPages = Math.max(1, Math.ceil(total / hitsPerPage));
const hasMultipleChannels = messagesByChannel.size > 1;
const hasMultipleChannels = countSearchResultChannels(resultGroups) > 1;
return (
<>
<CollapsedMessageVisibilityProvider
@@ -613,8 +613,9 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
data-message-selection-root="true"
data-flx="channel.channel-search-bottom-sheet.render-content.results-scroller"
>
{Array.from(messagesByChannel.entries()).map(([channelId, messages]) => {
const messageChannel = searchChannelsById.get(channelId) ?? Channels.getChannel(channelId);
{resultGroups.map((resultGroup) => {
const messageChannel =
searchChannelsById.get(resultGroup.channelId) ?? Channels.getChannel(resultGroup.channelId);
if (!messageChannel) {
return null;
}
@@ -648,7 +649,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
</LongPressable>
);
return (
<React.Fragment key={channelId}>
<React.Fragment key={resultGroup.key}>
{hasMultipleChannels && (
<div
className={styles.channelSection}
@@ -667,7 +668,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
)}
<SearchResultMessageList
channel={messageChannel}
messages={messages}
messages={resultGroup.messages}
revealedGroupKeys={revealedGroupKeys}
onGroupRevealChange={handleCollapsedGroupRevealChange}
collapsedGroupClassName={styles.collapsedMessageGroup}
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
BLUESKY_EMBED_MEDIA_FALLBACK_OUTER_WIDTH,
BLUESKY_EMBED_MIN_CONTENT_WIDTH,
calculateBlueskyMediaContainerWidth,
calculateBlueskyOuterMaxWidth,
EMBED_TEXT_OUTER_WIDTH,
formatResponsiveEmbedWidth,
} from '@app/features/channel/components/embeds/EmbedRenderUtils';
import {describe, expect, it} from 'vitest';
describe('formatResponsiveEmbedWidth', () => {
it('caps in rem so the width tracks the root font size at any zoom', () => {
expect(formatResponsiveEmbedWidth(EMBED_TEXT_OUTER_WIDTH)).toBe('min(100%, 32.25rem)');
expect(formatResponsiveEmbedWidth(BLUESKY_EMBED_MEDIA_FALLBACK_OUTER_WIDTH)).toBe('min(100%, 27rem)');
});
it('rounds to whole pixels before converting', () => {
expect(formatResponsiveEmbedWidth(400.4)).toBe('min(100%, 25rem)');
});
it('clamps non-positive and non-finite widths', () => {
expect(formatResponsiveEmbedWidth(0)).toBe('min(100%, 0rem)');
expect(formatResponsiveEmbedWidth(Number.NaN)).toBe('min(100%, 0rem)');
});
});
describe('calculateBlueskyOuterMaxWidth', () => {
it('falls back to the shared text width when there is no media', () => {
expect(calculateBlueskyOuterMaxWidth({hasMedia: false, chromeWidth: 29})).toBe(EMBED_TEXT_OUTER_WIDTH);
});
it('adds chrome around the media container width', () => {
expect(calculateBlueskyOuterMaxWidth({mediaWidth: 480, hasMedia: true, chromeWidth: 29})).toBe(509);
});
it('never goes below the minimum content width', () => {
expect(calculateBlueskyMediaContainerWidth(120)).toBe(BLUESKY_EMBED_MIN_CONTENT_WIDTH);
});
});
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {MessageEmbedTypes} from '@fluxer/constants/src/ChannelConstants';
import type {MessageEmbed} from '@fluxer/schema/src/domains/message/EmbedSchemas';
@@ -28,7 +29,7 @@ export function isMediaOnlyEmbed(embed: MediaOnlyEmbedFields): boolean {
export const BLUESKY_EMBED_MIN_CONTENT_WIDTH = 320;
export const BLUESKY_EMBED_MEDIA_FALLBACK_OUTER_WIDTH = 432;
export const BLUESKY_EMBED_TEXT_OUTER_WIDTH = 516;
export const EMBED_TEXT_OUTER_WIDTH = 516;
const normalizePositiveWidth = (width: number): number => {
if (!Number.isFinite(width) || width <= 0) {
return 0;
@@ -55,9 +56,9 @@ export function calculateBlueskyOuterMaxWidth({
if (mediaContainerWidth !== undefined) {
return mediaContainerWidth + normalizePositiveWidth(chromeWidth);
}
return hasMedia ? BLUESKY_EMBED_MEDIA_FALLBACK_OUTER_WIDTH : BLUESKY_EMBED_TEXT_OUTER_WIDTH;
return hasMedia ? BLUESKY_EMBED_MEDIA_FALLBACK_OUTER_WIDTH : EMBED_TEXT_OUTER_WIDTH;
}
export function formatResponsiveEmbedWidth(width: number): string {
return `min(100%, ${normalizePositiveWidth(width)}px)`;
return `min(100%, ${remFromPx(normalizePositiveWidth(width))})`;
}
@@ -3,6 +3,7 @@
import {TextualAttachmentPreview} from '@app/features/channel/components/embeds/attachments/TextualAttachmentPreview';
import {splitFilename} from '@app/features/channel/components/embeds/EmbedUtils';
import {canDeleteAttachmentUtil} from '@app/features/channel/components/MessageActionUtils';
import {ATTACHMENT_CARD_WIDTH} from '@app/features/channel/components/MessageAttachmentUtils';
import {useMaybeMessageViewContext} from '@app/features/channel/components/MessageViewContext';
import {DELETE_ATTACHMENT_DESCRIPTOR, DOWNLOAD_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {useDeleteAttachment} from '@app/features/messaging/hooks/useDeleteAttachment';
@@ -181,8 +182,8 @@ export const AttachmentFile = observer(({attachment, message, isPreview}: Attach
containerStyles = {
display: 'grid',
width: '100%',
maxWidth: '400px',
minWidth: 'min(400px, 100%)',
maxWidth: remFromPx(ATTACHMENT_CARD_WIDTH),
minWidth: `min(${remFromPx(ATTACHMENT_CARD_WIDTH)}, 100%)`,
};
}
const handleDownload = async (e: React.MouseEvent) => {
@@ -23,6 +23,10 @@ import {
EmbedProvider,
EmbedTitle,
} from '@app/features/channel/components/embeds/channel_embed/EmbedParts';
import {
EMBED_TEXT_OUTER_WIDTH,
formatResponsiveEmbedWidth,
} from '@app/features/channel/components/embeds/EmbedRenderUtils';
import markupStyles from '@app/features/theme/styles/Markup.module.css';
import {MessageEmbedTypes} from '@fluxer/constants/src/ChannelConstants';
import type {EmbedMedia} from '@fluxer/schema/src/domains/message/EmbedSchemas';
@@ -58,7 +62,7 @@ export const RichEmbed: FC<EmbedProps> = observer(
data-search-highlight-scope="message"
style={{
borderLeft: `4px solid ${getBorderColor(embed.color)}`,
maxWidth: useNarrowWidth ? `min(100%, ${EMBED_MEDIA_MAX_WIDTH}px)` : 'min(100%, 516px)',
maxWidth: formatResponsiveEmbedWidth(useNarrowWidth ? EMBED_MEDIA_MAX_WIDTH : EMBED_TEXT_OUTER_WIDTH),
}}
data-flx="channel.embeds.embed.rich-embed.embed"
>
@@ -4,6 +4,7 @@ import {deriveDefaultNameFromMessage} from '@app/features/channel/components/emb
import styles from '@app/features/channel/components/embeds/media/EmbedAudio.module.css';
import {getMediaButtonVisibility} from '@app/features/channel/components/embeds/media/MediaButtonUtils';
import type {BaseMediaProps} from '@app/features/channel/components/embeds/media/MediaTypes';
import {ATTACHMENT_CARD_WIDTH} from '@app/features/channel/components/MessageAttachmentUtils';
import {useMaybeMessageViewContext} from '@app/features/channel/components/MessageViewContext';
import {DELETE_ATTACHMENT_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {useDeleteAttachment} from '@app/features/messaging/hooks/useDeleteAttachment';
@@ -130,8 +131,8 @@ const EmbedAudio: FC<EmbedAudioProps> = observer(
: {
display: 'grid',
width: '100%',
maxWidth: 'min(100%, 400px)',
minWidth: 'min(400px, 100%)',
maxWidth: `min(100%, ${remFromPx(ATTACHMENT_CARD_WIDTH)})`,
minWidth: `min(${remFromPx(ATTACHMENT_CARD_WIDTH)}, 100%)`,
};
const {showDeleteButton, showDownloadButton} = getMediaButtonVisibility(
canFavorite,
@@ -233,7 +233,7 @@ export const EmbedYouTube: FC<EmbedYouTubeProps> = observer(({embed, width = YOU
className={styles.videoContainer}
style={{
...containerStyle,
width: `${dimensions.width}px`,
width: remFromPx(dimensions.width),
aspectRatio,
maxWidth: '100%',
}}
@@ -1,260 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import Accessibility from '@app/features/accessibility/state/Accessibility';
import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter';
import {TextareaButton} from '@app/features/channel/components/textarea/TextareaButton';
import styles from '@app/features/channel/components/textarea/TextareaButtons.module.css';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {prepareVoiceMessageWav} from '@app/features/voice/utils/VoiceMessageRecordingUtils';
import {sendVoiceMessage} from '@app/features/voice/utils/VoiceMessageSendUtils';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {MicrophoneIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {motion, type Variants} from 'framer-motion';
import type React from 'react';
import {useCallback, useMemo, useRef, useState} from 'react';
const RECORDING_FAILED_PLEASE_TRY_AGAIN_DESCRIPTOR = msg({
message: 'Recording failed. Try again.',
comment: 'Error toast shown when the hold-to-record voice message recording fails.',
});
const VOICE_MESSAGE_DESCRIPTOR = msg({
message: 'Voice message',
comment: 'Filename label used for voice messages sent via the hold-to-record button.',
});
const UNABLE_TO_SEND_VOICE_MESSAGE_PLEASE_TRY_AGAIN_DESCRIPTOR = msg({
message: 'Unable to send voice message. Try again.',
comment: 'Error toast shown when sending a hold-to-record voice message fails.',
});
const VOICE_RECORDING_IS_NOT_SUPPORTED_IN_THIS_BROWSER_DESCRIPTOR = msg({
message: 'Voice recording is not supported in this browser.',
comment: 'Inline error when MediaRecorder is unsupported for the hold-to-record voice message flow.',
});
const UNABLE_TO_START_RECORDING_PLEASE_ALLOW_MICROPHONE_ACCESS_DESCRIPTOR = msg({
message: 'Unable to start recording. Allow microphone access.',
comment: 'Inline error when microphone permission is denied for the hold-to-record voice message flow.',
});
const RELEASE_TO_SEND_DESCRIPTOR = msg({
message: 'Release to send',
comment: 'Tooltip on the hold-to-record button while the user is actively holding to record.',
});
const HOLD_TO_SEND_VOICE_MESSAGE_DESCRIPTOR = msg({
message: 'Hold to send voice message',
comment: 'Tooltip on the hold-to-record button in its idle state.',
});
const logger = new Logger('HoldToRecordButton');
const recordingVariants: Variants = {
idle: {
scale: 1,
boxShadow: '0 0 0 0px rgba(252, 95, 105, 0)',
},
recording: {
scale: [1, 1.05, 1],
boxShadow: [
'0 0 0 0px rgba(252, 95, 105, 0.35)',
'0 0 0 8px rgba(252, 95, 105, 0.15)',
'0 0 0 0px rgba(252, 95, 105, 0.35)',
],
transition: {
duration: 1.2,
repeat: Infinity,
ease: 'easeInOut',
},
},
};
interface HoldToRecordButtonProps {
channelId: string;
disabled?: boolean;
onFallback?: () => void;
}
const HoldToRecordButton: React.FC<HoldToRecordButtonProps> = ({channelId, disabled, onFallback}) => {
const {i18n} = useLingui();
const [status, setStatus] = useState<'idle' | 'recording' | 'sending'>('idle');
const [error, setError] = useState<string | null>(null);
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const streamRef = useRef<MediaStream | null>(null);
const chunksRef = useRef<Array<Blob>>([]);
const pointerIdRef = useRef<number | null>(null);
const maxTimerRef = useRef<number | null>(null);
const maxRecordingSeconds = useMemo(
() => LimitResolver.resolve({key: 'max_voice_message_duration', fallback: 1200}),
[],
);
const clearMaxTimer = useCallback(() => {
if (maxTimerRef.current) {
clearTimeout(maxTimerRef.current);
maxTimerRef.current = null;
}
}, []);
const releaseStream = useCallback(() => {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop());
streamRef.current = null;
}
}, []);
const cleanupRecording = useCallback(() => {
clearMaxTimer();
releaseStream();
mediaRecorderRef.current = null;
}, [clearMaxTimer, releaseStream]);
const handleRecordingStop = useCallback(
async (blob: Blob) => {
if (blob.size === 0) {
setStatus('idle');
setError(i18n._(RECORDING_FAILED_PLEASE_TRY_AGAIN_DESCRIPTOR));
return;
}
setStatus('sending');
try {
const {file, duration, waveform} = await prepareVoiceMessageWav(blob, `voice-message-${Date.now()}.wav`);
await sendVoiceMessage({
channelId,
file,
waveform,
duration,
title: i18n._(VOICE_MESSAGE_DESCRIPTOR),
});
setStatus('idle');
setError(null);
} catch (err) {
logger.error('Failed to send voice message', err);
setError(i18n._(UNABLE_TO_SEND_VOICE_MESSAGE_PLEASE_TRY_AGAIN_DESCRIPTOR));
setStatus('idle');
}
},
[channelId, i18n],
);
const handleStopEvent = useCallback(() => {
const recorder = mediaRecorderRef.current;
if (!recorder) {
setStatus('idle');
return;
}
const recordedChunks = chunksRef.current;
const blob = new Blob(recordedChunks, {type: recorder.mimeType || 'audio/ogg'});
chunksRef.current = [];
cleanupRecording();
if (recordedChunks.length === 0) {
setError(i18n._(RECORDING_FAILED_PLEASE_TRY_AGAIN_DESCRIPTOR));
setStatus('idle');
return;
}
void handleRecordingStop(blob);
}, [cleanupRecording, handleRecordingStop, i18n]);
const handleDataAvailable = useCallback((event: BlobEvent) => {
if (event.data && event.data.size > 0) {
chunksRef.current = [...chunksRef.current, event.data];
}
}, []);
const stopRecording = useCallback(() => {
const recorder = mediaRecorderRef.current;
if (recorder && recorder.state !== 'inactive') {
recorder.stop();
}
}, []);
const startRecording = useCallback(async () => {
if (status === 'recording' || status === 'sending') return;
if (typeof navigator.mediaDevices === 'undefined' || typeof MediaRecorder === 'undefined') {
setError(i18n._(VOICE_RECORDING_IS_NOT_SUPPORTED_IN_THIS_BROWSER_DESCRIPTOR));
onFallback?.();
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
const preferredType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')
? 'audio/ogg;codecs=opus'
: 'audio/webm;codecs=opus';
const recorder = new MediaRecorder(stream, {mimeType: preferredType});
chunksRef.current = [];
recorder.addEventListener('dataavailable', handleDataAvailable);
recorder.addEventListener('stop', handleStopEvent, {once: true});
recorder.start();
mediaRecorderRef.current = recorder;
streamRef.current = stream;
setStatus('recording');
setError(null);
clearMaxTimer();
maxTimerRef.current = window.setTimeout(() => {
stopRecording();
}, maxRecordingSeconds * 1000);
} catch (err) {
logger.error('Failed to start voice recording', err);
setError(i18n._(UNABLE_TO_START_RECORDING_PLEASE_ALLOW_MICROPHONE_ACCESS_DESCRIPTOR));
}
}, [clearMaxTimer, handleDataAvailable, handleStopEvent, maxRecordingSeconds, status, i18n]);
const handlePointerDown = useCallback(
(event: React.PointerEvent<HTMLButtonElement>) => {
if (disabled || status === 'sending') return;
event.preventDefault();
pointerIdRef.current = event.pointerId;
event.currentTarget.setPointerCapture(event.pointerId);
void startRecording();
},
[disabled, startRecording, status],
);
const handlePointerUp = useCallback(
(event: React.PointerEvent<HTMLButtonElement>) => {
if (pointerIdRef.current !== event.pointerId) return;
event.preventDefault();
try {
event.currentTarget.releasePointerCapture(event.pointerId);
} catch {}
pointerIdRef.current = null;
stopRecording();
},
[stopRecording],
);
const handlePointerCancel = useCallback(
(event: React.PointerEvent<HTMLButtonElement>) => {
if (pointerIdRef.current !== event.pointerId) return;
event.preventDefault();
pointerIdRef.current = null;
stopRecording();
},
[stopRecording],
);
const indicatorText = useMemo(() => {
if (error) return error;
if (status === 'recording') return i18n._(RELEASE_TO_SEND_DESCRIPTOR);
return i18n._(HOLD_TO_SEND_VOICE_MESSAGE_DESCRIPTOR);
}, [error, status, i18n.locale]);
return (
<motion.div
className={styles.holdButtonWrapper}
variants={recordingVariants}
animate={status === 'recording' ? 'recording' : 'idle'}
data-flx="channel.textarea.hold-to-record-button.hold-button-wrapper"
>
<TextareaButton
icon={MicrophoneIcon}
label={i18n._(VOICE_MESSAGE_DESCRIPTOR)}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel}
onPointerLeave={handlePointerCancel}
disabled={disabled || status === 'sending'}
className={clsx(styles.holdButton, status === 'recording' && styles.holdButtonRecording)}
data-flx="channel.textarea.hold-to-record-button.hold-button.pointer-down"
/>
<motion.span
key={indicatorText}
className={clsx(
styles.holdIndicator,
status === 'recording' && styles.holdIndicatorRecording,
error && styles.holdIndicatorError,
)}
initial={Accessibility.useReducedMotion ? {opacity: 1, y: 0} : {opacity: 0, y: 5}}
animate={{opacity: 1, y: 0}}
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.15}}
data-flx="channel.textarea.hold-to-record-button.hold-indicator"
>
{indicatorText}
</motion.span>
</motion.div>
);
};
export default HoldToRecordButton;
@@ -1,158 +0,0 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
.mobileTextareaWrapper {
display: flex;
align-items: flex-end;
gap: 0.375rem;
padding: 0.5rem;
width: 100%;
box-sizing: border-box;
}
.mobilePlusButtonContainer {
display: flex;
align-items: flex-end;
flex-shrink: 0;
height: 2.25rem;
}
.mobilePlusButton {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 50%;
background-color: var(--background-tertiary);
color: var(--text-primary);
border: none;
cursor: pointer;
flex-shrink: 0;
padding: 0;
}
.mobilePlusButtonIcon {
width: 1.25rem;
height: 1.25rem;
}
.mobileContentWrapper {
flex: 1;
min-width: 0;
display: flex;
align-items: flex-end;
gap: 0.375rem;
}
.mobileInputContainer {
flex: 1;
min-width: 0;
display: flex;
align-items: flex-end;
background-color: var(--background-tertiary);
border-radius: 1.125rem;
padding: 0;
position: relative;
min-height: 2.25rem;
}
.mobileInputContent {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0;
font-size: 0.9375rem;
}
.mobileEmojiButtonContainer {
position: absolute;
right: 0.25rem;
bottom: 0.125rem;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.mobileEmojiButton {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: transparent;
color: var(--text-primary-muted);
border: none;
cursor: pointer;
flex-shrink: 0;
padding: 0;
}
.mobileEmojiButtonIcon {
width: 1.375rem;
height: 1.375rem;
}
.mobileRightButtonContainer {
flex-shrink: 0;
display: flex;
align-items: flex-end;
position: relative;
width: 2.25rem;
height: 2.25rem;
}
.mobileRightButtonSlot {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
.mobileSendButton {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 50%;
background-color: var(--brand-primary);
color: white;
border: none;
cursor: pointer;
flex-shrink: 0;
}
.mobileSendButton:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.mobileVoiceButton {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 50%;
background-color: var(--background-tertiary);
color: var(--text-primary);
border: none;
cursor: pointer;
flex-shrink: 0;
touch-action: none;
}
.mobileVoiceButton:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.mobileRightButtonIcon {
width: 1.25rem;
height: 1.25rem;
}
@@ -1,297 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import Accessibility from '@app/features/accessibility/state/Accessibility';
import type {AutocompleteOption} from '@app/features/channel/components/Autocomplete';
import wrapperStyles from '@app/features/channel/components/textarea/InputWrapper.module.css';
import {transitionMobileTextareaButtonState} from '@app/features/channel/components/textarea/MobileTextareaButtonStateMachine';
import styles from '@app/features/channel/components/textarea/MobileTextareaLayout.module.css';
import textareaStyles from '@app/features/channel/components/textarea/TextareaInput.module.css';
import {TextareaInputField} from '@app/features/channel/components/textarea/TextareaInputField';
import VoiceMessageRecorder from '@app/features/channel/components/VoiceMessageRecorder';
import {EMOJIS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import type {TextareaTextChangeHint} from '@app/features/messaging/utils/TextareaSegmentManager';
import {Scroller, type ScrollerHandle} from '@app/features/ui/components/Scroller';
import {getReducedMotionProps, type MotionAnimation} from '@app/features/ui/utils/ReducedMotionAnimation';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {ArrowUpIcon, PlusIcon, SmileyIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {AnimatePresence, motion} from 'framer-motion';
import type React from 'react';
import {type Dispatch, type SetStateAction, useMemo, useRef} from 'react';
const OPEN_MENU_DESCRIPTOR = msg({
message: 'Open menu',
comment: 'Button or menu action label in the channel and chat mobile textarea layout. Keep it concise.',
});
const SEND_MESSAGE_DESCRIPTOR = msg({
message: 'Send message',
comment: 'Button or menu action label in the channel and chat mobile textarea layout. Keep it concise.',
});
const MOBILE_BUTTON_SWAP_MOTION: MotionAnimation = {
initial: {opacity: 0, scale: 0.86, y: 3},
animate: {opacity: 1, scale: 1, y: 0},
exit: {opacity: 0, scale: 0.9, y: -2},
transition: {
opacity: {duration: 0.08},
y: {duration: 0.08, ease: 'easeOut'},
scale: {type: 'spring', stiffness: 560, damping: 28, mass: 0.55},
},
};
interface MobileTextareaLayoutProps {
disabled: boolean;
canAttachFiles: boolean;
value: string;
placeholderText: string;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
scrollerRef: React.RefObject<ScrollerHandle | null>;
isFocused: boolean;
isAutocompleteAttached: boolean;
autocompleteListId?: string;
autocompleteOptions: Array<AutocompleteOption>;
selectedIndex: number;
channelId: string;
isSlowmodeActive: boolean;
isOverCharacterLimit: boolean;
isEditingMessage: boolean;
hasContent: boolean;
hasAttachments: boolean;
hasPendingSticker?: boolean;
isEditingScheduledMessage: boolean;
onFocus: () => void;
onBlur: () => void;
onChange: (value: string, inputType?: string, hint?: TextareaTextChangeHint) => void;
onHeightChange: (height: number) => void;
onCursorMove: () => void;
onArrowUp: (event: React.KeyboardEvent) => void;
onSubmit: () => void;
onAutocompleteSelect: (option: AutocompleteOption) => void;
setSelectedIndex: Dispatch<SetStateAction<number>>;
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
onPlusClick: () => void;
onEmojiClick: () => void;
}
export function MobileTextareaLayout({
disabled,
canAttachFiles,
value,
placeholderText,
textareaRef,
scrollerRef,
isFocused,
isAutocompleteAttached,
autocompleteListId,
autocompleteOptions,
selectedIndex,
channelId,
isSlowmodeActive,
isOverCharacterLimit,
isEditingMessage,
hasContent,
hasAttachments,
hasPendingSticker,
isEditingScheduledMessage,
onFocus,
onBlur,
onChange,
onHeightChange,
onCursorMove,
onArrowUp,
onSubmit,
onAutocompleteSelect,
setSelectedIndex,
onKeyDown,
onPlusClick,
onEmojiClick,
}: MobileTextareaLayoutProps) {
const {i18n} = useLingui();
const buttonSwapMotion = getReducedMotionProps(MOBILE_BUTTON_SWAP_MOTION, Accessibility.useReducedMotion);
const rightButtonState = useMemo(
() =>
transitionMobileTextareaButtonState({
disabled,
canRecordVoice: canAttachFiles,
value,
isSlowmodeActive,
isOverCharacterLimit,
isEditingMessage,
hasContent,
hasAttachments,
hasPendingSticker: Boolean(hasPendingSticker),
isEditingScheduledMessage,
}),
[
canAttachFiles,
disabled,
hasAttachments,
hasContent,
hasPendingSticker,
isEditingMessage,
isEditingScheduledMessage,
isOverCharacterLimit,
isSlowmodeActive,
value,
],
);
const sendButtonDisabled = rightButtonState.sendButton.disabled;
const sendLockRef = useRef(false);
const handleSendClick = () => {
if (sendLockRef.current) {
return;
}
sendLockRef.current = true;
setTimeout(() => {
sendLockRef.current = false;
}, 500);
onSubmit();
};
const shouldShowVoiceButton = rightButtonState.visibleButton === 'voice';
const voiceButtonDisabled = rightButtonState.voiceButton.disabled;
const voiceTooltipAnchorRef = useRef<HTMLDivElement | null>(null);
return (
<div
className={clsx(styles.mobileTextareaWrapper, disabled && wrapperStyles.disabled)}
data-flx="channel.textarea.mobile-textarea-layout.mobile-textarea-wrapper"
>
{!disabled && canAttachFiles && (
<div
className={styles.mobilePlusButtonContainer}
data-flx="channel.textarea.mobile-textarea-layout.mobile-plus-button-container"
>
<button
type="button"
className={styles.mobilePlusButton}
onClick={onPlusClick}
aria-label={i18n._(OPEN_MENU_DESCRIPTOR)}
data-flx="channel.textarea.mobile-textarea-layout.mobile-plus-button.plus-click"
>
<PlusIcon
className={styles.mobilePlusButtonIcon}
weight="bold"
data-flx="channel.textarea.mobile-textarea-layout.mobile-plus-button-icon"
/>
</button>
</div>
)}
<div
className={styles.mobileContentWrapper}
data-flx="channel.textarea.mobile-textarea-layout.mobile-content-wrapper"
>
<div
className={styles.mobileInputContainer}
ref={voiceTooltipAnchorRef}
data-flx="channel.textarea.mobile-textarea-layout.mobile-input-container"
>
<div
className={styles.mobileInputContent}
data-flx="channel.textarea.mobile-textarea-layout.mobile-input-content"
>
<Scroller
ref={scrollerRef}
fade={true}
className={textareaStyles.scroller}
key="mobile-textarea-scroller"
data-flx="channel.textarea.mobile-textarea-layout.scroller"
>
<div className={textareaStyles.flexColumn} data-flx="channel.textarea.mobile-textarea-layout.div">
<TextareaInputField
channelId={channelId}
disabled={disabled}
isMobile={true}
value={value}
placeholder={placeholderText}
textareaRef={textareaRef}
isFocused={isFocused}
isAutocompleteAttached={isAutocompleteAttached}
autocompleteListId={autocompleteListId}
autocompleteOptions={autocompleteOptions}
selectedIndex={selectedIndex}
className={textareaStyles.textareaMobile}
onFocus={onFocus}
onBlur={onBlur}
onChange={onChange}
onHeightChange={onHeightChange}
onCursorMove={onCursorMove}
onArrowUp={onArrowUp}
onEnter={onSubmit}
onAutocompleteSelect={onAutocompleteSelect}
setSelectedIndex={setSelectedIndex}
onKeyDown={onKeyDown}
data-flx="channel.textarea.mobile-textarea-layout.textarea-input-field.change"
/>
</div>
</Scroller>
</div>
{!disabled && (
<div
className={styles.mobileEmojiButtonContainer}
data-flx="channel.textarea.mobile-textarea-layout.mobile-emoji-button-container"
>
<button
type="button"
className={styles.mobileEmojiButton}
onClick={onEmojiClick}
aria-label={i18n._(EMOJIS_DESCRIPTOR)}
data-flx="channel.textarea.mobile-textarea-layout.mobile-emoji-button.emoji-click"
>
<SmileyIcon
className={styles.mobileEmojiButtonIcon}
weight="fill"
data-flx="channel.textarea.mobile-textarea-layout.mobile-emoji-button-icon"
/>
</button>
</div>
)}
</div>
<div
className={styles.mobileRightButtonContainer}
data-flx="channel.textarea.mobile-textarea-layout.mobile-right-button-container"
>
<AnimatePresence initial={false} data-flx="channel.textarea.mobile-textarea-layout.animate-presence">
{shouldShowVoiceButton ? (
<motion.div
key="voice-button"
className={styles.mobileRightButtonSlot}
data-flx="channel.textarea.mobile-textarea-layout.div--2"
{...buttonSwapMotion}
>
<VoiceMessageRecorder
channelId={channelId}
disabled={voiceButtonDisabled}
tooltipAnchorRef={voiceTooltipAnchorRef}
data-flx="channel.textarea.mobile-textarea-layout.voice-message-recorder"
/>
</motion.div>
) : (
<motion.div
key="send-button"
className={styles.mobileRightButtonSlot}
data-flx="channel.textarea.mobile-textarea-layout.mobile-send-button.send-click"
{...buttonSwapMotion}
>
<button
type="button"
className={styles.mobileSendButton}
onClick={handleSendClick}
aria-label={i18n._(SEND_MESSAGE_DESCRIPTOR)}
disabled={sendButtonDisabled}
data-flx="channel.textarea.mobile-textarea-layout.mobile-send-button.button"
>
<ArrowUpIcon
className={styles.mobileRightButtonIcon}
weight="bold"
data-flx="channel.textarea.mobile-textarea-layout.mobile-right-button-icon"
/>
</button>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
);
}
MobileTextareaLayout.displayName = 'MobileTextareaLayout';
@@ -14,33 +14,17 @@
align-items: stretch;
}
.holdButtonWrapper {
.mobileRightButtonContainer {
position: relative;
flex-shrink: 0;
width: var(--textarea-button-height);
height: var(--textarea-button-height);
}
.mobileRightButtonSlot {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
touch-action: none;
}
.holdButton {
touch-action: none;
}
.holdButtonRecording {
box-shadow: 0 0 0 0.125rem rgba(252, 95, 105, 0.35);
}
.holdIndicator {
font-size: 0.625rem;
text-transform: uppercase;
color: var(--text-primary-muted);
}
.holdIndicatorRecording {
color: var(--accent-danger);
}
.holdIndicatorError {
color: var(--accent-danger);
font-weight: 600;
justify-content: center;
}
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import Accessibility from '@app/features/accessibility/state/Accessibility';
import HoldToRecordButton from '@app/features/channel/components/textarea/HoldToRecordButton';
import {transitionMobileTextareaButtonState} from '@app/features/channel/components/textarea/MobileTextareaButtonStateMachine';
import {TextareaButton} from '@app/features/channel/components/textarea/TextareaButton';
import textareaButtonsStyles from '@app/features/channel/components/textarea/TextareaButtons.module.css';
import styles from '@app/features/channel/components/textarea/TextareaInput.module.css';
import VoiceMessageRecorder from '@app/features/channel/components/VoiceMessageRecorder';
import type {ExpressionPickerTabType} from '@app/features/expressions/components/popouts/ExpressionPickerPopout';
import {
EMOJIS_DESCRIPTOR,
@@ -12,29 +13,28 @@ import {
MEDIA_DESCRIPTOR,
STICKERS_DESCRIPTOR,
} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {getReducedMotionProps} from '@app/features/ui/utils/ReducedMotionAnimation';
import {getReducedMotionProps, type MotionAnimation} from '@app/features/ui/utils/ReducedMotionAnimation';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {
GifIcon,
ImageSquareIcon,
MicrophoneIcon,
PaperPlaneRightIcon,
SmileyIcon,
StickerIcon,
} from '@phosphor-icons/react';
import {GifIcon, ImageSquareIcon, PaperPlaneRightIcon, SmileyIcon, StickerIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {AnimatePresence, motion} from 'framer-motion';
import React from 'react';
import React, {useMemo} from 'react';
const VOICE_MESSAGE_DESCRIPTOR = msg({
message: 'Voice message',
comment: 'Short label in the channel and chat textarea buttons. Keep it concise.',
});
const SEND_MESSAGE_DESCRIPTOR = msg({
message: 'Send message',
comment: 'Button or menu action label in the channel and chat textarea buttons. Keep it concise.',
});
const MOBILE_BUTTON_SWAP_MOTION: MotionAnimation = {
initial: {opacity: 0, scale: 0.86, y: 3},
animate: {opacity: 1, scale: 1, y: 0},
exit: {opacity: 0, scale: 0.9, y: -2},
transition: {
opacity: {duration: 0.08},
y: {duration: 0.08, ease: 'easeOut'},
scale: {type: 'spring', stiffness: 560, damping: 28, mass: 0.55},
},
};
interface TextareaButtonsProps {
disabled: boolean;
@@ -44,8 +44,10 @@ interface TextareaButtonsProps {
showStickersButton: boolean;
showEmojiButton: boolean;
showMessageSendButton: boolean;
showVoiceMessageButton?: boolean;
onVoiceMessageClick?: () => void;
canRecordVoice: boolean;
isEditingMessage: boolean;
hasPendingSticker: boolean;
voiceTooltipAnchorRef?: React.RefObject<HTMLElement | null>;
channelId: string;
expressionPickerOpen: boolean;
selectedTab: ExpressionPickerTabType;
@@ -71,8 +73,10 @@ export const TextareaButtons = React.forwardRef<HTMLDivElement, TextareaButtonsP
showStickersButton,
showEmojiButton,
showMessageSendButton,
showVoiceMessageButton,
onVoiceMessageClick,
canRecordVoice,
isEditingMessage,
hasPendingSticker,
voiceTooltipAnchorRef,
channelId,
expressionPickerOpen,
selectedTab,
@@ -90,22 +94,40 @@ export const TextareaButtons = React.forwardRef<HTMLDivElement, TextareaButtonsP
ref,
) => {
const {i18n} = useLingui();
const buttonModel = useMemo(
() =>
transitionMobileTextareaButtonState({
disabled,
canRecordVoice,
value: '',
isSlowmodeActive,
isOverCharacterLimit: isOverLimit,
isEditingMessage,
hasContent,
hasAttachments,
hasPendingSticker,
isEditingScheduledMessage: false,
}),
[
canRecordVoice,
disabled,
hasAttachments,
hasContent,
hasPendingSticker,
isEditingMessage,
isOverLimit,
isSlowmodeActive,
],
);
if (disabled) {
return null;
}
const buttonSwapMotion = getReducedMotionProps(
{
initial: {scale: 0.8, opacity: 0},
animate: {scale: 1, opacity: 1},
exit: {scale: 0.8, opacity: 0},
transition: {duration: 0.15, ease: 'easeOut'},
},
Accessibility.useReducedMotion,
);
const buttonSwapMotion = getReducedMotionProps(MOBILE_BUTTON_SWAP_MOTION, Accessibility.useReducedMotion);
const shouldShowDesktopSendButton = showMessageSendButton;
const baseSendDisabled = isSlowmodeActive || isOverLimit || disableSendButton;
const sendButtonDisabled = baseSendDisabled || (!hasContent && !hasAttachments);
const shouldShowHoldToRecord = isMobile && showVoiceMessageButton && !hasContent && !hasAttachments;
const desktopSendDisabled =
isSlowmodeActive || isOverLimit || (!hasContent && !hasAttachments) || disableSendButton;
const shouldShowVoiceButton = buttonModel.visibleButton === 'voice';
const mobileSendDisabled = buttonModel.sendButton.disabled || Boolean(disableSendButton);
return (
<div
className={clsx(styles.buttonContainerDense, styles.sideButtonPadding)}
@@ -167,48 +189,51 @@ export const TextareaButtons = React.forwardRef<HTMLDivElement, TextareaButtonsP
className={textareaButtonsStyles.invisibleTrigger}
data-flx="channel.textarea.textarea-buttons.div"
/>
{isMobile && showVoiceMessageButton && onVoiceMessageClick && !shouldShowHoldToRecord && (
<TextareaButton
icon={MicrophoneIcon}
label={i18n._(VOICE_MESSAGE_DESCRIPTOR)}
onClick={onVoiceMessageClick}
data-flx="channel.textarea.textarea-buttons.textarea-button.voice-message-click"
/>
)}
{isMobile && (
<AnimatePresence mode="wait" initial={false} data-flx="channel.textarea.textarea-buttons.animate-presence">
{shouldShowHoldToRecord ? (
<motion.div
key="hold-to-record"
data-flx="channel.textarea.textarea-buttons.div--2"
{...buttonSwapMotion}
>
<HoldToRecordButton
channelId={channelId}
disabled={baseSendDisabled}
onFallback={onVoiceMessageClick}
data-flx="channel.textarea.textarea-buttons.hold-to-record-button"
/>
</motion.div>
) : (
<motion.div key="send-button" data-flx="channel.textarea.textarea-buttons.div--3" {...buttonSwapMotion}>
<TextareaButton
disabled={sendButtonDisabled}
icon={PaperPlaneRightIcon}
label={i18n._(SEND_MESSAGE_DESCRIPTOR)}
onClick={onSubmit}
keybindCombo={{key: 'Enter'}}
data-flx="channel.textarea.textarea-buttons.textarea-button.submit"
/>
</motion.div>
)}
</AnimatePresence>
<div
className={textareaButtonsStyles.mobileRightButtonContainer}
data-flx="channel.textarea.textarea-buttons.mobile-right-button-container"
>
<AnimatePresence initial={false} data-flx="channel.textarea.textarea-buttons.animate-presence">
{shouldShowVoiceButton ? (
<motion.div
key="voice-button"
className={textareaButtonsStyles.mobileRightButtonSlot}
data-flx="channel.textarea.textarea-buttons.div--2"
{...buttonSwapMotion}
>
<VoiceMessageRecorder
channelId={channelId}
disabled={buttonModel.voiceButton.disabled}
tooltipAnchorRef={voiceTooltipAnchorRef}
data-flx="channel.textarea.textarea-buttons.voice-message-recorder"
/>
</motion.div>
) : (
<motion.div
key="send-button"
className={textareaButtonsStyles.mobileRightButtonSlot}
data-flx="channel.textarea.textarea-buttons.div--3"
{...buttonSwapMotion}
>
<TextareaButton
disabled={mobileSendDisabled}
icon={PaperPlaneRightIcon}
label={i18n._(SEND_MESSAGE_DESCRIPTOR)}
onClick={onSubmit}
keybindCombo={{key: 'Enter'}}
data-flx="channel.textarea.textarea-buttons.textarea-button.submit"
/>
</motion.div>
)}
</AnimatePresence>
</div>
)}
{!isMobile && shouldShowDesktopSendButton && (
<>
<div className={styles.divider} data-flx="channel.textarea.textarea-buttons.divider" />
<TextareaButton
disabled={isSlowmodeActive || isOverLimit || (!hasContent && !hasAttachments) || disableSendButton}
disabled={desktopSendDisabled}
icon={PaperPlaneRightIcon}
label={i18n._(SEND_MESSAGE_DESCRIPTOR)}
onClick={onSubmit}
@@ -16225,6 +16225,10 @@ msgstr "مساعدة"
msgid "Help center"
msgstr "مركز المساعدة"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "ساعد في ترجمة {PRODUCT_NAME} إلى لغتك على <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "الترجمة"
msgid "Translation provider"
msgstr "مزوّد الترجمة"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "الترجمات آلية. ساعد في تحسين الترجمة عن طريق إرسال بريد إلكتروني إلى <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -1410,6 +1410,9 @@
{
"msgid": "Granted"
},
{
"msgid": "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
},
{
"msgid": "How much of the member's recent message history to delete."
},
@@ -16225,6 +16225,10 @@ msgstr "Помощ"
msgid "Help center"
msgstr "Помощен център"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Помогнете да преведем {PRODUCT_NAME} на вашия език на <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Превод"
msgid "Translation provider"
msgstr "Доставчик на преводи"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Преводите са автоматични. Помогнете за подобряване на локализацията, като изпратите имейл на <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Nápověda"
msgid "Help center"
msgstr "Centrum nápovědy"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Pomozte přeložit {PRODUCT_NAME} do vašeho jazyka na <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Překlad"
msgid "Translation provider"
msgstr "Poskytovatel překladu"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Překlady jsou automatické. Pomozte nám zlepšit lokalizaci e-mailem na <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Hjælp"
msgid "Help center"
msgstr "Hjælp"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Hjælp med at oversætte {PRODUCT_NAME} til dit sprog på <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Oversættelse"
msgid "Translation provider"
msgstr "Oversættelsesudbyder"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Oversættelser er automatiske. Hjælp os med at forbedre lokaliseringen ved at sende en e-mail til <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Hilfe"
msgid "Help center"
msgstr "Hilfebereich"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Hilf mit, {PRODUCT_NAME} in deiner Sprache auf <0>{I18N_WEBLATE_DOMAIN}</0> zu übersetzen."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Übersetzung"
msgid "Translation provider"
msgstr "Übersetzungsanbieter"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Übersetzungen erfolgen automatisch. Helfen Sie uns, die Lokalisierung zu verbessern, indem Sie eine E-Mail an <0>{I18N_EMAIL}</0> senden."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Βοήθεια"
msgid "Help center"
msgstr "Κέντρο βοήθειας"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Βοηθήστε στη μετάφραση του {PRODUCT_NAME} στη γλώσσα σας στο <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Μετάφραση"
msgid "Translation provider"
msgstr "Πάροχος μετάφρασης"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Οι μεταφράσεις είναι αυτοματοποιημένες. Βοηθήστε να βελτιώσουμε την τοπική προσαρμογή στέλνοντας email στο <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -14635,7 +14635,6 @@ msgstr "Filter mentions"
msgid "Filtering"
msgstr "Filtering"
# auto-i18n: reviewed unchanged
#. Label for the discovery language filter button once a language is chosen. Preserve {language}; it is inserted by code.
#: src/features/discovery/discovery/DiscoveryLanguageButton.tsx:20
msgid "Filtering by {language}"
@@ -16226,6 +16225,11 @@ msgstr "Help"
msgid "Help center"
msgstr "Help centre"
# auto-i18n: reviewed unchanged
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33557,10 +33561,6 @@ msgstr "Translation"
msgid "Translation provider"
msgstr "Translation provider"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Translations are automated. Help improve localisation by emailing <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16226,6 +16226,10 @@ msgstr "Help"
msgid "Help center"
msgstr "Help center"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33557,10 +33561,6 @@ msgstr "Translation"
msgid "Translation provider"
msgstr "Translation provider"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Ayuda"
msgid "Help center"
msgstr "Centro de ayuda"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Ayuda a traducir {PRODUCT_NAME} a tu idioma en <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Traducción"
msgid "Translation provider"
msgstr "Proveedor de traducción"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Las traducciones son automáticas. Ayuda a mejorar la localización enviando un correo electrónico a <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Ayuda"
msgid "Help center"
msgstr "Centro de ayuda"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Ayuda a traducir {PRODUCT_NAME} a tu idioma en <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Traducción"
msgid "Translation provider"
msgstr "Proveedor de traducción"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Las traducciones son automáticas. Ayuda a mejorar la localización enviando un correo electrónico a <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Ohje"
msgid "Help center"
msgstr "Ohjekeskus"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Auta kääntämään {PRODUCT_NAME} kielellesi osoitteessa <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Käännös"
msgid "Translation provider"
msgstr "Käännöspalvelu"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Käännökset ovat automaattisia. Auta parantamaan lokalisointia lähettämällä sähköpostia osoitteeseen <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Aide"
msgid "Help center"
msgstr "Centre d'aide"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Aidez à traduire {PRODUCT_NAME} dans votre langue sur <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Traduction"
msgid "Translation provider"
msgstr "Fournisseur de traduction"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Les traductions sont automatiques. Aidez-nous à améliorer la localisation en envoyant un e-mail à <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "עזרה"
msgid "Help center"
msgstr "מרכז עזרה"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "עזרו לתרגם את {PRODUCT_NAME} לשפה שלכם ב-<0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "תרגום"
msgid "Translation provider"
msgstr "ספק תרגום"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "התרגומים הם אוטומטיים. עזרו לשפר את הלוקליזציה על ידי שליחת אימייל לכתובת <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "सहायता"
msgid "Help center"
msgstr "सहायता केंद्र"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "{PRODUCT_NAME} को अपनी भाषा में <0>{I18N_WEBLATE_DOMAIN}</0> पर अनुवाद करने में मदद करें।"
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "अनुवाद"
msgid "Translation provider"
msgstr "अनुवाद प्रदाता"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "अनुवाद स्वचालित हैं। <0>{I18N_EMAIL}</0> पर ईमेल करके स्थानीयकरण को बेहतर बनाने में मदद करें।"
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Pomoć"
msgid "Help center"
msgstr "Centar za pomoć"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Pomozite prevesti {PRODUCT_NAME} na vaš jezik na <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Prijevod"
msgid "Translation provider"
msgstr "Davatelj prijevoda"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Prijevodi su automatizirani. Pomozite poboljšati lokalizaciju slanjem e-pošte na <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Súgó"
msgid "Help center"
msgstr "Súgóközpont"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Segíts a(z) {PRODUCT_NAME} fordításában a saját nyelvedre itt: <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Fordítás"
msgid "Translation provider"
msgstr "Fordítószolgáltató"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "A fordítások automatizáltak. Segíts a lokalizáció javításában, írj egy e-mailt ide: <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Bantuan"
msgid "Help center"
msgstr "Pusat bantuan"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Bantu terjemahkan {PRODUCT_NAME} ke bahasa Anda di <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Terjemahan"
msgid "Translation provider"
msgstr "Penyedia terjemahan"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Terjemahan dilakukan secara otomatis. Bantu tingkatkan lokalisasi dengan mengirim email ke <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Aiuto"
msgid "Help center"
msgstr "Centro assistenza"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Aiuta a tradurre {PRODUCT_NAME} nella tua lingua su <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Traduzione"
msgid "Translation provider"
msgstr "Fornitore di traduzione"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Le traduzioni sono automatiche. Contribuisci a migliorare la localizzazione inviando un'email a <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "ヘルプ"
msgid "Help center"
msgstr "ヘルプセンター"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "{PRODUCT_NAME} をあなたの言語に翻訳するお手伝いをしてください。 <0>{I18N_WEBLATE_DOMAIN}</0>"
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "翻訳"
msgid "Translation provider"
msgstr "翻訳プロバイダ"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "翻訳は自動で行われています。<0>{I18N_EMAIL}</0>までメールでご連絡いただければ、ローカライズの改善にご協力いただけます。"
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "도움말"
msgid "Help center"
msgstr "고객센터"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "{PRODUCT_NAME}을(를) 귀하의 언어로 번역하는 데 참여해 주세요. <0>{I18N_WEBLATE_DOMAIN}</0>에서 가능합니다."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "번역"
msgid "Translation provider"
msgstr "번역 서비스 제공자"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "번역은 자동입니다. <0>{I18N_EMAIL}</0>로 이메일을 보내 현지화 개선에 도움을 주세요."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Pagalba"
msgid "Help center"
msgstr "Pagalbos centras"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Padėkite versti {PRODUCT_NAME} į jūsų kalbą per <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Vertimas"
msgid "Translation provider"
msgstr "Vertimo paslaugų teikėjas"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Vertimai yra automatiniai. Padėkite pagerinti lokalizavimą el. paštu <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Help"
msgid "Help center"
msgstr "Helpcentrum"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Help {PRODUCT_NAME} vertalen naar jouw taal op <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Vertaling"
msgid "Translation provider"
msgstr "Vertaalservice"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Vertalingen zijn geautomatiseerd. Help de lokalisatie te verbeteren door een e-mail te sturen naar <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Hjelp"
msgid "Help center"
msgstr "Hjelpesenter"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Hjelp til med å oversette {PRODUCT_NAME} til ditt språk på <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Oversettelse"
msgid "Translation provider"
msgstr "Oversettelsesleverandør"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Oversettelser er automatiserte. Hjelp oss med å forbedre lokaliseringen ved å sende en e-post til <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Pomoc"
msgid "Help center"
msgstr "Centrum pomocy"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Pomóż przetłumaczyć {PRODUCT_NAME} na swój język na stronie <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Tłumaczenie"
msgid "Translation provider"
msgstr "Dostawca tłumaczeń"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Tłumaczenia są automatyczne. Pomóż nam je ulepszyć, wysyłając e-mail na adres <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Ajuda"
msgid "Help center"
msgstr "Central de ajuda"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Ajude a traduzir o {PRODUCT_NAME} para o seu idioma em <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Tradução"
msgid "Translation provider"
msgstr "Provedor de tradução"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "As traduções são automáticas. Ajude a melhorar a localização enviando um e-mail para <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Ajutor"
msgid "Help center"
msgstr "Centrul de ajutor"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Ajută la traducerea {PRODUCT_NAME} în limba ta pe <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Traducere"
msgid "Translation provider"
msgstr "Furnizor de traduceri"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Traducerile sunt automate. Ajută la îmbunătățirea localizării trimițând un e-mail la <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Помощь"
msgid "Help center"
msgstr "Справочный центр"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Помогите перевести {PRODUCT_NAME} на ваш язык на <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Перевод"
msgid "Translation provider"
msgstr "Провайдер перевода"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Перевод автоматический. Помогите улучшить локализацию, написав на <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Hjälp"
msgid "Help center"
msgstr "Hjälpcenter"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Hjälp till att översätta {PRODUCT_NAME} till ditt språk på <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Översättning"
msgid "Translation provider"
msgstr "Översättningstjänst"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Översättningar är automatiserade. Hjälp oss att förbättra lokaliseringen genom att mejla <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "ช่วยเหลือ"
msgid "Help center"
msgstr "ศูนย์ช่วยเหลือ"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "ช่วยแปล {PRODUCT_NAME} เป็นภาษาของคุณบน <0>{I18N_WEBLATE_DOMAIN}</0>"
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "การแปลภาษา"
msgid "Translation provider"
msgstr "ผู้ให้บริการแปลภาษา"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "การแปลเป็นแบบอัตโนมัติ ช่วยปรับปรุงการแปลให้ดียิ่งขึ้นได้โดยส่งอีเมลไปที่ <0>{I18N_EMAIL}</0>"
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Yardım"
msgid "Help center"
msgstr "Yardım merkezi"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "{PRODUCT_NAME}'i <0>{I18N_WEBLATE_DOMAIN}</0> üzerinde dilinize çevirmemize yardım edin."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Çeviri"
msgid "Translation provider"
msgstr "Çeviri sağlayıcısı"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Çeviriler otomatiktir. Yerelleştirmeyi iyileştirmeye yardımcı olmak için <0>{I18N_EMAIL}</0> adresine e-posta gönderin."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Довідка"
msgid "Help center"
msgstr "Довідковий центр"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Допоможіть перекласти {PRODUCT_NAME} вашою мовою на <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Переклад"
msgid "Translation provider"
msgstr "Провайдер перекладу"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Переклад автоматичний. Допоможіть покращити локалізацію, написавши на <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "Trợ giúp"
msgid "Help center"
msgstr "Trung tâm trợ giúp"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "Giúp dịch {PRODUCT_NAME} sang ngôn ngữ của bạn trên <0>{I18N_WEBLATE_DOMAIN}</0>."
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "Bản dịch"
msgid "Translation provider"
msgstr "Nhà cung cấp dịch"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "Bản dịch được tự động hóa. Hãy giúp cải thiện bản địa hóa bằng cách gửi email tới <0>{I18N_EMAIL}</0>."
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "帮助"
msgid "Help center"
msgstr "帮助中心"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "帮助我们将 {PRODUCT_NAME} 翻译成您的语言,请访问 <0>{I18N_WEBLATE_DOMAIN}</0>。"
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "翻译"
msgid "Translation provider"
msgstr "翻译提供方"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "翻译由机器自动完成。发送邮件至<0>{I18N_EMAIL}</0>,帮助我们改进本地化。"
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -16225,6 +16225,10 @@ msgstr "說明"
msgid "Help center"
msgstr "說明中心"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Help translate {PRODUCT_NAME} into your language on <0>{I18N_WEBLATE_DOMAIN}</0>."
msgstr "協助將 {PRODUCT_NAME} 翻譯成您的語言,請前往 <0>{I18N_WEBLATE_DOMAIN}</0>。"
#. Description for a WebRTC content hint select. Keep Motion, Detail, and text as plain option concepts.
#: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedVideoControls.tsx:135
msgid "Helps the browser choose motion, detail, or text handling."
@@ -33556,10 +33560,6 @@ msgstr "翻譯"
msgid "Translation provider"
msgstr "翻譯服務供應商"
#: src/features/user/components/modals/tabs/LanguageTab.tsx:360
msgid "Translations are automated. Help improve localization by emailing <0>{I18N_EMAIL}</0>."
msgstr "翻譯為自動產生。請來信至 <0>{I18N_EMAIL}</0> 協助改進本地化。"
#. Settings search synonym. Used to match this term when the user types it in the settings search bar.
#: src/features/user/components/settings_utils/search_index/ChatSettingsIndex.ts:247
msgid "Translator"
@@ -152,8 +152,8 @@ export class ComposerCustomEmojiNode extends DecoratorNode<JSX.Element> {
return true;
}
override isKeyboardSelectable(): boolean {
return true;
override isKeyboardSelectable(): false {
return false;
}
override decorate(): JSX.Element {
@@ -184,8 +184,8 @@ export class ComposerMentionNode extends DecoratorNode<JSX.Element> {
return true;
}
override isKeyboardSelectable(): boolean {
return true;
override isKeyboardSelectable(): false {
return false;
}
override decorate(): JSX.Element {
@@ -143,8 +143,8 @@ export class ComposerStandardEmojiNode extends DecoratorNode<JSX.Element> {
return true;
}
override isKeyboardSelectable(): boolean {
return true;
override isKeyboardSelectable(): false {
return false;
}
override decorate(): JSX.Element {
@@ -3,6 +3,7 @@
import Accessibility from '@app/features/accessibility/state/Accessibility';
import {useMergeRefs} from '@app/features/app/hooks/useMergeRefs';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
import {usePortalHost} from '@app/features/ui/overlay/PortalHostContext';
import ContextMenuState from '@app/features/ui/state/ContextMenu';
@@ -77,8 +78,8 @@ export const useTooltipPortalRoot = (enabled = true, targetDocument?: Document):
return scopedPortalHost ?? root;
};
const MAX_WIDTH_MAP = {
default: 190,
xl: 350,
default: remFromPx(190),
xl: remFromPx(350),
none: 'none' as const,
};
const TooltipPositionToStyle: Record<TooltipPosition, string> = {
@@ -9,7 +9,7 @@ import {
SettingsTabSection,
} from '@app/features/app/components/dialogs/shared/SettingsTabLayout';
import {ExternalLink} from '@app/features/app/components/shared/ExternalLink';
import {I18N_EMAIL, I18N_EMAIL_MAILTO, PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import {I18N_WEBLATE_DOMAIN, I18N_WEBLATE_URL, PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import * as EmojiUtils from '@app/features/expressions/utils/EmojiUtils';
import Spellcheck from '@app/features/messaging/state/Spellcheck';
import type {SpellcheckEngine} from '@app/features/platform/types/Electron';
@@ -358,9 +358,9 @@ const LanguageTab = observer(() => {
<div className={styles.notice} data-flx="user.language-tab.notice">
<p className={styles.noticeText} data-flx="user.language-tab.notice-text">
<Trans>
Translations are automated. Help improve localization by emailing{' '}
<ExternalLink href={I18N_EMAIL_MAILTO} className={styles.link} data-flx="user.language-tab.link">
{I18N_EMAIL}
Help translate {PRODUCT_NAME} into your language on{' '}
<ExternalLink href={I18N_WEBLATE_URL} className={styles.link} data-flx="user.language-tab.link">
{I18N_WEBLATE_DOMAIN}
</ExternalLink>
.
</Trans>
@@ -1,25 +1,22 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
.container {
display: flex;
flex-direction: column;
gap: 1rem;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(14rem, min(max-content, 55%));
align-items: center;
column-gap: 1rem;
row-gap: 1.5rem;
}
.row {
display: flex;
min-height: 2.75rem;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.25rem 0;
display: contents;
}
.label {
display: flex;
min-height: 2.25rem;
min-width: 0;
max-width: 100%;
flex: 1 1 0%;
align-items: center;
gap: 0.5rem;
font-weight: 500;
@@ -34,13 +31,14 @@
}
.choiceWrap {
flex: 0 0 14rem;
width: 14rem;
width: 100%;
}
.choiceGroup {
position: relative;
display: flex;
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 1fr);
width: 100%;
height: 2.125rem;
border-radius: 0.5rem;
@@ -57,7 +55,6 @@
.choiceButton {
position: relative;
z-index: 1;
flex: 1 1 0;
min-width: 0;
border: 0;
border-radius: 0.375rem;
@@ -69,6 +66,8 @@
line-height: 1rem;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
transition: color 150ms ease;
}
@@ -101,13 +100,22 @@
}
@media (max-width: 520px) {
.row {
.container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.choiceWrap {
flex: none;
width: min(100%, 14rem);
.row {
display: flex;
min-height: 2.75rem;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
padding: 0.25rem 0;
}
.label {
min-height: 0;
}
}
+8
View File
@@ -277,6 +277,14 @@ The `fluxer-static` image is part of the default stack, so static asset updates
To pin a specific release, set `FLUXER_IMAGE_TAG` in `.env` to the release tag you want, then pull and restart.
Each upgrade leaves the previous images behind, which adds up to a few gigabytes over time. To reclaim that space:
```bash
docker image prune -f
```
This removes only the untagged images the upgrade replaced. `docker system prune -a` reclaims more, but it also deletes every image not currently used by a running container, including ones unrelated to Fluxer.
## Getting help
- File issues and follow development on [GitHub](https://github.com/fluxerapp/fluxer).
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -571,10 +571,8 @@ msgstr "الكود المصدري"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"يجب أن تبدو الترجمة طبيعية لمن يتحدث اللغة. صحّح عبارة أو ابدأ لغة جديدة على {weblate}، وسنضمّها إلى {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "ساعد في ترجمة {product_name} إلى لغتك على {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2373,8 +2371,8 @@ msgstr "شارة مشترك على ملفك الشخصي"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "عناصر الوسائط التي يمكنك حفظها"
msgid "Media items you can save for later"
msgstr "عناصر الوسائط التي يمكنك حفظها لوقت لاحق"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: bg\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -573,10 +573,8 @@ msgstr "Изходен код"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Помогнете да преведем {product_name} на вашия език на {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2387,8 +2385,8 @@ msgstr "Subscriber badge on your profile"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Media items you can save"
msgid "Media items you can save for later"
msgstr "Медийни файлове, които можеш да запазиш за по-късно"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -572,10 +572,8 @@ msgstr "Zdrojový kód"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Překlad by měl znít přirozeně tomu, kdo daným jazykem mluví. Oprav formulaci nebo založ nový jazyk na {weblate} a my ho do {product_name} zahrneme."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Pomozte přeložit {product_name} do vašeho jazyka na {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2381,8 +2379,8 @@ msgstr "Odznak předplatitele na profilu"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Média, která si můžeš uložit"
msgid "Media items you can save for later"
msgstr "Média, která si můžeš uložit na později"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -573,10 +573,8 @@ msgstr "Kildekode"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"En oversættelse skal lyde naturlig for en, der taler sproget. Ret en formulering eller start et nyt sprog på {weblate}, så tager vi det med i {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Hjælp med at oversætte {product_name} til dit sprog på {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2388,8 +2386,8 @@ msgstr "Abonnentbadge på din profil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medieelementer, du kan gemme"
msgid "Media items you can save for later"
msgstr "Medieelementer, du kan gemme til senere"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Quellcode"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Eine Übersetzung sollte für Muttersprachler natürlich klingen. Korrigiere eine Formulierung oder starte eine neue Sprache auf {weblate}, und wir nehmen sie in {product_name} auf."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Hilf mit, {product_name} in deiner Sprache auf {weblate} zu übersetzen."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2396,8 +2394,8 @@ msgstr "Abonnenten-Abzeichen auf deinem Profil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medien, die du speichern kannst"
msgid "Media items you can save for later"
msgstr "Medien, die du für später speichern kannst"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -576,10 +576,8 @@ msgstr "Πηγαίος κώδικας"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Μια μετάφραση πρέπει να ακούγεται φυσική σε κάποιον που μιλάει τη γλώσσα. Διόρθωσε μια φράση ή ξεκίνα μια νέα γλώσσα στο {weblate} και θα την προσθέσουμε στο {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Βοηθήστε στη μετάφραση του {product_name} στη γλώσσα σας στο {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2404,8 +2402,8 @@ msgstr "Σήμα συνδρομητή στο προφίλ σου"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Πολυμέσα που μπορείς να αποθηκεύσεις"
msgid "Media items you can save for later"
msgstr "Πολυμέσα που μπορείς να αποθηκεύσεις για αργότερα"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: en-GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -571,10 +571,8 @@ msgstr "Source code"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Help translate {product_name} into your language on {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2379,8 +2377,8 @@ msgstr "Subscriber badge on your profile"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Media items you can save"
msgid "Media items you can save for later"
msgstr "Media items you can save for later"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: en-US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -571,10 +571,8 @@ msgstr "Source code"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Help translate {product_name} into your language on {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2379,8 +2377,8 @@ msgstr "Subscriber badge on your profile"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Media items you can save"
msgid "Media items you can save for later"
msgstr "Media items you can save for later"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: es-419\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -576,10 +576,8 @@ msgstr "Código fuente"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Una traducción debe sonar natural para quien habla el idioma. Corrige una frase o empieza un idioma nuevo en {weblate} y lo incluiremos en {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Ayuda a traducir {product_name} a tu idioma en {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2393,8 +2391,8 @@ msgstr "Insignia de suscriptor en tu perfil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elementos multimedia que puedes guardar"
msgid "Media items you can save for later"
msgstr "Elementos multimedia que puedes guardar para después"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: es-ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -576,10 +576,8 @@ msgstr "Código fuente"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Una traducción debe sonar natural para quien habla el idioma. Corrige una frase o empieza un idioma nuevo en {weblate} y lo incluiremos en {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Ayuda a traducir {product_name} a tu idioma en {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2395,8 +2393,8 @@ msgstr "Insignia de suscriptor en tu perfil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elementos multimedia que puedes guardar"
msgid "Media items you can save for later"
msgstr "Elementos multimedia que puedes guardar para más tarde"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Lähdekoodi"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Käännöksen pitäisi kuulostaa luontevalta kieltä puhuvan korvaan. Korjaa jokin ilmaus tai aloita uusi kieliversio {weblate}-palvelussa, niin otamme sen mukaan {product_name}-palveluun."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Auta kääntämään {product_name} kielellesi osoitteessa {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2394,8 +2392,8 @@ msgstr "Tilaajamerkki profiilissasi"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Tallennettavat mediatiedostot"
msgid "Media items you can save for later"
msgstr "Mediatiedostot, jotka voit tallentaa myöhempää varten"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -576,10 +576,8 @@ msgstr "Code source"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Une traduction doit sonner juste pour quelqu'un qui parle la langue. Corrige une formulation ou lance une nouvelle langue sur {weblate}, et nous l'intégrerons à {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Aidez à traduire {product_name} dans votre langue sur {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2400,8 +2398,8 @@ msgstr "Badge d'abonné sur ton profil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Médias que tu peux enregistrer"
msgid "Media items you can save for later"
msgstr "Médias que tu peux enregistrer pour plus tard"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: he\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -570,10 +570,8 @@ msgstr "קוד מקור"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"תרגום צריך להישמע טבעי למי שדובר את השפה. אפשר לתקן ניסוח או להתחיל שפה חדשה ב-{weblate}, ואנחנו נכלול אותו ב-{product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "עזרו לתרגם את {product_name} לשפה שלכם ב-{weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2366,8 +2364,8 @@ msgstr "תג מנוי בפרופיל שלך"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "פריטי מדיה שאפשר לשמור"
msgid "Media items you can save for later"
msgstr "פריטי מדיה שאפשר לשמור למועד מאוחר יותר"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: hi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -572,10 +572,8 @@ msgstr "सोर्स कोड"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"अनुवाद ऐसा होना चाहिए जो उस भाषा को बोलने वाले किसी व्यक्ति को स्वाभाविक लगे। {weblate} पर किसी वाक्य को सुधारें या नई लोकेल शुरू करें, और हम उसे {product_name} में शामिल करेंगे।"
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "{product_name} को अपनी भाषा में {weblate} पर अनुवाद करने में मदद करें।"
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2383,8 +2381,8 @@ msgstr "आपकी प्रोफ़ाइल पर सब्सक्रा
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "सेव किए जा सकने वाले मीडिया आइटम"
msgid "Media items you can save for later"
msgstr "बाद के लिए सेव किए जा सकने वाले मीडिया आइटम"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -574,10 +574,8 @@ msgstr "Izvorni kod"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Prijevod bi trebao zvučati prirodno onome tko govori taj jezik. Ispravi izraz ili pokreni novi jezik na {weblate}, a mi ćemo ga uključiti u {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Pomozite prevesti {product_name} na vaš jezik na {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2385,8 +2383,8 @@ msgstr "Značka pretplatnika na tvom profilu"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medijski sadržaji koje možeš spremiti"
msgid "Media items you can save for later"
msgstr "Medijski sadržaji koje možeš spremiti za poslije"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Forráskód"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Egy fordításnak természetesen kell hangoznia annak, aki beszéli az adott nyelvet. Javíts egy kifejezést vagy indíts új nyelvet a {weblate} oldalon, mi pedig beépítjük a {product_name}-be."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Segíts a(z) {product_name} fordításában a saját nyelvedre itt: {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2395,8 +2393,8 @@ msgstr "Előfizetői jelvény a profilodon"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elmenthető médiaelemek"
msgid "Media items you can save for later"
msgstr "Későbbre elmenthető médiaelemek"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -574,10 +574,8 @@ msgstr "Kode sumber"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Terjemahan seharusnya terdengar wajar bagi penutur bahasanya. Perbaiki satu frasa atau mulai bahasa baru di {weblate}, dan kami akan menyertakannya di {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Bantu terjemahkan {product_name} ke bahasa Anda di {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2384,8 +2382,8 @@ msgstr "Lencana pelanggan di profilmu"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Media yang bisa kamu simpan"
msgid "Media items you can save for later"
msgstr "Media yang bisa kamu simpan untuk nanti"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Codice sorgente"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Una traduzione deve suonare naturale a chi parla quella lingua. Correggi una frase o avvia una nuova lingua su {weblate} e la includeremo in {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Aiuta a tradurre {product_name} nella tua lingua su {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2392,8 +2390,8 @@ msgstr "Badge da abbonato sul tuo profilo"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elementi multimediali che puoi salvare"
msgid "Media items you can save for later"
msgstr "Elementi multimediali che puoi salvare per dopo"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -567,10 +567,8 @@ msgstr "ソースコード"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"翻訳は、その言語を話す人にとって自然に聞こえるものであるべきです。{weblate}で表現を修正したり、新しい言語を追加したりしていただければ、{product_name}に取り込みます。"
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "{product_name} をあなたの言語に翻訳するお手伝いをしてください。 {weblate}"
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2350,8 +2348,8 @@ msgstr "プロフィールに表示される加入者バッジ"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "保存できるメディア数"
msgid "Media items you can save for later"
msgstr "後で見るために保存できるメディア数"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -566,10 +566,8 @@ msgstr "소스 코드"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"번역은 그 언어를 쓰는 사람에게 자연스럽게 들려야 합니다. {weblate}에서 문구를 고치거나 새로운 언어를 시작해 주시면 {product_name}에 반영하겠습니다."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "{product_name}을(를) 귀하의 언어로 번역하는 데 참여해 주세요. {weblate}에서 가능합니다."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2349,8 +2347,8 @@ msgstr "프로필에 표시되는 구독자 배지"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "저장할 수 있는 미디어 수"
msgid "Media items you can save for later"
msgstr "나중에 보려고 저장할 수 있는 미디어 수"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: lt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Šaltinio kodas"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Vertimas turi skambėti natūraliai tam, kas kalba ta kalba. Pataisyk frazę arba pradėk naują kalbą {weblate} platformoje, o mes ją įtrauksime į {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Padėkite versti {product_name} į jūsų kalbą per {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2389,8 +2387,8 @@ msgstr "Prenumeratoriaus ženklelis profilyje"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medijos elementai, kuriuos gali išsaugoti"
msgid "Media items you can save for later"
msgstr "Medijos elementai, kuriuos gali išsaugoti vėlesniam laikui"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -574,10 +574,8 @@ msgstr "Broncode"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Een vertaling moet natuurlijk klinken voor iemand die de taal spreekt. Corrigeer een zin of begin een nieuwe taal op {weblate}, dan nemen we die op in {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Help {product_name} vertalen naar jouw taal op {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2389,8 +2387,8 @@ msgstr "Abonneebadge op je profiel"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Media die je kunt opslaan"
msgid "Media items you can save for later"
msgstr "Media die je kunt opslaan voor later"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: no\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -573,10 +573,8 @@ msgstr "Kildekode"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"En oversettelse skal høres naturlig ut for noen som snakker språket. Rett opp en formulering eller start et nytt språk på {weblate}, så tar vi det med i {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Hjelp til med å oversette {product_name} til ditt språk på {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2386,8 +2384,8 @@ msgstr "Abonnentmerke på profilen din"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medieelementer du kan lagre"
msgid "Media items you can save for later"
msgstr "Medieelementer du kan lagre til senere"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Kod źródłowy"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Tłumaczenie powinno brzmieć naturalnie dla osoby mówiącej danym językiem. Popraw sformułowanie lub rozpocznij nowy język na {weblate}, a dodamy je do {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Pomóż przetłumaczyć {product_name} na swój język na stronie {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2391,8 +2389,8 @@ msgstr "Odznaka subskrybenta na profilu"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elementy multimedialne, które możesz zapisać"
msgid "Media items you can save for later"
msgstr "Elementy multimedialne, które możesz zapisać na później"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: pt-BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Código-fonte"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Uma tradução deve soar natural para quem fala o idioma. Corrija uma frase ou comece um novo idioma no {weblate}, e nós o incluiremos no {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Ajude a traduzir o {product_name} para o seu idioma em {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2391,8 +2389,8 @@ msgstr "Selo de assinante no seu perfil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Itens de mídia que você pode salvar"
msgid "Media items you can save for later"
msgstr "Itens de mídia que você pode salvar para depois"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Cod sursă"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"O traducere ar trebui să sune natural pentru cineva care vorbește limba respectivă. Corectează o expresie sau începe o limbă nouă pe {weblate}, iar noi o vom include în {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Ajută la traducerea {product_name} în limba ta pe {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2395,8 +2393,8 @@ msgstr "Insignă de abonat pe profilul tău"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Elemente media pe care le poți salva"
msgid "Media items you can save for later"
msgstr "Elemente media pe care le poți salva pentru mai târziu"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -574,10 +574,8 @@ msgstr "Исходный код"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Перевод должен звучать естественно для того, кто говорит на этом языке. Исправь формулировку или начни новую локаль на {weblate}, и мы добавим её в {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Помогите перевести {product_name} на ваш язык на {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2391,8 +2389,8 @@ msgstr "Значок подписчика в профиле"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Медиафайлы, которые можно сохранить"
msgid "Media items you can save for later"
msgstr "Медиафайлы, которые можно сохранить на потом"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: sv-SE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -574,10 +574,8 @@ msgstr "Källkod"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"En översättning ska låta naturlig för den som talar språket. Rätta en formulering eller starta ett nytt språk på {weblate}, så tar vi med den i {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Hjälp till att översätta {product_name} till ditt språk på {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2389,8 +2387,8 @@ msgstr "Prenumerantmärke på din profil"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Medieobjekt du kan spara"
msgid "Media items you can save for later"
msgstr "Medieobjekt du kan spara till senare"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: th\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -573,10 +573,8 @@ msgstr "ซอร์สโค้ด"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"คำแปลที่ดีควรอ่านแล้วเป็นธรรมชาติสำหรับเจ้าของภาษา แก้ไขคำแปลหรือเริ่มภาษาใหม่ได้ที่ {weblate} แล้วเราจะนำไปรวมไว้ใน {product_name}"
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "ช่วยแปล {product_name} เป็นภาษาของคุณบน {weblate}"
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2378,8 +2376,8 @@ msgstr "ป้ายสมาชิกบนโปรไฟล์ของคุ
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "จำนวนสื่อที่บันทึกได้"
msgid "Media items you can save for later"
msgstr "จำนวนสื่อที่บันทึกไว้ดูภายหลังได้"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -573,10 +573,8 @@ msgstr "Kaynak kodu"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Bir çeviri, o dili konuşan birine doğal gelmelidir. {weblate} üzerinde bir ifadeyi düzelt veya yeni bir dil başlat; biz de onu {product_name}'a ekleyelim."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "{product_name}'i {weblate} üzerinde dilinize çevirmemize yardım edin."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2387,8 +2385,8 @@ msgstr "Profilinde abone rozeti"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Kaydedebileceğin medya öğesi sayısı"
msgid "Media items you can save for later"
msgstr "Daha sonrası için kaydedebileceğin medya öğesi sayısı"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Вихідний код"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Переклад має звучати природно для того, хто говорить цією мовою. Виправ формулювання або почни нову локаль на {weblate}, і ми додамо її до {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Допоможіть перекласти {product_name} вашою мовою на {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2389,8 +2387,8 @@ msgstr "Значок передплатника в профілі"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Медіа, які можна зберегти"
msgid "Media items you can save for later"
msgstr "Медіа, які можна зберегти на потім"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis
+6 -8
View File
@@ -4,8 +4,8 @@
msgid ""
msgstr ""
"Project-Id-Version: fluxer-marketing\n"
"POT-Creation-Date: 2026-08-15 00:00+0000\n"
"PO-Revision-Date: 2026-08-15 00:00+0000\n"
"POT-Creation-Date: 2026-08-19 00:00+0000\n"
"PO-Revision-Date: 2026-08-19 00:00+0000\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -575,10 +575,8 @@ msgstr "Mã nguồn"
#. Body copy in the locale picker inviting translation contributions through Weblate. Preserve the {weblate} and {product_name} placeholders exactly.
#: fluxer_marketing/generated:company_and_resources.source_and_contribution.translation.weblate_note
msgctxt "company_and_resources.source_and_contribution.translation.weblate_note"
msgid ""
"A translation should sound natural to someone who speaks the language. Correct a phrase or start a new locale on {weblate}, and we will include it in {product_name}."
msgstr ""
"Một bản dịch cần nghe thật tự nhiên với người nói ngôn ngữ đó. Hãy sửa một câu chữ hoặc bắt đầu một ngôn ngữ mới trên {weblate}, và chúng tôi sẽ đưa nó vào {product_name}."
msgid "Help translate {product_name} into your language on {weblate}."
msgstr "Giúp dịch {product_name} sang ngôn ngữ của bạn trên {weblate}."
#. Department or policy category label on content pages. Translate as a compact noun phrase, not as a sentence.
#: fluxer_marketing/generated:content.labels.community
@@ -2389,8 +2387,8 @@ msgstr "Huy hiệu người đăng ký trên hồ sơ của bạn"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.saved_media
msgctxt "pricing_and_tiers.plutonium.perks.saved_media"
msgid "Media items you can save"
msgstr "Mục phương tiện bạn có thể lưu"
msgid "Media items you can save for later"
msgstr "Mục phương tiện bạn có thể lưu để xem sau"
#. Feature label in the Plutonium comparison table. Keep it concise and consistent with product terminology.
#: fluxer_marketing/generated:pricing_and_tiers.plutonium.perks.use_animated_emojis

Some files were not shown because too many files have changed in this diff Show More