stream-chat-react-native-core 10.0.0-rc.4 → 10.0.0

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 (276) hide show
  1. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
  2. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  3. package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js +2 -2
  4. package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
  5. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
  6. package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  7. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
  8. package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
  9. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js +8 -5
  10. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js.map +1 -1
  11. package/lib/commonjs/components/Message/Message.js.map +1 -1
  12. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
  13. package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  14. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +2 -2
  15. package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
  16. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js +29 -9
  17. package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  18. package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
  19. package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
  20. package/lib/commonjs/components/Message/utils/messageActions.js +1 -1
  21. package/lib/commonjs/components/Message/utils/messageActions.js.map +1 -1
  22. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
  23. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  24. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
  25. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  26. package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js +2 -2
  27. package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
  28. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js +2 -2
  29. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  30. package/lib/commonjs/components/MessageList/MessageList.js +1 -4
  31. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  32. package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js +18 -2
  33. package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
  34. package/lib/commonjs/components/Poll/Poll.js +5 -2
  35. package/lib/commonjs/components/Poll/Poll.js.map +1 -1
  36. package/lib/commonjs/components/Poll/components/PollAnswersList.js +4 -2
  37. package/lib/commonjs/components/Poll/components/PollAnswersList.js.map +1 -1
  38. package/lib/commonjs/components/Poll/components/PollOption.js +8 -3
  39. package/lib/commonjs/components/Poll/components/PollOption.js.map +1 -1
  40. package/lib/commonjs/components/Poll/components/PollResults/PollOptionFullResults.js +5 -2
  41. package/lib/commonjs/components/Poll/components/PollResults/PollOptionFullResults.js.map +1 -1
  42. package/lib/commonjs/components/Poll/components/PollResults/PollResultItem.js +3 -1
  43. package/lib/commonjs/components/Poll/components/PollResults/PollResultItem.js.map +1 -1
  44. package/lib/commonjs/components/Poll/components/PollResults/PollResults.js +4 -1
  45. package/lib/commonjs/components/Poll/components/PollResults/PollResults.js.map +1 -1
  46. package/lib/commonjs/components/Poll/hooks/usePollState.js +3 -0
  47. package/lib/commonjs/components/Poll/hooks/usePollState.js.map +1 -1
  48. package/lib/commonjs/components/Poll/hooks/usePollTextTranslation.js +17 -0
  49. package/lib/commonjs/components/Poll/hooks/usePollTextTranslation.js.map +1 -0
  50. package/lib/commonjs/components/Poll/index.js +11 -0
  51. package/lib/commonjs/components/Poll/index.js.map +1 -1
  52. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js +1 -2
  53. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  54. package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
  55. package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
  56. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  57. package/lib/commonjs/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
  58. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js +9 -4
  59. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  60. package/lib/commonjs/hooks/useTranslatedMessage.js +4 -3
  61. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -1
  62. package/lib/commonjs/index.js +0 -12
  63. package/lib/commonjs/index.js.map +1 -1
  64. package/lib/commonjs/store/SqliteClient.js +1 -1
  65. package/lib/commonjs/store/mappers/mapPollToStorable.js +4 -0
  66. package/lib/commonjs/store/mappers/mapPollToStorable.js.map +1 -1
  67. package/lib/commonjs/store/mappers/mapStorableToPoll.js +5 -1
  68. package/lib/commonjs/store/mappers/mapStorableToPoll.js.map +1 -1
  69. package/lib/commonjs/store/schema.js +2 -0
  70. package/lib/commonjs/store/schema.js.map +1 -1
  71. package/lib/commonjs/types/types.js.map +1 -1
  72. package/lib/commonjs/utils/i18n/getTranslatedText.js +7 -0
  73. package/lib/commonjs/utils/i18n/getTranslatedText.js.map +1 -0
  74. package/lib/commonjs/utils/utils.js +35 -1
  75. package/lib/commonjs/utils/utils.js.map +1 -1
  76. package/lib/commonjs/version.json +1 -1
  77. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
  78. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  79. package/lib/module/components/Channel/hooks/useChannelLifecycle.js +2 -2
  80. package/lib/module/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
  81. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
  82. package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
  83. package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
  84. package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
  85. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js +8 -5
  86. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewPollLabel.js.map +1 -1
  87. package/lib/module/components/Message/Message.js.map +1 -1
  88. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
  89. package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
  90. package/lib/module/components/Message/MessageItemView/MessageContent.js +2 -2
  91. package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
  92. package/lib/module/components/Message/MessageItemView/MessageStatus.js +29 -9
  93. package/lib/module/components/Message/MessageItemView/MessageStatus.js.map +1 -1
  94. package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
  95. package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
  96. package/lib/module/components/Message/utils/messageActions.js +1 -1
  97. package/lib/module/components/Message/utils/messageActions.js.map +1 -1
  98. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
  99. package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
  100. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
  101. package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
  102. package/lib/module/components/MessageInput/hooks/useAudioRecorder.js +2 -2
  103. package/lib/module/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
  104. package/lib/module/components/MessageList/InlineUnreadIndicator.js +2 -2
  105. package/lib/module/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  106. package/lib/module/components/MessageList/MessageList.js +1 -4
  107. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  108. package/lib/module/components/MessageList/hooks/useMessageListFocus.js +18 -2
  109. package/lib/module/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
  110. package/lib/module/components/Poll/Poll.js +5 -2
  111. package/lib/module/components/Poll/Poll.js.map +1 -1
  112. package/lib/module/components/Poll/components/PollAnswersList.js +4 -2
  113. package/lib/module/components/Poll/components/PollAnswersList.js.map +1 -1
  114. package/lib/module/components/Poll/components/PollOption.js +8 -3
  115. package/lib/module/components/Poll/components/PollOption.js.map +1 -1
  116. package/lib/module/components/Poll/components/PollResults/PollOptionFullResults.js +5 -2
  117. package/lib/module/components/Poll/components/PollResults/PollOptionFullResults.js.map +1 -1
  118. package/lib/module/components/Poll/components/PollResults/PollResultItem.js +3 -1
  119. package/lib/module/components/Poll/components/PollResults/PollResultItem.js.map +1 -1
  120. package/lib/module/components/Poll/components/PollResults/PollResults.js +4 -1
  121. package/lib/module/components/Poll/components/PollResults/PollResults.js.map +1 -1
  122. package/lib/module/components/Poll/hooks/usePollState.js +3 -0
  123. package/lib/module/components/Poll/hooks/usePollState.js.map +1 -1
  124. package/lib/module/components/Poll/hooks/usePollTextTranslation.js +17 -0
  125. package/lib/module/components/Poll/hooks/usePollTextTranslation.js.map +1 -0
  126. package/lib/module/components/Poll/index.js +11 -0
  127. package/lib/module/components/Poll/index.js.map +1 -1
  128. package/lib/module/components/Thread/components/ThreadFooterComponent.js +1 -2
  129. package/lib/module/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  130. package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
  131. package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
  132. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  133. package/lib/module/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
  134. package/lib/module/hooks/messagePreview/useMessagePreviewText.js +9 -4
  135. package/lib/module/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  136. package/lib/module/hooks/useTranslatedMessage.js +4 -3
  137. package/lib/module/hooks/useTranslatedMessage.js.map +1 -1
  138. package/lib/module/index.js +0 -12
  139. package/lib/module/index.js.map +1 -1
  140. package/lib/module/store/SqliteClient.js +1 -1
  141. package/lib/module/store/mappers/mapPollToStorable.js +4 -0
  142. package/lib/module/store/mappers/mapPollToStorable.js.map +1 -1
  143. package/lib/module/store/mappers/mapStorableToPoll.js +5 -1
  144. package/lib/module/store/mappers/mapStorableToPoll.js.map +1 -1
  145. package/lib/module/store/schema.js +2 -0
  146. package/lib/module/store/schema.js.map +1 -1
  147. package/lib/module/types/types.js.map +1 -1
  148. package/lib/module/utils/i18n/getTranslatedText.js +7 -0
  149. package/lib/module/utils/i18n/getTranslatedText.js.map +1 -0
  150. package/lib/module/utils/utils.js +35 -1
  151. package/lib/module/utils/utils.js.map +1 -1
  152. package/lib/module/version.json +1 -1
  153. package/lib/typescript/ai-components/services/media-picker-service/index.d.ts +1 -1
  154. package/lib/typescript/components/Channel/hooks/useChannelLifecycle.d.ts.map +1 -1
  155. package/lib/typescript/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.d.ts.map +1 -1
  156. package/lib/typescript/components/ChannelPreview/ChannelPreviewMessage.d.ts.map +1 -1
  157. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewPollLabel.d.ts.map +1 -1
  158. package/lib/typescript/components/Message/Message.d.ts +2 -1
  159. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  160. package/lib/typescript/components/Message/MessageItemView/Headers/SentToChannelHeader.d.ts.map +1 -1
  161. package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
  162. package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts +6 -0
  163. package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts.map +1 -1
  164. package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts +14 -0
  165. package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts.map +1 -0
  166. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.d.ts.map +1 -1
  167. package/lib/typescript/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.d.ts.map +1 -1
  168. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  169. package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts +5 -2
  170. package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts.map +1 -1
  171. package/lib/typescript/components/Poll/Poll.d.ts.map +1 -1
  172. package/lib/typescript/components/Poll/components/PollAnswersList.d.ts.map +1 -1
  173. package/lib/typescript/components/Poll/components/PollOption.d.ts.map +1 -1
  174. package/lib/typescript/components/Poll/components/PollResults/PollOptionFullResults.d.ts.map +1 -1
  175. package/lib/typescript/components/Poll/components/PollResults/PollResultItem.d.ts.map +1 -1
  176. package/lib/typescript/components/Poll/components/PollResults/PollResults.d.ts.map +1 -1
  177. package/lib/typescript/components/Poll/hooks/usePollState.d.ts +1 -0
  178. package/lib/typescript/components/Poll/hooks/usePollState.d.ts.map +1 -1
  179. package/lib/typescript/components/Poll/hooks/usePollTextTranslation.d.ts +7 -0
  180. package/lib/typescript/components/Poll/hooks/usePollTextTranslation.d.ts.map +1 -0
  181. package/lib/typescript/components/Poll/index.d.ts +1 -0
  182. package/lib/typescript/components/Poll/index.d.ts.map +1 -1
  183. package/lib/typescript/components/Thread/components/ThreadFooterComponent.d.ts.map +1 -1
  184. package/lib/typescript/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.d.ts.map +1 -1
  185. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +2 -1
  186. package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
  187. package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts +10 -6
  188. package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts.map +1 -1
  189. package/lib/typescript/hooks/messagePreview/useMessagePreviewText.d.ts.map +1 -1
  190. package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
  191. package/lib/typescript/i18n/keys.d.ts +2 -2
  192. package/lib/typescript/i18n/keys.d.ts.map +1 -1
  193. package/lib/typescript/index.d.ts +0 -1
  194. package/lib/typescript/index.d.ts.map +1 -1
  195. package/lib/typescript/store/mappers/mapPollToStorable.d.ts.map +1 -1
  196. package/lib/typescript/store/mappers/mapStorableToPoll.d.ts.map +1 -1
  197. package/lib/typescript/store/schema.d.ts +2 -0
  198. package/lib/typescript/store/schema.d.ts.map +1 -1
  199. package/lib/typescript/types/types.d.ts +0 -4
  200. package/lib/typescript/types/types.d.ts.map +1 -1
  201. package/lib/typescript/utils/i18n/getTranslatedText.d.ts +8 -0
  202. package/lib/typescript/utils/i18n/getTranslatedText.d.ts.map +1 -0
  203. package/lib/typescript/utils/utils.d.ts.map +1 -1
  204. package/package.json +2 -2
  205. package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +1 -1
  206. package/src/components/AttachmentPicker/components/AttachmentMediaPicker/__tests__/AttachmentPickerItem.test.tsx +29 -1
  207. package/src/components/Channel/__tests__/Channel.test.tsx +40 -7
  208. package/src/components/Channel/hooks/useChannelLifecycle.ts +8 -4
  209. package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts +1 -1
  210. package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +8 -0
  211. package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +2 -7
  212. package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +41 -0
  213. package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewPollLabel.test.tsx +28 -3
  214. package/src/components/ChannelPreview/hooks/useChannelPreviewPollLabel.ts +8 -4
  215. package/src/components/Message/Message.tsx +5 -2
  216. package/src/components/Message/MessageItemView/Headers/SentToChannelHeader.tsx +4 -32
  217. package/src/components/Message/MessageItemView/MessageContent.tsx +3 -2
  218. package/src/components/Message/MessageItemView/MessageStatus.tsx +43 -4
  219. package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +68 -1
  220. package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +57 -0
  221. package/src/components/Message/hooks/useOpenThreadAtMessage.ts +50 -0
  222. package/src/components/Message/utils/__tests__/messageActions.test.ts +57 -0
  223. package/src/components/Message/utils/messageActions.ts +1 -1
  224. package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +34 -6
  225. package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +24 -3
  226. package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx +3 -6
  227. package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +19 -8
  228. package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx +2 -5
  229. package/src/components/MessageInput/hooks/useAudioRecorder.tsx +2 -2
  230. package/src/components/MessageList/InlineUnreadIndicator.tsx +2 -2
  231. package/src/components/MessageList/MessageList.tsx +1 -5
  232. package/src/components/MessageList/__tests__/InlineUnreadIndicator.test.tsx +2 -2
  233. package/src/components/MessageList/__tests__/MessageList.test.tsx +163 -0
  234. package/src/components/MessageList/hooks/useMessageListFocus.ts +22 -2
  235. package/src/components/Poll/Poll.tsx +4 -2
  236. package/src/components/Poll/__tests__/PollTranslation.test.tsx +152 -0
  237. package/src/components/Poll/components/PollAnswersList.tsx +5 -1
  238. package/src/components/Poll/components/PollOption.tsx +8 -4
  239. package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx +5 -2
  240. package/src/components/Poll/components/PollResults/PollResultItem.tsx +3 -1
  241. package/src/components/Poll/components/PollResults/PollResults.tsx +4 -2
  242. package/src/components/Poll/hooks/usePollState.ts +4 -0
  243. package/src/components/Poll/hooks/usePollTextTranslation.ts +19 -0
  244. package/src/components/Poll/index.ts +1 -0
  245. package/src/components/Thread/__tests__/Thread.test.tsx +46 -1
  246. package/src/components/Thread/components/ThreadFooterComponent.tsx +0 -1
  247. package/src/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.tsx +5 -14
  248. package/src/contexts/messageContext/MessageContext.tsx +2 -1
  249. package/src/contexts/messageListItemContext/MessageListItemContext.tsx +15 -6
  250. package/src/hooks/__tests__/useTranslatedMessage.test.tsx +20 -0
  251. package/src/hooks/messagePreview/__tests__/useMessagePreviewText.test.tsx +37 -2
  252. package/src/hooks/messagePreview/useMessagePreviewText.tsx +5 -2
  253. package/src/hooks/useTranslatedMessage.ts +6 -11
  254. package/src/i18n/__tests__/catalog.fixture.json +2 -2
  255. package/src/i18n/keys.ts +2 -2
  256. package/src/index.ts +0 -1
  257. package/src/store/SqliteClient.ts +1 -1
  258. package/src/store/apis/__tests__/pollTranslations.test.ts +136 -0
  259. package/src/store/mappers/mapPollToStorable.ts +6 -0
  260. package/src/store/mappers/mapStorableToPoll.ts +4 -0
  261. package/src/store/schema.ts +4 -0
  262. package/src/types/stream-chat-common-custom-data.d.ts +0 -3
  263. package/src/types/types.ts +0 -5
  264. package/src/utils/__tests__/utils.test.ts +42 -1
  265. package/src/utils/i18n/__tests__/getTranslatedText.test.ts +17 -0
  266. package/src/utils/i18n/getTranslatedText.ts +10 -0
  267. package/src/utils/utils.ts +57 -1
  268. package/src/version.json +1 -1
  269. package/lib/commonjs/utils/setupCommandUIMiddlewares.js +0 -37
  270. package/lib/commonjs/utils/setupCommandUIMiddlewares.js.map +0 -1
  271. package/lib/module/utils/setupCommandUIMiddlewares.js +0 -37
  272. package/lib/module/utils/setupCommandUIMiddlewares.js.map +0 -1
  273. package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts +0 -3
  274. package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts.map +0 -1
  275. package/src/utils/__tests__/setupCommandUIMiddlewares.test.ts +0 -49
  276. package/src/utils/setupCommandUIMiddlewares.ts +0 -34
