diff --git a/.claude/skills/accessibility/SKILL.md b/.claude/skills/accessibility/SKILL.md
index ab32176cce..fe57c6a277 100644
--- a/.claude/skills/accessibility/SKILL.md
+++ b/.claude/skills/accessibility/SKILL.md
@@ -10,7 +10,7 @@ Use this skill whenever code changes can affect screen-reader users (VoiceOver o
## Non-negotiable rules
1. **Native semantics first.** Use `Pressable`, `TextInput`, `Switch`, `Image` directly. Use `accessibilityRole` only when native semantics cannot represent the widget (`menu`, `menuitem`, `progressbar`, `radio`, `checkbox`, `article`, `alert`, `tablist`, `tab`).
-2. **Never hardcode English** in `accessibilityLabel`/`accessibilityHint`/announcement strings. For SDK `Button`, pass `accessibilityLabelKey='a11y/...'` (and `accessibilityLabelParams` when needed). For non-Button components, use `useA11yLabel('a11y/...', params)` or `t('a11y/...')` directly when you don't need the disabled-state short-circuit. Add the key to all 12 locale files in `package/src/i18n/`.
+2. **Never hardcode English** in `accessibilityLabel`/`accessibilityHint`/announcement strings. For SDK `Button`, pass `accessibilityLabelKey='a11y/...'` (and `accessibilityLabelParams` when needed). For non-Button components, use `useA11yLabel('a11y/...', params)` or `t('a11y/...')` directly when you don't need the disabled-state short-circuit. Add the key to all 13 locale files in `package/src/i18n/` (`ar, en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
3. **Gate behavior on `useAccessibilityContext().enabled`.** A11y is opt-in. New listeners, subscriptions, and announcer mounts must be no-ops when `enabled` is false. New `accessibilityRole`/`accessibilityState` props are fine to render unconditionally — they cost ~zero.
4. **One focusable target per action.** Don't nest `Pressable` inside `Pressable`. Mark inner decorative views with `accessibilityElementsHidden` (iOS) + `importantForAccessibility='no-hide-descendants'` (Android) so the parent carries the label.
5. **Decorative visuals stay hidden from AT.** Icon-only buttons must carry an `accessibilityLabel` on the wrapper, and the SVG icon should be hidden.
@@ -21,7 +21,7 @@ Use this skill whenever code changes can affect screen-reader users (VoiceOver o
- **Foundation primitives** → `package/src/a11y/` (utilities + low-level hooks).
- **Runtime announcer infra** → `package/src/components/Accessibility/` (`NotificationAnnouncer`, `useAccessibilityAnnouncer`, `useIncomingMessageAnnouncements`).
- **Config + provider** → `package/src/contexts/accessibilityContext/`, mounted by `OverlayProvider`.
-- **i18n** → `a11y/*` keys in all 12 locale JSONs (`en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
+- **i18n** → `a11y/*` keys in all 13 locale JSONs (`ar, en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
- **Component-level a11y attributes** → in the component itself.
- **Platform divergence (iOS vs Android)** → use `Platform.OS` or `useResolvedModalAccessibilityProps`. Don't duplicate the file — RN doesn't need `.ios.tsx`/`.android.tsx` splits for a11y.
- **Tests** → nearest `__tests__/` folder; use `@testing-library/react-native` semantic queries (`getByRole`, `getByLabelText`).
@@ -97,6 +97,81 @@ const transitionDuration = reduceMotion ? 0 : 250;
Disable spring animations and limit fade durations when this is true.
+### 6) Curated single focus stop for visual content — `CompositeAccessibilityProbe`
+
+```tsx
+import { CompositeAccessibilityProbe } from 'stream-chat-react-native';
+
+
+ {/* avatars, icons, composed graphics — visually decorative */}
+
+```
+
+Wraps non-Text visual content with a single, cross-platform-stable focus stop carrying the provided `label`. Renders a hidden `Text` sibling that carries the label + a `View accessibilityElementsHidden importantForAccessibility='no-hide-descendants'` around the children. Use for avatars, mute icons, isolated badges, composed graphics that should announce as one semantic unit.
+
+Pass the result of `useA11yLabel(...)` directly — when `label` is `undefined` (a11y opt-out), the probe is a no-op and renders children untouched.
+
+Live examples: `ChannelAvatar.tsx`, `ChannelPreviewMutedStatus.tsx`, `ChannelMessagePreviewDeliveryStatus.tsx`.
+
+### 7) Splicing extra a11y info into compose — `HiddenA11yText`
+
+```tsx
+import { HiddenA11yText } from 'stream-chat-react-native';
+
+
+
+ {selected ? : null}
+
+```
+
+A visually-invisible `` that exists only to contribute extra information to a parent's compose loop. Use it to splice in supplementary state ("you reacted", "and N more", "unread") that doesn't have a natural visible Text in the tree.
+
+Different concern from `CompositeAccessibilityProbe`:
+- `HiddenA11yText` — "inject extra a11y-only text into a compose chain"
+- `CompositeAccessibilityProbe` — "make this whole visual element one focus stop with a curated label"
+
+Live examples: `MessageStatus.tsx`, `ReactionListClustered.tsx`, `ReactionListItem.tsx`.
+
+### 8) Cross-platform auto-compose on a plain View
+
+```tsx
+
+ {/* children whose labels should auto-compose into one announcement */}
+
+```
+
+iOS auto-composes descendant labels when a `View` is `accessible={true}` without an explicit `accessibilityLabel`. Android requires the parent to trip a gate — set any of `accessibilityRole`, `accessibilityState`, `accessibilityActions`, or `accessibilityLabelledBy`. `accessibilityRole='text'` (or `'none'`) is the lightest gate-tripper and a no-op for iOS composition.
+
+`Pressable` defaults `accessibilityRole='button'`, so it auto-trips the gate. Plain `View accessible={true}` without a role does NOT — Android falls back to its default heuristic (reads one visible Text descendant only).
+
+Live example: `MessageFooter.tsx` — `` makes the footer one focus stop on both platforms reading `"Read 11:05 AM"`.
+
+See full memory: `rn_android_a11y_compose_gate.md`.
+
+### 9) Drill-in for interactive children inside a Pressable
+
+```tsx
+
+ {/* mix of interactive children — attachments, quoted reply, poll options, etc. */}
+
+```
+
+When a Pressable wraps mixed content that includes interactive children, the row's default single-focus-stop behavior subsumes them — screen-reader users can't activate the children individually. Setting `accessible={false}` on the Pressable removes the row stop, so VO/TalkBack drill into each interactive child. The Pressable's `onPress` / `onLongPress` still fire because VO/TalkBack synthesize taps at the focused child's coordinates, which land inside the Pressable's hit area.
+
+Live example: `MessageContent.tsx` — `accessible={hasInteractiveContent ? false : undefined}` where `hasInteractiveContent` covers poll, quoted message, attachments, shared location.
+
+### 10) Reshow announcements — `useAnnounceOnShow`
+
+```tsx
+useAnnounceOnShow(visible, useA11yLabel('a11y/Replying to {{user}}', { user: name }));
+```
+
+Announces `label` once each time `visible` flips from `false` to `true`. Resets on hide, so reshows re-announce — unlike `useAnnounceOnStateChange` which dedupes consecutive identical strings.
+
+Use for transient surfaces that appear and disappear repeatedly within a session (modals, autocomplete pickers, reply previews) where the user benefits from hearing the affordance on every reappearance.
+
+Live example: `Reply.tsx` — fires when a reply preview shows in the composer.
+
## Anti-patterns to avoid
- **Hardcoded English `accessibilityLabel`** strings inside component code. For SDK `Button`, use `accessibilityLabelKey='a11y/...'`; otherwise use `useA11yLabel('a11y/...')` or `t('a11y/...')`.
@@ -134,11 +209,17 @@ Recommended for non-trivial changes:
- `package/src/contexts/accessibilityContext/AccessibilityContext.tsx` — config schema + provider + imperative announcer context.
- `package/src/components/Accessibility/hooks/useIncomingMessageAnnouncements.ts` — port of stream-chat-react's hook.
+- `package/src/components/Accessibility/CompositeAccessibilityProbe.tsx` — curated-single-focus-stop wrapper for visual content (avatar, icons, badges).
+- `package/src/components/Accessibility/HiddenA11yText.tsx` — visually-invisible Text that splices extra info into a parent's compose chain ("you reacted", "and N more", etc).
- `package/src/a11y/hooks/useA11yLabel.ts` — translated-label-or-undefined.
+- `package/src/a11y/hooks/useAnnounceOnStateChange.ts` — announce on string-change with dedup.
+- `package/src/a11y/hooks/useAnnounceOnShow.ts` — announce on `visible: false → true` transitions, resets on hide (no dedup).
- `package/src/a11y/hooks/useResolvedModalAccessibilityProps.ts` — modal a11y props.
- `package/src/components/ui/Avatar/Avatar.tsx` — example of `name` + `useA11yLabel` usage.
- `package/src/components/UIComponents/BottomSheetModal.tsx` — example of `useResolvedModalAccessibilityProps`.
- `package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx` — example of `useAnnounceOnStateChange`.
+- `package/src/components/Message/MessageItemView/MessageFooter.tsx` — example of cross-platform auto-compose on a View (`accessible + accessibilityRole='text'`).
+- `package/src/components/Message/MessageItemView/MessageContent.tsx` — example of conditional drill-in (`accessible={hasInteractiveContent ? false : undefined}`).
## Cross-SDK parity
diff --git a/package/src/a11y/hooks/useAnnounceOnShow.ts b/package/src/a11y/hooks/useAnnounceOnShow.ts
new file mode 100644
index 0000000000..d186c9af1b
--- /dev/null
+++ b/package/src/a11y/hooks/useAnnounceOnShow.ts
@@ -0,0 +1,44 @@
+import { useEffect, useRef } from 'react';
+
+import { useAccessibilityAnnouncer } from '../../components/Accessibility/useAccessibilityAnnouncer';
+
+type Options = {
+ /** Delay before the announcement fires; lets entrance animations settle. */
+ delayMs?: number;
+ priority?: 'polite' | 'assertive';
+};
+
+/**
+ * Announces `message` once each time `visible` flips from false to true.
+ * Resets when `visible` flips back to false, so the next show re-announces —
+ * unlike `useAnnounceOnStateChange`, which announces on string change and
+ * dedupes consecutive identical strings.
+ *
+ * Use this for transient surfaces that appear and disappear repeatedly within
+ * a session (modals, autocomplete pickers, bottom sheets) where the user
+ * benefits from hearing the affordance on every reappearance.
+ *
+ * When `message` is undefined (typically because `useA11yLabel` returned
+ * undefined — a11y disabled or key missing), the hook is a no-op.
+ */
+export const useAnnounceOnShow = (
+ visible: boolean,
+ message: string | undefined,
+ { delayMs = 500, priority = 'polite' }: Options = {},
+) => {
+ const announce = useAccessibilityAnnouncer();
+ const announcedRef = useRef(false);
+
+ useEffect(() => {
+ if (!visible) {
+ announcedRef.current = false;
+ return;
+ }
+ if (!message || announcedRef.current) return;
+ const id = setTimeout(() => {
+ announce(message, priority);
+ announcedRef.current = true;
+ }, delayMs);
+ return () => clearTimeout(id);
+ }, [visible, message, announce, priority, delayMs]);
+};
diff --git a/package/src/a11y/index.ts b/package/src/a11y/index.ts
index 46279098ce..ffbd8b290f 100644
--- a/package/src/a11y/index.ts
+++ b/package/src/a11y/index.ts
@@ -3,5 +3,6 @@ export * from './hooks/useScreenReaderEnabled';
export * from './hooks/useReducedMotionPreference';
export * from './hooks/useResolvedModalAccessibilityProps';
export * from './hooks/useAnnounceOnStateChange';
+export * from './hooks/useAnnounceOnShow';
export * from './hooks/useA11yLabel';
export * from './hooks/useAccessibilityActivateAction';
diff --git a/package/src/components/Accessibility/CompositeAccessibilityProbe.tsx b/package/src/components/Accessibility/CompositeAccessibilityProbe.tsx
new file mode 100644
index 0000000000..27acd8364c
--- /dev/null
+++ b/package/src/components/Accessibility/CompositeAccessibilityProbe.tsx
@@ -0,0 +1,48 @@
+import React, { PropsWithChildren } from 'react';
+import { View } from 'react-native';
+
+import { HiddenA11yText } from './HiddenA11yText';
+
+export type CompositeAccessibilityProbeProps = {
+ /**
+ * The accessibility label that VoiceOver / TalkBack should announce for the
+ * wrapped content. When `undefined`, the probe is a no-op and the children
+ * render with no a11y modifications — use this to skip the wrapper when the
+ * SDK's a11y opt-in is off.
+ */
+ label: string | undefined;
+};
+
+/**
+ * Wraps decorative visual content with a single, cross platform stable
+ * accessibility focus stop carrying the provided `label`.
+ *
+ * iOS auto collapses descendants when a parent View is `accessible`, but on
+ * Android `importantForAccessibility='no-hide-descendants'` on the parent
+ * gets defeated by deeply nested descendants that set their own
+ * `accessible={true}` (our SDK's `` does this). A zero size accessible
+ * `` sidesteps that - Text is always accessible by default on both
+ * platforms and carries the label cleanly, while the visual subtree is
+ * marked decorative. More importantly, it's discoverable very very easily
+ * by screen readers.
+ *
+ * Use this anywhere you have non-Text visual content (avatars, icons,
+ * composed graphics) that should announce as a single semantic unit with a
+ * curated label, rather than letting screen readers walk the visual tree
+ * verbosely.
+ */
+export const CompositeAccessibilityProbe = ({
+ children,
+ label,
+}: PropsWithChildren) => {
+ if (!label) return <>{children}>;
+
+ return (
+ <>
+
+
+ {children}
+
+ >
+ );
+};
diff --git a/package/src/components/Accessibility/HiddenA11yText.tsx b/package/src/components/Accessibility/HiddenA11yText.tsx
new file mode 100644
index 0000000000..02200ccabd
--- /dev/null
+++ b/package/src/components/Accessibility/HiddenA11yText.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { StyleSheet, Text } from 'react-native';
+
+export type HiddenA11yTextProps = {
+ /**
+ * The text to inject into the accessibility tree. Rendered as actual Text
+ * content (not as `accessibilityLabel`) so the parent's compose loop on
+ * Android picks it up — Text without its own label isn't
+ * `isAccessibilityFocusable`, so it gets concatenated into the parent's
+ * announcement rather than being skipped as a drill-in target.
+ *
+ * Pass the result of `useA11yLabel(...)` directly: when SDK a11y is
+ * opt-out the value is `undefined` and the component renders nothing.
+ */
+ label: string | undefined;
+};
+
+/**
+ * A visually invisible Text that exists only to contribute extra information
+ * to a screen reader's announcement. Use it inside a parent that auto-composes
+ * descendant labels (Pressable, or any View with `accessible` + `accessibilityRole`)
+ * to splice in supplementary state like "you reacted", "and N more", etc.
+ *
+ * Not for "this whole element should be one focus stop with a curated label" -
+ * use `CompositeAccessibilityProbe` for that.
+ */
+export const HiddenA11yText = ({ label }: HiddenA11yTextProps) => {
+ if (!label) return null;
+ // Both content and accessibilityLabel are set to the same string. Content
+ // keeps the Text on the parent's compose loop (label-only would make it
+ // `isAccessibilityFocusable` and potentially skipped on Android — though
+ // the opacity:0 hidden style usually saves it). accessibilityLabel keeps
+ // testing-library `getByLabelText(...)` queries working.
+ return (
+
+ {label}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ hidden: {
+ height: 1,
+ opacity: 0,
+ overflow: 'hidden',
+ position: 'absolute',
+ width: 1,
+ },
+});
diff --git a/package/src/components/Accessibility/index.ts b/package/src/components/Accessibility/index.ts
index 14d86325c1..612ce7a92a 100644
--- a/package/src/components/Accessibility/index.ts
+++ b/package/src/components/Accessibility/index.ts
@@ -1,3 +1,5 @@
+export * from './CompositeAccessibilityProbe';
+export * from './HiddenA11yText';
export * from './NotificationAnnouncer';
export * from './useAccessibilityAnnouncer';
export * from './hooks/useIncomingMessageAnnouncements';
diff --git a/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx b/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx
index 642fc565ca..928b0ab77b 100644
--- a/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx
+++ b/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx
@@ -13,6 +13,7 @@ import { MessageDeliveryStatus, useMessageDeliveryStatus } from '../../hooks';
import { Check, CheckAll, Time } from '../../icons';
import { primitives } from '../../theme';
import { MessageStatusTypes } from '../../utils/utils';
+import { CompositeAccessibilityProbe } from '../Accessibility/CompositeAccessibilityProbe';
export type ChannelMessagePreviewDeliveryStatusProps = Pick & {
message: MessageResponse | LocalMessage;
@@ -66,11 +67,11 @@ export const ChannelMessagePreviewDeliveryStatus = ({
message.status === MessageStatusTypes.SENDING
? 'a11y/Sending'
: message.status === MessageStatusTypes.RECEIVED && status === MessageDeliveryStatus.READ
- ? 'a11y/Read'
+ ? 'a11y/Read, sent by you'
: status === MessageDeliveryStatus.DELIVERED
- ? 'a11y/Delivered'
+ ? 'a11y/Delivered, sent by you'
: status === MessageDeliveryStatus.SENT
- ? 'a11y/Sent'
+ ? 'a11y/Sent by you'
: 'a11y/Sending',
);
@@ -83,19 +84,21 @@ export const ChannelMessagePreviewDeliveryStatus = ({
}
return (
-
- {message.status === MessageStatusTypes.SENDING ? (
-
- ) : message.status === MessageStatusTypes.RECEIVED &&
- status === MessageDeliveryStatus.READ ? (
-
- ) : status === MessageDeliveryStatus.DELIVERED ? (
-
- ) : status === MessageDeliveryStatus.SENT ? (
-
- ) : null}
- {t('You')}:
-
+
+
+ {message.status === MessageStatusTypes.SENDING ? (
+
+ ) : message.status === MessageStatusTypes.RECEIVED &&
+ status === MessageDeliveryStatus.READ ? (
+
+ ) : status === MessageDeliveryStatus.DELIVERED ? (
+
+ ) : status === MessageDeliveryStatus.SENT ? (
+
+ ) : null}
+ {t('You')}:
+
+
);
};
diff --git a/package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx b/package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx
index d8bccb8563..0188dca1d3 100644
--- a/package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx
+++ b/package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx
@@ -1,7 +1,9 @@
import React from 'react';
+import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { Mute } from '../../icons';
+import { CompositeAccessibilityProbe } from '../Accessibility/CompositeAccessibilityProbe';
/**
* This UI component displays an avatar for a particular channel.
@@ -13,6 +15,11 @@ export const ChannelPreviewMutedStatus = () => {
semantics,
},
} = useTheme();
+ const accessibilityLabel = useA11yLabel('a11y/Muted');
- return ;
+ return (
+
+
+
+ );
};
diff --git a/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx b/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx
index 3d3c875ec5..e17d835b1e 100644
--- a/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx
+++ b/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx
@@ -4,6 +4,7 @@ import { StyleSheet, Text } from 'react-native';
import type { ChannelPreviewProps } from './ChannelPreview';
import type { ChannelPreviewViewPropsWithContext } from './ChannelPreviewView';
+import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
@@ -35,11 +36,16 @@ export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
[created_at, t, tDateTimeParser],
);
+ const visibleDate =
+ formatLatestMessageDate && latestMessageDate
+ ? formatLatestMessageDate(latestMessageDate).toString()
+ : formattedDate;
+ const labelParams = useMemo(() => ({ date: visibleDate ?? '' }), [visibleDate]);
+ const accessibilityLabel = useA11yLabel('a11y/Last message {{date}}', labelParams);
+
return (
-
- {formatLatestMessageDate && latestMessageDate
- ? formatLatestMessageDate(latestMessageDate).toString()
- : formattedDate}
+
+ {visibleDate}
);
};
diff --git a/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx b/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx
index e44ecd302d..34a392fdef 100644
--- a/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx
+++ b/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx
@@ -1,7 +1,8 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { ChannelPreviewProps } from './ChannelPreview';
+import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
import type { ChannelsContextValue } from '../../contexts/channelsContext/ChannelsContext';
import { BadgeNotification } from '../ui/Badge';
@@ -15,12 +16,15 @@ export type ChannelPreviewUnreadCountProps = Pick {
const { maxUnreadCount, unread } = props;
+ const labelParams = useMemo(() => ({ count: unread ?? 0 }), [unread]);
+ const accessibilityLabel = useA11yLabel('a11y/{{count}} unread messages', labelParams);
if (!unread) {
return null;
}
return (
maxUnreadCount ? maxUnreadCount : unread}
size='sm'
type='primary'
diff --git a/package/src/components/ChannelPreview/ChannelPreviewView.tsx b/package/src/components/ChannelPreview/ChannelPreviewView.tsx
index f57c2e066b..d8922068a6 100644
--- a/package/src/components/ChannelPreview/ChannelPreviewView.tsx
+++ b/package/src/components/ChannelPreview/ChannelPreviewView.tsx
@@ -5,6 +5,7 @@ import type { ChannelPreviewProps } from './ChannelPreview';
import type { LastMessageType } from './hooks/useChannelPreviewData';
+import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
import {
ChannelsContextValue,
useChannelsContext,
@@ -70,6 +71,7 @@ const ChannelPreviewViewWithContext = (props: ChannelPreviewViewPropsWithContext
} = useTheme();
const styles = useStyles();
const swipeRegistry = useSwipeRegistryContext();
+ const accessibilityHint = useA11yLabel('a11y/Double tap to open');
const onPress = useStableCallback(() => {
if (swipeRegistry?.hasOpen()) {
@@ -84,6 +86,7 @@ const ChannelPreviewViewWithContext = (props: ChannelPreviewViewPropsWithContext
return (
[
styles.container,
diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx
index 4ce983a7d0..cded1cabbb 100644
--- a/package/src/components/Message/MessageItemView/MessageContent.tsx
+++ b/package/src/components/Message/MessageItemView/MessageContent.tsx
@@ -318,10 +318,21 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
>
);
+ // Drop the Pressable's single-focus-stop behavior when the message contains
+ // interactive children (poll options, attachment cells, the quoted-reply
+ // navigator, shared location). Without this, VO/TalkBack subsume those
+ // children into the row's one announcement and they can't be activated.
+ const hasInteractiveContent = !!(
+ message.poll_id ||
+ message.quoted_message ||
+ message.attachments?.length ||
+ message.shared_location
+ );
+
return (
{
if (onLongPress) {
diff --git a/package/src/components/Message/MessageItemView/MessageFooter.tsx b/package/src/components/Message/MessageItemView/MessageFooter.tsx
index c9b4653aaa..2335b2bd8a 100644
--- a/package/src/components/Message/MessageItemView/MessageFooter.tsx
+++ b/package/src/components/Message/MessageItemView/MessageFooter.tsx
@@ -69,7 +69,12 @@ const MessageFooterWithContext = (props: MessageFooterPropsWithContext) => {
const isEdited = isEditedMessage(message) && !isAIGenerated;
return (
-
+
{Object.keys(members).length > 2 && alignment === 'left' && message.user?.name ? (
{message.user.name}
diff --git a/package/src/components/Message/MessageItemView/MessageStatus.tsx b/package/src/components/Message/MessageItemView/MessageStatus.tsx
index 5046ac7ba7..fffd49472c 100644
--- a/package/src/components/Message/MessageItemView/MessageStatus.tsx
+++ b/package/src/components/Message/MessageItemView/MessageStatus.tsx
@@ -1,6 +1,7 @@
import React, { useMemo } from 'react';
import { StyleSheet, View } from 'react-native';
+import { useA11yLabel } from '../../../a11y/hooks/useA11yLabel';
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
import {
MessageContextValue,
@@ -12,6 +13,7 @@ import { CheckAll } from '../../../icons/checks';
import { Time } from '../../../icons/clock';
import { primitives } from '../../../theme';
import { MessageStatusTypes } from '../../../utils/utils';
+import { HiddenA11yText } from '../../Accessibility/HiddenA11yText';
import { useShouldUseOverlayStyles } from '../hooks/useShouldUseOverlayStyles';
export type MessageStatusPropsWithContext = Pick<
@@ -32,10 +34,6 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
},
} = useTheme();
- if (message.status === MessageStatusTypes.FAILED || message.type === 'error') {
- return null;
- }
-
const hasReadByGreaterThanOne = typeof readBy === 'number' && readBy > 1;
// Variables to determine the status of the message
@@ -48,42 +46,41 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
!read &&
message.type !== 'ephemeral';
+ const accessibilityLabel = useA11yLabel(
+ read
+ ? 'a11y/Read'
+ : delivered
+ ? 'a11y/Delivered'
+ : sending
+ ? 'a11y/Sending'
+ : sent
+ ? 'a11y/Sent'
+ : '',
+ );
+
+ if (message.status === MessageStatusTypes.FAILED || message.type === 'error') {
+ return null;
+ }
+
return (
-
- {read ? (
-
- ) : delivered ? (
-
- ) : sending ? (
-
- ) : sent ? (
-
- ) : null}
-
+ <>
+
+
+ {read ? (
+
+ ) : delivered ? (
+
+ ) : sending ? (
+
+ ) : sent ? (
+
+ ) : null}
+
+ >
);
};
diff --git a/package/src/components/Message/MessageItemView/ReactionList/ReactionListClustered.tsx b/package/src/components/Message/MessageItemView/ReactionList/ReactionListClustered.tsx
index 6115b2e47f..3203f2fe8d 100644
--- a/package/src/components/Message/MessageItemView/ReactionList/ReactionListClustered.tsx
+++ b/package/src/components/Message/MessageItemView/ReactionList/ReactionListClustered.tsx
@@ -1,8 +1,9 @@
-import React, { useMemo } from 'react';
+import React, { Fragment, useMemo } from 'react';
import { StyleProp, StyleSheet, Text, ViewStyle } from 'react-native';
import { ReactionListItemWrapper } from './ReactionListItemWrapper';
+import { useA11yLabel } from '../../../../a11y/hooks/useA11yLabel';
import {
MessageContextValue,
useMessageContext,
@@ -19,6 +20,7 @@ import type { IconProps } from '../../../../icons/utils/base';
import { primitives } from '../../../../theme';
import type { ReactionData } from '../../../../utils/utils';
+import { HiddenA11yText } from '../../../Accessibility/HiddenA11yText';
type Props = Pick & {
size: number;
@@ -65,6 +67,8 @@ export const ReactionListClusteredWithContext = (props: ReactionListClusteredPro
},
} = useTheme();
const styles = useStyles();
+ const accessibilityHint = useA11yLabel('a11y/Double tap to view reactions');
+ const youReacted = useA11yLabel('a11y/you reacted');
const supportedReactionTypes = supportedReactions?.map(
(supportedReaction) => supportedReaction.type,
);
@@ -72,6 +76,9 @@ export const ReactionListClusteredWithContext = (props: ReactionListClusteredPro
const moreReactionsCount = reactionsCount - 4;
const reactionsCountText =
moreReactionsCount < 99 ? moreReactionsCount : `+${moreReactionsCount}`;
+ const moreReactionsA11yText = useA11yLabel('a11y/and {{count}} more reactions', {
+ count: moreReactionsCount,
+ });
const hasSupportedReactions = reactions?.some((reaction) =>
supportedReactionTypes?.includes(reaction.type),
@@ -83,6 +90,8 @@ export const ReactionListClusteredWithContext = (props: ReactionListClusteredPro
return (
{
if (onPress) {
@@ -111,18 +120,23 @@ export const ReactionListClusteredWithContext = (props: ReactionListClusteredPro
}
}}
style={containerStyle}
- accessibilityLabel={accessibilityLabel}
>
{reactions?.slice(0, 4).map((reaction) => (
-
+
+
+ {reaction.own ? : null}
+
))}
- {reactionsCount > 4 ? {reactionsCountText} : null}
+ {reactionsCount > 4 ? (
+
+ {reactionsCountText}
+
+ ) : null}
);
};
diff --git a/package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx b/package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx
index 5f45a5a20a..e91f7e9635 100644
--- a/package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx
+++ b/package/src/components/Message/MessageItemView/ReactionList/ReactionListItem.tsx
@@ -3,6 +3,7 @@ import { StyleSheet, Text } from 'react-native';
import { ReactionListItemWrapper } from './ReactionListItemWrapper';
+import { useA11yLabel } from '../../../../a11y/hooks/useA11yLabel';
import { MessageContextValue } from '../../../../contexts/messageContext/MessageContext';
import { MessagesContextValue } from '../../../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../../../contexts/themeContext/ThemeContext';
@@ -13,6 +14,7 @@ import type { IconProps } from '../../../../icons/utils/base';
import { primitives } from '../../../../theme';
import type { ReactionData } from '../../../../utils/utils';
+import { HiddenA11yText } from '../../../Accessibility/HiddenA11yText';
import { ReactionSummary } from '../../hooks/useProcessReactions';
type Props = Pick & {
@@ -66,12 +68,13 @@ export const ReactionListItem = (props: ReactionListItemProps) => {
},
} = useTheme();
const styles = useStyles();
+ const youReacted = useA11yLabel('a11y/you reacted');
return (
{
if (onLongPress) {
onLongPress({
@@ -121,6 +124,7 @@ export const ReactionListItem = (props: ReactionListItemProps) => {
type={reaction.type}
/>
{showCount ? {reaction.count} : null}
+ {selected ? : null}
);
};
diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx
index e8ea53fab7..acf705c17f 100644
--- a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx
+++ b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx
@@ -5,6 +5,7 @@ import type { Channel as ChannelType, StreamChat } from 'stream-chat';
import { Channel } from '../../..';
import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext';
+import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider';
import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
import { useMockedApis } from '../../../../mock-builders/api/useMockedApis';
import { generateChannelResponse } from '../../../../mock-builders/generator/channel';
@@ -54,13 +55,15 @@ describe('MessageStatus', () => {
channelProps?: Partial>,
) =>
render(
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+ ,
);
// NOTE: Original source had `it.each('string', async () => { ... })` which was a
diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx
index 6ff6d39dae..1ee46bdd8c 100644
--- a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx
+++ b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx
@@ -168,7 +168,7 @@ describe('ReactionListBottom', () => {
{ reactionListPosition: 'bottom', reactionListType: 'segmented' },
);
- const reactionListBottomItem = screen.getByLabelText('Reaction List Item');
+ const reactionListBottomItem = screen.getByTestId('reaction-list-item');
fireEvent(reactionListBottomItem, 'onPress');
diff --git a/package/src/components/Message/hooks/useMessageActionHandlers.ts b/package/src/components/Message/hooks/useMessageActionHandlers.ts
index 5bd9e65a95..8ed432d9c7 100644
--- a/package/src/components/Message/hooks/useMessageActionHandlers.ts
+++ b/package/src/components/Message/hooks/useMessageActionHandlers.ts
@@ -5,10 +5,12 @@ import { UserResponse } from 'stream-chat';
import { useUserMuteActive } from './useUserMuteActive';
+import { useScreenReaderEnabled } from '../../../a11y/hooks/useScreenReaderEnabled';
import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext';
import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext';
import { MessageComposerAPIContextValue } from '../../../contexts/messageComposerContext/MessageComposerAPIContext';
import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext';
+import { useMessageInputContext } from '../../../contexts/messageInputContext/MessageInputContext';
import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext';
import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
@@ -67,6 +69,8 @@ export const useMessageActionHandlers = ({
Pick) => {
const { t } = useTranslationContext();
const { addNotification } = useNotificationApi();
+ const { inputBoxRef } = useMessageInputContext();
+ const screenReaderEnabled = useScreenReaderEnabled();
const handleResendMessage = useStableCallback(() => retrySendMessage(message));
const translatedMessage = useTranslatedMessage(message);
@@ -74,6 +78,9 @@ export const useMessageActionHandlers = ({
const handleQuotedReplyMessage = useStableCallback(() => {
setQuotedMessage(message);
+ if (screenReaderEnabled) {
+ inputBoxRef.current?.focus();
+ }
});
const handleCopyMessage = useStableCallback(() => {
diff --git a/package/src/components/Poll/Poll.tsx b/package/src/components/Poll/Poll.tsx
index 8c839cfa81..0716485fbc 100644
--- a/package/src/components/Poll/Poll.tsx
+++ b/package/src/components/Poll/Poll.tsx
@@ -6,18 +6,14 @@ import { PollOption as PollOptionClass } from 'stream-chat';
import { PollOption, ShowAllOptionsButton } from './components';
import { PollUIStateProvider } from './contexts/PollUIStateContext';
-import { usePollAccessibilityActions } from './hooks/usePollAccessibilityActions';
-import { usePollAccessibilityLabel } from './hooks/usePollAccessibilityLabel';
import { usePollState } from './hooks/usePollState';
-import { useA11yLabel } from '../../a11y/hooks/useA11yLabel';
import {
PollContextProvider,
PollContextValue,
useTheme,
useTranslationContext,
} from '../../contexts';
-import { useAccessibilityContext } from '../../contexts/accessibilityContext/AccessibilityContext';
import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
import { primitives } from '../../theme';
@@ -66,10 +62,6 @@ export const PollContent = () => {
const styles = useStyles();
const { PollButtons: PollButtonsComponent, PollHeader: PollHeaderComponent } =
useComponentsContext();
- const { enabled: a11yEnabled } = useAccessibilityContext();
- const accessibilityHint = useA11yLabel('a11y/Double tap and hold to activate contextual menu');
- const accessibilityLabel = usePollAccessibilityLabel();
- const { accessibilityActions, onAccessibilityAction } = usePollAccessibilityActions();
const {
theme: {
@@ -79,24 +71,8 @@ export const PollContent = () => {
},
} = useTheme();
- // NOTE: Android custom accessibilityActions are broken in RN < 0.83.2 —
- // see facebook/react-native#47268, fixed by PR #52724. On affected versions
- // the actions menu surfaces only a subset of the list and dispatch
- // announces "Action not supported". iOS works correctly on all versions.
- // Once the SDK's minimum RN reaches 0.83.2, wrap the descendants below in
- // so Android
- // TalkBack groups them under the composite rather than exposing each
- // interactive child as a separate focus stop.
return (
-
+
{options?.slice(0, defaultPollOptionCount)?.map((option: PollOptionClass) => (
diff --git a/package/src/components/Poll/hooks/__tests__/usePollAccessibilityActions.test.tsx b/package/src/components/Poll/hooks/__tests__/usePollAccessibilityActions.test.tsx
deleted file mode 100644
index fabff2cf88..0000000000
--- a/package/src/components/Poll/hooks/__tests__/usePollAccessibilityActions.test.tsx
+++ /dev/null
@@ -1,358 +0,0 @@
-import React from 'react';
-
-import type { AccessibilityActionEvent } from 'react-native';
-
-import { act, renderHook } from '@testing-library/react-native';
-
-import { AccessibilityProvider } from '../../../../contexts/accessibilityContext/AccessibilityContext';
-import { TranslationProvider } from '../../../../contexts/translationContext/TranslationContext';
-import { usePollAccessibilityActions } from '../usePollAccessibilityActions';
-
-const mockOpenAddComment = jest.fn();
-const mockOpenAllComments = jest.fn();
-const mockOpenAllOptions = jest.fn();
-const mockOpenSuggestOption = jest.fn();
-const mockOpenViewResults = jest.fn();
-const mockEndVote = jest.fn();
-const mockToggleVote = jest.fn();
-
-jest.mock('../../contexts/PollUIStateContext', () => ({
- usePollUIStateContext: () => ({
- openAddComment: mockOpenAddComment,
- openAllComments: mockOpenAllComments,
- openAllOptions: mockOpenAllOptions,
- openSuggestOption: mockOpenSuggestOption,
- openViewResults: mockOpenViewResults,
- }),
-}));
-
-jest.mock('../usePollStateStore', () => ({
- usePollStateStore: (selector: (state: unknown) => unknown) => selector(mockPollState),
-}));
-
-jest.mock('../useEndVote', () => ({
- useEndVote: () => mockEndVote,
-}));
-
-jest.mock('../usePollVoteToggle', () => ({
- usePollVoteToggle: () => mockToggleVote,
-}));
-
-const mockChatContext = { client: { userID: 'me' } };
-const mockOwnCapabilities = { castPollVote: true };
-
-jest.mock('../../../../contexts', () => {
- const actual = jest.requireActual('../../../../contexts');
- return {
- ...actual,
- useChatContext: () => mockChatContext,
- useOwnCapabilitiesContext: () => mockOwnCapabilities,
- };
-});
-
-let mockPollState: Record = {};
-
-const setPollState = (state: Record) => {
- mockPollState = state;
-};
-
-const setCastPollVote = (allowed: boolean) => {
- mockOwnCapabilities.castPollVote = allowed;
-};
-
-const setUserID = (id: string) => {
- mockChatContext.client.userID = id;
-};
-
-const t = (key: string, vars?: Record) => {
- if (!vars) return key;
- if (key === 'a11y/Vote on {{option}}') return `Vote on ${vars.option}`;
- return key;
-};
-
-const wrapper =
- (enabled: boolean) =>
- ({ children }: { children: React.ReactNode }) => (
-
- null,
- } as never
- }
- >
- {children}
-
-
- );
-
-const buildOption = (id: string, text: string) => ({ id, text });
-
-const fireAction = (
- handler: ((event: AccessibilityActionEvent) => void) | undefined,
- actionName: string,
-) => {
- handler?.({ nativeEvent: { actionName } } as AccessibilityActionEvent);
-};
-
-beforeEach(() => {
- mockOpenAddComment.mockClear();
- mockOpenAllComments.mockClear();
- mockOpenAllOptions.mockClear();
- mockOpenSuggestOption.mockClear();
- mockOpenViewResults.mockClear();
- mockEndVote.mockClear();
- mockToggleVote.mockClear();
- setCastPollVote(true);
- setUserID('me');
-});
-
-describe('usePollAccessibilityActions', () => {
- it('returns undefined when accessibility is disabled', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'A')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(false),
- });
-
- expect(result.current.accessibilityActions).toBeUndefined();
- expect(result.current.onAccessibilityAction).toBeUndefined();
- });
-
- it('every action uses the same human label for name and label', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza'), buildOption('o2', 'Pasta')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const actions = result.current.accessibilityActions;
- expect(actions).toBeDefined();
- for (const action of actions ?? []) {
- expect(action.name).toBe(action.label);
- }
- });
-
- it('exposes only View Results for an ended poll', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: true,
- options: [buildOption('o1', 'A'), buildOption('o2', 'B')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels).toEqual(['View Results']);
- });
-
- it('lists vote actions with the option text, plus End vote / Add comment / Suggest option for creator', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza'), buildOption('o2', 'Pasta')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels).toEqual([
- 'View Results',
- 'Vote on Pizza',
- 'Vote on Pasta',
- 'a11y/End vote',
- 'Add a comment',
- 'Suggest an option',
- ]);
- });
-
- it('omits End vote when the current user is not the creator', () => {
- setUserID('someone-else');
- setPollState({
- allow_answers: false,
- allow_user_suggested_options: false,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels).toEqual(['View Results', 'Vote on Pizza']);
- });
-
- it('omits vote actions when the user lacks castPollVote capability', () => {
- setCastPollVote(false);
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: false,
- created_by: { id: 'somebody' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels?.some((l) => l?.startsWith('Vote on'))).toBe(false);
- });
-
- it('exposes "View N comments" when the poll has answers', () => {
- setPollState({
- allow_answers: false,
- allow_user_suggested_options: false,
- answers_count: 4,
- created_by: { id: 'somebody' },
- is_closed: true,
- options: [buildOption('o1', 'A')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels).toContain('View {{count}} comments');
- });
-
- it('omits "View N comments" when there are no answers', () => {
- setPollState({
- allow_answers: false,
- allow_user_suggested_options: false,
- answers_count: 0,
- created_by: { id: 'somebody' },
- is_closed: true,
- options: [buildOption('o1', 'A')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels?.some((l) => l?.includes('comments'))).toBe(false);
- });
-
- it('exposes Show all options when options exceed the visible cap', () => {
- const manyOptions = Array.from({ length: 12 }, (_, i) => buildOption(`o${i}`, `Option ${i}`));
- setPollState({
- allow_answers: false,
- allow_user_suggested_options: false,
- created_by: { id: 'somebody' },
- is_closed: true,
- options: manyOptions,
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- const labels = result.current.accessibilityActions?.map((a) => a.label);
- expect(labels).toContain('a11y/Show all options');
- });
-
- it('routes each action to the right side effect', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza'), buildOption('o2', 'Pasta')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'View Results');
- });
- expect(mockOpenViewResults).toHaveBeenCalledTimes(1);
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'a11y/End vote');
- });
- expect(mockEndVote).toHaveBeenCalledTimes(1);
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'Add a comment');
- });
- expect(mockOpenAddComment).toHaveBeenCalledTimes(1);
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'Suggest an option');
- });
- expect(mockOpenSuggestOption).toHaveBeenCalledTimes(1);
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'Vote on Pasta');
- });
- expect(mockToggleVote).toHaveBeenCalledWith('o2');
- });
-
- it('routes the "View N comments" action to openAllComments', () => {
- setPollState({
- allow_answers: false,
- allow_user_suggested_options: false,
- answers_count: 7,
- created_by: { id: 'somebody' },
- is_closed: true,
- options: [buildOption('o1', 'A')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'View {{count}} comments');
- });
- expect(mockOpenAllComments).toHaveBeenCalledTimes(1);
- });
-
- it('ignores unknown action names', () => {
- setPollState({
- allow_answers: true,
- allow_user_suggested_options: true,
- created_by: { id: 'me' },
- is_closed: false,
- options: [buildOption('o1', 'Pizza')],
- });
-
- const { result } = renderHook(() => usePollAccessibilityActions(), {
- wrapper: wrapper(true),
- });
-
- act(() => {
- fireAction(result.current.onAccessibilityAction, 'streamPollVoteOption_o1');
- });
- expect(mockToggleVote).not.toHaveBeenCalled();
- expect(mockOpenViewResults).not.toHaveBeenCalled();
- });
-});
diff --git a/package/src/components/Poll/hooks/__tests__/usePollAccessibilityLabel.test.tsx b/package/src/components/Poll/hooks/__tests__/usePollAccessibilityLabel.test.tsx
deleted file mode 100644
index 3a0ec50c2a..0000000000
--- a/package/src/components/Poll/hooks/__tests__/usePollAccessibilityLabel.test.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import React from 'react';
-
-import { renderHook } from '@testing-library/react-native';
-
-import { AccessibilityProvider } from '../../../../contexts/accessibilityContext/AccessibilityContext';
-import { TranslationProvider } from '../../../../contexts/translationContext/TranslationContext';
-import { usePollAccessibilityLabel } from '../usePollAccessibilityLabel';
-
-jest.mock('../usePollStateStore', () => ({
- usePollStateStore: (selector: (state: unknown) => unknown) => selector(mockPollState),
-}));
-
-let mockPollState: Record = {};
-
-const setPollState = (state: Record) => {
- mockPollState = state;
-};
-
-const t = (key: string, vars?: Record) => {
- if (!vars) return key;
- if (key === '{{count}} votes') return `${vars.count} votes`;
- if (key === 'Select up to {{count}}') return `Select up to ${vars.count}`;
- if (key === '+{{count}} More Options') return `+${vars.count} More Options`;
- return key;
-};
-
-const wrapper =
- (enabled: boolean) =>
- ({ children }: { children: React.ReactNode }) => (
-
- null,
- } as never
- }
- >
- {children}
-
-
- );
-
-const buildOption = (id: string, text: string) => ({ id, text });
-
-describe('usePollAccessibilityLabel', () => {
- it('returns undefined when accessibility is disabled', () => {
- setPollState({
- enforce_unique_vote: false,
- is_closed: true,
- max_votes_allowed: 0,
- name: 'Lunch?',
- options: [buildOption('o1', 'Pizza')],
- vote_counts_by_option: { o1: 3 },
- });
-
- const { result } = renderHook(() => usePollAccessibilityLabel(), {
- wrapper: wrapper(false),
- });
-
- expect(result.current).toBeUndefined();
- });
-
- it('builds composite label for an ended poll', () => {
- setPollState({
- enforce_unique_vote: false,
- is_closed: true,
- max_votes_allowed: 0,
- name: 'Test',
- options: [buildOption('o1', 'Option 1'), buildOption('o2', 'Option 2')],
- vote_counts_by_option: { o1: 0, o2: 0 },
- });
-
- const { result } = renderHook(() => usePollAccessibilityLabel(), {
- wrapper: wrapper(true),
- });
-
- expect(result.current).toBe(
- 'Test, Poll has ended, Option 1: 0 votes, Option 2: 0 votes, a11y/Activate to view results',
- );
- });
-
- it('uses "Select one" for an open enforce-unique-vote poll', () => {
- setPollState({
- enforce_unique_vote: true,
- is_closed: false,
- max_votes_allowed: 0,
- name: 'Pick a venue',
- options: [buildOption('o1', 'Cafe')],
- vote_counts_by_option: { o1: 2 },
- });
-
- const { result } = renderHook(() => usePollAccessibilityLabel(), {
- wrapper: wrapper(true),
- });
-
- expect(result.current).toBe(
- 'Pick a venue, Select one, Cafe: 2 votes, a11y/Activate to view results',
- );
- });
-
- it('uses "Select up to N" when maxVotesAllowed is set', () => {
- setPollState({
- enforce_unique_vote: false,
- is_closed: false,
- max_votes_allowed: 3,
- name: 'Top picks',
- options: [buildOption('o1', 'A')],
- vote_counts_by_option: { o1: 1 },
- });
-
- const { result } = renderHook(() => usePollAccessibilityLabel(), {
- wrapper: wrapper(true),
- });
-
- expect(result.current).toBe(
- 'Top picks, Select up to 3, A: 1 votes, a11y/Activate to view results',
- );
- });
-
- it('appends overflow hint when options exceed the visible cap', () => {
- const manyOptions = Array.from({ length: 12 }, (_, i) => buildOption(`o${i}`, `Option ${i}`));
- const counts = Object.fromEntries(manyOptions.map((o) => [o.id, 0]));
-
- setPollState({
- enforce_unique_vote: false,
- is_closed: false,
- max_votes_allowed: 0,
- name: 'Big poll',
- options: manyOptions,
- vote_counts_by_option: counts,
- });
-
- const { result } = renderHook(() => usePollAccessibilityLabel(), {
- wrapper: wrapper(true),
- });
-
- expect(result.current).toContain('+7 More Options');
- expect(result.current).toContain('Option 0: 0 votes');
- expect(result.current).not.toContain('Option 5:');
- });
-});
diff --git a/package/src/components/Poll/hooks/usePollAccessibilityActions.ts b/package/src/components/Poll/hooks/usePollAccessibilityActions.ts
deleted file mode 100644
index 86d85735d7..0000000000
--- a/package/src/components/Poll/hooks/usePollAccessibilityActions.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { useMemo } from 'react';
-
-import type { AccessibilityActionEvent, AccessibilityProps } from 'react-native';
-
-import { PollOption, PollState, UserResponse } from 'stream-chat';
-
-import { useEndVote } from './useEndVote';
-
-import { usePollStateStore } from './usePollStateStore';
-
-import { usePollVoteToggle } from './usePollVoteToggle';
-
-import {
- useChatContext,
- useOwnCapabilitiesContext,
- useTranslationContext,
-} from '../../../contexts';
-import { useAccessibilityContext } from '../../../contexts/accessibilityContext/AccessibilityContext';
-import { useStableCallback } from '../../../hooks';
-import { defaultPollOptionCount } from '../../../utils/constants';
-import { usePollUIStateContext } from '../contexts/PollUIStateContext';
-
-type AccessibilityAction = NonNullable[number];
-type OnAccessibilityAction = NonNullable;
-
-type PollA11yActionsSelectorResult = {
- allowAnswers: boolean | undefined;
- allowUserSuggestedOptions: boolean | undefined;
- answersCount: number;
- createdBy: UserResponse | null;
- isClosed: boolean | undefined;
- options: PollOption[];
-};
-
-const a11yActionsSelector = (state: PollState): PollA11yActionsSelectorResult => ({
- allowAnswers: state.allow_answers,
- allowUserSuggestedOptions: state.allow_user_suggested_options,
- answersCount: state.answers_count,
- createdBy: state.created_by,
- isClosed: state.is_closed,
- options: state.options,
-});
-
-export type UsePollAccessibilityActionsResult = {
- accessibilityActions: readonly AccessibilityAction[] | undefined;
- onAccessibilityAction: OnAccessibilityAction | undefined;
-};
-
-type ActionKind =
- | { type: 'addComment' }
- | { type: 'endVote' }
- | { type: 'showAllComments' }
- | { type: 'showAllOptions' }
- | { type: 'suggestOption' }
- | { type: 'viewResults' }
- | { type: 'vote'; optionId: string };
-
-/**
- * Returns the `accessibilityActions` array and `onAccessibilityAction` handler
- * for the poll composite container. Action set is gated by poll state +
- * capabilities so each rotor entry corresponds to an interaction the user is
- * actually allowed to perform. Returns `undefined`s when a11y is disabled.
- *
- * NOTE: We set both `name` and `label` to the same human-readable string on
- * every action. iOS Fabric (new architecture, on by default in RN 0.81+) uses
- * `accessibilityAction.name` as the string VoiceOver reads — `label` is
- * ignored on that path (RCTViewComponentView.mm). iOS legacy (Paper) and
- * Android both read `label`. Using the same value for both fields means the
- * announcement is human-readable on every platform/architecture. Dispatch
- * uses the action name as the lookup key into an internal kind map, so the
- * raw strings never need to be exposed to consumers.
- */
-export const usePollAccessibilityActions = (): UsePollAccessibilityActionsResult => {
- const { enabled } = useAccessibilityContext();
- const { t } = useTranslationContext();
- const { client } = useChatContext();
- const { castPollVote } = useOwnCapabilitiesContext();
- const { allowAnswers, allowUserSuggestedOptions, answersCount, createdBy, isClosed, options } =
- usePollStateStore(a11yActionsSelector);
- const { openAddComment, openAllComments, openAllOptions, openSuggestOption, openViewResults } =
- usePollUIStateContext();
- const toggleVote = usePollVoteToggle();
- const endVote = useEndVote();
-
- const canVote = !isClosed && !!castPollVote;
- const canEnd = !isClosed && createdBy?.id === client.userID;
- const canComment = !isClosed && !!allowAnswers;
- const canSuggest = !isClosed && !!allowUserSuggestedOptions;
- const hasMoreOptions = !!options && options.length > defaultPollOptionCount;
- const hasComments = answersCount > 0;
-
- const { accessibilityActions, actionKindByName } = useMemo<{
- accessibilityActions: readonly AccessibilityAction[] | undefined;
- actionKindByName: Map | undefined;
- }>(() => {
- if (!enabled) {
- return { accessibilityActions: undefined, actionKindByName: undefined };
- }
-
- const actions: AccessibilityAction[] = [];
- const kindByName = new Map();
-
- const push = (name: string, kind: ActionKind) => {
- actions.push({ label: name, name });
- kindByName.set(name, kind);
- };
-
- push(t('View Results'), { type: 'viewResults' });
-
- if (canVote && options) {
- for (const option of options.slice(0, defaultPollOptionCount)) {
- push(t('a11y/Vote on {{option}}', { option: option.text }), {
- optionId: option.id,
- type: 'vote',
- });
- }
- }
-
- if (hasMoreOptions) {
- push(t('a11y/Show all options'), { type: 'showAllOptions' });
- }
-
- if (canEnd) {
- push(t('a11y/End vote'), { type: 'endVote' });
- }
-
- if (canComment) {
- push(t('Add a comment'), { type: 'addComment' });
- }
-
- if (canSuggest) {
- push(t('Suggest an option'), { type: 'suggestOption' });
- }
-
- if (hasComments) {
- push(t('View {{count}} comments', { count: answersCount }), { type: 'showAllComments' });
- }
-
- return { accessibilityActions: actions, actionKindByName: kindByName };
- }, [
- answersCount,
- canComment,
- canEnd,
- canSuggest,
- canVote,
- enabled,
- hasComments,
- hasMoreOptions,
- options,
- t,
- ]);
-
- const onAccessibilityAction = useStableCallback((event: AccessibilityActionEvent) => {
- const kind = actionKindByName?.get(event.nativeEvent.actionName);
- if (!kind) return;
-
- switch (kind.type) {
- case 'viewResults':
- openViewResults();
- return;
- case 'showAllOptions':
- openAllOptions();
- return;
- case 'endVote':
- void endVote();
- return;
- case 'addComment':
- openAddComment();
- return;
- case 'suggestOption':
- openSuggestOption();
- return;
- case 'showAllComments':
- openAllComments();
- return;
- case 'vote':
- void toggleVote(kind.optionId);
- return;
- default:
- return;
- }
- });
-
- return useMemo(
- () => ({
- accessibilityActions,
- onAccessibilityAction: enabled ? onAccessibilityAction : undefined,
- }),
- [accessibilityActions, enabled, onAccessibilityAction],
- );
-};
diff --git a/package/src/components/Poll/hooks/usePollAccessibilityLabel.ts b/package/src/components/Poll/hooks/usePollAccessibilityLabel.ts
deleted file mode 100644
index 5617ae8fd0..0000000000
--- a/package/src/components/Poll/hooks/usePollAccessibilityLabel.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useMemo } from 'react';
-
-import { PollOption, PollState } from 'stream-chat';
-
-import { usePollStateStore } from './usePollStateStore';
-
-import { composeAccessibilityLabel } from '../../../a11y/a11yUtils';
-import { useTranslationContext } from '../../../contexts';
-import { useAccessibilityContext } from '../../../contexts/accessibilityContext/AccessibilityContext';
-import { defaultPollOptionCount } from '../../../utils/constants';
-
-type PollA11yLabelSelectorResult = {
- enforceUniqueVote: boolean;
- isClosed: boolean | undefined;
- maxVotesAllowed: number;
- name: string;
- options: PollOption[];
- voteCountsByOption: Record;
-};
-
-const a11yLabelSelector = (state: PollState): PollA11yLabelSelectorResult => ({
- enforceUniqueVote: state.enforce_unique_vote,
- isClosed: state.is_closed,
- maxVotesAllowed: state.max_votes_allowed,
- name: state.name,
- options: state.options,
- voteCountsByOption: state.vote_counts_by_option,
-});
-
-/**
- * Builds the composite accessibility label for a poll bubble: name, status,
- * up to `defaultPollOptionCount` options with vote counts, an overflow hint,
- * and the primary-tap hint. Returns `undefined` when a11y is disabled so the
- * Poll container can leave its `accessibilityLabel` unset.
- */
-export const usePollAccessibilityLabel = (): string | undefined => {
- const { enabled } = useAccessibilityContext();
- const { t } = useTranslationContext();
- const { enforceUniqueVote, isClosed, maxVotesAllowed, name, options, voteCountsByOption } =
- usePollStateStore(a11yLabelSelector);
-
- return useMemo(() => {
- if (!enabled) return undefined;
-
- let status: string;
- if (isClosed) {
- status = t('Poll has ended');
- } else if (enforceUniqueVote) {
- status = t('Select one');
- } else if (maxVotesAllowed) {
- status = t('Select up to {{count}}', { count: maxVotesAllowed });
- } else {
- status = t('Select one or more');
- }
-
- const visibleOptions = options?.slice(0, defaultPollOptionCount) ?? [];
- const optionParts = visibleOptions.map((option) => {
- const count = voteCountsByOption?.[option.id] ?? 0;
- return `${option.text}: ${t('{{count}} votes', { count })}`;
- });
-
- const overflow =
- options && options.length > defaultPollOptionCount
- ? t('+{{count}} More Options', { count: options.length - defaultPollOptionCount })
- : null;
-
- return composeAccessibilityLabel(
- name,
- status,
- ...optionParts,
- overflow,
- t('a11y/Activate to view results'),
- );
- }, [enabled, enforceUniqueVote, isClosed, maxVotesAllowed, name, options, t, voteCountsByOption]);
-};
diff --git a/package/src/components/Reply/Reply.tsx b/package/src/components/Reply/Reply.tsx
index 819388e542..6578237412 100644
--- a/package/src/components/Reply/Reply.tsx
+++ b/package/src/components/Reply/Reply.tsx
@@ -20,6 +20,7 @@ import {
import { ReplyMessageView } from './ReplyMessageView';
+import { useAnnounceOnShow } from '../../a11y/hooks/useAnnounceOnShow';
import { useChatContext } from '../../contexts/chatContext/ChatContext';
import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext';
import {
@@ -42,6 +43,8 @@ const messageComposerStateStoreSelector = (state: MessageComposerState) => ({
quotedMessage: state.quotedMessage,
});
+const ANNOUNCEMENT_TEXT_MAX_LENGTH = 120;
+
const RightContent = React.memo(
(props: Pick) => {
const { ImageComponent, message } = props;
@@ -234,6 +237,43 @@ export const MemoizedReply = React.memo(ReplyWithContext, areEqual) as typeof Re
export type ReplyProps = Partial &
Pick;
+/**
+ * Mounted only when the Reply is rendered as the composer header preview
+ * (edit/reply). Keeps the translation + announcer subscriptions off the
+ * per-row in-message quoted-reply render path.
+ */
+const ReplyComposerAnnouncer = ({
+ message,
+ mode,
+}: {
+ message: ReplyPropsWithContext['quotedMessage'];
+ mode: ReplyPropsWithContext['mode'];
+}) => {
+ const { t } = useTranslationContext();
+ const truncatedText = useMemo(() => {
+ const raw = message?.text?.trim();
+ if (!raw) return undefined;
+ return raw.length > ANNOUNCEMENT_TEXT_MAX_LENGTH
+ ? `${raw.slice(0, ANNOUNCEMENT_TEXT_MAX_LENGTH).trimEnd()}…`
+ : raw;
+ }, [message?.text]);
+ const announcement = useMemo(() => {
+ if (mode === 'edit') {
+ return truncatedText
+ ? t('a11y/Editing message: {{text}}', { text: truncatedText })
+ : t('a11y/Editing message');
+ }
+ const name = message?.user?.name;
+ if (!name) return undefined;
+ return truncatedText
+ ? t('a11y/Replying to {{user}}: {{text}}', { text: truncatedText, user: name })
+ : t('a11y/Replying to {{user}}', { user: name });
+ }, [mode, message?.user?.name, truncatedText, t]);
+
+ useAnnounceOnShow(true, announcement);
+ return null;
+};
+
export const Reply = (props: ReplyProps) => {
const { message: messageFromContext } = useMessageContext();
const { client } = useChatContext();
@@ -251,14 +291,25 @@ export const Reply = (props: ReplyProps) => {
const isMyMessage = client.user?.id === quotedMessage?.user?.id;
+ // Composer header passes `onDismiss`; the in-message quoted-reply renderer
+ // does not. Only the composer-preview path pays for announcement work.
+ const isComposerPreview = !!props.onDismiss;
+
return (
-
+ <>
+ {isComposerPreview ? (
+ // Edit passes the message via `quotedMessage` prop; reply uses the
+ // composer-state quoted message we computed locally.
+
+ ) : null}
+
+ >
);
};
diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap
index 593a2c7d0c..9f68c78a50 100644
--- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap
+++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap
@@ -588,6 +588,8 @@ exports[`Thread should match thread snapshot 1`] = `
{
const channelName = (channel.data?.name as string | undefined) ?? channel.cid;
- if (channelImage) {
- return (
-
- );
- }
+ const memberCount = Object.keys(channel.state.members).length;
+ const isGroup = !!channel.data?.name || memberCount > 2;
+ const otherUserName = usersWithoutSelf[0]?.name || usersWithoutSelf[0]?.id;
+ const labelParams = useMemo(
+ () => ({ count: memberCount, name: otherUserName ?? '' }),
+ [memberCount, otherUserName],
+ );
+ const accessibilityLabel = useA11yLabel(
+ isGroup ? 'a11y/Channel with {{count}} members' : 'a11y/Direct chat with {{name}}',
+ labelParams,
+ );
- if (usersWithoutSelf.length > 1) {
- return (
-
- );
- } else {
- return (
-
- );
- }
+ return (
+
+ {channelImage ? (
+
+ ) : usersWithoutSelf.length > 1 ? (
+
+ ) : (
+
+ )}
+
+ );
};
diff --git a/package/src/components/ui/Badge/BadgeNotification.tsx b/package/src/components/ui/Badge/BadgeNotification.tsx
index cc47b5dad7..3c08dc099a 100644
--- a/package/src/components/ui/Badge/BadgeNotification.tsx
+++ b/package/src/components/ui/Badge/BadgeNotification.tsx
@@ -9,6 +9,11 @@ export type BadgeNotificationProps = {
count: number;
size: 'sm' | 'xs';
testID?: string;
+ /**
+ * Optional accessibility label override. When provided, screen readers
+ * announce this string instead of the bare count.
+ */
+ accessibilityLabel?: string;
};
const sizes = {
@@ -34,7 +39,7 @@ const textStyles = {
};
export const BadgeNotification = (props: BadgeNotificationProps) => {
- const { type = 'primary', count, size = 'sm', testID } = props;
+ const { accessibilityLabel, type = 'primary', count, size = 'sm', testID } = props;
const styles = useStyles();
const {
theme: { semantics },
@@ -49,7 +54,11 @@ export const BadgeNotification = (props: BadgeNotificationProps) => {
return (
-
+
{count}
diff --git a/package/src/i18n/ar.json b/package/src/i18n/ar.json
index 829a76af98..c8441fd0ca 100644
--- a/package/src/i18n/ar.json
+++ b/package/src/i18n/ar.json
@@ -253,28 +253,43 @@
"a11y/AI is generating": "AI is generating",
"a11y/AI is thinking": "AI is thinking",
"a11y/Avatar of {{name}}": "Avatar of {{name}}",
+ "a11y/Channel with {{count}} members": "قناة بها {{count}} أعضاء",
"a11y/Connected": "Connected",
- "a11y/Delivered": "Delivered",
+ "a11y/Delivered": "تم التسليم",
+ "a11y/Delivered, sent by you": "تم التسليم، مُرسلة منك",
+ "a11y/Direct chat with {{name}}": "محادثة مباشرة مع {{name}}",
+ "a11y/Double tap to open": "انقر مرتين للفتح",
+ "a11y/Double tap to view reactions": "انقر مرتين لعرض التفاعلات",
+ "a11y/Editing message": "تعديل الرسالة",
+ "a11y/Editing message: {{text}}": "تعديل الرسالة: {{text}}",
+ "a11y/Last message {{date}}": "آخر رسالة {{date}}",
"a11y/Loading": "Loading",
"a11y/Loading failed": "Loading failed",
"a11y/Message actions": "Message actions",
+ "a11y/Muted": "مكتوم",
"a11y/New message from {{user}}": "New message from {{user}}",
"a11y/Offline": "Offline",
"a11y/Open message actions": "Open message actions",
"a11y/Reaction {{emoji}} by {{count}} users": "Reaction {{emoji}} by {{count}} users",
- "a11y/Read": "Read",
+ "a11y/Read": "مقروءة",
+ "a11y/Read, sent by you": "مقروءة، مُرسلة منك",
"a11y/Reconnecting": "Reconnecting",
"a11y/Reply to {{user}}": "Reply to {{user}}",
"a11y/Remove edit": "Remove edit",
"a11y/Remove reply": "Remove reply",
+ "a11y/Replying to {{user}}": "الرد على {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "الرد على {{user}}: {{text}}",
"a11y/Scroll to bottom": "Scroll to bottom",
"a11y/Scroll to bottom, {{count}} new messages": "Scroll to bottom, {{count}} new messages",
"a11y/Scroll to latest": "Scroll to latest",
"a11y/Scroll to latest, {{count}} unread": "Scroll to latest, {{count}} unread",
"a11y/Send message": "Send message",
"a11y/Sending": "Sending",
- "a11y/Sent": "Sent",
+ "a11y/Sent": "مُرسلة",
+ "a11y/Sent by you": "مُرسلة منك",
"a11y/Voice message recording. Hold to record.": "Voice message recording. Hold to record.",
+ "a11y/and {{count}} more reactions": "و{{count}} تفاعلات أخرى",
+ "a11y/you reacted": "تفاعلت",
"a11y/{{count}} new messages": "{{count}} new messages",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "حد الحجم",
"unknown error": "خطأ غير معروف",
"unsupported file type": "نوع ملف غير مدعوم",
- "a11y/Activate to view results": "فعّل لعرض النتائج",
- "a11y/End vote": "إنهاء التصويت",
- "a11y/Show all options": "إظهار جميع الخيارات",
- "a11y/Vote on {{option}}": "صوّت على {{option}}",
"a11y/Double tap and hold to activate contextual menu": "انقر نقرًا مزدوجًا مع الاستمرار لتفعيل قائمة السياق",
"a11y/Swipe right to go through different actions": "اسحب لليمين للتنقل بين الإجراءات المختلفة",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} رسائل غير مقروءة"
}
diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json
index d5d3f16471..fac2d54a8f 100644
--- a/package/src/i18n/en.json
+++ b/package/src/i18n/en.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "AI is generating",
"a11y/AI is thinking": "AI is thinking",
"a11y/Avatar of {{name}}": "Avatar of {{name}}",
+ "a11y/Channel with {{count}} members": "Channel with {{count}} members",
"a11y/Connected": "Connected",
"a11y/Delivered": "Delivered",
+ "a11y/Delivered, sent by you": "Delivered, sent by you",
+ "a11y/Direct chat with {{name}}": "Direct chat with {{name}}",
+ "a11y/Double tap to open": "Double tap to open",
+ "a11y/Double tap to view reactions": "Double tap to view reactions",
+ "a11y/Editing message": "Editing message",
+ "a11y/Editing message: {{text}}": "Editing message: {{text}}",
+ "a11y/Last message {{date}}": "Last message {{date}}",
"a11y/Loading": "Loading",
"a11y/Loading failed": "Loading failed",
"a11y/Message actions": "Message actions",
+ "a11y/Muted": "Muted",
"a11y/New message from {{user}}": "New message from {{user}}",
"a11y/Offline": "Offline",
"a11y/Open message actions": "Open message actions",
"a11y/Reaction {{emoji}} by {{count}} users": "Reaction {{emoji}} by {{count}} users",
"a11y/Read": "Read",
+ "a11y/Read, sent by you": "Read, sent by you",
"a11y/Reconnecting": "Reconnecting",
"a11y/Reply to {{user}}": "Reply to {{user}}",
"a11y/Remove edit": "Remove edit",
"a11y/Remove reply": "Remove reply",
+ "a11y/Replying to {{user}}": "Replying to {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Replying to {{user}}: {{text}}",
"a11y/Scroll to bottom": "Scroll to bottom",
"a11y/Scroll to bottom, {{count}} new messages": "Scroll to bottom, {{count}} new messages",
"a11y/Scroll to latest": "Scroll to latest",
@@ -274,7 +286,10 @@
"a11y/Send message": "Send message",
"a11y/Sending": "Sending",
"a11y/Sent": "Sent",
+ "a11y/Sent by you": "Sent by you",
"a11y/Voice message recording. Hold to record.": "Voice message recording. Hold to record.",
+ "a11y/and {{count}} more reactions": "and {{count}} more reactions",
+ "a11y/you reacted": "you reacted",
"a11y/{{count}} new messages": "{{count}} new messages",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -308,14 +323,10 @@
"a11y/Stop voice recording": "Stop voice recording",
"a11y/Notifications": "Notifications",
"a11y/Dismiss notification": "Dismiss notification",
- "a11y/Activate to view results": "Activate to view results",
"a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
"a11y/Close": "Close",
"a11y/Double tap and hold to activate contextual menu": "Double tap and hold to activate contextual menu",
- "a11y/End vote": "End vote",
- "a11y/Show all options": "Show all options",
"a11y/Swipe right to go through different actions": "Swipe right to go through different actions",
- "a11y/Vote on {{option}}": "Vote on {{option}}",
"Attachment upload blocked due to {{reason}}": "Attachment upload blocked due to {{reason}}",
"Attachment upload failed due to {{reason}}": "Attachment upload failed due to {{reason}}",
"Command not available": "Command not available",
@@ -359,5 +370,6 @@
"{{ user }} has been unmuted": "{{ user }} has been unmuted",
"size limit": "size limit",
"unknown error": "unknown error",
- "unsupported file type": "unsupported file type"
+ "unsupported file type": "unsupported file type",
+ "a11y/{{count}} unread messages": "{{count}} unread messages"
}
diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json
index 75fdc566e5..3755ab3a59 100644
--- a/package/src/i18n/es.json
+++ b/package/src/i18n/es.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "La IA está generando",
"a11y/AI is thinking": "La IA está pensando",
"a11y/Avatar of {{name}}": "Avatar de {{name}}",
+ "a11y/Channel with {{count}} members": "Canal con {{count}} miembros",
"a11y/Connected": "Conectado",
"a11y/Delivered": "Entregado",
+ "a11y/Delivered, sent by you": "Entregado, enviado por ti",
+ "a11y/Direct chat with {{name}}": "Chat directo con {{name}}",
+ "a11y/Double tap to open": "Toca dos veces para abrir",
+ "a11y/Double tap to view reactions": "Toca dos veces para ver las reacciones",
+ "a11y/Editing message": "Editando mensaje",
+ "a11y/Editing message: {{text}}": "Editando mensaje: {{text}}",
+ "a11y/Last message {{date}}": "Último mensaje {{date}}",
"a11y/Loading": "Cargando",
"a11y/Loading failed": "Error al cargar",
"a11y/Message actions": "Acciones del mensaje",
+ "a11y/Muted": "Silenciado",
"a11y/New message from {{user}}": "Nuevo mensaje de {{user}}",
"a11y/Offline": "Sin conexión",
"a11y/Open message actions": "Abrir acciones del mensaje",
"a11y/Reaction {{emoji}} by {{count}} users": "Reacción {{emoji}} de {{count}} usuarios",
"a11y/Read": "Leído",
+ "a11y/Read, sent by you": "Leído, enviado por ti",
"a11y/Reconnecting": "Reconectando",
"a11y/Reply to {{user}}": "Responder a {{user}}",
"a11y/Remove edit": "Eliminar edición",
"a11y/Remove reply": "Eliminar respuesta",
+ "a11y/Replying to {{user}}": "Respondiendo a {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Respondiendo a {{user}}: {{text}}",
"a11y/Scroll to bottom": "Ir al final",
"a11y/Scroll to bottom, {{count}} new messages": "Ir al final, {{count}} mensajes nuevos",
"a11y/Scroll to latest": "Ir al último mensaje",
@@ -274,7 +286,10 @@
"a11y/Send message": "Enviar mensaje",
"a11y/Sending": "Enviando",
"a11y/Sent": "Enviado",
+ "a11y/Sent by you": "Enviado por ti",
"a11y/Voice message recording. Hold to record.": "Grabación de mensaje de voz. Mantén pulsado para grabar.",
+ "a11y/and {{count}} more reactions": "y {{count}} reacciones más",
+ "a11y/you reacted": "tú reaccionaste",
"a11y/{{count}} new messages": "{{count}} mensajes nuevos",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "límite de tamaño",
"unknown error": "error desconocido",
"unsupported file type": "tipo de archivo no compatible",
- "a11y/Activate to view results": "Activa para ver los resultados",
- "a11y/End vote": "Finalizar votación",
- "a11y/Show all options": "Mostrar todas las opciones",
- "a11y/Vote on {{option}}": "Votar por {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Toca dos veces y mantén pulsado para activar el menú contextual",
"a11y/Swipe right to go through different actions": "Desliza a la derecha para recorrer las diferentes acciones",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} mensajes sin leer"
}
diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json
index bc498a9f71..0c264d6978 100644
--- a/package/src/i18n/fr.json
+++ b/package/src/i18n/fr.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "L'IA génère une réponse",
"a11y/AI is thinking": "L'IA réfléchit",
"a11y/Avatar of {{name}}": "Avatar de {{name}}",
+ "a11y/Channel with {{count}} members": "Canal avec {{count}} membres",
"a11y/Connected": "Connecté",
"a11y/Delivered": "Distribué",
+ "a11y/Delivered, sent by you": "Distribué, envoyé par vous",
+ "a11y/Direct chat with {{name}}": "Discussion directe avec {{name}}",
+ "a11y/Double tap to open": "Appuyez deux fois pour ouvrir",
+ "a11y/Double tap to view reactions": "Appuyez deux fois pour voir les réactions",
+ "a11y/Editing message": "Modification du message",
+ "a11y/Editing message: {{text}}": "Modification du message : {{text}}",
+ "a11y/Last message {{date}}": "Dernier message {{date}}",
"a11y/Loading": "Chargement",
"a11y/Loading failed": "Échec du chargement",
"a11y/Message actions": "Actions du message",
+ "a11y/Muted": "Mis en sourdine",
"a11y/New message from {{user}}": "Nouveau message de {{user}}",
"a11y/Offline": "Hors ligne",
"a11y/Open message actions": "Ouvrir les actions du message",
"a11y/Reaction {{emoji}} by {{count}} users": "Réaction {{emoji}} par {{count}} utilisateurs",
"a11y/Read": "Lu",
+ "a11y/Read, sent by you": "Lu, envoyé par vous",
"a11y/Reconnecting": "Reconnexion",
"a11y/Reply to {{user}}": "Répondre à {{user}}",
"a11y/Remove edit": "Supprimer la modification",
"a11y/Remove reply": "Supprimer la réponse",
+ "a11y/Replying to {{user}}": "Réponse à {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Réponse à {{user}} : {{text}}",
"a11y/Scroll to bottom": "Aller en bas",
"a11y/Scroll to bottom, {{count}} new messages": "Aller en bas, {{count}} nouveaux messages",
"a11y/Scroll to latest": "Aller au dernier message",
@@ -274,7 +286,10 @@
"a11y/Send message": "Envoyer le message",
"a11y/Sending": "Envoi",
"a11y/Sent": "Envoyé",
+ "a11y/Sent by you": "Envoyé par vous",
"a11y/Voice message recording. Hold to record.": "Enregistrement d'un message vocal. Maintenez appuyé pour enregistrer.",
+ "a11y/and {{count}} more reactions": "et {{count}} réactions de plus",
+ "a11y/you reacted": "vous avez réagi",
"a11y/{{count}} new messages": "{{count}} nouveaux messages",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "limite de taille",
"unknown error": "erreur inconnue",
"unsupported file type": "type de fichier non pris en charge",
- "a11y/Activate to view results": "Activer pour voir les résultats",
- "a11y/End vote": "Terminer le vote",
- "a11y/Show all options": "Afficher toutes les options",
- "a11y/Vote on {{option}}": "Voter pour {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Appuyez deux fois et maintenez pour activer le menu contextuel",
"a11y/Swipe right to go through different actions": "Glissez vers la droite pour parcourir les différentes actions",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} messages non lus"
}
diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json
index 42c9e51ee0..b59b165300 100644
--- a/package/src/i18n/he.json
+++ b/package/src/i18n/he.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "הבינה המלאכותית יוצרת תשובה",
"a11y/AI is thinking": "הבינה המלאכותית חושבת",
"a11y/Avatar of {{name}}": "תמונת פרופיל של {{name}}",
+ "a11y/Channel with {{count}} members": "ערוץ עם {{count}} חברים",
"a11y/Connected": "מחובר",
"a11y/Delivered": "נמסר",
+ "a11y/Delivered, sent by you": "נמסר, נשלח על ידך",
+ "a11y/Direct chat with {{name}}": "צ׳אט ישיר עם {{name}}",
+ "a11y/Double tap to open": "הקש פעמיים כדי לפתוח",
+ "a11y/Double tap to view reactions": "הקש פעמיים כדי לראות תגובות",
+ "a11y/Editing message": "עריכת הודעה",
+ "a11y/Editing message: {{text}}": "עריכת הודעה: {{text}}",
+ "a11y/Last message {{date}}": "הודעה אחרונה {{date}}",
"a11y/Loading": "טוען",
"a11y/Loading failed": "הטעינה נכשלה",
"a11y/Message actions": "פעולות הודעה",
+ "a11y/Muted": "מושתק",
"a11y/New message from {{user}}": "הודעה חדשה מ-{{user}}",
"a11y/Offline": "לא מקוון",
"a11y/Open message actions": "פתח פעולות הודעה",
"a11y/Reaction {{emoji}} by {{count}} users": "תגובה {{emoji}} מאת {{count}} משתמשים",
"a11y/Read": "נקרא",
+ "a11y/Read, sent by you": "נקרא, נשלח על ידך",
"a11y/Reconnecting": "מתחבר מחדש",
"a11y/Reply to {{user}}": "השב ל-{{user}}",
"a11y/Remove edit": "הסר עריכה",
"a11y/Remove reply": "הסר תגובה",
+ "a11y/Replying to {{user}}": "מגיב/ה ל-{{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "מגיב/ה ל-{{user}}: {{text}}",
"a11y/Scroll to bottom": "גלול לתחתית",
"a11y/Scroll to bottom, {{count}} new messages": "גלול לתחתית, {{count}} הודעות חדשות",
"a11y/Scroll to latest": "גלול להודעה האחרונה",
@@ -274,7 +286,10 @@
"a11y/Send message": "שלח הודעה",
"a11y/Sending": "שולח",
"a11y/Sent": "נשלח",
+ "a11y/Sent by you": "נשלח על ידך",
"a11y/Voice message recording. Hold to record.": "הקלטת הודעה קולית. החזק כדי להקליט.",
+ "a11y/and {{count}} more reactions": "ועוד {{count}} תגובות",
+ "a11y/you reacted": "הגבת",
"a11y/{{count}} new messages": "{{count}} הודעות חדשות",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "מגבלת גודל",
"unknown error": "שגיאה לא ידועה",
"unsupported file type": "סוג קובץ לא נתמך",
- "a11y/Activate to view results": "הפעל כדי לראות את התוצאות",
- "a11y/End vote": "סיים הצבעה",
- "a11y/Show all options": "הצג את כל האפשרויות",
- "a11y/Vote on {{option}}": "הצבע עבור {{option}}",
"a11y/Double tap and hold to activate contextual menu": "הקש פעמיים והחזק כדי להפעיל את התפריט ההקשרי",
"a11y/Swipe right to go through different actions": "החלק ימינה כדי לעבור בין הפעולות השונות",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} הודעות שלא נקראו"
}
diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json
index 84586cafd8..aeed1621c3 100644
--- a/package/src/i18n/hi.json
+++ b/package/src/i18n/hi.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "AI जवाब तैयार कर रहा है",
"a11y/AI is thinking": "AI सोच रहा है",
"a11y/Avatar of {{name}}": "{{name}} का अवतार",
+ "a11y/Channel with {{count}} members": "{{count}} सदस्यों वाला चैनल",
"a11y/Connected": "कनेक्टेड",
- "a11y/Delivered": "डिलीवर हुआ",
+ "a11y/Delivered": "डिलीवर हो गया",
+ "a11y/Delivered, sent by you": "डिलीवर हो गया, आपके द्वारा भेजा गया",
+ "a11y/Direct chat with {{name}}": "{{name}} के साथ सीधी चैट",
+ "a11y/Double tap to open": "खोलने के लिए दो बार टैप करें",
+ "a11y/Double tap to view reactions": "प्रतिक्रियाएँ देखने के लिए दो बार टैप करें",
+ "a11y/Editing message": "संदेश संपादित कर रहे हैं",
+ "a11y/Editing message: {{text}}": "संदेश संपादित कर रहे हैं: {{text}}",
+ "a11y/Last message {{date}}": "अंतिम संदेश {{date}}",
"a11y/Loading": "लोड हो रहा है",
"a11y/Loading failed": "लोड नहीं हो सका",
"a11y/Message actions": "संदेश की कार्रवाइयां",
+ "a11y/Muted": "म्यूट किया गया",
"a11y/New message from {{user}}": "{{user}} से नया संदेश",
"a11y/Offline": "ऑफलाइन",
"a11y/Open message actions": "संदेश की कार्रवाइयां खोलें",
"a11y/Reaction {{emoji}} by {{count}} users": "{{count}} उपयोगकर्ताओं की {{emoji}} प्रतिक्रिया",
"a11y/Read": "पढ़ा गया",
+ "a11y/Read, sent by you": "पढ़ा गया, आपके द्वारा भेजा गया",
"a11y/Reconnecting": "फिर से कनेक्ट हो रहा है",
"a11y/Reply to {{user}}": "{{user}} को जवाब दें",
"a11y/Remove edit": "संपादन हटाएं",
"a11y/Remove reply": "जवाब हटाएं",
+ "a11y/Replying to {{user}}": "{{user}} को उत्तर दे रहे हैं",
+ "a11y/Replying to {{user}}: {{text}}": "{{user}} को उत्तर दे रहे हैं: {{text}}",
"a11y/Scroll to bottom": "नीचे स्क्रॉल करें",
"a11y/Scroll to bottom, {{count}} new messages": "नीचे स्क्रॉल करें, {{count}} नए संदेश",
"a11y/Scroll to latest": "नवीनतम संदेश पर जाएं",
@@ -274,7 +286,10 @@
"a11y/Send message": "संदेश भेजें",
"a11y/Sending": "भेजा जा रहा है",
"a11y/Sent": "भेजा गया",
+ "a11y/Sent by you": "आपके द्वारा भेजा गया",
"a11y/Voice message recording. Hold to record.": "वॉइस संदेश रिकॉर्डिंग। रिकॉर्ड करने के लिए दबाकर रखें।",
+ "a11y/and {{count}} more reactions": "और {{count}} प्रतिक्रियाएँ",
+ "a11y/you reacted": "आपने प्रतिक्रिया दी",
"a11y/{{count}} new messages": "{{count}} नए संदेश",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "आकार सीमा",
"unknown error": "अज्ञात त्रुटि",
"unsupported file type": "असमर्थित फ़ाइल प्रकार",
- "a11y/Activate to view results": "परिणाम देखने के लिए सक्रिय करें",
- "a11y/End vote": "मतदान समाप्त करें",
- "a11y/Show all options": "सभी विकल्प दिखाएं",
- "a11y/Vote on {{option}}": "{{option}} पर वोट करें",
"a11y/Double tap and hold to activate contextual menu": "संदर्भ मेनू सक्रिय करने के लिए दो बार टैप करें और होल्ड करें",
"a11y/Swipe right to go through different actions": "विभिन्न क्रियाओं के बीच जाने के लिए दाएं स्वाइप करें",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} अपठित संदेश"
}
diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json
index 4452fb0009..cafe8dcdf8 100644
--- a/package/src/i18n/it.json
+++ b/package/src/i18n/it.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "L'IA sta generando",
"a11y/AI is thinking": "L'IA sta pensando",
"a11y/Avatar of {{name}}": "Avatar di {{name}}",
+ "a11y/Channel with {{count}} members": "Canale con {{count}} membri",
"a11y/Connected": "Connesso",
"a11y/Delivered": "Consegnato",
+ "a11y/Delivered, sent by you": "Consegnato, inviato da te",
+ "a11y/Direct chat with {{name}}": "Chat diretta con {{name}}",
+ "a11y/Double tap to open": "Tocca due volte per aprire",
+ "a11y/Double tap to view reactions": "Tocca due volte per vedere le reazioni",
+ "a11y/Editing message": "Modifica del messaggio",
+ "a11y/Editing message: {{text}}": "Modifica del messaggio: {{text}}",
+ "a11y/Last message {{date}}": "Ultimo messaggio {{date}}",
"a11y/Loading": "Caricamento",
"a11y/Loading failed": "Caricamento non riuscito",
"a11y/Message actions": "Azioni del messaggio",
+ "a11y/Muted": "Silenziato",
"a11y/New message from {{user}}": "Nuovo messaggio da {{user}}",
"a11y/Offline": "Offline",
"a11y/Open message actions": "Apri azioni del messaggio",
"a11y/Reaction {{emoji}} by {{count}} users": "Reazione {{emoji}} di {{count}} utenti",
"a11y/Read": "Letto",
+ "a11y/Read, sent by you": "Letto, inviato da te",
"a11y/Reconnecting": "Riconnessione",
"a11y/Reply to {{user}}": "Rispondi a {{user}}",
"a11y/Remove edit": "Rimuovi modifica",
"a11y/Remove reply": "Rimuovi risposta",
+ "a11y/Replying to {{user}}": "Rispondendo a {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Rispondendo a {{user}}: {{text}}",
"a11y/Scroll to bottom": "Vai in fondo",
"a11y/Scroll to bottom, {{count}} new messages": "Vai in fondo, {{count}} nuovi messaggi",
"a11y/Scroll to latest": "Vai al messaggio più recente",
@@ -274,7 +286,10 @@
"a11y/Send message": "Invia messaggio",
"a11y/Sending": "Invio in corso",
"a11y/Sent": "Inviato",
+ "a11y/Sent by you": "Inviato da te",
"a11y/Voice message recording. Hold to record.": "Registrazione del messaggio vocale. Tieni premuto per registrare.",
+ "a11y/and {{count}} more reactions": "e altre {{count}} reazioni",
+ "a11y/you reacted": "hai reagito",
"a11y/{{count}} new messages": "{{count}} nuovi messaggi",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "limite di dimensione",
"unknown error": "errore sconosciuto",
"unsupported file type": "tipo di file non supportato",
- "a11y/Activate to view results": "Attiva per vedere i risultati",
- "a11y/End vote": "Termina sondaggio",
- "a11y/Show all options": "Mostra tutte le opzioni",
- "a11y/Vote on {{option}}": "Vota per {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Tocca due volte e tieni premuto per attivare il menu contestuale",
"a11y/Swipe right to go through different actions": "Scorri a destra per passare in rassegna le diverse azioni",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} messaggi non letti"
}
diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json
index 73f05ecc4a..d9ba98adf1 100644
--- a/package/src/i18n/ja.json
+++ b/package/src/i18n/ja.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "AIが生成しています",
"a11y/AI is thinking": "AIが考えています",
"a11y/Avatar of {{name}}": "{{name}}のアバター",
+ "a11y/Channel with {{count}} members": "メンバー{{count}}人のチャンネル",
"a11y/Connected": "接続済み",
"a11y/Delivered": "配信済み",
+ "a11y/Delivered, sent by you": "配信済み、あなたから送信",
+ "a11y/Direct chat with {{name}}": "{{name}}とのダイレクトチャット",
+ "a11y/Double tap to open": "ダブルタップで開く",
+ "a11y/Double tap to view reactions": "ダブルタップでリアクションを表示",
+ "a11y/Editing message": "メッセージを編集中",
+ "a11y/Editing message: {{text}}": "メッセージを編集中: {{text}}",
+ "a11y/Last message {{date}}": "最後のメッセージ {{date}}",
"a11y/Loading": "読み込み中",
"a11y/Loading failed": "読み込みに失敗しました",
"a11y/Message actions": "メッセージの操作",
+ "a11y/Muted": "ミュート中",
"a11y/New message from {{user}}": "{{user}}からの新しいメッセージ",
"a11y/Offline": "オフライン",
"a11y/Open message actions": "メッセージの操作を開く",
"a11y/Reaction {{emoji}} by {{count}} users": "{{count}}人のユーザーによるリアクション{{emoji}}",
"a11y/Read": "既読",
+ "a11y/Read, sent by you": "既読、あなたから送信",
"a11y/Reconnecting": "再接続中",
"a11y/Reply to {{user}}": "{{user}}に返信",
"a11y/Remove edit": "編集を削除",
"a11y/Remove reply": "返信を削除",
+ "a11y/Replying to {{user}}": "{{user}}に返信中",
+ "a11y/Replying to {{user}}: {{text}}": "{{user}}に返信中: {{text}}",
"a11y/Scroll to bottom": "一番下へ移動",
"a11y/Scroll to bottom, {{count}} new messages": "一番下へ移動、新しいメッセージ{{count}}件",
"a11y/Scroll to latest": "最新のメッセージへ移動",
@@ -274,7 +286,10 @@
"a11y/Send message": "メッセージを送信",
"a11y/Sending": "送信中",
"a11y/Sent": "送信済み",
+ "a11y/Sent by you": "あなたから送信済み",
"a11y/Voice message recording. Hold to record.": "音声メッセージの録音。長押しして録音します。",
+ "a11y/and {{count}} more reactions": "および{{count}}件のリアクション",
+ "a11y/you reacted": "あなたがリアクション",
"a11y/{{count}} new messages": "新しいメッセージ{{count}}件",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "サイズ制限",
"unknown error": "不明なエラー",
"unsupported file type": "サポートされていないファイル形式",
- "a11y/Activate to view results": "結果を表示するには有効化",
- "a11y/End vote": "投票を終了",
- "a11y/Show all options": "すべてのオプションを表示",
- "a11y/Vote on {{option}}": "{{option}}に投票",
"a11y/Double tap and hold to activate contextual menu": "コンテキストメニューを表示するにはダブルタップして長押し",
"a11y/Swipe right to go through different actions": "右にスワイプして異なるアクションを切り替えます",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "未読メッセージ{{count}}件"
}
diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json
index bec7e20f17..a9dfc16186 100644
--- a/package/src/i18n/ko.json
+++ b/package/src/i18n/ko.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "AI가 생성 중입니다",
"a11y/AI is thinking": "AI가 생각 중입니다",
"a11y/Avatar of {{name}}": "{{name}}의 아바타",
+ "a11y/Channel with {{count}} members": "멤버 {{count}}명의 채널",
"a11y/Connected": "연결됨",
"a11y/Delivered": "전달됨",
+ "a11y/Delivered, sent by you": "전달됨, 내가 보냄",
+ "a11y/Direct chat with {{name}}": "{{name}}님과의 다이렉트 채팅",
+ "a11y/Double tap to open": "두 번 탭하여 열기",
+ "a11y/Double tap to view reactions": "두 번 탭하여 반응 보기",
+ "a11y/Editing message": "메시지 편집 중",
+ "a11y/Editing message: {{text}}": "메시지 편집 중: {{text}}",
+ "a11y/Last message {{date}}": "마지막 메시지 {{date}}",
"a11y/Loading": "로드 중",
"a11y/Loading failed": "로드 실패",
"a11y/Message actions": "메시지 작업",
+ "a11y/Muted": "음소거됨",
"a11y/New message from {{user}}": "{{user}}님의 새 메시지",
"a11y/Offline": "오프라인",
"a11y/Open message actions": "메시지 작업 열기",
"a11y/Reaction {{emoji}} by {{count}} users": "{{count}}명의 사용자가 남긴 {{emoji}} 반응",
"a11y/Read": "읽음",
+ "a11y/Read, sent by you": "읽음, 내가 보냄",
"a11y/Reconnecting": "다시 연결 중",
"a11y/Reply to {{user}}": "{{user}}님에게 답장",
"a11y/Remove edit": "편집 제거",
"a11y/Remove reply": "답장 제거",
+ "a11y/Replying to {{user}}": "{{user}}님에게 답장 중",
+ "a11y/Replying to {{user}}: {{text}}": "{{user}}님에게 답장 중: {{text}}",
"a11y/Scroll to bottom": "맨 아래로 이동",
"a11y/Scroll to bottom, {{count}} new messages": "맨 아래로 이동, 새 메시지 {{count}}개",
"a11y/Scroll to latest": "최신 메시지로 이동",
@@ -274,7 +286,10 @@
"a11y/Send message": "메시지 보내기",
"a11y/Sending": "보내는 중",
"a11y/Sent": "보냄",
+ "a11y/Sent by you": "내가 보냄",
"a11y/Voice message recording. Hold to record.": "음성 메시지 녹음. 길게 눌러 녹음하세요.",
+ "a11y/and {{count}} more reactions": "및 {{count}}개의 추가 반응",
+ "a11y/you reacted": "내가 반응함",
"a11y/{{count}} new messages": "새 메시지 {{count}}개",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "크기 제한",
"unknown error": "알 수 없는 오류",
"unsupported file type": "지원되지 않는 파일 형식",
- "a11y/Activate to view results": "결과를 보려면 활성화",
- "a11y/End vote": "투표 종료",
- "a11y/Show all options": "모든 옵션 표시",
- "a11y/Vote on {{option}}": "{{option}}에 투표",
"a11y/Double tap and hold to activate contextual menu": "컨텍스트 메뉴를 활성화하려면 두 번 탭하고 길게 누르세요",
"a11y/Swipe right to go through different actions": "다른 작업을 탐색하려면 오른쪽으로 스와이프하세요",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "읽지 않은 메시지 {{count}}개"
}
diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json
index 1d0a400967..e748128250 100644
--- a/package/src/i18n/nl.json
+++ b/package/src/i18n/nl.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "AI genereert",
"a11y/AI is thinking": "AI denkt na",
"a11y/Avatar of {{name}}": "Avatar van {{name}}",
+ "a11y/Channel with {{count}} members": "Kanaal met {{count}} leden",
"a11y/Connected": "Verbonden",
- "a11y/Delivered": "Afgeleverd",
+ "a11y/Delivered": "Bezorgd",
+ "a11y/Delivered, sent by you": "Bezorgd, door jou verzonden",
+ "a11y/Direct chat with {{name}}": "Direct chat met {{name}}",
+ "a11y/Double tap to open": "Dubbeltik om te openen",
+ "a11y/Double tap to view reactions": "Dubbeltik om reacties te bekijken",
+ "a11y/Editing message": "Bericht bewerken",
+ "a11y/Editing message: {{text}}": "Bericht bewerken: {{text}}",
+ "a11y/Last message {{date}}": "Laatste bericht {{date}}",
"a11y/Loading": "Laden",
"a11y/Loading failed": "Laden mislukt",
"a11y/Message actions": "Berichtacties",
+ "a11y/Muted": "Gedempt",
"a11y/New message from {{user}}": "Nieuw bericht van {{user}}",
"a11y/Offline": "Offline",
"a11y/Open message actions": "Berichtacties openen",
"a11y/Reaction {{emoji}} by {{count}} users": "Reactie {{emoji}} door {{count}} gebruikers",
"a11y/Read": "Gelezen",
+ "a11y/Read, sent by you": "Gelezen, door jou verzonden",
"a11y/Reconnecting": "Opnieuw verbinden",
"a11y/Reply to {{user}}": "Antwoorden op {{user}}",
"a11y/Remove edit": "Bewerking verwijderen",
"a11y/Remove reply": "Antwoord verwijderen",
+ "a11y/Replying to {{user}}": "Antwoorden op {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Antwoorden op {{user}}: {{text}}",
"a11y/Scroll to bottom": "Ga naar beneden",
"a11y/Scroll to bottom, {{count}} new messages": "Ga naar beneden, {{count}} nieuwe berichten",
"a11y/Scroll to latest": "Ga naar het nieuwste bericht",
@@ -274,7 +286,10 @@
"a11y/Send message": "Bericht verzenden",
"a11y/Sending": "Verzenden",
"a11y/Sent": "Verzonden",
+ "a11y/Sent by you": "Door jou verzonden",
"a11y/Voice message recording. Hold to record.": "Spraakbericht opnemen. Houd ingedrukt om op te nemen.",
+ "a11y/and {{count}} more reactions": "en {{count}} meer reacties",
+ "a11y/you reacted": "jij hebt gereageerd",
"a11y/{{count}} new messages": "{{count}} nieuwe berichten",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "groottelimiet",
"unknown error": "onbekende fout",
"unsupported file type": "niet-ondersteund bestandstype",
- "a11y/Activate to view results": "Activeer om resultaten te bekijken",
- "a11y/End vote": "Stemming beëindigen",
- "a11y/Show all options": "Alle opties weergeven",
- "a11y/Vote on {{option}}": "Stem op {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Dubbeltik en houd vast om het contextmenu te openen",
"a11y/Swipe right to go through different actions": "Veeg naar rechts om door verschillende acties te bladeren",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} ongelezen berichten"
}
diff --git a/package/src/i18n/pt-br.json b/package/src/i18n/pt-br.json
index 54da88ca23..149dd79384 100644
--- a/package/src/i18n/pt-br.json
+++ b/package/src/i18n/pt-br.json
@@ -253,28 +253,43 @@
"a11y/AI is generating": "A IA está gerando",
"a11y/AI is thinking": "A IA está pensando",
"a11y/Avatar of {{name}}": "Avatar de {{name}}",
+ "a11y/Channel with {{count}} members": "Canal com {{count}} membros",
"a11y/Connected": "Conectado",
"a11y/Delivered": "Entregue",
+ "a11y/Delivered, sent by you": "Entregue, enviada por você",
+ "a11y/Direct chat with {{name}}": "Chat direto com {{name}}",
+ "a11y/Double tap to open": "Toque duas vezes para abrir",
+ "a11y/Double tap to view reactions": "Toque duas vezes para ver as reações",
+ "a11y/Editing message": "Editando mensagem",
+ "a11y/Editing message: {{text}}": "Editando mensagem: {{text}}",
+ "a11y/Last message {{date}}": "Última mensagem {{date}}",
"a11y/Loading": "Carregando",
"a11y/Loading failed": "Falha ao carregar",
"a11y/Message actions": "Ações da mensagem",
+ "a11y/Muted": "Silenciado",
"a11y/New message from {{user}}": "Nova mensagem de {{user}}",
"a11y/Offline": "Offline",
"a11y/Open message actions": "Abrir ações da mensagem",
"a11y/Reaction {{emoji}} by {{count}} users": "Reação {{emoji}} de {{count}} usuários",
- "a11y/Read": "Lido",
+ "a11y/Read": "Lida",
+ "a11y/Read, sent by you": "Lida, enviada por você",
"a11y/Reconnecting": "Reconectando",
"a11y/Reply to {{user}}": "Responder a {{user}}",
"a11y/Remove edit": "Remover edição",
"a11y/Remove reply": "Remover resposta",
+ "a11y/Replying to {{user}}": "Respondendo a {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Respondendo a {{user}}: {{text}}",
"a11y/Scroll to bottom": "Ir para o final",
"a11y/Scroll to bottom, {{count}} new messages": "Ir para o final, {{count}} novas mensagens",
"a11y/Scroll to latest": "Ir para a mensagem mais recente",
"a11y/Scroll to latest, {{count}} unread": "Ir para a mensagem mais recente, {{count}} não lidas",
"a11y/Send message": "Enviar mensagem",
"a11y/Sending": "Enviando",
- "a11y/Sent": "Enviado",
+ "a11y/Sent": "Enviada",
+ "a11y/Sent by you": "Enviada por você",
"a11y/Voice message recording. Hold to record.": "Gravação de mensagem de voz. Mantenha pressionado para gravar.",
+ "a11y/and {{count}} more reactions": "e mais {{count}} reações",
+ "a11y/you reacted": "você reagiu",
"a11y/{{count}} new messages": "{{count}} novas mensagens",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "limite de tamanho",
"unknown error": "erro desconhecido",
"unsupported file type": "tipo de arquivo não compatível",
- "a11y/Activate to view results": "Ative para ver os resultados",
- "a11y/End vote": "Encerrar votação",
- "a11y/Show all options": "Mostrar todas as opções",
- "a11y/Vote on {{option}}": "Votar em {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Toque duas vezes e segure para ativar o menu contextual",
"a11y/Swipe right to go through different actions": "Deslize para a direita para percorrer as diferentes ações",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} mensagens não lidas"
}
diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json
index 32b2f456ed..0040618446 100644
--- a/package/src/i18n/ru.json
+++ b/package/src/i18n/ru.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "ИИ генерирует ответ",
"a11y/AI is thinking": "ИИ думает",
"a11y/Avatar of {{name}}": "Аватар {{name}}",
+ "a11y/Channel with {{count}} members": "Канал с {{count}} участниками",
"a11y/Connected": "Подключено",
"a11y/Delivered": "Доставлено",
+ "a11y/Delivered, sent by you": "Доставлено, отправлено вами",
+ "a11y/Direct chat with {{name}}": "Прямой чат с {{name}}",
+ "a11y/Double tap to open": "Дважды коснитесь, чтобы открыть",
+ "a11y/Double tap to view reactions": "Дважды коснитесь, чтобы посмотреть реакции",
+ "a11y/Editing message": "Редактирование сообщения",
+ "a11y/Editing message: {{text}}": "Редактирование сообщения: {{text}}",
+ "a11y/Last message {{date}}": "Последнее сообщение {{date}}",
"a11y/Loading": "Загрузка",
"a11y/Loading failed": "Не удалось загрузить",
"a11y/Message actions": "Действия с сообщением",
+ "a11y/Muted": "Без звука",
"a11y/New message from {{user}}": "Новое сообщение от {{user}}",
"a11y/Offline": "Не в сети",
"a11y/Open message actions": "Открыть действия с сообщением",
"a11y/Reaction {{emoji}} by {{count}} users": "Реакция {{emoji}} от {{count}} пользователей",
"a11y/Read": "Прочитано",
+ "a11y/Read, sent by you": "Прочитано, отправлено вами",
"a11y/Reconnecting": "Повторное подключение",
"a11y/Reply to {{user}}": "Ответить {{user}}",
"a11y/Remove edit": "Удалить редактирование",
"a11y/Remove reply": "Удалить ответ",
+ "a11y/Replying to {{user}}": "Ответ {{user}}",
+ "a11y/Replying to {{user}}: {{text}}": "Ответ {{user}}: {{text}}",
"a11y/Scroll to bottom": "Прокрутить вниз",
"a11y/Scroll to bottom, {{count}} new messages": "Прокрутить вниз, {{count}} новых сообщений",
"a11y/Scroll to latest": "Перейти к последнему сообщению",
@@ -274,7 +286,10 @@
"a11y/Send message": "Отправить сообщение",
"a11y/Sending": "Отправка",
"a11y/Sent": "Отправлено",
+ "a11y/Sent by you": "Отправлено вами",
"a11y/Voice message recording. Hold to record.": "Запись голосового сообщения. Удерживайте, чтобы записать.",
+ "a11y/and {{count}} more reactions": "и ещё {{count}} реакций",
+ "a11y/you reacted": "вы отреагировали",
"a11y/{{count}} new messages": "{{count}} новых сообщений",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "лимит размера",
"unknown error": "неизвестная ошибка",
"unsupported file type": "неподдерживаемый тип файла",
- "a11y/Activate to view results": "Активируйте, чтобы увидеть результаты",
- "a11y/End vote": "Завершить голосование",
- "a11y/Show all options": "Показать все варианты",
- "a11y/Vote on {{option}}": "Голосовать за {{option}}",
"a11y/Double tap and hold to activate contextual menu": "Дважды коснитесь и удерживайте, чтобы открыть контекстное меню",
"a11y/Swipe right to go through different actions": "Смахните вправо, чтобы переключаться между действиями",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} непрочитанных сообщений"
}
diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json
index b3a80c6a3f..9008b4dd16 100644
--- a/package/src/i18n/tr.json
+++ b/package/src/i18n/tr.json
@@ -253,20 +253,32 @@
"a11y/AI is generating": "Yapay zeka oluşturuyor",
"a11y/AI is thinking": "Yapay zeka düşünüyor",
"a11y/Avatar of {{name}}": "{{name}} avatarı",
+ "a11y/Channel with {{count}} members": "{{count}} üyeli kanal",
"a11y/Connected": "Bağlandı",
- "a11y/Delivered": "Teslim edildi",
+ "a11y/Delivered": "İletildi",
+ "a11y/Delivered, sent by you": "İletildi, sizin tarafınızdan gönderildi",
+ "a11y/Direct chat with {{name}}": "{{name}} ile doğrudan sohbet",
+ "a11y/Double tap to open": "Açmak için iki kez dokun",
+ "a11y/Double tap to view reactions": "Tepkileri görmek için iki kez dokun",
+ "a11y/Editing message": "Mesaj düzenleniyor",
+ "a11y/Editing message: {{text}}": "Mesaj düzenleniyor: {{text}}",
+ "a11y/Last message {{date}}": "Son mesaj {{date}}",
"a11y/Loading": "Yükleniyor",
"a11y/Loading failed": "Yükleme başarısız",
"a11y/Message actions": "Mesaj eylemleri",
+ "a11y/Muted": "Sessize alındı",
"a11y/New message from {{user}}": "{{user}} kullanıcısından yeni mesaj",
"a11y/Offline": "Çevrimdışı",
"a11y/Open message actions": "Mesaj eylemlerini aç",
"a11y/Reaction {{emoji}} by {{count}} users": "{{count}} kullanıcıdan {{emoji}} tepkisi",
"a11y/Read": "Okundu",
+ "a11y/Read, sent by you": "Okundu, sizin tarafınızdan gönderildi",
"a11y/Reconnecting": "Yeniden bağlanıyor",
"a11y/Reply to {{user}}": "{{user}} kullanıcısına yanıt ver",
"a11y/Remove edit": "Düzenlemeyi kaldır",
"a11y/Remove reply": "Yanıtı kaldır",
+ "a11y/Replying to {{user}}": "{{user}} kullanıcısına yanıt veriliyor",
+ "a11y/Replying to {{user}}: {{text}}": "{{user}} kullanıcısına yanıt veriliyor: {{text}}",
"a11y/Scroll to bottom": "En alta git",
"a11y/Scroll to bottom, {{count}} new messages": "En alta git, {{count}} yeni mesaj",
"a11y/Scroll to latest": "En son mesaja git",
@@ -274,7 +286,10 @@
"a11y/Send message": "Mesaj gönder",
"a11y/Sending": "Gönderiliyor",
"a11y/Sent": "Gönderildi",
+ "a11y/Sent by you": "Sizin tarafınızdan gönderildi",
"a11y/Voice message recording. Hold to record.": "Sesli mesaj kaydı. Kaydetmek için basılı tutun.",
+ "a11y/and {{count}} more reactions": "ve {{count}} tepki daha",
+ "a11y/you reacted": "siz tepki verdiniz",
"a11y/{{count}} new messages": "{{count}} yeni mesaj",
"a11y/Add attachment": "Add attachment",
"a11y/Close attachments": "Close attachments",
@@ -352,12 +367,9 @@
"size limit": "boyut sınırı",
"unknown error": "bilinmeyen hata",
"unsupported file type": "desteklenmeyen dosya türü",
- "a11y/Activate to view results": "Sonuçları görmek için etkinleştir",
- "a11y/End vote": "Oylamayı sonlandır",
- "a11y/Show all options": "Tüm seçenekleri göster",
- "a11y/Vote on {{option}}": "{{option}} için oy ver",
"a11y/Double tap and hold to activate contextual menu": "Bağlam menüsünü etkinleştirmek için çift dokunup basılı tut",
"a11y/Swipe right to go through different actions": "Farklı eylemler arasında geçiş yapmak için sağa kaydır",
"a11y/Close": "Close",
- "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss."
+ "a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.": "Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.",
+ "a11y/{{count}} unread messages": "{{count}} okunmamış mesaj"
}