@ethora/chat-component 26.6.7 → 26.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/VideoCallOverlay-BgYU8sTM.js +23647 -0
  2. package/dist/VideoCallOverlay-BgYU8sTM.js.map +1 -0
  3. package/dist/assets/icons.d.ts +4 -0
  4. package/dist/components/MainComponents/ChatHeader.d.ts +2 -1
  5. package/dist/components/MainComponents/LanguageSelectorModal.d.ts +3 -0
  6. package/dist/components/MainComponents/ProfileImagePlaceholder.d.ts +2 -0
  7. package/dist/components/MainComponents/Switch.d.ts +8 -0
  8. package/dist/components/MessageBubble/MessageTranslate.d.ts +25 -0
  9. package/dist/components/MessageBubble/TranslatedMessageBody.d.ts +21 -0
  10. package/dist/components/Notification/NotificationPermissionBanner.d.ts +16 -0
  11. package/dist/components/RoomComponents/ChatRoomItem.d.ts +2 -2
  12. package/dist/components/RoomComponents/OnlineUsersPopover.d.ts +11 -0
  13. package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +4 -0
  14. package/dist/firebase-messaging-sw.js +16 -4
  15. package/dist/helpers/callLogMessage.d.ts +22 -0
  16. package/dist/helpers/createUserNameFromSetUser.d.ts +36 -1
  17. package/dist/helpers/isLikelyMucJid.d.ts +18 -0
  18. package/dist/helpers/resolveIconColor.d.ts +5 -3
  19. package/dist/helpers/scheduleAckCatchup.d.ts +27 -0
  20. package/dist/helpers/toBaseLanguage.d.ts +8 -0
  21. package/dist/hooks/useChatSettingState.d.ts +2 -0
  22. package/dist/hooks/useChatWrapperInit.d.ts +3 -1
  23. package/dist/hooks/useIsMobileViewport.d.ts +1 -0
  24. package/dist/hooks/useMessageTranslation.d.ts +28 -0
  25. package/dist/hooks/useRoomPresence.d.ts +10 -0
  26. package/dist/hooks/useSendMessage.d.ts +1 -0
  27. package/dist/i18n/strings.d.ts +15 -0
  28. package/dist/i18n/useT.d.ts +33 -0
  29. package/dist/main-82wctwRX.js +74711 -0
  30. package/dist/main-82wctwRX.js.map +1 -0
  31. package/dist/main.d.ts +1 -0
  32. package/dist/main.js +23 -77070
  33. package/dist/main.js.map +1 -1
  34. package/dist/networking/authRefresh.d.ts +6 -0
  35. package/dist/networking/xmppClient.d.ts +2 -0
  36. package/dist/roomStore/Middleware/unreadMidlleware.d.ts +1 -0
  37. package/dist/roomStore/callSlice.d.ts +1 -0
  38. package/dist/roomStore/chatSettingsSlice.d.ts +25 -1
  39. package/dist/roomStore/roomsSlice.d.ts +8 -1
  40. package/dist/types/models/config.model.d.ts +122 -0
  41. package/dist/types/models/language.model.d.ts +1 -1
  42. package/dist/types/models/message.model.d.ts +1 -0
  43. package/dist/utils/translateModePolicy.d.ts +19 -0
  44. package/package.json +1 -1
  45. package/public/firebase-messaging-sw.js +16 -4
  46. package/dist/components/MessageBubble/MessageTranslations.d.ts +0 -10