@@ -16,6 +16,7 @@ import { useComponentsContext } from '../../../../contexts/componentsContext/Com
16
16
  import { primitives } from '../../../../theme';
17
17
  import { usePollOptionVotesPagination } from '../../hooks/usePollOptionVotesPagination';
18
18
  import { usePollState } from '../../hooks/usePollState';
19
+ import { usePollTextTranslation } from '../../hooks/usePollTextTranslation';
19
20
 
20
21
  export type PollOptionFullResultsProps = PollContextValue & {
21
22
  option: PollOptionResponseData;
@@ -33,6 +34,8 @@ export const PollOptionFullResultsContent = ({
33
34
  const { t } = useTranslationContext();
34
35
  const { hasNextPage, loadMore, votes } = usePollOptionVotesPagination({ option });
35
36
  const { voteCountsByOption } = usePollState();
37
+ const translate = usePollTextTranslation();
38
+ const optionText = translate(option.text, option.text_i18n);
36
39
 
37
40
  const {
38
41
  theme: {
@@ -46,7 +49,7 @@ export const PollOptionFullResultsContent = ({
46
49
  const PollOptionFullResultsHeader = useCallback(
47
50
  () => (
48
51
  <View style={[styles.headerContainer, headerContainer]}>
49
- <Text style={[styles.headerTitle, headerTitle]}>{option.text}</Text>
52
+ <Text style={[styles.headerTitle, headerTitle]}>{optionText}</Text>
50
53
  <Text style={[styles.headerText, headerText]}>
51
54
  {t('poll.votes.text', {
52
55
  count: voteCountsByOption[option.id] ?? 0,
@@ -61,7 +64,7 @@ export const PollOptionFullResultsContent = ({
61
64
  headerText,
62
65
  headerTitle,
63
66
  option.id,
64
- option.text,
67
+ optionText,
65
68
  styles.headerContainer,
66
69
  styles.headerText,
67
70
  styles.headerTitle,
@@ -20,6 +20,7 @@ import {
20
20
 
21
21
  import { primitives } from '../../../../theme';
22
22
  import { usePollState } from '../../hooks/usePollState';
23
+ import { usePollTextTranslation } from '../../hooks/usePollTextTranslation';
23
24
  import { GenericPollButton } from '../Button';
24
25
  import { PollModal } from '../PollModal';
25
26
  import { PollModalHeader } from '../PollModalHeader';
@@ -97,6 +98,7 @@ const PollResultsVoteItem = (vote: PollVoteClass) => (
97
98
  export const PollResultsItem = ({ option, index }: PollResultItemProps) => {
98
99
  const { t } = useTranslationContext();
99
100
  const { latestVotesByOption, voteCountsByOption } = usePollState();
101
+ const translate = usePollTextTranslation();
100
102
 
101
103
  const {
102
104
  theme: {
@@ -116,7 +118,7 @@ export const PollResultsItem = ({ option, index }: PollResultItemProps) => {
116
118
  {t('poll.results.optionNumber.text', 'Option {{count}}', { count: index + 1 })}
117
119
  </Text>
118
120
  <View style={[styles.headerContainer, headerContainer]}>
119
- <Text style={[styles.title, title]}>{option.text}</Text>
121
+ <Text style={[styles.title, title]}>{translate(option.text, option.text_i18n)}</Text>
120
122
  <Text style={[styles.voteCount, voteCount]}>
121
123
  {t('poll.votes.text', {
122
124
  count: voteCountsByOption[option.id] ?? 0,
@@ -14,6 +14,7 @@ import {
14
14
  import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext';
15
15
  import { primitives } from '../../../../theme';
16
16
  import { usePollState } from '../../hooks/usePollState';
17
+ import { usePollTextTranslation } from '../../hooks/usePollTextTranslation';
17
18
 
18
19
  export type PollResultsProps = PollContextValue & {
19
20
  additionalScrollViewProps?: Partial<ScrollViewProps>;
@@ -22,7 +23,8 @@ export type PollResultsProps = PollContextValue & {
22
23
  export const PollResultsContent = ({
23
24
  additionalScrollViewProps,
24
25
  }: Pick<PollResultsProps, 'additionalScrollViewProps'>) => {
25
- const { name, options, voteCountsByOption } = usePollState();
26
+ const { name, nameI18n, options, voteCountsByOption } = usePollState();
27
+ const translate = usePollTextTranslation();
26
28
 
27
29
  const sortedOptions = useMemo(
28
30
  () =>
@@ -51,7 +53,7 @@ export const PollResultsContent = ({
51
53
  >
52
54
  <View style={[styles.container, container]}>
53
55
  <Text style={[styles.titleMeta, titleMeta]}>{t('poll.question.label', 'Question')}</Text>
54
- <Text style={[styles.title, title]}>{name}</Text>
56
+ <Text style={[styles.title, title]}>{translate(name, nameI18n)}</Text>
55
57
  </View>
56
58
  <View style={styles.resultsContainer}>
57
59
  {sortedOptions.map((option, index) => (
@@ -26,6 +26,7 @@ export type UsePollStateSelectorReturnType = {
26
26
  maxVotedOptionIds: string[];
27
27
  maxVotesAllowed: number;
28
28
  name: string;
29
+ nameI18n: Record<string, string> | undefined;
29
30
  options: PollOptionResponseData[];
30
31
  ownAnswer: PollVoteResponseData | undefined;
31
32
  ownVotesByOptionId: Record<string, PollVoteResponseData>;
@@ -51,6 +52,7 @@ const selector = (nextValue: PollState): UsePollStateSelectorReturnType => ({
51
52
  maxVotedOptionIds: nextValue.maxVotedOptionIds,
52
53
  maxVotesAllowed: nextValue.max_votes_allowed ?? 0,
53
54
  name: nextValue.name,
55
+ nameI18n: nextValue.name_i18n,
54
56
  options: nextValue.options,
55
57
  ownAnswer: nextValue.ownAnswer,
56
58
  ownVotesByOptionId: nextValue.ownVotesByOptionId,
@@ -72,6 +74,7 @@ export const usePollState = (): UsePollStateReturnType => {
72
74
  maxVotedOptionIds,
73
75
  maxVotesAllowed,
74
76
  name,
77
+ nameI18n,
75
78
  options,
76
79
  ownAnswer,
77
80
  ownVotesByOptionId,
@@ -106,6 +109,7 @@ export const usePollState = (): UsePollStateReturnType => {
106
109
  maxVotedOptionIds,
107
110
  maxVotesAllowed,
108
111
  name,
112
+ nameI18n,
109
113
  options,
110
114
  ownAnswer,
111
115
  ownVotesByOptionId,
@@ -0,0 +1,19 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
4
+ import { getTranslatedText } from '../../../utils/i18n/getTranslatedText';
5
+
6
+ /**
7
+ * Returns `translate(original, i18n)`, which resolves a poll text (question, option or answer)
8
+ * into the current user's language. Falls back to `original` when there is no translation for it,
9
+ * the same way auto-translated message text does.
10
+ */
11
+ export const usePollTextTranslation = () => {
12
+ const { userLanguage } = useTranslationContext();
13
+
14
+ return useCallback(
15
+ (original: string, i18n: Record<string, string> | undefined): string =>
16
+ getTranslatedText(i18n, userLanguage) ?? original,
17
+ [userLanguage],
18
+ );
19
+ };
@@ -6,3 +6,4 @@ export * from './hooks/usePollState';
6
6
  export * from './hooks/usePollStateStore';
7
7
  export * from './hooks/usePollAnswersPagination';
8
8
  export * from './hooks/usePollOptionVotesPagination';
9
+ export * from './hooks/usePollTextTranslation';
@@ -1,6 +1,14 @@
1
1
  import React from 'react';
2
2
 
3
- import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native';
3
+ import {
4
+ act,
5
+ cleanup,
6
+ fireEvent,
7
+ render,
8
+ screen,
9
+ waitFor,
10
+ within,
11
+ } from '@testing-library/react-native';
4
12
  import type {
5
13
  Channel as ChannelType,
6
14
  MessageResponse,
@@ -212,6 +220,43 @@ describe('Thread', () => {
212
220
  // already has its data doesn't.
213
221
  // An activation that is never released keeps the thread active for the session, so recovery
214
222
  // keeps reloading it and it keeps being read automatically.
223
+ it.each([
224
+ ['shows', true],
225
+ ['does not show', false],
226
+ ])(
227
+ '%s the delivery status of the parent message when it is the current user’s',
228
+ async (_, isOwn) => {
229
+ const parentMessage = generateMessage({
230
+ cid: channel.cid,
231
+ status: 'received',
232
+ text: 'Parent Message Text',
233
+ ...(isOwn ? { user: generateUser({ id: chatClient.userId }) } : {}),
234
+ });
235
+ const threadInstance = new ThreadClass({ channel, client: chatClient, parentMessage });
236
+ // A thread with its replies loaded is not reloaded on open (see "metadata reload on open").
237
+ threadInstance.messagePaginator.setItems({
238
+ valueOrFactory: [
239
+ channel.state.formatMessage(
240
+ generateMessage({
241
+ cid: channel.cid,
242
+ parent_id: parentMessage.id,
243
+ }) as unknown as MessageResponse,
244
+ ),
245
+ ],
246
+ isFirstPage: true,
247
+ isLastPage: true,
248
+ });
249
+
250
+ renderComponent({ chatClient, thread: threadInstance });
251
+
252
+ const header = await screen.findByTestId('thread-footer-component');
253
+ expect(within(header).getByText('Parent Message Text')).toBeTruthy();
254
+ expect(within(header).queryByTestId('message-status-sent')).toBe(
255
+ isOwn ? within(header).getByTestId('message-status-sent') : null,
256
+ );
257
+ },
258
+ );
259
+
215
260
  it('releases its activation of the thread on unmount', async () => {
216
261
  const parentMessage = generateMessage({ cid: 'messaging:test-channel', text: 'Parent' });
217
262
  const threadInstance = chatClient.threads.ensure({ channel, parentMessage });
@@ -67,7 +67,6 @@ const ThreadFooterComponentWithContext = (props: ThreadFooterComponentPropsWithC
67
67
  groupStyles={['single']}
68
68
  message={parentMessage}
69
69
  preventPress={parentMessagePreventPress}
70
- showMessageStatus={false}
71
70
  />
72
71
  <View style={styles.newThread}>
73
72
  <Text style={styles.text}>
@@ -21,20 +21,11 @@ export const ChannelPinnedMessageListProvider = ({
21
21
  }: PropsWithChildren<{ channel: Channel; searchSource?: MessageSearchSource }>) => {
22
22
  const { client } = useChatContext();
23
23
  const [defaultSearchSource] = useState(() => {
24
- const source = new MessageSearchSource(
25
- client,
26
- { pageSize: 25, allowEmptySearchString: true, resetOnNewSearchQuery: false },
27
- {
28
- messageSearch: {
29
- initialFilterConfig: {
30
- $or: {
31
- enabled: true,
32
- generate: ({ searchQuery }) => (searchQuery ? { text: { $q: searchQuery } } : {}),
33
- },
34
- },
35
- },
36
- },
37
- );
24
+ const source = new MessageSearchSource(client, {
25
+ pageSize: 25,
26
+ allowEmptySearchString: true,
27
+ resetOnNewSearchQuery: false,
28
+ });
38
29
  source.messageSearchChannelFilters = { cid: channel.cid, members: undefined };
39
30
  source.messageSearchFilters = { pinned: true, type: undefined };
40
31
  source.activate();
@@ -19,6 +19,7 @@ import type { Rect } from '../../state-store/message-overlay-store';
19
19
  import type { ViewRef } from '../../types/react-native-compat';
20
20
 
21
21
  import type { MessageComposerAPIContextValue } from '../messageComposerContext/MessageComposerAPIContext';
22
+ import type { GoToMessage } from '../messageListItemContext/MessageListItemContext';
22
23
  import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
23
24
 
24
25
  export type Alignment = 'right' | 'left';
@@ -113,7 +114,7 @@ export type MessageContextValue = {
113
114
  showMessageStatus: boolean;
114
115
  /** The video attachments of the message. */
115
116
  videos: Attachment[];
116
- goToMessage?: (messageId: string) => void;
117
+ goToMessage?: GoToMessage;
117
118
  /**
118
119
  * Handles a reaction on the message.
119
120
  *
@@ -1,18 +1,27 @@
1
1
  import React, { createContext, PropsWithChildren, useContext } from 'react';
2
2
 
3
+ import type { LocalMessage } from 'stream-chat';
4
+
3
5
  import { MessageListProps } from '../../components/MessageList/MessageList';
4
6
 
5
7
  import { Theme } from '../themeContext/utils/theme';
6
8
  import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
7
9
  import { isTestEnvironment } from '../utils/isTestEnvironment';
8
10
 
11
+ /**
12
+ * Goes to a message, used when a quoted message is pressed.
13
+ *
14
+ * @param messageId The ID of the message to go to.
15
+ * @param message The message, when the caller has it (the pressed quote passes its quoted message).
16
+ */
17
+ export type GoToMessage = (
18
+ messageId: string,
19
+ message?: NonNullable<LocalMessage['quoted_message']>,
20
+ ) => void;
21
+
9
22
  export type MessageListItemContextValue = {
10
- /**
11
- * Goes to a message, used when a quoted message is pressed.
12
- *
13
- * @param messageId The ID of the message to go to.
14
- */
15
- goToMessage: (messageId: string) => void;
23
+ /** Goes to a message, used when a quoted message is pressed. */
24
+ goToMessage: GoToMessage;
16
25
  /** The theme of the list items, with `myMessageTheme` merged in. */
17
26
  modifiedTheme: Theme;
18
27
  /** Turns off grouping of consecutive messages by user. */
@@ -59,6 +59,26 @@ describe('useTranslatedMessage', () => {
59
59
  });
60
60
  });
61
61
 
62
+ it("doesn't translate a deleted message", async () => {
63
+ const message = {
64
+ i18n: {
65
+ no_text: 'Hallo verden!',
66
+ },
67
+ text: 'Hello world!',
68
+ type: 'deleted',
69
+ } as unknown as MessageResponse;
70
+
71
+ render(
72
+ <TranslationProvider value={{ userLanguage: 'no' } as TranslationContextValue}>
73
+ <TestComponent message={message} />
74
+ </TranslationProvider>,
75
+ );
76
+
77
+ await waitFor(() => {
78
+ expect(screen.getByText('Hello world!')).toBeTruthy();
79
+ });
80
+ });
81
+
62
82
  it("returns the original text if the message doesn't contain any translations", async () => {
63
83
  const message = {
64
84
  text: 'Hello world!',
@@ -2,7 +2,7 @@ import React, { PropsWithChildren } from 'react';
2
2
 
3
3
  import { renderHook } from '@testing-library/react-native';
4
4
 
5
- import type { Attachment } from 'stream-chat';
5
+ import type { Attachment, PollResponseData, StreamChat } from 'stream-chat';
6
6
 
7
7
  import { Chat } from '../../../components/Chat/Chat';
8
8
  import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels';
@@ -15,8 +15,12 @@ import { generateMessage } from '../../../mock-builders/generator/message';
15
15
  import { useMessagePreviewText } from '../useMessagePreviewText';
16
16
 
17
17
  describe('useMessagePreviewText', () => {
18
- const render = async (message: ReturnType<typeof generateMessage>) => {
18
+ const render = async (
19
+ message: ReturnType<typeof generateMessage>,
20
+ prepareClient?: (client: StreamChat) => void,
21
+ ) => {
19
22
  const { client } = await initiateClientWithChannels();
23
+ prepareClient?.(client);
20
24
  const wrapper = ({ children }: PropsWithChildren) => <Chat client={client}>{children}</Chat>;
21
25
  const { result } = renderHook(() => useMessagePreviewText({ message }), { wrapper });
22
26
  return result;
@@ -54,4 +58,35 @@ describe('useMessagePreviewText', () => {
54
58
  );
55
59
  expect(result.current).toBe('hello');
56
60
  });
61
+
62
+ describe('poll messages', () => {
63
+ const poll = {
64
+ id: 'poll-id',
65
+ latest_answers: [],
66
+ latest_votes_by_option: {},
67
+ name: 'Favourite colour?',
68
+ name_i18n: { es_text: '¿Color favorito?', language: 'en' },
69
+ options: [],
70
+ vote_count: 0,
71
+ vote_counts_by_option: {},
72
+ } as unknown as PollResponseData;
73
+
74
+ const renderPollMessage = (language: string) => {
75
+ const message = generateMessage({ poll, poll_id: poll.id, text: '' });
76
+ return render(message, (client) => {
77
+ (client.user as { language?: string }).language = language;
78
+ client.polls.hydratePollCache([message]);
79
+ });
80
+ };
81
+
82
+ it("shows the poll question in the user's language", async () => {
83
+ const result = await renderPollMessage('es');
84
+ expect(result.current).toBe('¿Color favorito?');
85
+ });
86
+
87
+ it('shows the original poll question when it has no translation', async () => {
88
+ const result = await renderPollMessage('de');
89
+ expect(result.current).toBe('Favourite colour?');
90
+ });
91
+ });
57
92
  });
@@ -14,9 +14,11 @@ import { useGroupedAttachments } from './useGroupedAttachments';
14
14
  import { useChatContext } from '../../contexts/chatContext/ChatContext';
15
15
  import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
16
16
  import { useStateStore } from '../../hooks/useStateStore';
17
+ import { getTranslatedText } from '../../utils/i18n/getTranslatedText';
17
18
 
18
19
  const selector = (nextValue: PollState) => ({
19
20
  name: nextValue.name,
21
+ name_i18n: nextValue.name_i18n,
20
22
  });
21
23
 
22
24
  export const useMessagePreviewText = ({
@@ -24,10 +26,11 @@ export const useMessagePreviewText = ({
24
26
  }: {
25
27
  message?: LocalMessage | MessageResponse | DraftMessage | null;
26
28
  }) => {
27
- const { t } = useTranslationContext();
29
+ const { t, userLanguage } = useTranslationContext();
28
30
  const { client } = useChatContext();
29
31
  const poll = client.polls.fromState(message?.poll_id ?? '');
30
- const { name: pollName } = useStateStore(poll?.state, selector) ?? {};
32
+ const { name, name_i18n } = useStateStore(poll?.state, selector) ?? {};
33
+ const pollName = getTranslatedText(name_i18n, userLanguage) ?? name;
31
34
  const { giphys, audios, images, videos, files, voiceRecordings } = useGroupedAttachments(
32
35
  message?.attachments,
33
36
  );
@@ -1,13 +1,7 @@
1
1
  import type { LocalMessage, MessageResponse } from 'stream-chat';
2
2
 
3
3
  import { useTranslationContext } from '../contexts/translationContext/TranslationContext';
4
-
5
- /**
6
- * The key under which auto-translated text lives in `message.i18n`. The UI language can be any
7
- * registered string, while `message.i18n` only carries the languages the auto-translation endpoint
8
- * supports: the `in` check below makes an unsupported language fall through to the original text.
9
- */
10
- type TranslationKey = `${string}_text`;
4
+ import { getTranslatedText } from '../utils/i18n/getTranslatedText';
11
5
 
12
6
  /**
13
7
  * Returns a copy of `message` with its text swapped for the auto-translation into `userLanguage`,
@@ -21,16 +15,17 @@ export const getTranslatedMessage = (
21
15
  message: LocalMessage | MessageResponse | undefined,
22
16
  userLanguage: string,
23
17
  ) => {
24
- const translationKey: TranslationKey = `${userLanguage}_text`;
25
-
26
18
  if (!message) {
27
19
  return undefined;
28
20
  }
29
21
 
30
- if (message.i18n && translationKey in message.i18n && message.type !== 'deleted') {
22
+ const translatedText =
23
+ message.type !== 'deleted' ? getTranslatedText(message.i18n, userLanguage) : undefined;
24
+
25
+ if (translatedText !== undefined) {
31
26
  return {
32
27
  ...message,
33
- text: message.i18n[translationKey],
28
+ text: translatedText,
34
29
  };
35
30
  }
36
31
 
@@ -295,8 +295,8 @@
295
295
  "messageInput.sendMessage.accessibilityLabel": "Send message",
296
296
  "messageInput.sendMessageDisallowed.text": "You can't send messages in this channel",
297
297
  "messageList.dismissUnread.accessibilityLabel": "Dismiss unread messages",
298
- "messageList.newMessages.text_one": "{{count}} new message",
299
- "messageList.newMessages.text_other": "{{count}} new messages",
298
+ "messageList.newMessages.text_one": "{{count}} unread message",
299
+ "messageList.newMessages.text_other": "{{count}} unread messages",
300
300
  "messageList.scrollToBottom.accessibilityLabel": "Scroll to bottom",
301
301
  "messageList.scrollToBottom.withCount.accessibilityLabel_one": "Scroll to bottom, {{count}} new message",
302
302
  "messageList.scrollToBottom.withCount.accessibilityLabel_other": "Scroll to bottom, {{count}} new messages",
package/src/i18n/keys.ts CHANGED
@@ -306,8 +306,8 @@ export type TranslationCatalog = {
306
306
  'messageInput.sendMessage.accessibilityLabel': 'Send message';
307
307
  'messageInput.sendMessageDisallowed.text': "You can't send messages in this channel";
308
308
  'messageList.dismissUnread.accessibilityLabel': 'Dismiss unread messages';
309
- 'messageList.newMessages.text_one': '{{count}} new message';
310
- 'messageList.newMessages.text_other': '{{count}} new messages';
309
+ 'messageList.newMessages.text_one': '{{count}} unread message';
310
+ 'messageList.newMessages.text_other': '{{count}} unread messages';
311
311
  'messageList.scrollToBottom.accessibilityLabel': 'Scroll to bottom';
312
312
  'messageList.scrollToBottom.withCount.accessibilityLabel_one': 'Scroll to bottom, {{count}} new message';
313
313
  'messageList.scrollToBottom.withCount.accessibilityLabel_other': 'Scroll to bottom, {{count}} new messages';
package/src/index.ts CHANGED
@@ -23,7 +23,6 @@ export * from './types/types';
23
23
  export * from './utils/attachmentUrls';
24
24
  export * from './utils/i18n/Streami18n';
25
25
  export * from './i18n';
26
- export * from './utils/setupCommandUIMiddlewares';
27
26
  export * from './utils/createGenerateVideoThumbnails';
28
27
  export * from './utils/utils';
29
28
  export * from './nativeMultipartUpload';
@@ -44,7 +44,7 @@ export class SqliteClientError extends Error {
44
44
 
45
45
  /** Wraps all direct use of `@op-engineering/op-sqlite`. */
46
46
  export class SqliteClient {
47
- static dbVersion = 18;
47
+ static dbVersion = 19;
48
48
 
49
49
  static dbName = DB_NAME;
50
50
  static dbLocation = DB_LOCATION;
@@ -0,0 +1,136 @@
1
+ import type { ChannelStateResponseFields, PollResponseData } from 'stream-chat';
2
+
3
+ import { generateChannelResponse } from '../../../mock-builders/generator/channel';
4
+ import { generateMessage } from '../../../mock-builders/generator/message';
5
+ import { BetterSqlite } from '../../../test-utils/BetterSqlite';
6
+ import { SqliteClient } from '../../SqliteClient';
7
+ import { getChannels } from '../getChannels';
8
+ import { upsertChannels } from '../upsertChannels';
9
+ import { upsertPoll } from '../upsertPoll';
10
+
11
+ const POLL_ID = 'poll-id';
12
+
13
+ const translatedPoll = {
14
+ answers_count: 0,
15
+ created_at: new Date('2026-10-27T10:00:00.000Z').getTime(),
16
+ created_by: { id: 'other-user' },
17
+ created_by_id: 'other-user',
18
+ description: 'For the team dinner',
19
+ description_i18n: { es_text: 'Para la cena del equipo', language: 'en' },
20
+ enforce_unique_vote: true,
21
+ id: POLL_ID,
22
+ latest_answers: [],
23
+ latest_votes_by_option: {},
24
+ max_votes_allowed: 1,
25
+ name: 'Favourite colour?',
26
+ name_i18n: { es_text: '¿Color favorito?', language: 'en' },
27
+ options: [
28
+ {
29
+ id: 'option-red',
30
+ poll_id: POLL_ID,
31
+ text: 'Red',
32
+ text_i18n: { es_text: 'Rojo', language: 'en' },
33
+ },
34
+ ],
35
+ updated_at: new Date('2026-10-27T10:00:00.000Z').getTime(),
36
+ vote_count: 0,
37
+ vote_counts_by_option: {},
38
+ } as unknown as PollResponseData;
39
+
40
+ /**
41
+ * The backend translates poll texts. The offline DB has to keep those translations, so a cold start
42
+ * offline shows the poll in the user's language, the same way message translations are kept.
43
+ */
44
+ describe('poll translations in the offline DB', () => {
45
+ let cid: string;
46
+
47
+ beforeEach(async () => {
48
+ await SqliteClient.initializeDatabase();
49
+ await BetterSqlite.openDB();
50
+
51
+ const channelResponse = generateChannelResponse({ members: [], messages: [] });
52
+ cid = channelResponse.channel.cid as string;
53
+ channelResponse.messages = [
54
+ generateMessage({ cid, poll: translatedPoll, poll_id: POLL_ID, text: '' }),
55
+ ] as unknown as typeof channelResponse.messages;
56
+ await upsertChannels({
57
+ channels: [channelResponse] as unknown as ChannelStateResponseFields[],
58
+ isLatestMessagesSet: true,
59
+ });
60
+ });
61
+
62
+ afterEach(() => {
63
+ BetterSqlite.dropAllTables();
64
+ BetterSqlite.closeDB();
65
+ });
66
+
67
+ const getStoredPoll = async () => {
68
+ const [channel] = await getChannels({ channelIds: [cid], userId: 'some-user' });
69
+ return channel.messages[0].poll;
70
+ };
71
+
72
+ it("keeps the question's, description's and options' translations", async () => {
73
+ const poll = await getStoredPoll();
74
+
75
+ expect(poll?.name_i18n).toEqual(translatedPoll.name_i18n);
76
+ expect(poll?.description_i18n).toEqual(translatedPoll.description_i18n);
77
+ expect(poll?.options[0].text_i18n).toEqual(translatedPoll.options[0].text_i18n);
78
+ });
79
+
80
+ it('replaces the translations when the poll is stored again', async () => {
81
+ const name_i18n = { de_text: 'Lieblingsfarbe?', language: 'en' };
82
+ await upsertPoll({ poll: { ...translatedPoll, name_i18n } as PollResponseData });
83
+
84
+ expect((await getStoredPoll())?.name_i18n).toEqual(name_i18n);
85
+ });
86
+
87
+ it('stores a poll without translations unchanged', async () => {
88
+ const {
89
+ description_i18n: _description_i18n,
90
+ name_i18n: _name_i18n,
91
+ ...untranslatedPoll
92
+ } = translatedPoll;
93
+ const untranslatedChannel = generateChannelResponse({ members: [], messages: [] });
94
+ const untranslatedCid = untranslatedChannel.channel.cid as string;
95
+ untranslatedChannel.messages = [
96
+ generateMessage({
97
+ cid: untranslatedCid,
98
+ poll: {
99
+ ...untranslatedPoll,
100
+ id: 'untranslated-poll-id',
101
+ options: [{ id: 'option-red', poll_id: 'untranslated-poll-id', text: 'Red' }],
102
+ } as unknown as PollResponseData,
103
+ poll_id: 'untranslated-poll-id',
104
+ text: '',
105
+ }),
106
+ ] as unknown as typeof untranslatedChannel.messages;
107
+ await upsertChannels({
108
+ channels: [untranslatedChannel] as unknown as ChannelStateResponseFields[],
109
+ isLatestMessagesSet: true,
110
+ });
111
+
112
+ const [channel] = await getChannels({
113
+ channelIds: [untranslatedCid],
114
+ userId: 'some-user',
115
+ });
116
+ const poll = channel.messages[0].poll;
117
+
118
+ expect(poll?.name).toBe('Favourite colour?');
119
+ expect(poll?.description).toBe('For the team dinner');
120
+ expect(poll?.options).toEqual([
121
+ { id: 'option-red', poll_id: 'untranslated-poll-id', text: 'Red' },
122
+ ]);
123
+ expect(poll?.name_i18n).toBeUndefined();
124
+ expect(poll?.description_i18n).toBeUndefined();
125
+ });
126
+
127
+ it('clears translations that the poll no longer has', async () => {
128
+ await upsertPoll({
129
+ poll: { ...translatedPoll, description_i18n: undefined, name_i18n: undefined },
130
+ });
131
+
132
+ const poll = await getStoredPoll();
133
+ expect(poll?.name_i18n).toBeUndefined();
134
+ expect(poll?.description_i18n).toBeUndefined();
135
+ });
136
+ });
@@ -13,6 +13,7 @@ export const mapPollToStorable = (poll: PollResponseData): TableRow<'poll'> => {
13
13
  created_by,
14
14
  created_by_id,
15
15
  description,
16
+ description_i18n,
16
17
  enforce_unique_vote,
17
18
  id,
18
19
  is_closed,
@@ -20,6 +21,7 @@ export const mapPollToStorable = (poll: PollResponseData): TableRow<'poll'> => {
20
21
  latest_votes_by_option,
21
22
  max_votes_allowed,
22
23
  name,
24
+ name_i18n,
23
25
  options,
24
26
  own_votes,
25
27
  updated_at,
@@ -36,6 +38,9 @@ export const mapPollToStorable = (poll: PollResponseData): TableRow<'poll'> => {
36
38
  created_by: JSON.stringify(created_by), // stored as JSON to decouple the user from the poll
37
39
  created_by_id,
38
40
  description,
41
+ // Always written (as `null` when absent): an upsert skips undefined columns, which would keep
42
+ // translations the server has since dropped, e.g. after the question was edited.
43
+ description_i18n: JSON.stringify(description_i18n ?? null),
39
44
  enforce_unique_vote,
40
45
  id,
41
46
  is_closed,
@@ -43,6 +48,7 @@ export const mapPollToStorable = (poll: PollResponseData): TableRow<'poll'> => {
43
48
  latest_votes_by_option: JSON.stringify(latest_votes_by_option),
44
49
  max_votes_allowed: max_votes_allowed ?? 1,
45
50
  name,
51
+ name_i18n: JSON.stringify(name_i18n ?? null),
46
52
  options: JSON.stringify(options),
47
53
  own_votes: JSON.stringify(own_votes),
48
54
  updated_at: mapTimestampToStorable(updated_at),