diff --git a/examples/vite/src/index.scss b/examples/vite/src/index.scss index 54a8f367a..be264710e 100644 --- a/examples/vite/src/index.scss +++ b/examples/vite/src/index.scss @@ -5,7 +5,6 @@ // v3 CSS import @import url('stream-chat-react/dist/css/index.css') layer(stream-new); -@import url('stream-chat-react/dist/css/emojis.css') layer(stream-new); @import url('./AppSettings/AppSettings.scss') layer(stream-app-overrides); :root { diff --git a/examples/vite/src/stream-imports-layout.scss b/examples/vite/src/stream-imports-layout.scss index 388903a36..dbb5dcb0f 100644 --- a/examples/vite/src/stream-imports-layout.scss +++ b/examples/vite/src/stream-imports-layout.scss @@ -28,7 +28,7 @@ @use 'stream-chat-react/dist/scss/v2/Location/Location-layout'; //@use 'stream-chat-react/dist/scss/v2/Message/Message-layout'; //@use 'stream-chat-react/dist/scss/v2/MessageActionsBox/MessageActionsBox-layout'; -@use 'stream-chat-react/dist/scss/v2/MessageBouncePrompt/MessageBouncePrompt-layout'; +//@use 'stream-chat-react/dist/scss/v2/MessageBouncePrompt/MessageBouncePrompt-layout'; //@use 'stream-chat-react/dist/scss/v2/MessageInput/MessageInput-layout'; // X @use 'stream-chat-react/dist/scss/v2/MessageList/MessageList-layout'; @use 'stream-chat-react/dist/scss/v2/MessageList/VirtualizedMessageList-layout'; diff --git a/examples/vite/src/stream-imports-theme.scss b/examples/vite/src/stream-imports-theme.scss index f93d94dd7..9213f0ea4 100644 --- a/examples/vite/src/stream-imports-theme.scss +++ b/examples/vite/src/stream-imports-theme.scss @@ -23,7 +23,7 @@ @use 'stream-chat-react/dist/scss/v2/Location/Location-theme'; //@use 'stream-chat-react/dist/scss/v2/Message/Message-theme'; //@use 'stream-chat-react/dist/scss/v2/MessageActionsBox/MessageActionsBox-theme'; -@use 'stream-chat-react/dist/scss/v2/MessageBouncePrompt/MessageBouncePrompt-theme'; +//@use 'stream-chat-react/dist/scss/v2/MessageBouncePrompt/MessageBouncePrompt-theme'; @use 'stream-chat-react/dist/scss/v2/MessageList/MessageList-theme'; @use 'stream-chat-react/dist/scss/v2/MessageList/VirtualizedMessageList-theme'; // @use 'stream-chat-react/dist/scss/v2/MessageReactions/MessageReactions-theme'; diff --git a/package.json b/package.json index 23396c725..ad1be162a 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "emoji-mart": "^5.4.0", "react": "^19.0.0 || ^18.0.0 || ^17.0.0", "react-dom": "^19.0.0 || ^18.0.0 || ^17.0.0", - "stream-chat": "^9.32.0" + "stream-chat": "^9.35.0" }, "peerDependenciesMeta": { "@breezystack/lamejs": { @@ -212,7 +212,7 @@ "react-dom": "^19.0.0", "sass": "^1.97.2", "semantic-release": "^25.0.2", - "stream-chat": "^9.32.0", + "stream-chat": "^9.35.0", "ts-jest": "^29.2.5", "typescript": "^5.4.5", "typescript-eslint": "^8.17.0", @@ -221,7 +221,7 @@ "scripts": { "clean": "rm -rf dist", "build": "yarn clean && concurrently './scripts/copy-css.sh' 'yarn build-translations' 'vite build' 'tsc --project tsconfig.lib.json' 'yarn build-styling'", - "build-styling": "sass src/styling/index.scss dist/css/index.css && sass src/plugins/Emojis/styling/index.scss dist/css/emojis.css", + "build-styling": "sass src/styling/index.scss dist/css/index.css", "build-translations": "i18next-cli extract", "coverage": "jest --collectCoverage && codecov", "lint": "yarn prettier --list-different && yarn eslint && yarn validate-translations", diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx index 17c000821..4117ccde1 100644 --- a/src/components/Channel/Channel.tsx +++ b/src/components/Channel/Channel.tsx @@ -967,15 +967,23 @@ const ChannelInner = ( }; const retrySendMessage = async (localMessage: LocalMessage) => { + /** + * If type is not checked, and we for example send message.type === 'error', + * then request fails with error: "message.type must be one of ['' regular system]". + * For now, we re-send any other type to prevent breaking behavior. + */ + + const type = localMessage.type === 'error' ? 'regular' : localMessage.type; updateMessage({ ...localMessage, error: undefined, status: 'sending', + type, }); await doSendMessage({ localMessage, - message: localMessageToNewMessagePayload(localMessage), + message: localMessageToNewMessagePayload({ ...localMessage, type }), }); }; diff --git a/src/components/Dialog/base/ContextMenu.tsx b/src/components/Dialog/base/ContextMenu.tsx index a05fc776e..b7e551a40 100644 --- a/src/components/Dialog/base/ContextMenu.tsx +++ b/src/components/Dialog/base/ContextMenu.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import React, { type ComponentProps, type ComponentType, @@ -10,6 +11,9 @@ import React, { } from 'react'; import clsx from 'clsx'; import { IconChevronLeft } from '../../Icons'; +import { useDialogIsOpen } from '../hooks'; +import type { DialogAnchorProps } from '../service/DialogAnchor'; +import { DialogAnchor } from '../service/DialogAnchor'; export const ContextMenuBackButton = ({ children, @@ -96,7 +100,7 @@ type ContextMenuLevel = { menuClassName?: string; }; -export type ContextMenuProps = Omit, 'children'> & { +type ContextMenuBaseProps = Omit, 'children'> & { backLabel?: ReactNode; items: ContextMenuItemComponent[]; Header?: ContextMenuHeaderComponent; @@ -106,7 +110,24 @@ export type ContextMenuProps = Omit, 'children'> & { onMenuLevelChange?: (level: number) => void; }; -export const ContextMenu = ({ +/** When provided, ContextMenu renders inside DialogAnchor and wires menu level for submenu alignment. */ +type ContextMenuAnchorProps = Partial< + Pick< + DialogAnchorProps, + | 'id' + | 'dialogManagerId' + | 'placement' + | 'referenceElement' + | 'tabIndex' + | 'trapFocus' + | 'allowFlip' + | 'focus' + > +>; + +export type ContextMenuProps = ContextMenuBaseProps & ContextMenuAnchorProps; + +function ContextMenuContent({ backLabel = 'Back', className, Header, @@ -116,7 +137,7 @@ export const ContextMenu = ({ onClose, onMenuLevelChange, ...props -}: ContextMenuProps) => { +}: ContextMenuBaseProps) { const rootLevel = useMemo( () => ({ Header, @@ -207,4 +228,65 @@ export const ContextMenu = ({ ); +} + +export const ContextMenu = (props: ContextMenuProps) => { + const { + allowFlip, + dialogManagerId, + focus, + id, + placement, + referenceElement, + tabIndex, + trapFocus, + ...menuProps + } = props; + + const isAnchored = id != null; + + const [menuLevel, setMenuLevel] = useState(1); + const open = useDialogIsOpen(id ?? '', dialogManagerId); + + useEffect(() => { + if (isAnchored && !open) setMenuLevel(1); + }, [isAnchored, open]); + + const content = ( + + ); + + if (isAnchored) { + const { + backLabel: _b, + Header: _h, + items: _i, + ItemsWrapper: _w, + menuClassName: _m, + onClose: _c, + onMenuLevelChange: _l, + ...anchorDivProps + } = menuProps; + return ( + + {content} + + ); + } + + return content; }; diff --git a/src/components/Dialog/base/ContextMenuButton.tsx b/src/components/Dialog/base/ContextMenuButton.tsx index 3f8a28739..5aebdbca5 100644 --- a/src/components/Dialog/base/ContextMenuButton.tsx +++ b/src/components/Dialog/base/ContextMenuButton.tsx @@ -234,6 +234,24 @@ const ContextMenuButtonWithSubmenu = ({ type ContextMenuButtonProps = BaseContextMenuButtonProps; -export const ContextMenuButton = (props: ContextMenuButtonProps) => ( - -); +export const ContextMenuButton = ({ + onBlur, + onFocus, + ...props +}: ContextMenuButtonProps) => { + const [isFocused, setIsFocused] = useState(false); + return ( + { + setIsFocused(false); + onBlur?.(e); + }} + onFocus={(e) => { + setIsFocused(true); + onFocus?.(e); + }} + /> + ); +}; diff --git a/src/components/Dialog/base/Prompt.tsx b/src/components/Dialog/base/Prompt.tsx new file mode 100644 index 000000000..3d9d5dbf9 --- /dev/null +++ b/src/components/Dialog/base/Prompt.tsx @@ -0,0 +1,59 @@ +import { type ComponentProps, type ComponentType, forwardRef } from 'react'; +import clsx from 'clsx'; + +export const Root = forwardRef>(function PromptRoot( + { children, className, ...props }: ComponentProps<'div'>, + ref, +) { + return ( +
+ {children} +
+ ); +}); + +export type PromptHeaderProps = ComponentProps<'div'> & { + title?: string; + description?: string; + Icon?: ComponentType; +}; + +export const Header = forwardRef(function PromptRoot( + { children, className, description, Icon, title, ...props }, + ref, +) { + return ( +
+ {title ? ( + <> + {Icon && } +
+
{title}
+ {description && ( +
{description}
+ )} +
+ + ) : ( + children + )} +
+ ); +}); + +const Actions = forwardRef>(function PromptRoot( + { children, className, ...props }, + ref, +) { + return ( +
+ {children} +
+ ); +}); + +export const Prompt = { + Actions, + Header, + Root, +}; diff --git a/src/components/Dialog/base/index.ts b/src/components/Dialog/base/index.ts index 31a15e47c..6b34ea2c4 100644 --- a/src/components/Dialog/base/index.ts +++ b/src/components/Dialog/base/index.ts @@ -1,3 +1,4 @@ export * from './Callout'; export * from './ContextMenuButton'; export * from './ContextMenu'; +export * from './Prompt'; diff --git a/src/components/Dialog/service/DialogAnchor.tsx b/src/components/Dialog/service/DialogAnchor.tsx index a7213c5cb..dadfc1408 100644 --- a/src/components/Dialog/service/DialogAnchor.tsx +++ b/src/components/Dialog/service/DialogAnchor.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import type { ComponentProps, PropsWithChildren } from 'react'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { FocusScope } from '@react-aria/focus'; import { DialogPortalEntry } from './DialogPortal'; import { useDialog, useDialogIsOpen } from '../hooks'; @@ -29,22 +29,33 @@ export function useDialogAnchor({ placement, }); + // Freeze reference when dialog opens so submenus (e.g. ContextMenu level 2+) stay aligned to the original anchor + const frozenReferenceRef = useRef(null); + if (open && referenceElement && !frozenReferenceRef.current) { + frozenReferenceRef.current = referenceElement; + } + if (!open) { + frozenReferenceRef.current = null; + } + const effectiveReference = open ? frozenReferenceRef.current : referenceElement; + useEffect(() => { - refs.setReference(referenceElement); - }, [referenceElement, refs]); + refs.setReference(effectiveReference); + }, [effectiveReference, refs]); useEffect(() => { refs.setFloating(popperElement); }, [popperElement, refs]); useEffect(() => { - if (open && popperElement) { + if (open && popperElement && effectiveReference) { + // Re-run when reference becomes available (e.g. after ref is set) or when updateKey changes (e.g. submenu open) // Since the popper's reference element might not be (and usually is not) visible // all the time, it's safer to force popper update before showing it. // update is non-null only if popperElement is non-null update?.(); } - }, [open, placement, popperElement, update, updateKey]); + }, [open, placement, popperElement, update, updateKey, effectiveReference]); if (popperElement && !open) { setPopperElement(null); @@ -83,6 +94,7 @@ export const DialogAnchor = ({ }: DialogAnchorProps) => { const dialog = useDialog({ dialogManagerId, id }); const open = useDialogIsOpen(id, dialogManagerId); + const { setPopperElement, styles } = useDialogAnchor({ allowFlip, open, diff --git a/src/components/Dialog/styling/Prompt.scss b/src/components/Dialog/styling/Prompt.scss new file mode 100644 index 000000000..fe8287244 --- /dev/null +++ b/src/components/Dialog/styling/Prompt.scss @@ -0,0 +1,48 @@ + +@mixin flex-column { + display: flex; + flex-direction: column; + align-items: center; +} + +.str-chat__prompt-root { + @include flex-column; + gap: var(--spacing-2xl); + padding: var(--spacing-xl); + text-align: center; + + .str-chat__prompt-header { + @include flex-column; + gap: var(--spacing-md); + width: 100%; + + svg { + height: var(--button-visual-height-sm); + width: var(--button-visual-height-sm); + } + + .str-chat__prompt-header__copy { + @include flex-column; + gap: var(--spacing-xs); + width: 100%; + + .str-chat__prompt-header__title { + font: var(--str-chat__heading-sm-text); + } + + .str-chat__prompt-header__description { + font: var(--str-chat__caption-default-tex); + } + } + } + + .str-chat__prompt-actions { + @include flex-column; + gap: var(--spacing-xs); + width: 100%; + + button { + width: 100%; + } + } +} \ No newline at end of file diff --git a/src/components/Dialog/styling/index.scss b/src/components/Dialog/styling/index.scss index d7053bb60..9c492da9c 100644 --- a/src/components/Dialog/styling/index.scss +++ b/src/components/Dialog/styling/index.scss @@ -1,3 +1,4 @@ @use 'Callout'; @use 'ContextMenu'; @use 'Dialog'; +@use 'Prompt'; diff --git a/src/components/Form/styling/Form.scss b/src/components/Form/styling/Form.scss index cf5d78bdc..b62dfcb99 100644 --- a/src/components/Form/styling/Form.scss +++ b/src/components/Form/styling/Form.scss @@ -21,4 +21,4 @@ input[type='number'] { .str-chat__form-field-error { margin-left: 0.5rem; } -} \ No newline at end of file +} diff --git a/src/components/Form/styling/NumericInput.scss b/src/components/Form/styling/NumericInput.scss index 04cc55caa..99db30069 100644 --- a/src/components/Form/styling/NumericInput.scss +++ b/src/components/Form/styling/NumericInput.scss @@ -40,7 +40,9 @@ border-radius: var(--button-radius-full, 9999px); color: var(--text-tertiary, #687385); cursor: pointer; - transition: border-color 0.15s ease, color 0.15s ease; + transition: + border-color 0.15s ease, + color 0.15s ease; &:hover:not(:disabled) { color: var(--text-primary, #1a1b25); diff --git a/src/components/Form/styling/SwitchField.scss b/src/components/Form/styling/SwitchField.scss index 3e464b93a..f2617a817 100644 --- a/src/components/Form/styling/SwitchField.scss +++ b/src/components/Form/styling/SwitchField.scss @@ -3,17 +3,29 @@ // CSS variables aligned with Figma tokens; fallbacks from get_variable_defs. .str-chat { - --str-chat__switch-field-background-color: var(--input-cards-bg, var(--background-core-surface-subtle)); + --str-chat__switch-field-background-color: var( + --input-cards-bg, + var(--background-core-surface-subtle) + ); --str-chat__switch-field-border-radius: var(--radius-md); --str-chat__switch-field-title-font-size: var(--typography-font-size-sm, 14px); --str-chat__switch-field-title-font-weight: var(--typography-font-weight-medium, 500); --str-chat__switch-field-title-line-height: var(--typography-line-height-tight, 16px); --str-chat__switch-field-title-color: var(--text-primary, #1a1b25); --str-chat__switch-field-description-font-size: var(--typography-font-size-xs, 12px); - --str-chat__switch-field-description-font-weight: var(--typography-font-weight-regular, 400); + --str-chat__switch-field-description-font-weight: var( + --typography-font-weight-regular, + 400 + ); --str-chat__switch-field-description-color: var(--text-tertiary, #687385); - --str-chat__switch-field__track-off-bg: var(--control-toggle-switch-bg, var(--border-core-on-surface, #a3acba)); - --str-chat__switch-field__track-on-bg: var(--control-toggle-switch-bg-selected, #005fff); + --str-chat__switch-field__track-off-bg: var( + --control-toggle-switch-bg, + var(--border-core-on-surface, #a3acba) + ); + --str-chat__switch-field__track-on-bg: var( + --control-toggle-switch-bg-selected, + #005fff + ); --str-chat__switch-field__track-thumb-bg: var(--base-white, #ffffff); --str-chat__switch-field__track-height: 24px; --str-chat__switch-field__track-radius: var(--button-radius-full, 9999px); @@ -25,12 +37,11 @@ } .str-chat__form__switch-field { - display: flex; align-items: center; gap: var(--spacing-sm); width: 100%; - padding: var(--spacing-sm ) var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); background-color: var(--str-chat__switch-field-background-color); border-radius: var(--str-chat__switch-field-border-radius); box-sizing: border-box; @@ -137,7 +148,7 @@ .str-chat__form__switch-field__label, .str-chat__form__switch-field__label__content { - flex: 1 + flex: 1; } .str-chat__form__switch-field__label--as-error { diff --git a/src/components/Form/styling/TextInputFieldset.scss b/src/components/Form/styling/TextInputFieldset.scss index 5c84534e6..1490d8559 100644 --- a/src/components/Form/styling/TextInputFieldset.scss +++ b/src/components/Form/styling/TextInputFieldset.scss @@ -42,4 +42,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Icons/icons.tsx b/src/components/Icons/icons.tsx index e839dab56..7990dae7a 100644 --- a/src/components/Icons/icons.tsx +++ b/src/components/Icons/icons.tsx @@ -76,11 +76,35 @@ export const IconBellNotification = createIcon( , ); +export const IconBellOff = createIcon( + 'IconBellOff', + , +); + export const IconBookmark = createIcon( 'IconBookmark', , ); +export const IconBookmarkRemove = createIcon( + 'IconBookmarkRemove', + , +); + export const IconBrowserAISparkle = createIcon( 'IconBrowserAISparkle', <> @@ -277,6 +301,26 @@ export const IconClockSolid = createIcon( />, ); +export const IconCloseQuote2 = createIcon( + 'IconCloseQuote2', + <> + + + , +); + export const IconCode = createIcon( 'IconCode', , @@ -400,11 +444,25 @@ export const IconExclamationCircle1 = createIcon( export const IconExclamationCircle = createIcon( 'IconExclamationCircle', - , + <> + + + + , ); export const IconExclamationTriangle1 = createIcon( @@ -846,6 +904,18 @@ export const IconUnlocked = createIcon( , ); +export const IconUnpin = createIcon( + 'IconUnpin', + , +); + export const IconUsers = createIcon( 'IconUsers', , diff --git a/src/components/Icons/styling/Icons.scss b/src/components/Icons/styling/Icons.scss index b28f54b48..b43a14347 100644 --- a/src/components/Icons/styling/Icons.scss +++ b/src/components/Icons/styling/Icons.scss @@ -3,4 +3,9 @@ width: 1em; height: 1em; fill: currentColor; -} \ No newline at end of file +} + +.str-chat__icon--exclamation-circle { + //fill: none; + stroke: currentColor; +} diff --git a/src/components/Icons/styling/index.scss b/src/components/Icons/styling/index.scss index 4089cfd73..c2efae148 100644 --- a/src/components/Icons/styling/index.scss +++ b/src/components/Icons/styling/index.scss @@ -1 +1 @@ -@use 'Icons'; \ No newline at end of file +@use 'Icons'; diff --git a/src/components/MediaRecorder/AudioRecorder/styling/AudioRecorder.scss b/src/components/MediaRecorder/AudioRecorder/styling/AudioRecorder.scss index 9a58cc58e..7f24d7cfb 100644 --- a/src/components/MediaRecorder/AudioRecorder/styling/AudioRecorder.scss +++ b/src/components/MediaRecorder/AudioRecorder/styling/AudioRecorder.scss @@ -42,6 +42,7 @@ .str-chat__audio-recorder__recording-preview { .str-chat__icon--microphone { + height: var(--icon-size-sm); width: var(--icon-size-sm); color: var(--button-destructive-text); } diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 950c3cbeb..43c17790a 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -21,6 +21,7 @@ import { isMessageBlocked, isMessageBounced, isMessageEdited, + isMessageErrorRetryable, isOnlyEmojis, messageHasAttachments, messageHasGiphyAttachment, @@ -52,7 +53,6 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { groupedByUser, handleAction, handleOpenThread, - handleRetry, highlighted, isMessageAIGenerated, isMyMessage, @@ -120,15 +120,14 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { const showReplyCountButton = !threadList && !!message.reply_count; const showIsReplyInChannel = !threadList && message.show_in_channel && message.parent_id; - const allowRetry = message.status === 'failed' && message.error?.status !== 403; + const allowRetry = isMessageErrorRetryable(message); const isBounced = isMessageBounced(message); const isEdited = isMessageEdited(message) && !isAIGenerated; let handleClick: (() => void) | undefined = undefined; - if (allowRetry) { - handleClick = () => handleRetry(message); - } else if (isBounced) { + // todo: should we keep the behavior with click-on-blubble -> show the MessageBounceModal? + if (isBounced) { handleClick = () => setIsBounceDialogOpen(true); } else if (isEdited) { handleClick = () => setEditedTimestampOpen((prev) => !prev); diff --git a/src/components/Message/hooks/useUserRole.ts b/src/components/Message/hooks/useUserRole.ts index bcbcf7a50..443eee9f2 100644 --- a/src/components/Message/hooks/useUserRole.ts +++ b/src/components/Message/hooks/useUserRole.ts @@ -46,13 +46,16 @@ export const useUserRole = ( (isMyMessage && channelCapabilities['delete-own-message']); const canFlag = !isMyMessage && channelCapabilities['flag-message']; - const canMarkUnread = channelCapabilities['read-events']; const canMute = !isMyMessage && channelCapabilities['mute-channel']; + const canBlockUser = !isMyMessage; + const canMarkUnread = channelCapabilities['read-events']; const canQuote = !disableQuotedMessages && channelCapabilities['quote-message']; const canReact = channelCapabilities['send-reaction']; const canReply = channelCapabilities['send-reply']; + const canSendMessage = channelCapabilities['send-message']; return { + canBlockUser, canDelete, canEdit, canFlag, @@ -61,6 +64,7 @@ export const useUserRole = ( canQuote, canReact, canReply, + canSendMessage, isAdmin, isModerator, isMyMessage, diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index c2cd0a193..dbfaa09bd 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -486,7 +486,7 @@ .str-chat__message-error-icon { display: block; position: absolute; - bottom: 0; + top: 8px; inset-inline-end: calc(-1 * calc(#{$icon-size} / 2)); svg { diff --git a/src/components/Message/utils.tsx b/src/components/Message/utils.tsx index 2faf3f04c..2727845fe 100644 --- a/src/components/Message/utils.tsx +++ b/src/components/Message/utils.tsx @@ -441,6 +441,9 @@ export const countEmojis = (text?: string) => { return matches ? matches.length : 0; }; +export const isMessageErrorRetryable = (message: LocalMessage) => + message.status === 'failed' && message.error?.status !== 403; + export const isMessageBounced = ( message: Pick, ) => diff --git a/src/components/MessageActions/MessageActions.tsx b/src/components/MessageActions/MessageActions.tsx index cff1ffc5f..dab95c2ff 100644 --- a/src/components/MessageActions/MessageActions.tsx +++ b/src/components/MessageActions/MessageActions.tsx @@ -6,7 +6,6 @@ import { ContextMenu, type ContextMenuItemComponent, type ContextMenuItemProps, - DialogAnchor, useDialogIsOpen, useDialogOnNearestManager, } from '../Dialog'; @@ -117,25 +116,20 @@ export const MessageActions = ({ - - - + /> )} {quickActionSet.map(({ Component: QuickActionComponent, type }) => ( diff --git a/src/components/MessageActions/RemindMeSubmenu.tsx b/src/components/MessageActions/RemindMeSubmenu.tsx index 5713dae2a..804a04b2a 100644 --- a/src/components/MessageActions/RemindMeSubmenu.tsx +++ b/src/components/MessageActions/RemindMeSubmenu.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useChatContext, useMessageContext, useTranslationContext } from '../../context'; -import type { BaseContextMenuButtonProps } from '../Dialog'; import { ContextMenuBackButton, ContextMenuButton, @@ -9,23 +8,6 @@ import { } from '../Dialog'; import { IconChevronLeft } from '../Icons'; -// todo: do we need to have isMine as a prop? -export type RemindMeActionButtonProps = { isMine: boolean } & BaseContextMenuButtonProps; - -export const RemindMeActionButton = ({ - className, - isMine: _, // eslint-disable-line @typescript-eslint/no-unused-vars - ...props -}: RemindMeActionButtonProps) => { - const { t } = useTranslationContext(); - - return ( - - {t('Remind Me')} - - ); -}; - export const RemindMeSubmenuHeader = () => { const { t } = useTranslationContext(); const { returnToParentMenu } = useContextMenuContext(); diff --git a/src/components/MessageActions/__tests__/MessageActions.test.js b/src/components/MessageActions/__tests__/MessageActions.test.js index b37dc02aa..b735ad887 100644 --- a/src/components/MessageActions/__tests__/MessageActions.test.js +++ b/src/components/MessageActions/__tests__/MessageActions.test.js @@ -399,7 +399,7 @@ describe('', () => { await toggleOpenMessageActions(); await act(async () => { - await fireEvent.click(screen.getByText('Quote')); + await fireEvent.click(screen.getByText('Quote Reply')); }); expect(setQuotedMessageSpy).toHaveBeenCalledWith(message); diff --git a/src/components/MessageActions/defaults.tsx b/src/components/MessageActions/defaults.tsx index 2406572a0..d5d169ae4 100644 --- a/src/components/MessageActions/defaults.tsx +++ b/src/components/MessageActions/defaults.tsx @@ -1,15 +1,41 @@ /* eslint-disable sort-keys */ import React from 'react'; -import { isUserMuted, useMessageComposer, useMessageReminder } from '../../components'; +import { + IconArrowRotateClockwise, + IconBellNotification, + IconBellOff, + IconBookmark, + IconBookmarkRemove, + IconBubbleText6ChatMessage, + IconBubbleWideNotificationChatMessage, + IconCircleBanSign, + IconCloseQuote2, + IconEditBig, + IconFlag2, + IconMute, + IconPeopleAdded, + IconPin, + IconSquareBehindSquare2_Copy, + IconTrashBin, + IconUnpin, + IconVolumeFull, + isUserMuted, + useMessageComposer, + useMessageReminder, +} from '../../components'; import { ReactionIcon as DefaultReactionIcon, ThreadIcon, } from '../../components/Message/icons'; import { ReactionSelectorWithButton } from '../../components/Reactions/ReactionSelectorWithButton'; -import { useChatContext, useMessageContext, useTranslationContext } from '../../context'; import { - RemindMeActionButton, + useChannelActionContext, + useChatContext, + useMessageContext, + useTranslationContext, +} from '../../context'; +import { RemindMeSubmenu, RemindMeSubmenuHeader, } from '../../components/MessageActions/RemindMeSubmenu'; @@ -17,12 +43,34 @@ import type { ContextMenuItemProps } from '../../components/Dialog'; import { ContextMenuButton } from '../../components/Dialog'; import type { MessageActionSetItem } from './MessageActions'; import { QuickMessageActionsButton } from './QuickMessageActionButton'; +import clsx from 'clsx'; const msgActionsBoxButtonClassName = 'str-chat__message-actions-list-item-button' as const; +const msgActionsBoxButtonClassNameDestructive = + 'str-chat__message-actions-list-item-button--destructive' as const; const DefaultMessageActionComponents = { dropdown: { + ThreadReply({ closeMenu }: ContextMenuItemProps) { + const { handleOpenThread } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + { + handleOpenThread(e); + closeMenu(); + }} + > + {t('Thread Reply')} + + ); + }, Quote({ closeMenu }: ContextMenuItemProps) { const { message } = useMessageContext(); const { t } = useTranslationContext(); @@ -43,151 +91,250 @@ const DefaultMessageActionComponents = { return ( { handleQuote(); closeMenu(); }} > - {t('Quote')} + {t('Quote Reply')} ); }, Pin({ closeMenu }: ContextMenuItemProps) { const { handlePin, message } = useMessageContext(); const { t } = useTranslationContext(); - + const isPinned = !!message.pinned; return ( { handlePin(event); closeMenu(); }} > - {!message.pinned ? t('Pin') : t('Unpin')} + {isPinned ? t('Unpin') : t('Pin')} ); }, - MarkUnread({ closeMenu }: ContextMenuItemProps) { - const { handleMarkUnread } = useMessageContext(); + CopyMessageText({ closeMenu }: ContextMenuItemProps) { + const { message } = useMessageContext(); const { t } = useTranslationContext(); return ( { - handleMarkUnread(event); + Icon={IconSquareBehindSquare2_Copy} + onClick={() => { + if (message.text) navigator.clipboard.writeText(message.text); closeMenu(); }} > - {t('Mark as unread')} + {t('Copy Message')} ); }, - Flag({ closeMenu }: ContextMenuItemProps) { - const { handleFlag } = useMessageContext(); + Resend({ closeMenu }: ContextMenuItemProps) { + const { handleRetry, message } = useMessageContext(); const { t } = useTranslationContext(); return ( { - handleFlag(event); + Icon={IconArrowRotateClockwise} + onClick={() => { + handleRetry(message); closeMenu(); }} > - {t('Flag')} + {t('Resend')} ); }, - Mute({ closeMenu }: ContextMenuItemProps) { - const { handleMute, message } = useMessageContext(); - const { mutes } = useChatContext(); + Edit({ closeMenu }: ContextMenuItemProps) { + const messageComposer = useMessageComposer(); + const { message } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + { + messageComposer.initState({ composition: message }); + closeMenu(); + }} + > + {t('Edit Message')} + + ); + }, + MarkUnread({ closeMenu }: ContextMenuItemProps) { + const { handleMarkUnread } = useMessageContext(); const { t } = useTranslationContext(); return ( { - handleMute(event); + handleMarkUnread(event); closeMenu(); }} > - {isUserMuted(message, mutes) ? t('Unmute') : t('Mute')} + {t('Mark as unread')} ); }, - Edit({ closeMenu }: ContextMenuItemProps) { - const messageComposer = useMessageComposer(); + RemindMe({ closeMenu, openSubmenu }: ContextMenuItemProps) { + const { client } = useChatContext(); + const { t } = useTranslationContext(); + const { message } = useMessageContext(); + const reminder = useMessageReminder(message.id); + const messageAlreadyBookmarked = reminder && !reminder?.remindAt; + + if (messageAlreadyBookmarked) return null; + + return ( + { + if (reminder) { + client.reminders.deleteReminder(reminder.id); + closeMenu(); + } else { + openSubmenu({ + Header: RemindMeSubmenuHeader, + Submenu: RemindMeSubmenu, + }); + } + }} + > + {reminder ? t('Remove reminder') : t('Remind me')} + + ); + }, + SaveForLater({ closeMenu }: ContextMenuItemProps) { + const { client } = useChatContext(); const { message } = useMessageContext(); const { t } = useTranslationContext(); + const reminder = useMessageReminder(message.id); + const messageAlreadyHasReminderScheduled = Boolean(reminder && reminder?.remindAt); + + if (messageAlreadyHasReminderScheduled) return null; return ( { - messageComposer.initState({ composition: message }); + if (reminder) client.reminders.deleteReminder(reminder.id); + else client.reminders.createReminder({ messageId: message.id }); closeMenu(); }} > - {t('Edit Message')} + {reminder ? t('Remove save for later') : t('Save for later')} ); }, - Delete({ closeMenu }: ContextMenuItemProps) { - const { handleDelete } = useMessageContext(); + Flag({ closeMenu }: ContextMenuItemProps) { + const { handleFlag } = useMessageContext(); const { t } = useTranslationContext(); return ( { - handleDelete(event); + handleFlag(event); closeMenu(); }} > - {t('Delete')} + {t('Flag')} ); }, - RemindMe({ openSubmenu }: ContextMenuItemProps) { - const { isMyMessage } = useMessageContext(); + Mute({ closeMenu }: ContextMenuItemProps) { + const { handleMute, message } = useMessageContext(); + const { mutes } = useChatContext(); + const { t } = useTranslationContext(); + + const isMuted = isUserMuted(message, mutes); return ( - { - openSubmenu({ - Header: RemindMeSubmenuHeader, - Submenu: RemindMeSubmenu, - }); + Icon={isMuted ? IconVolumeFull : IconMute} + onClick={(event) => { + handleMute(event); + closeMenu(); }} - /> + > + {isMuted ? t('Unmute') : t('Mute')} + ); }, - SaveForLater({ closeMenu }: ContextMenuItemProps) { + Delete({ closeMenu }: ContextMenuItemProps) { + const { removeMessage } = useChannelActionContext(); + const { handleDelete, message } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + { + if (message.type === 'error') removeMessage(message); + else handleDelete(event); + closeMenu(); + }} + > + {t('Delete')} + + ); + }, + BlockUser({ closeMenu }: ContextMenuItemProps) { const { client } = useChatContext(); const { message } = useMessageContext(); const { t } = useTranslationContext(); - const reminder = useMessageReminder(message.id); + const isBlocked = + !message.user?.id || + new Set(client.blockedUsers.getLatestValue().userIds).has(message.user?.id); return ( { - if (reminder) { - client.reminders.deleteReminder(reminder.id); - } else { - client.reminders.createReminder({ messageId: message.id }); + const targetId = message.user?.id; + if (targetId) { + if (isBlocked) client.unBlockUser(targetId); + else client.blockUser(targetId); } closeMenu(); }} > - {reminder ? t('Remove reminder') : t('Save for later')} + {isBlocked ? t('Unblock User') : t('Block User')} ); }, @@ -227,34 +374,34 @@ export const defaultMessageActionSet: MessageActionSetItem[] = [ type: 'react', }, { - Component: DefaultMessageActionComponents.dropdown.Delete, + Component: DefaultMessageActionComponents.dropdown.ThreadReply, placement: 'dropdown', - type: 'delete', + type: 'reply', }, { - Component: DefaultMessageActionComponents.dropdown.Edit, + Component: DefaultMessageActionComponents.dropdown.Quote, placement: 'dropdown', - type: 'edit', + type: 'quote', }, { - Component: DefaultMessageActionComponents.dropdown.Mute, + Component: DefaultMessageActionComponents.dropdown.Pin, placement: 'dropdown', - type: 'mute', + type: 'pin', }, { - Component: DefaultMessageActionComponents.dropdown.Flag, + Component: DefaultMessageActionComponents.dropdown.CopyMessageText, placement: 'dropdown', - type: 'flag', + type: 'copyMessageText', }, { - Component: DefaultMessageActionComponents.dropdown.Pin, + Component: DefaultMessageActionComponents.dropdown.Resend, placement: 'dropdown', - type: 'pin', + type: 'resendMessage', }, { - Component: DefaultMessageActionComponents.dropdown.Quote, + Component: DefaultMessageActionComponents.dropdown.Edit, placement: 'dropdown', - type: 'quote', + type: 'edit', }, { Component: DefaultMessageActionComponents.dropdown.MarkUnread, @@ -271,4 +418,24 @@ export const defaultMessageActionSet: MessageActionSetItem[] = [ placement: 'dropdown', type: 'saveForLater', }, + { + Component: DefaultMessageActionComponents.dropdown.Flag, + placement: 'dropdown', + type: 'flag', + }, + { + Component: DefaultMessageActionComponents.dropdown.Mute, + placement: 'dropdown', + type: 'mute', + }, + { + Component: DefaultMessageActionComponents.dropdown.Delete, + placement: 'dropdown', + type: 'delete', + }, + { + Component: DefaultMessageActionComponents.dropdown.BlockUser, + placement: 'dropdown', + type: 'blockUser', + }, ] as const; diff --git a/src/components/MessageActions/hooks/useBaseMessageActionSetFilter.ts b/src/components/MessageActions/hooks/useBaseMessageActionSetFilter.ts index 96d12dc31..22e1d319c 100644 --- a/src/components/MessageActions/hooks/useBaseMessageActionSetFilter.ts +++ b/src/components/MessageActions/hooks/useBaseMessageActionSetFilter.ts @@ -2,7 +2,11 @@ import { useMemo } from 'react'; import { useChannelStateContext, useMessageContext } from '../../../context'; import { useUserRole } from '../../Message/hooks'; -import { ACTIONS_NOT_WORKING_IN_THREAD } from '../../Message/utils'; +import { + ACTIONS_NOT_WORKING_IN_THREAD, + isMessageBounced, + isMessageErrorRetryable, +} from '../../Message/utils'; import type { MessageActionSetItem } from '../MessageActions'; @@ -19,6 +23,7 @@ export const useBaseMessageActionSetFilter = ( const { initialMessage: isInitialMessage, message } = useMessageContext(); const { channelConfig } = useChannelStateContext(); const { + canBlockUser, canDelete, canEdit, canFlag, @@ -27,20 +32,22 @@ export const useBaseMessageActionSetFilter = ( canQuote, canReact, canReply, + canSendMessage, } = useUserRole(message); const isMessageThreadReply = typeof message.parent_id === 'string'; + const isBounced = isMessageBounced(message); + const allowRetry = isMessageErrorRetryable(message); return useMemo(() => { if (disable) return messageActionSet; // filter out all actions if any of these are true if ( + isBounced || isInitialMessage || // not sure whether this thing even works anymore !message.type || - message.type === 'error' || message.type === 'system' || message.type === 'ephemeral' || - message.status === 'failed' || message.status === 'sending' ) return []; @@ -50,7 +57,19 @@ export const useBaseMessageActionSetFilter = ( if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply) return false; + // failed message menu has special treatment + if (message.error) { + return ( + (type === 'resendMessage' && canSendMessage && (allowRetry || isBounced)) || + (type === 'edit' && canEdit && isBounced) || + (type === 'delete' && canDelete && isBounced) + ); + } + if ( + type === 'resendMessage' || + (type === 'blockUser' && !canBlockUser) || + (type === 'copyMessageText' && !message.text) || (type === 'delete' && !canDelete) || (type === 'edit' && !canEdit) || (type === 'flag' && !canFlag) || @@ -67,6 +86,8 @@ export const useBaseMessageActionSetFilter = ( return true; }); }, [ + allowRetry, + canBlockUser, canDelete, canEdit, canFlag, @@ -75,10 +96,14 @@ export const useBaseMessageActionSetFilter = ( canQuote, canReact, canReply, + canSendMessage, channelConfig, + isBounced, isInitialMessage, isMessageThreadReply, + message.error, message.status, + message.text, message.type, disable, messageActionSet, diff --git a/src/components/MessageActions/styling/MessageActions.scss b/src/components/MessageActions/styling/MessageActions.scss index 0611e9f62..b0179a279 100644 --- a/src/components/MessageActions/styling/MessageActions.scss +++ b/src/components/MessageActions/styling/MessageActions.scss @@ -2,6 +2,13 @@ .str-chat__message-actions-box { min-width: 180px; + + .str-chat__context-menu__button.str-chat__message-actions-list-item-button--destructive { + svg, + .str-chat__context-menu__button__label { + color: var(--accent-error); + } + } } .str-chat__message-options { diff --git a/src/components/MessageBounce/MessageBouncePrompt.tsx b/src/components/MessageBounce/MessageBouncePrompt.tsx index ea77981a7..2e3bc1c09 100644 --- a/src/components/MessageBounce/MessageBouncePrompt.tsx +++ b/src/components/MessageBounce/MessageBouncePrompt.tsx @@ -4,6 +4,10 @@ import { useMessageBounceContext, useTranslationContext } from '../../context'; import type { MouseEventHandler, PropsWithChildren } from 'react'; import type { ModalProps } from '../Modal'; +import { Button } from '../Button'; +import clsx from 'clsx'; +import { IconExclamationCircle } from '../Icons'; +import { Prompt } from '../Dialog/base/Prompt'; export type MessageBouncePromptProps = PropsWithChildren>; @@ -22,34 +26,58 @@ export function MessageBouncePrompt({ children, onClose }: MessageBouncePromptPr } return ( -
-
- {children ?? t('This message did not meet our content guidelines')} -
-
- + - - -
-
+ + + ); } diff --git a/src/components/MessageBounce/styling/MessageBouncePrompt.scss b/src/components/MessageBounce/styling/MessageBouncePrompt.scss new file mode 100644 index 000000000..d9ab9a743 --- /dev/null +++ b/src/components/MessageBounce/styling/MessageBouncePrompt.scss @@ -0,0 +1,9 @@ +@use '../../../styling/utils'; + +.str-chat__message-bounce-prompt { + max-width: 300px; + + .str-chat__prompt-header svg.str-chat__icon--exclamation-circle { + color: var(--text-tertiary); + } +} diff --git a/src/components/MessageBounce/styling/index.scss b/src/components/MessageBounce/styling/index.scss new file mode 100644 index 000000000..b9060626b --- /dev/null +++ b/src/components/MessageBounce/styling/index.scss @@ -0,0 +1 @@ +@use "MessageBouncePrompt"; \ No newline at end of file diff --git a/src/components/MessageInput/AttachmentSelector/AttachmentSelector.tsx b/src/components/MessageInput/AttachmentSelector/AttachmentSelector.tsx index 1b76095b7..223acc11f 100644 --- a/src/components/MessageInput/AttachmentSelector/AttachmentSelector.tsx +++ b/src/components/MessageInput/AttachmentSelector/AttachmentSelector.tsx @@ -17,7 +17,6 @@ import { type ContextMenuItemProps, type ContextMenuOpenSubmenuParams, type ContextMenuSubmenu, - DialogAnchor, useDialogIsOpen, useDialogOnNearestManager, } from '../../Dialog'; @@ -329,7 +328,6 @@ export const AttachmentSelector = ({ const closeModal = useCallback(() => setModalContentActionAction(undefined), []); const [fileInput, setFileInput] = useState(null); - const [menuLevel, setMenuLevel] = useState(1); const menuButtonRef = useRef(null); const contextMenuItems = useMemo( @@ -378,25 +376,20 @@ export const AttachmentSelector = ({ onClick={() => menuDialog?.toggle()} ref={menuButtonRef} /> - - - + /> MAX_OPTIONS_DISPLAYED || - canCastVote || + (canCastVote && allow_user_suggested_options && options.length < MAX_POLL_OPTIONS) || (!is_closed && allow_answers) || (answers_count > 0 && channelCapabilities['query-poll-votes']); diff --git a/src/components/Poll/styling/PollCreationDialog.scss b/src/components/Poll/styling/PollCreationDialog.scss index 007e2cd05..fbda78f2d 100644 --- a/src/components/Poll/styling/PollCreationDialog.scss +++ b/src/components/Poll/styling/PollCreationDialog.scss @@ -24,11 +24,13 @@ } .str-chat__form__input-field__value input, - .str-chat__form__input-field__value.str-chat__form-text-input .str-chat__form-text-input__wrapper { + .str-chat__form__input-field__value.str-chat__form-text-input + .str-chat__form-text-input__wrapper { width: 100%; } - .str-chat__form__input-field__value.str-chat__form-text-input .str-chat__form-text-input__wrapper { + .str-chat__form__input-field__value.str-chat__form-text-input + .str-chat__form-text-input__wrapper { background-color: transparent; } diff --git a/src/components/Poll/styling/PollResults.scss b/src/components/Poll/styling/PollResults.scss index c686bc02b..9d8ea927d 100644 --- a/src/components/Poll/styling/PollResults.scss +++ b/src/components/Poll/styling/PollResults.scss @@ -19,4 +19,3 @@ padding-inline: 1rem; } } - diff --git a/src/i18n/de.json b/src/i18n/de.json index f81194789..102bc1302 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -41,34 +41,51 @@ "Anonymous poll": "Anonyme Umfrage", "Archive": "Archivieren", "aria/Attachment": "Anhang", + "aria/Block User": "Benutzer blockieren", + "aria/Bookmark Message": "Nachricht für später speichern", "aria/Cancel Reply": "Antwort abbrechen", "aria/Cancel upload": "Upload abbrechen", "aria/Channel list": "Kanalliste", "aria/Channel search results": "Kanalsuchergebnisse", "aria/Close thread": "Thread schließen", + "aria/Copy Message Text": "Nachrichtentext kopieren", + "aria/Delete Message": "Nachricht löschen", "aria/Download attachment": "Anhang herunterladen", + "aria/Edit Message": "Nachricht bearbeiten", "aria/Emoji picker": "Emoji-Auswahl", "aria/File input": "Dateieingabe", "aria/File upload": "Datei hochladen", + "aria/Flag Message": "Nachricht melden", "aria/Image input": "Bildeingabe", "aria/Load More Channels": "Mehr Kanäle laden", + "aria/Mark Message Unread": "Als ungelesen markieren", "aria/Menu": "Menü", "aria/Message Options": "Nachrichtenoptionen", + "aria/Mute User": "Benutzer stummschalten", "aria/Open Attachment Selector": "Anhang-Auswahl öffnen", "aria/Open Menu": "Menü öffnen", "aria/Open Message Actions Menu": "Nachrichtenaktionsmenü öffnen", "aria/Open Reaction Selector": "Reaktionsauswahl öffnen", "aria/Open Thread": "Thread öffnen", + "aria/Pin Message": "Nachricht anheften", + "aria/Quote Message": "Nachricht zitieren", "aria/Reaction list": "Reaktionsliste", + "aria/Remind Me Message": "Erinnern", "aria/Remind Me Options": "Erinnerungsoptionen", "aria/Remove attachment": "Anhang entfernen", "aria/Remove location attachment": "Standortanhang entfernen", + "aria/Remove Reminder": "Erinnerung entfernen", + "aria/Remove Save For Later": "„Später ansehen“ entfernen", + "aria/Resend Message": "Nachricht erneut senden", "aria/Retry upload": "Upload erneut versuchen", "aria/Search results": "Suchergebnisse", "aria/Search results header filter button": "Suchergebnisse-Kopfzeilen-Filterbutton", "aria/Send": "Senden", "aria/Show preview": "Vorschau anzeigen", "aria/Stop AI Generation": "KI-Generierung stoppen", + "aria/Unblock User": "Benutzer entsperren", + "aria/Unmute User": "Stummschaltung aufheben", + "aria/Unpin Message": "Anheftung aufheben", "Ask a question": "Eine Frage stellen", "Attach": "Anhängen", "Attach files": "Dateien anhängen", @@ -77,6 +94,7 @@ "Back": "Back", "ban-command-args": "[@Benutzername] [Text]", "ban-command-description": "Einen Benutzer verbannen", + "Block User": "Benutzer blockieren", "Cancel": "Abbrechen", "Cannot seek in the recording": "In der Aufnahme kann nicht gesucht werden", "Channel Missing": "Kanal fehlt", @@ -87,6 +105,7 @@ "Commands": "Befehle", "Commands matching": "Übereinstimmende Befehle", "Connection failure, reconnecting now...": "Verbindungsfehler, Wiederherstellung der Verbindung...", + "Copy Message": "Nachricht kopieren", "Create": "Erstellen", "Create poll": "Umfrage erstellen", "Current location": "Aktueller Standort", @@ -196,16 +215,19 @@ "Previous image": "Vorheriges Bild", "Question": "Frage", "Question is required": "Frage ist erforderlich", - "Quote": "Zitieren", + "Quote Reply": "Zitat-Antwort", "Reached the vote limit. Remove an existing vote first.": "Das Abstimmungslimit wurde erreicht. Entfernen Sie zuerst eine bestehende Stimme.", "Recording format is not supported and cannot be reproduced": "Aufnahmeformat wird nicht unterstützt und kann nicht wiedergegeben werden", + "Remind me": "Erinnern", "Remind Me": "Erinnern", "Remove reminder": "Erinnerung entfernen", + "Remove save for later": "„Später ansehen“ entfernen", "Reply": "Antworten", "Reply to {{ authorName }}": "Antwort an {{ authorName }}", "Reply to Message": "Auf Nachricht antworten", "replyCount_one": "1 Antwort", "replyCount_other": "{{ count }} Antworten", + "Resend": "Erneut senden", "Retry upload": "Upload erneut versuchen", "Save for later": "Für später speichern", "Saved for later": "Für später gespeichert", @@ -253,6 +275,7 @@ "Thread": "Thread", "Thread has not been found": "Thread wurde nicht gefunden", "Thread reply": "Thread-Antwort", + "Thread Reply": "Thread-Antwort", "Threads": "Diskussionen", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -269,6 +292,7 @@ "Unarchive": "Archivierung aufheben", "unban-command-args": "[@Benutzername]", "unban-command-description": "Einen Benutzer entbannen", + "Unblock User": "Benutzer entsperren", "unknown error": "Unbekannter Fehler", "Unmute": "Stummschaltung aufheben", "unmute-command-args": "[@Benutzername]", diff --git a/src/i18n/en.json b/src/i18n/en.json index aa9a16201..f420fb85c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -41,34 +41,51 @@ "Anonymous poll": "Anonymous poll", "Archive": "Archive", "aria/Attachment": "Attachment", + "aria/Block User": "Block User", + "aria/Bookmark Message": "Bookmark Message", "aria/Cancel Reply": "Cancel Reply", "aria/Cancel upload": "Cancel upload", "aria/Channel list": "Channel list", "aria/Channel search results": "Channel search results", "aria/Close thread": "Close thread", + "aria/Copy Message Text": "Copy Message Text", + "aria/Delete Message": "Delete Message", "aria/Download attachment": "Download attachment", + "aria/Edit Message": "Edit Message", "aria/Emoji picker": "Emoji picker", "aria/File input": "File input", "aria/File upload": "File upload", + "aria/Flag Message": "Flag Message", "aria/Image input": "Image input", "aria/Load More Channels": "Load More Channels", + "aria/Mark Message Unread": "Mark Message Unread", "aria/Menu": "Menu", "aria/Message Options": "Message Options", + "aria/Mute User": "Mute User", "aria/Open Attachment Selector": "Open Attachment Selector", "aria/Open Menu": "Open Menu", "aria/Open Message Actions Menu": "Open Message Actions Menu", "aria/Open Reaction Selector": "Open Reaction Selector", "aria/Open Thread": "Open Thread", + "aria/Pin Message": "Pin Message", + "aria/Quote Message": "Quote Message", "aria/Reaction list": "Reaction list", + "aria/Remind Me Message": "Remind Me Message", "aria/Remind Me Options": "aria/Remind Me Options", "aria/Remove attachment": "Remove attachment", "aria/Remove location attachment": "Remove location attachment", + "aria/Remove Reminder": "Remove Reminder", + "aria/Remove Save For Later": "Remove Save For Later", + "aria/Resend Message": "Resend Message", "aria/Retry upload": "Retry upload", "aria/Search results": "Search results", "aria/Search results header filter button": "Search results header filter button", "aria/Send": "Send", "aria/Show preview": "Show preview", "aria/Stop AI Generation": "Stop AI Generation", + "aria/Unblock User": "Unblock User", + "aria/Unmute User": "Unmute User", + "aria/Unpin Message": "Unpin Message", "Ask a question": "Ask a question", "Attach": "Attach", "Attach files": "Attach files", @@ -77,6 +94,7 @@ "Back": "Back", "ban-command-args": "[@username] [text]", "ban-command-description": "Ban a user", + "Block User": "Block User", "Cancel": "Cancel", "Cannot seek in the recording": "Cannot seek in the recording", "Channel Missing": "Channel Missing", @@ -87,6 +105,7 @@ "Commands": "Commands", "Commands matching": "Commands matching", "Connection failure, reconnecting now...": "Connection failure, reconnecting now...", + "Copy Message": "Copy Message", "Create": "Create", "Create poll": "Create poll", "Current location": "Current location", @@ -196,16 +215,19 @@ "Previous image": "Previous image", "Question": "Question", "Question is required": "Question is required", - "Quote": "Quote", + "Quote Reply": "Quote Reply", "Reached the vote limit. Remove an existing vote first.": "Reached the vote limit. Remove an existing vote first.", "Recording format is not supported and cannot be reproduced": "Recording format is not supported and cannot be reproduced", + "Remind me": "Remind me", "Remind Me": "Remind Me", "Remove reminder": "Remove reminder", + "Remove save for later": "Remove save for later", "Reply": "Reply", "Reply to {{ authorName }}": "Reply to {{ authorName }}", "Reply to Message": "Reply to Message", "replyCount_one": "1 reply", "replyCount_other": "{{ count }} replies", + "Resend": "Resend", "Retry upload": "Retry upload", "Save for later": "Save for later", "Saved for later": "Saved for later", @@ -253,6 +275,7 @@ "Thread": "Thread", "Thread has not been found": "Thread has not been found", "Thread reply": "Thread reply", + "Thread Reply": "Thread Reply", "Threads": "Threads", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -269,6 +292,7 @@ "Unarchive": "Unarchive", "unban-command-args": "[@username]", "unban-command-description": "Unban a user", + "Unblock User": "Unblock User", "unknown error": "unknown error", "Unmute": "Unmute", "unmute-command-args": "[@username]", diff --git a/src/i18n/es.json b/src/i18n/es.json index e9a44f21f..9cb16d692 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -46,34 +46,51 @@ "Anonymous poll": "Encuesta anónima", "Archive": "Archivo", "aria/Attachment": "Adjunto", + "aria/Block User": "Bloquear usuario", + "aria/Bookmark Message": "Guardar mensaje", "aria/Cancel Reply": "Cancelar respuesta", "aria/Cancel upload": "Cancelar carga", "aria/Channel list": "Lista de canales", "aria/Channel search results": "Resultados de búsqueda de canales", "aria/Close thread": "Cerrar hilo", + "aria/Copy Message Text": "Copiar texto del mensaje", + "aria/Delete Message": "Eliminar mensaje", "aria/Download attachment": "Descargar adjunto", + "aria/Edit Message": "Editar mensaje", "aria/Emoji picker": "Selector de emojis", "aria/File input": "Entrada de archivo", "aria/File upload": "Carga de archivo", + "aria/Flag Message": "Marcar mensaje", "aria/Image input": "Entrada de imagen", "aria/Load More Channels": "Cargar más canales", + "aria/Mark Message Unread": "Marcar como no leído", "aria/Menu": "Menú", "aria/Message Options": "Opciones de mensaje", + "aria/Mute User": "Silenciar usuario", "aria/Open Attachment Selector": "Abrir selector de adjuntos", "aria/Open Menu": "Abrir menú", "aria/Open Message Actions Menu": "Abrir menú de acciones de mensaje", "aria/Open Reaction Selector": "Abrir selector de reacciones", "aria/Open Thread": "Abrir hilo", + "aria/Pin Message": "Fijar mensaje", + "aria/Quote Message": "Citar mensaje", "aria/Reaction list": "Lista de reacciones", + "aria/Remind Me Message": "Recordarme", "aria/Remind Me Options": "Opciones de recordatorio", "aria/Remove attachment": "Eliminar adjunto", "aria/Remove location attachment": "Eliminar adjunto de ubicación", + "aria/Remove Reminder": "Quitar recordatorio", + "aria/Remove Save For Later": "Quitar guardar para después", + "aria/Resend Message": "Reenviar mensaje", "aria/Retry upload": "Reintentar carga", "aria/Search results": "Resultados de búsqueda", "aria/Search results header filter button": "Botón de filtro del encabezado de resultados de búsqueda", "aria/Send": "Enviar", "aria/Show preview": "Mostrar vista previa", "aria/Stop AI Generation": "Detener generación de IA", + "aria/Unblock User": "Desbloquear usuario", + "aria/Unmute User": "Activar sonido", + "aria/Unpin Message": "Desfijar mensaje", "Ask a question": "Hacer una pregunta", "Attach": "Adjuntar", "Attach files": "Adjuntar archivos", @@ -82,6 +99,7 @@ "Back": "Atrás", "ban-command-args": "[@usuario] [texto]", "ban-command-description": "Prohibir a un usuario", + "Block User": "Bloquear usuario", "Cancel": "Cancelar", "Cannot seek in the recording": "No se puede buscar en la grabación", "Channel Missing": "Falta canal", @@ -92,6 +110,7 @@ "Commands": "Comandos", "Commands matching": "Coincidencia de comandos", "Connection failure, reconnecting now...": "Fallo de conexión, reconectando ahora...", + "Copy Message": "Copiar mensaje", "Create": "Crear", "Create poll": "Crear encuesta", "Current location": "Ubicación actual", @@ -201,17 +220,20 @@ "Previous image": "Imagen anterior", "Question": "Pregunta", "Question is required": "La pregunta es obligatoria", - "Quote": "Citar", + "Quote Reply": "Responder con cita", "Reached the vote limit. Remove an existing vote first.": "Se ha alcanzado el límite de votos. Elimina un voto existente primero.", "Recording format is not supported and cannot be reproduced": "El formato de grabación no es compatible y no se puede reproducir", + "Remind me": "Recordarme", "Remind Me": "Recordarme", "Remove reminder": "Eliminar recordatorio", + "Remove save for later": "Quitar guardar para después", "Reply": "Responder", "Reply to {{ authorName }}": "Responder a {{ authorName }}", "Reply to Message": "Responder al mensaje", "replyCount_one": "1 respuesta", "replyCount_many": "{{ count }} respuestas", "replyCount_other": "{{ count }} respuestas", + "Resend": "Reenviar", "Retry upload": "Reintentar la carga", "Save for later": "Guardar para más tarde", "Saved for later": "Guardado para más tarde", @@ -262,6 +284,7 @@ "Thread": "Hilo", "Thread has not been found": "No se ha encontrado el hilo", "Thread reply": "Respuesta en hilo", + "Thread Reply": "Respuesta en hilo", "Threads": "Hilos", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -278,6 +301,7 @@ "Unarchive": "Desarchivar", "unban-command-args": "[@usuario]", "unban-command-description": "Quitar la prohibición a un usuario", + "Unblock User": "Desbloquear usuario", "unknown error": "error desconocido", "Unmute": "Activar sonido", "unmute-command-args": "[@usuario]", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index a7a1d253a..f6d5ed5e7 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -46,34 +46,51 @@ "Anonymous poll": "Sondage anonyme", "Archive": "Archiver", "aria/Attachment": "Pièce jointe", + "aria/Block User": "Bloquer l'utilisateur", + "aria/Bookmark Message": "Enregistrer le message", "aria/Cancel Reply": "Annuler la réponse", "aria/Cancel upload": "Annuler le téléchargement", "aria/Channel list": "Liste des canaux", "aria/Channel search results": "Résultats de recherche de canaux", "aria/Close thread": "Fermer le fil", + "aria/Copy Message Text": "Copier le texte du message", + "aria/Delete Message": "Supprimer le message", "aria/Download attachment": "Télécharger la pièce jointe", + "aria/Edit Message": "Éditer un message", "aria/Emoji picker": "Sélecteur d'émojis", "aria/File input": "Entrée de fichier", "aria/File upload": "Téléchargement de fichier", + "aria/Flag Message": "Signaler le message", "aria/Image input": "Entrée d'image", "aria/Load More Channels": "Charger plus de canaux", + "aria/Mark Message Unread": "Marquer comme non lu", "aria/Menu": "Menu", "aria/Message Options": "Options du message", + "aria/Mute User": "Mettre en sourdine", "aria/Open Attachment Selector": "Ouvrir le sélecteur de pièces jointes", "aria/Open Menu": "Ouvrir le menu", "aria/Open Message Actions Menu": "Ouvrir le menu des actions du message", "aria/Open Reaction Selector": "Ouvrir le sélecteur de réactions", "aria/Open Thread": "Ouvrir le fil", + "aria/Pin Message": "Épingler le message", + "aria/Quote Message": "Citer le message", "aria/Reaction list": "Liste des réactions", + "aria/Remind Me Message": "Me rappeler", "aria/Remind Me Options": "Options de rappel", "aria/Remove attachment": "Supprimer la pièce jointe", "aria/Remove location attachment": "Supprimer la pièce jointe d'emplacement", + "aria/Remove Reminder": "Supprimer le rappel", + "aria/Remove Save For Later": "Supprimer « Enregistrer pour plus tard »", + "aria/Resend Message": "Renvoyer le message", "aria/Retry upload": "Réessayer le téléchargement", "aria/Search results": "Résultats de recherche", "aria/Search results header filter button": "Bouton de filtre d'en-tête des résultats de recherche", "aria/Send": "Envoyer", "aria/Show preview": "Afficher l'aperçu", "aria/Stop AI Generation": "Arrêter la génération d'IA", + "aria/Unblock User": "Débloquer l'utilisateur", + "aria/Unmute User": "Désactiver muet", + "aria/Unpin Message": "Détacher le message", "Ask a question": "Poser une question", "Attach": "Joindre", "Attach files": "Joindre des fichiers", @@ -82,6 +99,7 @@ "Back": "Retour", "ban-command-args": "[@nomdutilisateur] [texte]", "ban-command-description": "Bannir un utilisateur", + "Block User": "Bloquer l'utilisateur", "Cancel": "Annuler", "Cannot seek in the recording": "Impossible de rechercher dans l'enregistrement", "Channel Missing": "Canal Manquant", @@ -92,6 +110,7 @@ "Commands": "Commandes", "Commands matching": "Correspondance des commandes", "Connection failure, reconnecting now...": "Échec de la connexion, reconnexion en cours...", + "Copy Message": "Copier le message", "Create": "Créer", "Create poll": "Créer un sondage", "Current location": "Emplacement actuel", @@ -201,17 +220,20 @@ "Previous image": "Image précédente", "Question": "Question", "Question is required": "La question est obligatoire", - "Quote": "Citer", + "Quote Reply": "Répondre par citation", "Reached the vote limit. Remove an existing vote first.": "La limite de votes a été atteinte. Supprimez d'abord un vote existant.", "Recording format is not supported and cannot be reproduced": "Le format d'enregistrement n'est pas pris en charge et ne peut pas être reproduit", + "Remind me": "Me rappeler", "Remind Me": "Me rappeler", "Remove reminder": "Supprimer le rappel", + "Remove save for later": "Supprimer « Enregistrer pour plus tard »", "Reply": "Répondre", "Reply to {{ authorName }}": "Répondre à {{ authorName }}", "Reply to Message": "Répondre au message", "replyCount_one": "1 réponse", "replyCount_many": "{{ count }} réponses", "replyCount_other": "{{ count }} réponses", + "Resend": "Renvoyer", "Retry upload": "Réessayer le téléchargement", "Save for later": "Enregistrer pour plus tard", "Saved for later": "Enregistré pour plus tard", @@ -262,6 +284,7 @@ "Thread": "Fil de discussion", "Thread has not been found": "Le fil de discussion n'a pas été trouvé", "Thread reply": "Réponse dans le fil", + "Thread Reply": "Réponse dans le fil", "Threads": "Fils", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -278,6 +301,7 @@ "Unarchive": "Désarchiver", "unban-command-args": "[@nomdutilisateur]", "unban-command-description": "Débannir un utilisateur", + "Unblock User": "Débloquer l'utilisateur", "unknown error": "erreur inconnue", "Unmute": "Désactiver muet", "unmute-command-args": "[@nomdutilisateur]", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index f955595ee..190bb9e90 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -41,34 +41,51 @@ "Anonymous poll": "गुमनाम मतदान", "Archive": "आर्काइव", "aria/Attachment": "अनुलग्नक", + "aria/Block User": "उपयोगकर्ता को ब्लॉक करें", + "aria/Bookmark Message": "संदेश बुकमार्क करें", "aria/Cancel Reply": "उत्तर रद्द करें", "aria/Cancel upload": "अपलोड रद्द करें", "aria/Channel list": "चैनल सूची", "aria/Channel search results": "चैनल खोज परिणाम", "aria/Close thread": "थ्रेड बंद करें", + "aria/Copy Message Text": "संदेश की टेक्स्ट कॉपी करें", + "aria/Delete Message": "संदेश डिलीट करें", "aria/Download attachment": "अनुलग्नक डाउनलोड करें", + "aria/Edit Message": "मैसेज में बदलाव करे", "aria/Emoji picker": "इमोजी चुनने वाला", "aria/File input": "फ़ाइल इनपुट", "aria/File upload": "फ़ाइल अपलोड", + "aria/Flag Message": "संदेश फ्लैग करें", "aria/Image input": "छवि इनपुट", "aria/Load More Channels": "और चैनल लोड करें", + "aria/Mark Message Unread": "अपठित चिह्नित करें", "aria/Menu": "मेन्यू", "aria/Message Options": "संदेश विकल्प", + "aria/Mute User": "उपयोगकर्ता म्यूट करें", "aria/Open Attachment Selector": "अटैचमेंट चयनकर्ता खोलें", "aria/Open Menu": "मेन्यू खोलें", "aria/Open Message Actions Menu": "संदेश क्रिया मेन्यू खोलें", "aria/Open Reaction Selector": "प्रतिक्रिया चयनकर्ता खोलें", "aria/Open Thread": "थ्रेड खोलें", + "aria/Pin Message": "संदेश पिन करें", + "aria/Quote Message": "संदेश उद्धरण", "aria/Reaction list": "प्रतिक्रिया सूची", + "aria/Remind Me Message": "मुझे याद दिलाएं", "aria/Remind Me Options": "रिमाइंडर विकल्प", "aria/Remove attachment": "संलग्नक हटाएं", "aria/Remove location attachment": "स्थान संलग्नक हटाएं", + "aria/Remove Reminder": "अनुस्मारक हटाएं", + "aria/Remove Save For Later": "बाद में देखें हटाएं", + "aria/Resend Message": "संदेश फिर से भेजें", "aria/Retry upload": "अपलोड पुनः प्रयास करें", "aria/Search results": "खोज परिणाम", "aria/Search results header filter button": "खोज परिणाम हेडर फ़िल्टर बटन", "aria/Send": "भेजें", "aria/Show preview": "पूर्वावलोकन दिखाएं", "aria/Stop AI Generation": "एआई जनरेशन रोकें", + "aria/Unblock User": "उपयोगकर्ता अनब्लॉक करें", + "aria/Unmute User": "उपयोगकर्ता अनम्यूट करें", + "aria/Unpin Message": "संदेश अनपिन करें", "Ask a question": "एक प्रश्न पूछें", "Attach": "संलग्न करें", "Attach files": "फाइल्स अटैच करे", @@ -77,6 +94,7 @@ "Back": "वापस", "ban-command-args": "[@उपयोगकर्तनाम] [पाठ]", "ban-command-description": "एक उपयोगकर्ता को प्रतिषेधित करें", + "Block User": "उपयोगकर्ता को ब्लॉक करें", "Cancel": "रद्द करें", "Cannot seek in the recording": "रेकॉर्डिंग में खोज नहीं की जा सकती", "Channel Missing": "चैनल उपलब्ध नहीं है", @@ -87,6 +105,7 @@ "Commands": "कमांड", "Commands matching": "मेल खाती है", "Connection failure, reconnecting now...": "कनेक्शन विफल रहा, अब पुनः कनेक्ट हो रहा है ...", + "Copy Message": "संदेश कॉपी करें", "Create": "बनाएँ", "Create poll": "मतदान बनाएँ", "Current location": "वर्तमान स्थान", @@ -197,16 +216,19 @@ "Previous image": "पिछली छवि", "Question": "प्रश्न", "Question is required": "प्रश्न आवश्यक है", - "Quote": "उद्धरण", + "Quote Reply": "उद्धरण जवाब", "Reached the vote limit. Remove an existing vote first.": "मतदान सीमा तक पहुंच गया। पहले एक मौजूदा वोट हटाएं।", "Recording format is not supported and cannot be reproduced": "रेकॉर्डिंग फ़ॉर्मेट समर्थित नहीं है और पुनः उत्पन्न नहीं किया जा सकता", + "Remind me": "मुझे याद दिलाएं", "Remind Me": "मुझे याद दिलाएं", "Remove reminder": "रिमाइंडर हटाएं", + "Remove save for later": "बाद में देखें हटाएं", "Reply": "जवाब दे दो", "Reply to {{ authorName }}": "{{ authorName }} को जवाब दें", "Reply to Message": "संदेश का जवाब दें", "replyCount_one": "1 रिप्लाई", "replyCount_other": "{{ count }} रिप्लाई", + "Resend": "फिर से भेजें", "Retry upload": "अपलोड फिर से करें", "Save for later": "बाद के लिए सहेजें", "Saved for later": "बाद के लिए सहेजा गया", @@ -254,6 +276,7 @@ "Thread": "रिप्लाई थ्रेड", "Thread has not been found": "थ्रेड नहीं मिला", "Thread reply": "थ्रेड में उत्तर", + "Thread Reply": "थ्रेड में उत्तर", "Threads": "थ्रेड्स", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -270,6 +293,7 @@ "Unarchive": "अनआर्काइव", "unban-command-args": "[@उपयोगकर्तनाम]", "unban-command-description": "एक उपयोगकर्ता को प्रतिषेध से मुक्त करें", + "Unblock User": "उपयोगकर्ता अनब्लॉक करें", "unknown error": "अज्ञात त्रुटि", "Unmute": "अनम्यूट", "unmute-command-args": "[@उपयोगकर्तनाम]", diff --git a/src/i18n/it.json b/src/i18n/it.json index d76ff3ec0..0a9b3ae03 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -46,34 +46,51 @@ "Anonymous poll": "Sondaggio anonimo", "Archive": "Archivia", "aria/Attachment": "Allegato", + "aria/Block User": "Blocca utente", + "aria/Bookmark Message": "Salva messaggio", "aria/Cancel Reply": "Annulla risposta", "aria/Cancel upload": "Annulla caricamento", "aria/Channel list": "Elenco dei canali", "aria/Channel search results": "Risultati della ricerca dei canali", "aria/Close thread": "Chiudi discussione", + "aria/Copy Message Text": "Copia testo messaggio", + "aria/Delete Message": "Elimina messaggio", "aria/Download attachment": "Scarica l'allegato", + "aria/Edit Message": "Modifica messaggio", "aria/Emoji picker": "Selettore di emoji", "aria/File input": "Input di file", "aria/File upload": "Caricamento di file", + "aria/Flag Message": "Segnala messaggio", "aria/Image input": "Input di immagine", "aria/Load More Channels": "Carica altri canali", + "aria/Mark Message Unread": "Contrassegna come non letto", "aria/Menu": "Menu", "aria/Message Options": "Opzioni di messaggio", + "aria/Mute User": "Mute utente", "aria/Open Attachment Selector": "Apri selettore allegati", "aria/Open Menu": "Apri menu", "aria/Open Message Actions Menu": "Apri il menu delle azioni di messaggio", "aria/Open Reaction Selector": "Apri il selettore di reazione", "aria/Open Thread": "Apri discussione", + "aria/Pin Message": "Appunta messaggio", + "aria/Quote Message": "Citazione messaggio", "aria/Reaction list": "Elenco delle reazioni", + "aria/Remind Me Message": "Ricordami", "aria/Remind Me Options": "Opzioni promemoria", "aria/Remove attachment": "Rimuovi allegato", "aria/Remove location attachment": "Rimuovi allegato posizione", + "aria/Remove Reminder": "Rimuovi promemoria", + "aria/Remove Save For Later": "Rimuovi Salva per dopo", + "aria/Resend Message": "Invia di nuovo messaggio", "aria/Retry upload": "Riprova caricamento", "aria/Search results": "Risultati della ricerca", "aria/Search results header filter button": "Pulsante filtro intestazione risultati ricerca", "aria/Send": "Invia", "aria/Show preview": "Mostra anteprima", "aria/Stop AI Generation": "Interrompi generazione IA", + "aria/Unblock User": "Sblocca utente", + "aria/Unmute User": "Riattiva il notifiche", + "aria/Unpin Message": "Rimuovi messaggio appuntato", "Ask a question": "Fai una domanda", "Attach": "Allega", "Attach files": "Allega file", @@ -82,6 +99,7 @@ "Back": "Indietro", "ban-command-args": "[@nomeutente] [testo]", "ban-command-description": "Vietare un utente", + "Block User": "Blocca utente", "Cancel": "Annulla", "Cannot seek in the recording": "Impossibile cercare nella registrazione", "Channel Missing": "Il canale non esiste", @@ -92,6 +110,7 @@ "Commands": "Comandi", "Commands matching": "Comandi corrispondenti", "Connection failure, reconnecting now...": "Errore di connessione, riconnessione in corso...", + "Copy Message": "Copia messaggio", "Create": "Crea", "Create poll": "Crea sondaggio", "Current location": "Posizione attuale", @@ -201,17 +220,20 @@ "Previous image": "Immagine precedente", "Question": "Domanda", "Question is required": "La domanda è obbligatoria", - "Quote": "Citazione", + "Quote Reply": "Rispondi con citazione", "Reached the vote limit. Remove an existing vote first.": "Raggiunto il limite di voti. Rimuovi prima un voto esistente.", "Recording format is not supported and cannot be reproduced": "Il formato di registrazione non è supportato e non può essere riprodotto", + "Remind me": "Promemoria", "Remind Me": "Ricordami", "Remove reminder": "Rimuovi promemoria", + "Remove save for later": "Rimuovi Salva per dopo", "Reply": "Rispondi", "Reply to {{ authorName }}": "Rispondi a {{ authorName }}", "Reply to Message": "Rispondi al messaggio", "replyCount_one": "Una risposta", "replyCount_many": "{{ count }} risposte", "replyCount_other": "{{ count }} risposte", + "Resend": "Invia di nuovo", "Retry upload": "Riprova caricamento", "Save for later": "Salva per dopo", "Saved for later": "Salvato per dopo", @@ -262,6 +284,7 @@ "Thread": "Discussione", "Thread has not been found": "Discussione non trovata", "Thread reply": "Risposta nella discussione", + "Thread Reply": "Risposta nella discussione", "Threads": "Thread", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -278,6 +301,7 @@ "Unarchive": "Ripristina", "unban-command-args": "[@nomeutente]", "unban-command-description": "Togliere il divieto a un utente", + "Unblock User": "Sblocca utente", "unknown error": "errore sconosciuto", "Unmute": "Riattiva il notifiche", "unmute-command-args": "[@nomeutente]", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 97e26d129..f302be471 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -41,34 +41,51 @@ "Anonymous poll": "匿名投票", "Archive": "アーカイブ", "aria/Attachment": "添付ファイル", + "aria/Block User": "ユーザーをブロック", + "aria/Bookmark Message": "メッセージをブックマーク", "aria/Cancel Reply": "返信をキャンセル", "aria/Cancel upload": "アップロードをキャンセル", "aria/Channel list": "チャンネル一覧", "aria/Channel search results": "チャンネル検索結果", "aria/Close thread": "スレッドを閉じる", + "aria/Copy Message Text": "メッセージテキストをコピー", + "aria/Delete Message": "メッセージを削除", "aria/Download attachment": "添付ファイルをダウンロード", + "aria/Edit Message": "メッセージを編集", "aria/Emoji picker": "絵文字ピッカー", "aria/File input": "ファイル入力", "aria/File upload": "ファイルアップロード", + "aria/Flag Message": "メッセージをフラグ", "aria/Image input": "画像入力", "aria/Load More Channels": "さらにチャンネルを読み込む", + "aria/Mark Message Unread": "未読としてマーク", "aria/Menu": "メニュー", "aria/Message Options": "メッセージオプション", + "aria/Mute User": "ユーザーをミュート", "aria/Open Attachment Selector": "添付ファイル選択を開く", "aria/Open Menu": "メニューを開く", "aria/Open Message Actions Menu": "メッセージアクションメニューを開く", "aria/Open Reaction Selector": "リアクションセレクターを開く", "aria/Open Thread": "スレッドを開く", + "aria/Pin Message": "メッセージをピン", + "aria/Quote Message": "メッセージを引用", "aria/Reaction list": "リアクション一覧", + "aria/Remind Me Message": "リマインダー", "aria/Remind Me Options": "リマインダーオプション", "aria/Remove attachment": "添付ファイルを削除", "aria/Remove location attachment": "位置情報の添付ファイルを削除", + "aria/Remove Reminder": "リマインダーを削除", + "aria/Remove Save For Later": "「後で見る」を削除", + "aria/Resend Message": "メッセージを再送信", "aria/Retry upload": "アップロードを再試行", "aria/Search results": "検索結果", "aria/Search results header filter button": "検索結果ヘッダーフィルターボタン", "aria/Send": "送信", "aria/Show preview": "プレビューを表示", "aria/Stop AI Generation": "AI生成を停止", + "aria/Unblock User": "ユーザーのブロックを解除", + "aria/Unmute User": "無音を解除する", + "aria/Unpin Message": "ピンを解除", "Ask a question": "質問する", "Attach": "添付", "Attach files": "ファイルを添付する", @@ -77,6 +94,7 @@ "Back": "戻る", "ban-command-args": "[@ユーザ名] [テキスト]", "ban-command-description": "ユーザーを禁止する", + "Block User": "ユーザーをブロック", "Cancel": "キャンセル", "Cannot seek in the recording": "録音中にシークできません", "Channel Missing": "チャネルがありません", @@ -87,6 +105,7 @@ "Commands": "コマンド", "Commands matching": "一致するコマンド", "Connection failure, reconnecting now...": "接続が失敗しました。再接続中...", + "Copy Message": "メッセージをコピー", "Create": "作成", "Create poll": "投票を作成", "Current location": "現在の位置", @@ -196,16 +215,19 @@ "Previous image": "前の画像", "Question": "質問", "Question is required": "質問は必須です", - "Quote": "引用", + "Quote Reply": "引用返信", "Reached the vote limit. Remove an existing vote first.": "投票制限に達しました。既存の投票を先に削除してください。", "Recording format is not supported and cannot be reproduced": "録音形式はサポートされておらず、再生できません", + "Remind me": "リマインド", "Remind Me": "リマインダー", "Remove reminder": "リマインダーを削除", + "Remove save for later": "「後で見る」を削除", "Reply": "返事", "Reply to {{ authorName }}": "{{ authorName }} に返信", "Reply to Message": "メッセージに返信", "replyCount_one": "1件の返信", "replyCount_other": "{{ count }} 返信", + "Resend": "再送信", "Retry upload": "アップロードを再試行", "Save for later": "後で保存", "Saved for later": "後で保存済み", @@ -253,6 +275,7 @@ "Thread": "スレッド", "Thread has not been found": "スレッドが見つかりませんでした", "Thread reply": "スレッドの返信", + "Thread Reply": "スレッドの返信", "Threads": "スレッド", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -269,6 +292,7 @@ "Unarchive": "アーカイブ解除", "unban-command-args": "[@ユーザ名]", "unban-command-description": "ユーザーの禁止を解除する", + "Unblock User": "ユーザーのブロックを解除", "unknown error": "不明なエラー", "Unmute": "無音を解除する", "unmute-command-args": "[@ユーザ名]", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index d2b0aff4c..d31fed054 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -41,34 +41,51 @@ "Anonymous poll": "익명 투표", "Archive": "아카이브", "aria/Attachment": "첨부 파일", + "aria/Block User": "사용자 차단", + "aria/Bookmark Message": "메시지 북마크", "aria/Cancel Reply": "답장 취소", "aria/Cancel upload": "업로드 취소", "aria/Channel list": "채널 목록", "aria/Channel search results": "채널 검색 결과", "aria/Close thread": "스레드 닫기", + "aria/Copy Message Text": "메시지 텍스트 복사", + "aria/Delete Message": "메시지 삭제", "aria/Download attachment": "첨부 파일 다운로드", + "aria/Edit Message": "메시지 수정", "aria/Emoji picker": "이모지 선택기", "aria/File input": "파일 입력", "aria/File upload": "파일 업로드", + "aria/Flag Message": "메시지 신고", "aria/Image input": "이미지 입력", "aria/Load More Channels": "더 많은 채널 불러오기", + "aria/Mark Message Unread": "읽지 않음으로 표시", "aria/Menu": "메뉴", "aria/Message Options": "메시지 옵션", + "aria/Mute User": "사용자 음소거", "aria/Open Attachment Selector": "첨부 파일 선택기 열기", "aria/Open Menu": "메뉴 열기", "aria/Open Message Actions Menu": "메시지 액션 메뉴 열기", "aria/Open Reaction Selector": "반응 선택기 열기", "aria/Open Thread": "스레드 열기", + "aria/Pin Message": "메시지 고정", + "aria/Quote Message": "메시지 인용", "aria/Reaction list": "반응 목록", + "aria/Remind Me Message": "알림 설정", "aria/Remind Me Options": "알림 옵션", "aria/Remove attachment": "첨부 파일 제거", "aria/Remove location attachment": "위치 첨부 파일 제거", + "aria/Remove Reminder": "알림 제거", + "aria/Remove Save For Later": "나중에 보기 제거", + "aria/Resend Message": "메시지 다시 보내기", "aria/Retry upload": "업로드 다시 시도", "aria/Search results": "검색 결과", "aria/Search results header filter button": "검색 결과 헤더 필터 버튼", "aria/Send": "보내기", "aria/Show preview": "미리보기 표시", "aria/Stop AI Generation": "AI 생성 중지", + "aria/Unblock User": "사용자 차단 해제", + "aria/Unmute User": "음소거 해제", + "aria/Unpin Message": "핀 해제", "Ask a question": "질문하기", "Attach": "첨부", "Attach files": "파일 첨부", @@ -77,6 +94,7 @@ "Back": "뒤로", "ban-command-args": "[@사용자이름] [텍스트]", "ban-command-description": "사용자를 차단", + "Block User": "사용자 차단", "Cancel": "취소", "Cannot seek in the recording": "녹음에서 찾을 수 없습니다", "Channel Missing": "채널 누락", @@ -87,6 +105,7 @@ "Commands": "명령어", "Commands matching": "일치하는 명령", "Connection failure, reconnecting now...": "연결 실패, 지금 다시 연결 중...", + "Copy Message": "메시지 복사", "Create": "생성", "Create poll": "투표 생성", "Current location": "현재 위치", @@ -196,16 +215,19 @@ "Previous image": "이전 이미지", "Question": "질문", "Question is required": "질문이 필요합니다", - "Quote": "인용", + "Quote Reply": "인용 답장", "Reached the vote limit. Remove an existing vote first.": "투표 한도에 도달했습니다. 기존 투표를 먼저 제거하세요.", "Recording format is not supported and cannot be reproduced": "녹음 형식이 지원되지 않으므로 재생할 수 없습니다", + "Remind me": "알림", "Remind Me": "알림 설정", "Remove reminder": "알림 제거", + "Remove save for later": "나중에 보기 제거", "Reply": "답장", "Reply to {{ authorName }}": "{{ authorName }}님에게 답장", "Reply to Message": "메시지에 답장", "replyCount_one": "답장 1개", "replyCount_other": "{{ count }} 답장", + "Resend": "다시 보내기", "Retry upload": "업로드 다시 시도", "Save for later": "나중에 저장", "Saved for later": "나중에 저장됨", @@ -253,6 +275,7 @@ "Thread": "스레드", "Thread has not been found": "스레드를 찾을 수 없습니다", "Thread reply": "스레드 답장", + "Thread Reply": "스레드 답장", "Threads": "스레드", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -269,6 +292,7 @@ "Unarchive": "아카이브 해제", "unban-command-args": "[@사용자이름]", "unban-command-description": "사용자 차단 해제", + "Unblock User": "사용자 차단 해제", "unknown error": "알 수 없는 오류", "Unmute": "음소거 해제", "unmute-command-args": "[@사용자이름]", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 4c11da0fa..ff5157d87 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -41,34 +41,51 @@ "Anonymous poll": "Anonieme peiling", "Archive": "Archief", "aria/Attachment": "Bijlage", + "aria/Block User": "Gebruiker blokkeren", + "aria/Bookmark Message": "Bericht bookmarken", "aria/Cancel Reply": "Antwoord annuleren", "aria/Cancel upload": "Upload annuleren", "aria/Channel list": "Kanaallijst", "aria/Channel search results": "Zoekresultaten voor kanalen", "aria/Close thread": "Draad sluiten", + "aria/Copy Message Text": "Berichttekst kopiëren", + "aria/Delete Message": "Bericht verwijderen", "aria/Download attachment": "Bijlage downloaden", + "aria/Edit Message": "Bericht bewerken", "aria/Emoji picker": "Emoji kiezer", "aria/File input": "Bestandsinvoer", "aria/File upload": "Bestand uploaden", + "aria/Flag Message": "Bericht markeren", "aria/Image input": "Afbeelding invoeren", "aria/Load More Channels": "Meer kanalen laden", + "aria/Mark Message Unread": "Markeren als ongelezen", "aria/Menu": "Menu", "aria/Message Options": "Berichtopties", + "aria/Mute User": "Gebruiker dempen", "aria/Open Attachment Selector": "Open bijlage selector", "aria/Open Menu": "Menu openen", "aria/Open Message Actions Menu": "Menu voor berichtacties openen", "aria/Open Reaction Selector": "Reactiekiezer openen", "aria/Open Thread": "Draad openen", + "aria/Pin Message": "Bericht vastmaken", + "aria/Quote Message": "Bericht citeren", "aria/Reaction list": "Reactielijst", + "aria/Remind Me Message": "Herinner mij", "aria/Remind Me Options": "Herinneringsopties", "aria/Remove attachment": "Bijlage verwijderen", "aria/Remove location attachment": "Locatie bijlage verwijderen", + "aria/Remove Reminder": "Herinnering verwijderen", + "aria/Remove Save For Later": "Verwijder 'Bewaren voor later'", + "aria/Resend Message": "Bericht opnieuw verzenden", "aria/Retry upload": "Upload opnieuw proberen", "aria/Search results": "Zoekresultaten", "aria/Search results header filter button": "Zoekresultaten header filter knop", "aria/Send": "Verzenden", "aria/Show preview": "Voorbeeld tonen", "aria/Stop AI Generation": "AI-generatie stoppen", + "aria/Unblock User": "Gebruiker deblokkeren", + "aria/Unmute User": "Dempen opheffen", + "aria/Unpin Message": "Losmaken", "Ask a question": "Stel een vraag", "Attach": "Bijvoegen", "Attach files": "Bijlage toevoegen", @@ -77,6 +94,7 @@ "Back": "Terug", "ban-command-args": "[@gebruikersnaam] [tekst]", "ban-command-description": "Een gebruiker verbannen", + "Block User": "Gebruiker blokkeren", "Cancel": "Annuleer", "Cannot seek in the recording": "Kan niet zoeken in de opname", "Channel Missing": "Kanaal niet gevonden", @@ -87,6 +105,7 @@ "Commands": "Commando's", "Commands matching": "Bijpassende opdrachten", "Connection failure, reconnecting now...": "Verbindingsfout, opnieuw verbinden...", + "Copy Message": "Bericht kopiëren", "Create": "Maak", "Create poll": "Maak peiling", "Current location": "Huidige locatie", @@ -196,16 +215,19 @@ "Previous image": "Vorige afbeelding", "Question": "Vraag", "Question is required": "Vraag is verplicht", - "Quote": "Citeer", + "Quote Reply": "Citaatantwoord", "Reached the vote limit. Remove an existing vote first.": "Stemlimiet bereikt. Verwijder eerst een bestaande stem.", "Recording format is not supported and cannot be reproduced": "Opnameformaat wordt niet ondersteund en kan niet worden gereproduceerd", + "Remind me": "Herinner me", "Remind Me": "Herinner mij", "Remove reminder": "Herinnering verwijderen", + "Remove save for later": "Verwijder 'Bewaren voor later'", "Reply": "Antwoord", "Reply to {{ authorName }}": "Antwoord aan {{ authorName }}", "Reply to Message": "Antwoord op bericht", "replyCount_one": "1 antwoord", "replyCount_other": "{{ count }} antwoorden", + "Resend": "Opnieuw verzenden", "Retry upload": "Upload opnieuw proberen", "Save for later": "Bewaren voor later", "Saved for later": "Bewaard voor later", @@ -255,6 +277,7 @@ "Thread": "Draadje", "Thread has not been found": "Draadje niet gevonden", "Thread reply": "Draadje antwoord", + "Thread Reply": "Draadje antwoord", "Threads": "Discussies", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -271,6 +294,7 @@ "Unarchive": "Uit archief halen", "unban-command-args": "[@gebruikersnaam]", "unban-command-description": "Een gebruiker debannen", + "Unblock User": "Gebruiker deblokkeren", "unknown error": "onbekende fout", "Unmute": "Dempen opheffen", "unmute-command-args": "[@gebruikersnaam]", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 9197a60b5..41d3582d2 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -46,34 +46,51 @@ "Anonymous poll": "Enquete anônima", "Archive": "Arquivar", "aria/Attachment": "Anexo", + "aria/Block User": "Bloquear usuário", + "aria/Bookmark Message": "Marcar mensagem", "aria/Cancel Reply": "Cancelar resposta", "aria/Cancel upload": "Cancelar upload", "aria/Channel list": "Lista de canais", "aria/Channel search results": "Resultados de pesquisa de canais", "aria/Close thread": "Fechar tópico", + "aria/Copy Message Text": "Copiar texto da mensagem", + "aria/Delete Message": "Excluir mensagem", "aria/Download attachment": "Baixar anexo", + "aria/Edit Message": "Editar Mensagem", "aria/Emoji picker": "Seletor de emojis", "aria/File input": "Entrada de arquivo", "aria/File upload": "Carregar arquivo", + "aria/Flag Message": "Reportar mensagem", "aria/Image input": "Entrada de imagem", "aria/Load More Channels": "Carregar mais canais", + "aria/Mark Message Unread": "Marcar como não lida", "aria/Menu": "Menu", "aria/Message Options": "Opções de mensagem", + "aria/Mute User": "Silenciar usuário", "aria/Open Attachment Selector": "Abrir seletor de anexos", "aria/Open Menu": "Abrir menu", "aria/Open Message Actions Menu": "Abrir menu de ações de mensagem", "aria/Open Reaction Selector": "Abrir seletor de reações", "aria/Open Thread": "Abrir tópico", + "aria/Pin Message": "Fixar mensagem", + "aria/Quote Message": "Citar mensagem", "aria/Reaction list": "Lista de reações", + "aria/Remind Me Message": "Lembrar-me", "aria/Remind Me Options": "Opções de lembrete", "aria/Remove attachment": "Remover anexo", "aria/Remove location attachment": "Remover anexo de localização", + "aria/Remove Reminder": "Remover lembrete", + "aria/Remove Save For Later": "Remover Salvar para depois", + "aria/Resend Message": "Reenviar mensagem", "aria/Retry upload": "Tentar upload novamente", "aria/Search results": "Resultados da pesquisa", "aria/Search results header filter button": "Botão de filtro do cabeçalho dos resultados da pesquisa", "aria/Send": "Enviar", "aria/Show preview": "Mostrar prévia", "aria/Stop AI Generation": "Parar geração de IA", + "aria/Unblock User": "Desbloquear usuário", + "aria/Unmute User": "Ativar som", + "aria/Unpin Message": "Desfixar mensagem", "Ask a question": "Faça uma pergunta", "Attach": "Anexar", "Attach files": "Anexar arquivos", @@ -82,6 +99,7 @@ "Back": "Voltar", "ban-command-args": "[@nomedeusuário] [texto]", "ban-command-description": "Banir um usuário", + "Block User": "Bloquear usuário", "Cancel": "Cancelar", "Cannot seek in the recording": "Não é possível buscar na gravação", "Channel Missing": "Canal ausente", @@ -92,6 +110,7 @@ "Commands": "Comandos", "Commands matching": "Comandos correspondentes", "Connection failure, reconnecting now...": "Falha de conexão, reconectando agora...", + "Copy Message": "Copiar mensagem", "Create": "Criar", "Create poll": "Criar enquete", "Current location": "Localização atual", @@ -201,17 +220,20 @@ "Previous image": "Imagem anterior", "Question": "Pergunta", "Question is required": "A pergunta é obrigatória", - "Quote": "Citar", + "Quote Reply": "Responder com citação", "Reached the vote limit. Remove an existing vote first.": "Limite de votos atingido. Remova um voto existente primeiro.", "Recording format is not supported and cannot be reproduced": "Formato de gravação não é suportado e não pode ser reproduzido", + "Remind me": "Lembrar-me", "Remind Me": "Lembrar-me", "Remove reminder": "Remover lembrete", + "Remove save for later": "Remover Salvar para depois", "Reply": "Responder", "Reply to {{ authorName }}": "Responder a {{ authorName }}", "Reply to Message": "Responder à mensagem", "replyCount_one": "1 resposta", "replyCount_many": "{{ count }} respostas", "replyCount_other": "{{ count }} respostas", + "Resend": "Reenviar", "Retry upload": "Tentar enviar novamente", "Save for later": "Salvar para depois", "Saved for later": "Salvo para depois", @@ -262,6 +284,7 @@ "Thread": "Fio", "Thread has not been found": "Fio não encontrado", "Thread reply": "Resposta no fio", + "Thread Reply": "Resposta no fio", "Threads": "Tópicos", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -278,6 +301,7 @@ "Unarchive": "Desarquivar", "unban-command-args": "[@nomedeusuário]", "unban-command-description": "Desbanir um usuário", + "Unblock User": "Desbloquear usuário", "unknown error": "erro desconhecido", "Unmute": "Ativar som", "unmute-command-args": "[@nomedeusuário]", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 1f6033beb..449ae5c37 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -51,34 +51,51 @@ "Anonymous poll": "Анонимный опрос", "Archive": "Aрхивировать", "aria/Attachment": "Вложение", + "aria/Block User": "Заблокировать пользователя", + "aria/Bookmark Message": "Сохранить сообщение", "aria/Cancel Reply": "Отменить ответ", "aria/Cancel upload": "Отменить загрузку", "aria/Channel list": "Список каналов", "aria/Channel search results": "Результаты поиска по каналам", "aria/Close thread": "Закрыть тему", + "aria/Copy Message Text": "Копировать текст сообщения", + "aria/Delete Message": "Удалить сообщение", "aria/Download attachment": "Скачать вложение", + "aria/Edit Message": "Редактировать сообщение", "aria/Emoji picker": "Выбор эмодзи", "aria/File input": "Ввод файла", "aria/File upload": "Загрузка файла", + "aria/Flag Message": "Пожаловаться на сообщение", "aria/Image input": "Ввод изображения", "aria/Load More Channels": "Загрузить больше каналов", + "aria/Mark Message Unread": "Отметить как непрочитанное", "aria/Menu": "Меню", "aria/Message Options": "Параметры сообщения", + "aria/Mute User": "Отключить уведомления", "aria/Open Attachment Selector": "Открыть выбор вложений", "aria/Open Menu": "Открыть меню", "aria/Open Message Actions Menu": "Открыть меню действий с сообщениями", "aria/Open Reaction Selector": "Открыть селектор реакций", "aria/Open Thread": "Открыть тему", + "aria/Pin Message": "Закрепить сообщение", + "aria/Quote Message": "Цитировать сообщение", "aria/Reaction list": "Список реакций", + "aria/Remind Me Message": "Напомнить мне", "aria/Remind Me Options": "Параметры напоминания", "aria/Remove attachment": "Удалить вложение", "aria/Remove location attachment": "Удалить вложение местоположения", + "aria/Remove Reminder": "Удалить напоминание", + "aria/Remove Save For Later": "Удалить «Сохранить на потом»", + "aria/Resend Message": "Отправить сообщение повторно", "aria/Retry upload": "Повторить загрузку", "aria/Search results": "Результаты поиска", "aria/Search results header filter button": "Кнопка фильтра заголовка результатов поиска", "aria/Send": "Отправить", "aria/Show preview": "Показать предпросмотр", "aria/Stop AI Generation": "Остановить генерацию ИИ", + "aria/Unblock User": "Разблокировать пользователя", + "aria/Unmute User": "Включить уведомления", + "aria/Unpin Message": "Открепить сообщение", "Ask a question": "Задать вопрос", "Attach": "Прикрепить", "Attach files": "Прикрепить файлы", @@ -87,6 +104,7 @@ "Back": "Назад", "ban-command-args": "[@имяпользователя] [текст]", "ban-command-description": "Заблокировать пользователя", + "Block User": "Заблокировать пользователя", "Cancel": "Отмена", "Cannot seek in the recording": "Невозможно осуществить поиск в записи", "Channel Missing": "Канал не найден", @@ -97,6 +115,7 @@ "Commands": "Команды", "Commands matching": "Соответствие команд", "Connection failure, reconnecting now...": "Ошибка соединения, переподключение...", + "Copy Message": "Копировать сообщение", "Create": "Создать", "Create poll": "Создать опрос", "Current location": "Текущее местоположение", @@ -206,11 +225,13 @@ "Previous image": "Предыдущее изображение", "Question": "Вопрос", "Question is required": "Вопрос обязателен", - "Quote": "Цитировать", + "Quote Reply": "Ответ со цитатой", "Reached the vote limit. Remove an existing vote first.": "Достигнут лимит голосов. Сначала удалите существующий голос.", "Recording format is not supported and cannot be reproduced": "Формат записи не поддерживается и не может быть воспроизведен", + "Remind me": "Напомнить мне", "Remind Me": "Напомнить мне", "Remove reminder": "Удалить напоминание", + "Remove save for later": "Удалить «Сохранить на потом»", "Reply": "Ответить", "Reply to {{ authorName }}": "Ответить {{ authorName }}", "Reply to Message": "Ответить на сообщение", @@ -218,6 +239,7 @@ "replyCount_few": "{{ count }} ответов", "replyCount_many": "{{ count }} ответов", "replyCount_other": "{{ count }} ответов", + "Resend": "Отправить повторно", "Retry upload": "Повторить загрузку", "Save for later": "Сохранить на потом", "Saved for later": "Сохранено на потом", @@ -271,6 +293,7 @@ "Thread": "Ветка", "Thread has not been found": "Ветка не найдена", "Thread reply": "Ответ в ветке", + "Thread Reply": "Ответ в ветке", "Threads": "Треды", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -287,6 +310,7 @@ "Unarchive": "Удалить из архива", "unban-command-args": "[@имяпользователя]", "unban-command-description": "Разблокировать пользователя", + "Unblock User": "Разблокировать пользователя", "unknown error": "неизвестная ошибка", "Unmute": "Включить уведомления", "unmute-command-args": "[@имяпользователя]", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 44c8ae7cb..1401a0b78 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -41,34 +41,51 @@ "Anonymous poll": "Anonim anket", "Archive": "Arşivle", "aria/Attachment": "Ek", + "aria/Block User": "Kullanıcıyı engelle", + "aria/Bookmark Message": "Mesajı yer imi ekle", "aria/Cancel Reply": "Cevabı İptal Et", "aria/Cancel upload": "Yüklemeyi İptal Et", "aria/Channel list": "Kanal listesi", "aria/Channel search results": "Kanal arama sonuçları", "aria/Close thread": "Konuyu kapat", + "aria/Copy Message Text": "Mesaj metnini kopyala", + "aria/Delete Message": "Mesajı sil", "aria/Download attachment": "Ek indir", + "aria/Edit Message": "Mesajı Düzenle", "aria/Emoji picker": "Emoji seçici", "aria/File input": "Dosya girişi", "aria/File upload": "Dosya yükleme", + "aria/Flag Message": "Mesajı bayrakla", "aria/Image input": "Resim girişi", "aria/Load More Channels": "Daha Fazla Kanal Yükle", + "aria/Mark Message Unread": "Okunmamış olarak işaretle", "aria/Menu": "Menü", "aria/Message Options": "Mesaj Seçenekleri", + "aria/Mute User": "Kullanıcıyı sustur", "aria/Open Attachment Selector": "Ek Seçiciyi Aç", "aria/Open Menu": "Menüyü Aç", "aria/Open Message Actions Menu": "Mesaj İşlemleri Menüsünü Aç", "aria/Open Reaction Selector": "Tepki Seçiciyi Aç", "aria/Open Thread": "Konuyu Aç", + "aria/Pin Message": "Mesajı sabitle", + "aria/Quote Message": "Mesajı alıntıla", "aria/Reaction list": "Tepki listesi", + "aria/Remind Me Message": "Hatırlat", "aria/Remind Me Options": "Hatırlatma seçenekleri", "aria/Remove attachment": "Eki kaldır", "aria/Remove location attachment": "Konum ekini kaldır", + "aria/Remove Reminder": "Hatırlatıcıyı kaldır", + "aria/Remove Save For Later": "Sonraya kaydet'i kaldır", + "aria/Resend Message": "Mesajı tekrar gönder", "aria/Retry upload": "Yüklemeyi Tekrar Dene", "aria/Search results": "Arama sonuçları", "aria/Search results header filter button": "Arama sonuçları başlık filtre düğmesi", "aria/Send": "Gönder", "aria/Show preview": "Önizlemeyi göster", "aria/Stop AI Generation": "Yapay Zeka Üretimini Durdur", + "aria/Unblock User": "Kullanıcının engelini kaldır", + "aria/Unmute User": "Sesini aç", + "aria/Unpin Message": "Sabitlemeyi kaldır", "Ask a question": "Bir soru sor", "Attach": "Ekle", "Attach files": "Dosya ekle", @@ -77,6 +94,7 @@ "Back": "Geri", "ban-command-args": "[@kullanıcıadı] [metin]", "ban-command-description": "Bir kullanıcıyı yasakla", + "Block User": "Kullanıcıyı engelle", "Cancel": "İptal", "Cannot seek in the recording": "Kayıtta arama yapılamıyor", "Channel Missing": "Kanal bulunamıyor", @@ -87,6 +105,7 @@ "Commands": "Komutlar", "Commands matching": "Eşleşen komutlar", "Connection failure, reconnecting now...": "Bağlantı hatası, tekrar bağlanılıyor...", + "Copy Message": "Mesajı kopyala", "Create": "Oluştur", "Create poll": "Anket oluştur", "Current location": "Mevcut konum", @@ -196,16 +215,19 @@ "Previous image": "Önceki görsel", "Question": "Soru", "Question is required": "Soru gereklidir", - "Quote": "Alıntı", + "Quote Reply": "Alıntıyla yanıtla", "Reached the vote limit. Remove an existing vote first.": "Oylama sınırına ulaşıldı. Önce mevcut bir oyu kaldırın.", "Recording format is not supported and cannot be reproduced": "Kayıt formatı desteklenmiyor ve çoğaltılamıyor", + "Remind me": "Bana hatırlat", "Remind Me": "Hatırlat", "Remove reminder": "Hatırlatıcıyı kaldır", + "Remove save for later": "Sonraya kaydet'i kaldır", "Reply": "Cevapla", "Reply to {{ authorName }}": "{{ authorName }} kişisine yanıt ver", "Reply to Message": "Mesaja Cevapla", "replyCount_one": "1 cevap", "replyCount_other": "{{ count }} cevap", + "Resend": "Tekrar gönder", "Retry upload": "Yüklemeyi yeniden dene", "Save for later": "Daha sonra kaydet", "Saved for later": "Daha sonra kaydedildi", @@ -253,6 +275,7 @@ "Thread": "Konu", "Thread has not been found": "Konu bulunamadı", "Thread reply": "Konu yanıtı", + "Thread Reply": "Konu yanıtı", "Threads": "İleti dizileri", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", @@ -269,6 +292,7 @@ "Unarchive": "Arşivden çıkar", "unban-command-args": "[@kullanıcıadı]", "unban-command-description": "Bir kullanıcının yasağını kaldır", + "Unblock User": "Kullanıcının engelini kaldır", "unknown error": "bilinmeyen hata", "Unmute": "Sesini aç", "unmute-command-args": "[@kullanıcıadı]", diff --git a/src/plugins/Emojis/styling/icons.scss b/src/plugins/Emojis/styling/icons.scss deleted file mode 100644 index 91df62eb1..000000000 --- a/src/plugins/Emojis/styling/icons.scss +++ /dev/null @@ -1,7 +0,0 @@ -.str-chat { - .str-chat__icon--emoji { - path { - stroke-width: 1.2; - } - } -} diff --git a/src/plugins/Emojis/styling/index.scss b/src/plugins/Emojis/styling/index.scss deleted file mode 100644 index eba2d4d73..000000000 --- a/src/plugins/Emojis/styling/index.scss +++ /dev/null @@ -1 +0,0 @@ -@use 'icons'; diff --git a/src/styling/_global-theme-variables.scss b/src/styling/_global-theme-variables.scss index 860f0a9ac..4664ac581 100644 --- a/src/styling/_global-theme-variables.scss +++ b/src/styling/_global-theme-variables.scss @@ -37,36 +37,29 @@ var(--typography-font-family-sans), system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif; - --str-chat__metadata-default-text: normal - var(--typography-font-weight-regular) - var(--typography-font-size-xs) / - var(--typography-line-height-tight) - var(--str-chat__font-family); - - --str-chat__caption-emphasis-text: normal - var(--typography-font-weight-semi-bold) - var(--typography-font-size-sm) / - var(--typography-line-height-tight) - var(--str-chat__font-family); - - --str-chat__body-emphasis-text: normal - var(--typography-font-weight-semi-bold) - var(--typography-font-size-md) / - var(--typography-line-height-normal) - var(--str-chat__font-family); - - --str-chat__heading-xs-text: normal - var(--typography-font-weight-medium) - var(--typography-font-size-sm) / - var(--typography-line-height-tight) - var(--str-chat__font-family); - - --str-chat__heading-sm-text: normal - var(--typography-font-weight-semi-bold) - var(--typography-font-size-md) / - var(--typography-line-height-normal) - var(--str-chat__font-family); + --str-chat__metadata-default-text: normal var(--typography-font-weight-regular) + var(--typography-font-size-xs) / var(--typography-line-height-tight) + var(--str-chat__font-family); + --str-chat__caption-default-text: normal var(--typography-font-weight-regular) + var(--typography-font-size-sm) / var(--typography-line-height-normal) + var(--str-chat__font-family); + + --str-chat__caption-emphasis-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-sm) / var(--typography-line-height-tight) + var(--str-chat__font-family); + + --str-chat__body-emphasis-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-md) / var(--typography-line-height-normal) + var(--str-chat__font-family); + + --str-chat__heading-xs-text: normal var(--typography-font-weight-medium) + var(--typography-font-size-sm) / var(--typography-line-height-tight) + var(--str-chat__font-family); + + --str-chat__heading-sm-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-md) / var(--typography-line-height-normal) + var(--str-chat__font-family); // todo: adapt the old text variables to so that they use the new semantic text variables /* The font used for caption texts */ @@ -189,7 +182,9 @@ /* If a component has a box shadow applied to it, this will be the color used for the shadow */ --str-chat__box-shadow-color: rgba(0, 0, 0, 0.18); - --str-chat__box-shadow-elevation-1: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 6px 12px 0 rgba(0, 0, 0, 0.16), 0 20px 32px 0 rgba(0, 0, 0, 0.12); + --str-chat__box-shadow-elevation-1: + 0 0 0 1px rgba(0, 0, 0, 0.05), 0 6px 12px 0 rgba(0, 0, 0, 0.16), + 0 20px 32px 0 rgba(0, 0, 0, 0.12); /* Used for online indicator and success messages */ --str-chat__info-color: var(--str-chat__green500); diff --git a/src/styling/index.scss b/src/styling/index.scss index ab73b32ae..0eb12d9cc 100644 --- a/src/styling/index.scss +++ b/src/styling/index.scss @@ -33,6 +33,7 @@ @use '../components/MediaRecorder/AudioRecorder/styling' as AudioRecorder; @use '../components/Message/styling' as Message; @use '../components/MessageActions/styling' as MessageActions; +@use '../components/MessageBounce/styling' as MessageBounce; @use '../components/MessageInput/styling' as MessageComposer; @use '../components/Poll/styling' as Poll; @use '../components/Reactions/styling/ReactionList' as ReactionList; diff --git a/yarn.lock b/yarn.lock index a86467892..61543ca41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11919,10 +11919,10 @@ stdin-discarder@^0.2.2: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stream-chat@^9.32.0: - version "9.32.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.32.0.tgz#7bf3fe8bf41ee38f312ed06ad1b59eb0b887cdda" - integrity sha512-7gQ86GNbVj9R6QP4XpJD+2mTJZkkHnrtWq9jdf/pmf5JnKqIET6irXYfJa7pIyZRrFMDL1Iv6eXez34ubg/vHw== +stream-chat@^9.35.0: + version "9.35.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.35.0.tgz#667a046b16dc60675eca102e2c7a4f2a0ca23351" + integrity sha512-WQU3IbVmqUUjwNPgMCiPSKSOUXwfYDbTrQF/ijrSrT0XAFGyi7uZff8vdiTnz9jPE+ZZcuHojN9c9DAw4m1FFQ== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14"