@@ -1,3 +1,7 @@
1
+ export declare const GlobeIcon: ({ color, ...props }: {
2
+ [x: string]: any;
3
+ color?: string;
4
+ }) => import("react/jsx-runtime").JSX.Element;
1
5
  export declare const AddNewIcon: ({ color, ...props }: {
2
6
  [x: string]: any;
3
7
  color?: string;
@@ -1,8 +1,9 @@
1
1
  import { default as React } from 'react';
2
- import { IRoom } from '../../types/types';
2
+ import { IConfig, IRoom } from '../../types/types';
3
3
  interface ChatHeaderProps {
4
4
  currentRoom: IRoom;
5
5
  handleBackClick?: (value: boolean) => void;
6
6
  }
7
+ export declare const shouldShowLanguageSelector: (translatesConfig: IConfig["translates"] | undefined) => boolean;
7
8
  declare const ChatHeader: React.FC<ChatHeaderProps>;
8
9
  export default ChatHeader;
@@ -0,0 +1,3 @@
1
+ import { default as React } from 'react';
2
+ export declare const LanguageSelectorButton: React.FC;
3
+ export default LanguageSelectorButton;
@@ -16,6 +16,8 @@ interface ProfileImagePlaceholderProps {
16
16
  active?: boolean;
17
17
  placeholderIcon?: React.ReactNode;
18
18
  disableOverlay?: boolean;
19
+ /** Show a green online-status dot on the avatar. */
20
+ online?: boolean;
19
21
  }
20
22
  export declare const ProfileImagePlaceholder: React.FC<ProfileImagePlaceholderProps>;
21
23
  export {};
@@ -4,6 +4,14 @@ interface SwitchProps {
4
4
  bgColor?: string;
5
5
  onSwitchOn?: () => void;
6
6
  onSwitchOff?: () => void;
7
+ /**
8
+ * Reflects an existing value (e.g. redux state) instead of always
9
+ * starting from "off". Optional and defaults to undefined so every
10
+ * existing fire-and-forget caller keeps its original always-starts-off
11
+ * behaviour unchanged - only pass this when the switch represents a
12
+ * real, already-known boolean.
13
+ */
14
+ checked?: boolean;
7
15
  }
8
16
  declare const Switch: React.FC<SwitchProps>;
9
17
  export default Switch;
@@ -0,0 +1,25 @@
1
+ import { FC } from 'react';
2
+ import { IConfig, IMessage } from '../../types/types';
3
+ interface MessageTranslateProps {
4
+ message: IMessage;
5
+ isUser: boolean;
6
+ config?: IConfig;
7
+ }
8
+ /**
9
+ * Manual-mode message translation (LinkedIn-style). Renders a "Translate"
10
+ * link under an incoming message; on click it calls
11
+ * `config.translates.onTranslate` (host-provided) - or, when the host
12
+ * doesn't supply one, reads whatever translation already arrived attached
13
+ * to the stanza (`message.translations`) - and shows the result inline
14
+ * with a "Show original" toggle. No default translation service is called
15
+ * here; a message with nothing attached and no host `onTranslate` simply
16
+ * has nothing to reveal (see `translation.failed`).
17
+ *
18
+ * Visibility: `config.translates.showTranslateForMessage(message)` if the host
19
+ * supplies it (they keep the locale logic and just tell us yes/no); otherwise
20
+ * we compare the message's source base-language with the reader's base-language
21
+ * (region ignored, so en-US vs en-CA shows nothing). The reader's FULL locale
22
+ * (fr-CA vs fr-FR) is still forwarded to `onTranslate` as `targetLocale`.
23
+ */
24
+ declare const MessageTranslate: FC<MessageTranslateProps>;
25
+ export default MessageTranslate;
@@ -0,0 +1,21 @@
1
+ import { default as React, ReactNode } from 'react';
2
+ interface TranslatedMessageBodyProps {
3
+ /** Text as sent, in the sender's language. */
4
+ originalText: string;
5
+ accentColor?: string;
6
+ /** Renders the translated text (markdown pipeline, mentions, etc). */
7
+ children: ReactNode;
8
+ /**
9
+ * Whether this message was sent by the current user. The sender already
10
+ * knows what they wrote, so their own messages skip the quote-plus-
11
+ * translation treatment and just show the original text.
12
+ */
13
+ isUser: boolean;
14
+ }
15
+ /**
16
+ * A message body that is showing a translation: the translated text reads
17
+ * as the primary content, with the original quoted above it for reference
18
+ * - skipped for the current user's own messages (see `isUser`).
19
+ */
20
+ export declare const TranslatedMessageBody: React.FC<TranslatedMessageBodyProps>;
21
+ export default TranslatedMessageBody;
@@ -0,0 +1,16 @@
1
+ import { default as React } from 'react';
2
+ import { IConfig } from '../../types/types';
3
+ interface NotificationPermissionBannerProps {
4
+ config?: IConfig;
5
+ /** Triggers the browser permission prompt + push registration. */
6
+ requestPermission: () => Promise<void>;
7
+ }
8
+ /**
9
+ * In-app banner nudging the user to enable browser notifications. Fully
10
+ * config-driven (config.pushNotifications.permissionBanner) and self-contained:
11
+ * it reads the live Notification permission, renders the "enable" or "blocked"
12
+ * variant accordingly, and hides itself when notifications are granted /
13
+ * unsupported / disabled. SSR-safe.
14
+ */
15
+ declare const NotificationPermissionBanner: React.FC<NotificationPermissionBannerProps>;
16
+ export default NotificationPermissionBanner;
@@ -7,5 +7,5 @@ interface ChatRoomItemProps {
7
7
  performClick: (chat: IRoom) => void;
8
8
  config: IConfig;
9
9
  }
10
- declare const ChatRoomItem: React.FC<ChatRoomItemProps>;
11
- export default ChatRoomItem;
10
+ declare const _default: React.NamedExoticComponent<ChatRoomItemProps>;
11
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { default as React } from 'react';
2
+ import { RoomMember } from '../../types/types';
3
+ interface OnlineUsersPopoverProps {
4
+ /** xmppUsernames currently online in this room (from useRoomPresence). */
5
+ onlineUsernames: string[];
6
+ members?: RoomMember[];
7
+ myXmppUsername: string;
8
+ isChatActive?: boolean;
9
+ }
10
+ declare const OnlineUsersPopover: React.FC<OnlineUsersPopoverProps>;
11
+ export default OnlineUsersPopover;
@@ -1,3 +1,7 @@
1
+ export declare const MediaLoadingSkeleton: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components/dist/types').Substitute<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
2
+ $width?: number | string;
3
+ $height?: number | string;
4
+ }>> & string;
1
5
  export declare const Container: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
2
6
  export declare const FullScreenImage: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, never>> & string;
3
7
  export declare const ModalContent: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
@@ -384,9 +384,10 @@ async function handlePush(raw = {}, source = 'push') {
384
384
  });
385
385
 
386
386
  const visible = isVisibleClients(windowClients);
387
+ const hasAnyClient = windowClients.length > 0;
387
388
 
388
389
  if (DEBUG) log('clients:', dumpClients(windowClients));
389
- log('visibleClientExists:', visible);
390
+ log('visibleClientExists:', visible, 'anyClientExists:', hasAnyClient);
390
391
 
391
392
  await broadcastDebugMessage({
392
393
  source,
@@ -445,9 +446,20 @@ async function handlePush(raw = {}, source = 'push') {
445
446
  },
446
447
  };
447
448
 
448
- if (!visible) {
449
+ // Calls: a client that's merely backgrounded (open, just not the
450
+ // visible/focused tab) is NOT the "nobody will hear this ring" case -
451
+ // VideoCallOverlay already rings its own OS notification for exactly
452
+ // that state (see its comment: this SW push is meant to be the
453
+ // fully-offline fallback). `visible` collapses "backgrounded but open"
454
+ // and "fully closed" into the same false, which duplicated that
455
+ // client-side ring every time the tab was merely hidden. Suppress on
456
+ // ANY open client for calls; regular messages keep the visible-only
457
+ // check they already had.
458
+ const suppress = isCall ? hasAnyClient : visible;
459
+
460
+ if (!suppress) {
449
461
  log('SHOW NOTIFICATION', {
450
- reason: 'NO_VISIBLE_CLIENT',
462
+ reason: isCall ? 'NO_CLIENT_AT_ALL' : 'NO_VISIBLE_CLIENT',
451
463
  tag: options.tag,
452
464
  isCall,
453
465
  });
@@ -456,7 +468,7 @@ async function handlePush(raw = {}, source = 'push') {
456
468
  }
457
469
 
458
470
  log('SKIP NOTIFICATION', {
459
- reason: 'VISIBLE_CLIENT',
471
+ reason: isCall ? 'CLIENT_OPEN_WILL_RING_ITSELF' : 'VISIBLE_CLIENT',
460
472
  });
461
473
  }
462
474
 
@@ -8,4 +8,26 @@ export interface CallLogMeta {
8
8
  }
9
9
  export declare const isCallLogMessage: (message: unknown) => boolean;
10
10
  export declare const callStateHasLogData: (attrs: Record<string, any>) => boolean;
11
+ export declare const formatCallDuration: (ms: number) => string;
11
12
  export declare const transformCallLogMessage: (message: IMessage, selfXmppUsername: string) => IMessage;
13
+ export declare const buildLocalCallLogMessage: (params: {
14
+ callId: string;
15
+ direction: "outgoing" | "incoming";
16
+ durationMs: number;
17
+ kind: "audio" | "video";
18
+ selfXmppUsername: string;
19
+ }) => IMessage;
20
+ /**
21
+ * The label for a call-log entry, built at RENDER time from the callLog
22
+ * meta rather than read off `message.body`.
23
+ *
24
+ * transformCallLogMessage bakes an English sentence into `body` the moment
25
+ * the stanza arrives, which freezes the language: switch to Chinese and
26
+ * every call log you already received stays English forever, because the
27
+ * text is data by then, not a rendering. The meta (direction/duration/
28
+ * missed) is the actual fact - the sentence is a view of it, so it belongs
29
+ * where the view is. `body` stays as the fallback for entries received
30
+ * before this existed, and for the notification/sidebar preview paths that
31
+ * only have the raw message.
32
+ */
33
+ export declare const formatCallLogLabel: (callLog: CallLogMeta | undefined, t: (key: string, vars?: Record<string, string | number>) => string, fallbackBody?: string) => string;
@@ -1,2 +1,37 @@
1
- import { RoomMember } from '../types/types';
1
+ import { IMessage, RoomMember } from '../types/types';
2
+ /**
3
+ * Cache-only lookup. Returns the literal "Deleted User" as a MISS sentinel
4
+ * when `usersSet` has no entry for `userId` yet - callers that want a
5
+ * better fallback (e.g. resolveSenderDisplayName below) check for that
6
+ * exact string rather than treating it as a real name.
7
+ */
2
8
  export declare const createUserNameFromSetUser: (usersSet: Record<string, RoomMember>, userId: string) => string;
9
+ /**
10
+ * The sender's display name, resolved WITHOUT depending on `usersSet`
11
+ * having already been hydrated.
12
+ *
13
+ * `usersSet` is populated by a separate fetch (member list / presence) that
14
+ * can still be in flight when a message arrives - most reliably right after
15
+ * reconnect, when a burst of catch-up messages can land before that fetch
16
+ * resolves. A cache-only lookup shows the literal "Deleted User" for every
17
+ * one of those messages, even though the sender is very much not deleted.
18
+ *
19
+ * The message itself already carries the sender's name on the wire: every
20
+ * client that sends through this SDK stamps `senderFirstName` /
21
+ * `senderLastName` / `fullName` onto the outgoing stanza's `<data>` element
22
+ * (see sendTextMessage.xmpp.ts / sendTextMessageWithTranslateTag.xmpp.ts),
23
+ * and createMessageFromXml spreads those onto the top-level message object.
24
+ * Preferring that over the cache removes the timing dependency entirely for
25
+ * the common case, and "Deleted User" becomes what it should have always
26
+ * been: a rare last resort, not "usersSet hasn't loaded yet".
27
+ *
28
+ * Mirrors roomsSlice.ts's enrichMessageAuthor, which does the same
29
+ * self-healing for the message list - this is that same resolution used
30
+ * where a live decision is needed once (e.g. a notification), rather than
31
+ * baked into a message object that re-resolves on every store update.
32
+ */
33
+ export declare const resolveSenderDisplayName: (message: Pick<IMessage, "user"> & {
34
+ fullName?: string;
35
+ senderFirstName?: string;
36
+ senderLastName?: string;
37
+ }, usersSet: Record<string, RoomMember>) => string;
@@ -0,0 +1,18 @@
1
+ export declare const isLikelyMucJid: (jid: unknown) => jid is string;
2
+ /**
3
+ * True when `value` can be used as the localpart of a MUC room JID, i.e. it is
4
+ * safe to build `${value}@conference.<host>` from it.
5
+ *
6
+ * Ethora room localparts are always `<appId>_<roomId>`, so the underscore is a
7
+ * reliable discriminator against slice keys, plain flags and stray strings.
8
+ * Callers that already hold a full JID should use `isLikelyMucJid` instead.
9
+ */
10
+ export declare const isLikelyRoomLocalpart: (value: unknown) => value is string;
11
+ /**
12
+ * Normalise a room reference into a full MUC JID, or return null when the
13
+ * input cannot be one. Use at every point that would otherwise blindly do
14
+ * `chatJID.includes('@') ? chatJID : chatJID + '@' + conference`: that pattern
15
+ * silently manufactures a valid-looking JID out of any garbage handed to it,
16
+ * which is how slice keys ended up being joined as rooms.
17
+ */
18
+ export declare const toRoomJid: (chatJID: unknown, conference: string | undefined) => string | null;
@@ -12,9 +12,11 @@ export declare const resolveIconColor: (config?: Pick<IConfig, "colors"> | null)
12
12
  * Resolve the background colour to use behind the chat's icon "chips" (attach,
13
13
  * send, microphone, file preview, etc.).
14
14
  *
15
- * Priority: explicit `config.colors.iconsBg` → `config.colors.secondary` →
16
- * white. Lets a host theme the icon backgrounds independently from `secondary`
17
- * via `colors.iconsBg`, while keeping the historical white default.
15
+ * Priority: explicit `config.colors.iconsBg` → a soft neutral default. We do
16
+ * NOT fall back to `colors.secondary`: that's a text/heading colour (often
17
+ * near-black), and using it here rendered the icon chips as harsh black
18
+ * squares. `secondary` also drives avatar backgrounds, so keeping it out of
19
+ * this resolver lets hosts theme text without blacking out the icons.
18
20
  */
19
21
  export declare const resolveIconBgColor: (config?: Pick<IConfig, "colors"> | null) => string;
20
22
  /** CSS variable read by the chat's chrome icons as their default colour. */
@@ -0,0 +1,27 @@
1
+ import { default as XmppClient } from '../networking/xmppClient';
2
+ /**
3
+ * How long to wait for the MUC to reflect our own message back before
4
+ * assuming it never will. A MUC echo normally lands in ~100-300ms - far
5
+ * faster than a MAM round trip could answer anyway - so this only has to
6
+ * be generous enough not to fire during a slow-but-healthy send.
7
+ */
8
+ export declare const ACK_CATCHUP_DELAY_MS = 2500;
9
+ /** The echo is what flips `pending` to false (see addRoomMessage). */
10
+ export declare const hasEchoLanded: (roomJID: string, messageId: string) => boolean;
11
+ /**
12
+ * Safety net for a send whose MUC echo never arrives (e.g. our presence
13
+ * silently dropped): re-assert presence and pull history - ONCE, and only
14
+ * if the echo really is missing.
15
+ *
16
+ * It must stay a safety net. This used to be a poll: an immediate fetch on
17
+ * every send plus a retry loop re-arming every 700ms for 5s until the echo
18
+ * landed. Every getHistoryStanza makes the server re-send the room's last
19
+ * 10-20 archived messages down the socket, so one send produced ~6 MAM
20
+ * replies (9 worst case) - the "I get my message back 4 times" seen in the
21
+ * WS frames. It was self-amplifying too: a slower echo fired more history
22
+ * storms, and each storm's stanzas fed the reducer/persist churn that was
23
+ * slowing the echo down in the first place.
24
+ *
25
+ * @returns the timer, so the caller can cancel it on unmount.
26
+ */
27
+ export declare const scheduleAckCatchup: (client: XmppClient, roomJID: string, messageId: string, onSettled?: () => void) => ReturnType<typeof setTimeout>;
@@ -0,0 +1,8 @@
1
+ import { Iso639_1Codes } from '../types/models/language.model';
2
+ /**
3
+ * "fr-CA" -> "fr", "EN" -> "en". Region only matters for the translation
4
+ * service's dialect handling - everywhere else (matching a source language
5
+ * against a reader's, caching, `langSource`) only the base language is
6
+ * meaningful.
7
+ */
8
+ export declare const toBaseLanguage: (locale?: string | null) => Iso639_1Codes;
@@ -6,4 +6,6 @@ export declare const useChatSettingState: () => {
6
6
  deleteModal: any;
7
7
  selectedUser: any;
8
8
  langSource: any;
9
+ translateSendEnabled: any;
10
+ translateMode: any;
9
11
  };
@@ -1,4 +1,4 @@
1
- import { IConfig } from '../types/types';
1
+ import { IConfig, Iso639_1Codes } from '../types/types';
2
2
  import { default as XmppClient } from '../networking/xmppClient';
3
3
  interface useChatWrapperInitProps {
4
4
  roomJID: string | null | undefined;
@@ -15,5 +15,7 @@ interface useChatWrapperInitResult {
15
15
  setClient: React.Dispatch<React.SetStateAction<XmppClient | null>>;
16
16
  isConnectionLost: boolean;
17
17
  }
18
+ export declare const resolveLegacyTranslatesLangSource: (translatesConfig?: IConfig["translates"]) => Iso639_1Codes | undefined;
19
+ export declare const resolveExternalReaderLocaleLangSource: (readerLocale?: string) => Iso639_1Codes | undefined;
18
20
  declare const useChatWrapperInit: ({ roomJID, wasAutoSelected, config, }: useChatWrapperInitProps) => useChatWrapperInitResult;
19
21
  export default useChatWrapperInit;
@@ -0,0 +1 @@
1
+ export declare const useIsMobileViewport: (breakpoint?: number) => boolean;
@@ -0,0 +1,28 @@
1
+ import { IMessage } from '../types/types';
2
+ export interface MessageTranslationState {
3
+ /** False when the message is already in the reader's language, or no
4
+ * translation is available - render the plain body, no quote. */
5
+ hasTranslation: boolean;
6
+ originalText: string;
7
+ /** Translation when hasTranslation, else the original - always safe to
8
+ * render directly. */
9
+ displayText: string;
10
+ }
11
+ /**
12
+ * Translation for ONE message, into the reader's language only.
13
+ *
14
+ * Pure and synchronous - no HTTP call. Every translation this client ever
15
+ * shows arrives over XMPP already: the sender tags outgoing messages with
16
+ * `<translate source="xx"/>` (see sendTextMessageWithTranslateTag) and
17
+ * whatever process attaches actual translations does so server-side,
18
+ * landing on `message.translations` when the stanza is parsed (see
19
+ * getDataFromXml). This function only ever reads that - it never calls a
20
+ * translation service itself, so there's nothing to fail when one isn't
21
+ * reachable, and nothing to await.
22
+ *
23
+ * A message sent before the sender had translate-sending turned on simply
24
+ * never got tagged, so it has no translation to show - not an error, just
25
+ * absence of the underlying data (see the enable/disable-translates
26
+ * disclaimer in LanguageSelectorModal).
27
+ */
28
+ export declare const useMessageTranslation: (message: Pick<IMessage, "body" | "langSource" | "translations">, readerLocale?: string, enabled?: boolean) => MessageTranslationState;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * xmppUsernames currently ONLINE (available MUC presence) in the given room.
3
+ * Populated from the room's presence stanzas: an available presence marks a
4
+ * user online; a `type='unavailable'` presence marks them offline. When you
5
+ * join a room the server sends the presence of everyone already online, so the
6
+ * list is correct from the first render and updates live as users come/go.
7
+ */
8
+ export declare const useRoomPresence: (roomJID?: string) => string[];
9
+ /** Whether a specific member (by xmppUsername) is currently online in the room. */
10
+ export declare const useIsUserOnline: (roomJID?: string, xmppUsername?: string) => boolean;
@@ -1,3 +1,4 @@
1
+ export declare const shouldTagOutgoingTranslateSource: (translatesEnabled: boolean | undefined, translateSendEnabled: boolean | undefined) => boolean;
1
2
  export declare const useSendMessage: () => {
2
3
  sendMessage: (message: string, activeRoomJID: string, isReply?: boolean, isChecked?: boolean, mainMessage?: string) => Promise<void>;
3
4
  sendMedia: (data: File, type: string, activeRoomJID: string, isReply?: boolean, isChecked?: boolean, mainMessage?: string) => Promise<void>;
@@ -0,0 +1,15 @@
1
+ export type UiStringTable = Record<string, string>;
2
+ export declare const BUILTIN_STRINGS: Record<string, UiStringTable>;
3
+ export declare const DEFAULT_UI_LANGUAGE = "en";
4
+ /** Reduce a BCP-47 locale ("fr-CA") to its base language ("fr"), lowercased. */
5
+ export declare const toBaseLanguage: (locale?: string | null) => string;
6
+ /**
7
+ * Build the effective string table for a locale: the built-in table for that
8
+ * base language (falling back to English for any missing key), with host
9
+ * overrides from `config.i18n.strings` merged on top.
10
+ */
11
+ export declare const resolveStringTable: (locale?: string | null, overrides?: UiStringTable | null) => UiStringTable;
12
+ /** Replace `{name}` placeholders with values from `vars`. */
13
+ export declare const interpolate: (template: string, vars?: Record<string, string | number>) => string;
14
+ /** One-shot translate for non-React code (helpers). */
15
+ export declare const translateKey: (key: string, locale?: string | null, overrides?: UiStringTable | null, vars?: Record<string, string | number>) => string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * UI translation hook. Reads the active locale + host overrides from
3
+ * `config.i18n` and returns a `t(key, vars?)` function.
4
+ *
5
+ * const t = useT();
6
+ * <SearchInput placeholder={t('search.placeholder')} />
7
+ * <span>{t('presence.onlineCount', { count })}</span>
8
+ *
9
+ * Locale resolves to the base language for captions (region is only used for
10
+ * message translation). Missing keys fall back to English, then to the raw
11
+ * key, so nothing ever renders blank.
12
+ *
13
+ * When the host doesn't set `config.i18n.locale` explicitly, this falls
14
+ * back to `langSource` - the same reader-language state the in-chat globe
15
+ * picker (LanguageSelectorButton) and `config.translates.readerLocale`
16
+ * drive (see useChatWrapperInit's resolveExternalReaderLocaleLangSource).
17
+ * So picking a language in the chat changes both message translations AND
18
+ * static captions together, as one language switch, unless the host is
19
+ * explicitly managing UI locale separately via `config.i18n.locale`.
20
+ */
21
+ /**
22
+ * The locale the UI is currently in, resolved by the same rule useT uses
23
+ * (explicit `config.i18n.locale`, else the reader's `langSource`).
24
+ *
25
+ * For anything Intl already localises - dates, numbers, relative times -
26
+ * where you need the locale itself rather than a string from the table.
27
+ * Without it components reach for a hardcoded 'en-US' and quietly render
28
+ * American dates under a Chinese UI (DateLabel did exactly that).
29
+ * Undefined means "no explicit choice", which Intl reads as the browser's
30
+ * own locale - the right default.
31
+ */
32
+ export declare const useUiLocale: () => string | undefined;
33
+ export declare const useT: () => (key: string, vars?: Record<string, string | number>) => string;