@ethora/chat-component-rn 26.8.7 → 26.8.8

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 (195) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +25 -3
  3. package/lib/commonjs/components/MainComponents/ChatWrapper.js +5 -3
  4. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +8 -0
  6. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  7. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +26 -13
  8. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  9. package/lib/commonjs/components/MenuRoom/MenuRoom.js +5 -3
  10. package/lib/commonjs/components/MenuRoom/MenuRoom.js.map +1 -1
  11. package/lib/commonjs/components/MessageBubble/Message.js +3 -3
  12. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  13. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +5 -3
  14. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  15. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +1 -1
  16. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  17. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +2 -2
  18. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
  19. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -5
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  21. package/lib/commonjs/components/Modals/Modal/Modal.js +33 -11
  22. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js +8 -8
  24. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  25. package/lib/commonjs/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js +279 -0
  26. package/lib/commonjs/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js.map +1 -0
  27. package/lib/commonjs/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js +10 -8
  28. package/lib/commonjs/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js.map +1 -1
  29. package/lib/commonjs/components/Modals/SettingsModals/Visibility/VisibilityModal.js +13 -11
  30. package/lib/commonjs/components/Modals/SettingsModals/Visibility/VisibilityModal.js.map +1 -1
  31. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +21 -9
  32. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  33. package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js +121 -0
  34. package/lib/commonjs/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -0
  35. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +11 -6
  36. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  37. package/lib/commonjs/components/Thread/ThreadWrapper.js +4 -2
  38. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  39. package/lib/commonjs/e2ee/stanza.js +1 -1
  40. package/lib/commonjs/e2ee/stanza.js.map +1 -1
  41. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +2 -0
  42. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  43. package/lib/commonjs/helpers/constants/MODAL_TYPES.js +1 -0
  44. package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
  45. package/lib/commonjs/helpers/preferencesStorage.js.map +1 -1
  46. package/lib/commonjs/hooks/useChatSettingState.js +2 -0
  47. package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
  48. package/lib/commonjs/hooks/useChatWrapperInit.js +4 -0
  49. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  50. package/lib/commonjs/i18n/strings.js +187 -1
  51. package/lib/commonjs/i18n/strings.js.map +1 -1
  52. package/lib/commonjs/i18n/useT.js +8 -4
  53. package/lib/commonjs/i18n/useT.js.map +1 -1
  54. package/lib/commonjs/networking/api-requests/user.api.js +13 -0
  55. package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
  56. package/lib/commonjs/networking/stanzaHandlers.js +1 -1
  57. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  58. package/lib/commonjs/roomStore/chatSettingsSlice.js +7 -2
  59. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  60. package/lib/commonjs/services/languageSettings.js +177 -0
  61. package/lib/commonjs/services/languageSettings.js.map +1 -0
  62. package/lib/commonjs/version.js +1 -1
  63. package/lib/module/components/MainComponents/ChatWrapper.js +5 -3
  64. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  65. package/lib/module/components/MainComponents/ReduxWrapper.js +9 -1
  66. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  67. package/lib/module/components/Menu/HeaderRoomListMenu.js +26 -13
  68. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  69. package/lib/module/components/MenuRoom/MenuRoom.js +5 -3
  70. package/lib/module/components/MenuRoom/MenuRoom.js.map +1 -1
  71. package/lib/module/components/MessageBubble/Message.js +3 -3
  72. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  73. package/lib/module/components/MessageBubble/MessageInteractions.js +5 -3
  74. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  75. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +1 -1
  76. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  77. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +2 -2
  78. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -1
  79. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -5
  80. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  81. package/lib/module/components/Modals/Modal/Modal.js +33 -11
  82. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  83. package/lib/module/components/Modals/NewChatModal/NewChatModal.js +8 -8
  84. package/lib/module/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  85. package/lib/module/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js +274 -0
  86. package/lib/module/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.js.map +1 -0
  87. package/lib/module/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js +10 -8
  88. package/lib/module/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.js.map +1 -1
  89. package/lib/module/components/Modals/SettingsModals/Visibility/VisibilityModal.js +13 -11
  90. package/lib/module/components/Modals/SettingsModals/Visibility/VisibilityModal.js.map +1 -1
  91. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +22 -10
  92. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  93. package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js +116 -0
  94. package/lib/module/components/Modals/UserSettingsModal/LanguageCard.js.map +1 -0
  95. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +10 -5
  96. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  97. package/lib/module/components/Thread/ThreadWrapper.js +4 -2
  98. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  99. package/lib/module/e2ee/stanza.js +1 -1
  100. package/lib/module/e2ee/stanza.js.map +1 -1
  101. package/lib/module/helpers/constants/MODAL_COMPONENTS.js +2 -0
  102. package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  103. package/lib/module/helpers/constants/MODAL_TYPES.js +1 -0
  104. package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
  105. package/lib/module/helpers/preferencesStorage.js.map +1 -1
  106. package/lib/module/hooks/useChatSettingState.js +2 -0
  107. package/lib/module/hooks/useChatSettingState.js.map +1 -1
  108. package/lib/module/hooks/useChatWrapperInit.js +4 -0
  109. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  110. package/lib/module/i18n/strings.js +187 -1
  111. package/lib/module/i18n/strings.js.map +1 -1
  112. package/lib/module/i18n/useT.js +8 -4
  113. package/lib/module/i18n/useT.js.map +1 -1
  114. package/lib/module/networking/api-requests/user.api.js +12 -0
  115. package/lib/module/networking/api-requests/user.api.js.map +1 -1
  116. package/lib/module/networking/stanzaHandlers.js +1 -1
  117. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  118. package/lib/module/roomStore/chatSettingsSlice.js +5 -1
  119. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  120. package/lib/module/services/languageSettings.js +164 -0
  121. package/lib/module/services/languageSettings.js.map +1 -0
  122. package/lib/module/version.js +1 -1
  123. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  124. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  125. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts +7 -1
  126. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  127. package/lib/typescript/components/MenuRoom/MenuRoom.d.ts.map +1 -1
  128. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  129. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  130. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  131. package/lib/typescript/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.d.ts +15 -0
  132. package/lib/typescript/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.d.ts.map +1 -0
  133. package/lib/typescript/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.d.ts.map +1 -1
  134. package/lib/typescript/components/Modals/SettingsModals/Visibility/VisibilityModal.d.ts.map +1 -1
  135. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  136. package/lib/typescript/components/Modals/UserSettingsModal/LanguageCard.d.ts +14 -0
  137. package/lib/typescript/components/Modals/UserSettingsModal/LanguageCard.d.ts.map +1 -0
  138. package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
  139. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  140. package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
  141. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +1 -0
  142. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
  143. package/lib/typescript/helpers/preferencesStorage.d.ts +4 -0
  144. package/lib/typescript/helpers/preferencesStorage.d.ts.map +1 -1
  145. package/lib/typescript/hooks/useChatSettingState.d.ts +1 -0
  146. package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
  147. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  148. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  149. package/lib/typescript/i18n/useT.d.ts.map +1 -1
  150. package/lib/typescript/networking/api-requests/user.api.d.ts +6 -0
  151. package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
  152. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  153. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +8 -1
  154. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  155. package/lib/typescript/services/languageSettings.d.ts +29 -0
  156. package/lib/typescript/services/languageSettings.d.ts.map +1 -0
  157. package/lib/typescript/types/models/user.model.d.ts +4 -0
  158. package/lib/typescript/types/models/user.model.d.ts.map +1 -1
  159. package/lib/typescript/types/types.d.ts +21 -0
  160. package/lib/typescript/types/types.d.ts.map +1 -1
  161. package/lib/typescript/version.d.ts +1 -1
  162. package/package.json +1 -1
  163. package/src/components/MainComponents/ChatWrapper.tsx +5 -3
  164. package/src/components/MainComponents/ReduxWrapper.tsx +6 -0
  165. package/src/components/Menu/HeaderRoomListMenu.tsx +40 -13
  166. package/src/components/MenuRoom/MenuRoom.tsx +5 -3
  167. package/src/components/MessageBubble/Message.tsx +3 -3
  168. package/src/components/MessageBubble/MessageInteractions.tsx +5 -3
  169. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +1 -1
  170. package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +2 -2
  171. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +7 -5
  172. package/src/components/Modals/Modal/Modal.tsx +44 -16
  173. package/src/components/Modals/NewChatModal/NewChatModal.tsx +8 -8
  174. package/src/components/Modals/SettingsModals/ChangePassword/ChangePasswordModal.tsx +316 -0
  175. package/src/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.tsx +10 -15
  176. package/src/components/Modals/SettingsModals/Visibility/VisibilityModal.tsx +13 -14
  177. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -12
  178. package/src/components/Modals/UserSettingsModal/LanguageCard.tsx +144 -0
  179. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +11 -4
  180. package/src/components/Thread/ThreadWrapper.tsx +4 -2
  181. package/src/e2ee/stanza.ts +1 -1
  182. package/src/helpers/constants/MODAL_COMPONENTS.tsx +2 -0
  183. package/src/helpers/constants/MODAL_TYPES.ts +1 -0
  184. package/src/helpers/preferencesStorage.ts +4 -0
  185. package/src/hooks/useChatSettingState.tsx +4 -0
  186. package/src/hooks/useChatWrapperInit.ts +4 -0
  187. package/src/i18n/strings.ts +187 -1
  188. package/src/i18n/useT.ts +6 -4
  189. package/src/networking/api-requests/user.api.ts +9 -0
  190. package/src/networking/stanzaHandlers.ts +1 -0
  191. package/src/roomStore/chatSettingsSlice.ts +13 -0
  192. package/src/services/languageSettings.ts +143 -0
  193. package/src/types/models/user.model.ts +4 -0
  194. package/src/types/types.ts +21 -0
  195. package/src/version.ts +1 -1
@@ -16,6 +16,11 @@ export type UiStringTable = Record<string, string>;
16
16
  const en: UiStringTable = {
17
17
  'search.placeholder': 'Search...',
18
18
  'roomList.title': 'Chats',
19
+ 'menu.newChat': 'New Chat',
20
+ 'menu.profile': 'Profile',
21
+ 'menu.settings': 'Settings',
22
+ 'menu.logout': 'Sign out',
23
+ 'menu.logoutConfirm': 'Are you sure you want to sign out?',
19
24
  'search.members': 'Search members',
20
25
  'search.noResults': 'Nobody found',
21
26
  'input.placeholder': 'Type message',
@@ -25,6 +30,10 @@ const en: UiStringTable = {
25
30
  'date.yesterday': 'Yesterday',
26
31
  'message.deleted': 'Message deleted',
27
32
  'message.edited': 'edited',
33
+ 'message.sending': 'sending...',
34
+ 'message.failedRetry': '! Failed — tap to retry',
35
+ 'thread.title': 'Thread',
36
+ 'thread.alsoSendTo': 'Also send to',
28
37
  'e2ee.roomEncrypted': 'End-to-end encrypted',
29
38
  'e2ee.notEncrypted': 'Not encrypted',
30
39
  'e2ee.notEncryptedHint': 'Sent without end-to-end encryption, because no recipient has published a key yet.',
@@ -173,6 +182,10 @@ const en: UiStringTable = {
173
182
  'settings.menu.title': 'Settings',
174
183
  'modal.filePreview.title': 'File preview',
175
184
  'toast.error': 'Error',
185
+ 'toast.failedToCreateRoom': 'Failed to create room',
186
+ 'toast.copied': 'Copied to clipboard!',
187
+ 'toast.copyFailed': 'Copy failed',
188
+ 'toast.saveSuccessful': 'Save successful',
176
189
  'toast.couldNotOpenPrivateChat': 'Could not open the new private chat',
177
190
  'toast.couldNotResolveRecipient': 'Could not resolve recipient',
178
191
  'toast.failedToCreatePrivateChat': 'Failed to create private chat',
@@ -240,6 +253,8 @@ const en: UiStringTable = {
240
253
  'modal.chatProfile.removeMember': 'Remove from this chat?',
241
254
  'modal.chatProfile.memberListUnavailable': 'Member list unavailable',
242
255
  'modal.deleteChat.title': 'Delete this chat ?',
256
+ 'modal.deleteMessage.title': 'Delete message',
257
+ 'modal.deleteMessage.description': 'Are you sure you want to delete this message?',
243
258
  'modal.addMembers.title': 'Add New Member',
244
259
  'modal.addMembers.userIdPlaceholder': 'Enter User Id',
245
260
  'modal.leaveChat.title': 'Leave Chat',
@@ -276,6 +291,22 @@ const en: UiStringTable = {
276
291
  'settings.notifications.title': 'Notifications',
277
292
  'settings.notifications.push': 'Push notifications',
278
293
  'settings.notifications.pushHint': 'New messages and calls wake this device even when the app is closed.',
294
+ 'settings.language.app': 'App language',
295
+ 'settings.language.appHint': 'The language of the interface.',
296
+ 'settings.language.chat': 'Chat language',
297
+ 'settings.language.chatHint': 'Messages are translated into this language.',
298
+ 'settings.password.title': 'Change password',
299
+ 'settings.password.current': 'Current password',
300
+ 'settings.password.new': 'New password',
301
+ 'settings.password.again': 'Repeat new password',
302
+ 'settings.password.submit': 'Change password',
303
+ 'settings.password.changed': 'Password changed',
304
+ 'settings.password.wrongCurrent': 'The current password is not right',
305
+ 'settings.password.failed': 'The password could not be changed',
306
+ 'settings.password.tooShort': 'At least {min} characters',
307
+ 'settings.password.sameAsCurrent': 'Must differ from the current password',
308
+ 'settings.password.mismatch': 'The passwords do not match',
309
+ 'settings.password.hint': 'Use the new password the next time you sign in.',
279
310
  'settings.visibility.title': 'Visibility',
280
311
  'settings.visibility.profileLabel': 'Profile Visibility',
281
312
  'settings.visibility.open': 'Open (default)',
@@ -304,6 +335,11 @@ const en: UiStringTable = {
304
335
  const fr: UiStringTable = {
305
336
  'search.placeholder': 'Rechercher...',
306
337
  'roomList.title': 'Discussions',
338
+ 'menu.newChat': 'Nouvelle discussion',
339
+ 'menu.profile': 'Profil',
340
+ 'menu.settings': 'Paramètres',
341
+ 'menu.logout': 'Se déconnecter',
342
+ 'menu.logoutConfirm': 'Voulez-vous vraiment vous déconnecter ?',
307
343
  'search.members': 'Rechercher des membres',
308
344
  'search.noResults': 'Personne trouvée',
309
345
  'input.placeholder': 'Écrire un message',
@@ -313,6 +349,10 @@ const fr: UiStringTable = {
313
349
  'date.yesterday': 'Hier',
314
350
  'message.deleted': 'Message supprimé',
315
351
  'message.edited': 'modifié',
352
+ 'message.sending': 'envoi...',
353
+ 'message.failedRetry': '! Échec — touchez pour réessayer',
354
+ 'thread.title': 'Fil',
355
+ 'thread.alsoSendTo': 'Envoyer aussi dans',
316
356
  'e2ee.roomEncrypted': 'Chiffré de bout en bout',
317
357
  'e2ee.notEncrypted': 'Non chiffré',
318
358
  'e2ee.notEncryptedHint': "Envoyé sans chiffrement de bout en bout, car aucun destinataire n'a encore publié de clé.",
@@ -461,6 +501,10 @@ const fr: UiStringTable = {
461
501
  'settings.menu.title': 'Paramètres',
462
502
  'modal.filePreview.title': 'Aperçu du fichier',
463
503
  'toast.error': 'Erreur',
504
+ 'toast.failedToCreateRoom': 'Impossible de créer le salon',
505
+ 'toast.copied': 'Copié dans le presse-papiers !',
506
+ 'toast.copyFailed': 'Échec de la copie',
507
+ 'toast.saveSuccessful': 'Enregistré',
464
508
  'toast.couldNotOpenPrivateChat': "Impossible d'ouvrir la nouvelle discussion privée",
465
509
  'toast.couldNotResolveRecipient': 'Impossible de résoudre le destinataire',
466
510
  'toast.failedToCreatePrivateChat': 'Échec de la création de la discussion privée',
@@ -528,6 +572,8 @@ const fr: UiStringTable = {
528
572
  'modal.chatProfile.removeMember': 'Retirer de cette discussion ?',
529
573
  'modal.chatProfile.memberListUnavailable': 'Liste des membres indisponible',
530
574
  'modal.deleteChat.title': 'Supprimer cette discussion ?',
575
+ 'modal.deleteMessage.title': 'Supprimer le message',
576
+ 'modal.deleteMessage.description': 'Voulez-vous vraiment supprimer ce message ?',
531
577
  'modal.addMembers.title': 'Ajouter un nouveau membre',
532
578
  'modal.addMembers.userIdPlaceholder': "Entrez l'ID utilisateur",
533
579
  'modal.leaveChat.title': 'Quitter la discussion',
@@ -564,6 +610,22 @@ const fr: UiStringTable = {
564
610
  'settings.notifications.title': 'Notifications',
565
611
  'settings.notifications.push': 'Notifications push',
566
612
  'settings.notifications.pushHint': 'Les nouveaux messages et appels réveillent cet appareil même quand l\'application est fermée.',
613
+ 'settings.language.app': "Langue de l'application",
614
+ 'settings.language.appHint': "La langue de l'interface.",
615
+ 'settings.language.chat': 'Langue du chat',
616
+ 'settings.language.chatHint': 'Les messages sont traduits dans cette langue.',
617
+ 'settings.password.title': 'Changer le mot de passe',
618
+ 'settings.password.current': 'Mot de passe actuel',
619
+ 'settings.password.new': 'Nouveau mot de passe',
620
+ 'settings.password.again': 'Répéter le nouveau mot de passe',
621
+ 'settings.password.submit': 'Changer le mot de passe',
622
+ 'settings.password.changed': 'Mot de passe modifié',
623
+ 'settings.password.wrongCurrent': 'Le mot de passe actuel est incorrect',
624
+ 'settings.password.failed': "Le mot de passe n'a pas pu être modifié",
625
+ 'settings.password.tooShort': 'Au moins {min} caractères',
626
+ 'settings.password.sameAsCurrent': 'Doit être différent du mot de passe actuel',
627
+ 'settings.password.mismatch': 'Les mots de passe ne correspondent pas',
628
+ 'settings.password.hint': 'Utilisez le nouveau mot de passe à votre prochaine connexion.',
567
629
  'settings.visibility.title': 'Visibilité',
568
630
  'settings.visibility.profileLabel': 'Visibilité du profil',
569
631
  'settings.visibility.open': 'Ouvert (par défaut)',
@@ -591,7 +653,12 @@ const fr: UiStringTable = {
591
653
 
592
654
  const es: UiStringTable = {
593
655
  'search.placeholder': 'Buscar...',
594
- 'roomList.title': 'Chats',
656
+ 'roomList.title': 'Conversaciones',
657
+ 'menu.newChat': 'Nuevo chat',
658
+ 'menu.profile': 'Perfil',
659
+ 'menu.settings': 'Configuración',
660
+ 'menu.logout': 'Cerrar sesión',
661
+ 'menu.logoutConfirm': '¿Seguro que quieres cerrar sesión?',
595
662
  'search.members': 'Buscar miembros',
596
663
  'search.noResults': 'No se encontró a nadie',
597
664
  'input.placeholder': 'Escribe un mensaje',
@@ -601,6 +668,10 @@ const es: UiStringTable = {
601
668
  'date.yesterday': 'Ayer',
602
669
  'message.deleted': 'Mensaje eliminado',
603
670
  'message.edited': 'editado',
671
+ 'message.sending': 'enviando...',
672
+ 'message.failedRetry': '! Error — toca para reintentar',
673
+ 'thread.title': 'Hilo',
674
+ 'thread.alsoSendTo': 'Enviar también a',
604
675
  'e2ee.roomEncrypted': 'Cifrado de extremo a extremo',
605
676
  'e2ee.notEncrypted': 'Sin cifrar',
606
677
  'e2ee.notEncryptedHint': 'Enviado sin cifrado de extremo a extremo, porque ningún destinatario ha publicado una clave todavía.',
@@ -749,6 +820,10 @@ const es: UiStringTable = {
749
820
  'settings.menu.title': 'Configuración',
750
821
  'modal.filePreview.title': 'Vista previa del archivo',
751
822
  'toast.error': 'Error',
823
+ 'toast.failedToCreateRoom': 'No se pudo crear la sala',
824
+ 'toast.copied': '¡Copiado al portapapeles!',
825
+ 'toast.copyFailed': 'No se pudo copiar',
826
+ 'toast.saveSuccessful': 'Guardado correctamente',
752
827
  'toast.couldNotOpenPrivateChat': 'No se pudo abrir el nuevo chat privado',
753
828
  'toast.couldNotResolveRecipient': 'No se pudo resolver el destinatario',
754
829
  'toast.failedToCreatePrivateChat': 'No se pudo crear el chat privado',
@@ -816,6 +891,8 @@ const es: UiStringTable = {
816
891
  'modal.chatProfile.removeMember': '¿Quitar de este chat?',
817
892
  'modal.chatProfile.memberListUnavailable': 'Lista de miembros no disponible',
818
893
  'modal.deleteChat.title': '¿Eliminar este chat?',
894
+ 'modal.deleteMessage.title': 'Eliminar mensaje',
895
+ 'modal.deleteMessage.description': '¿Seguro que quieres eliminar este mensaje?',
819
896
  'modal.addMembers.title': 'Añadir nuevo miembro',
820
897
  'modal.addMembers.userIdPlaceholder': 'Introduce el ID de usuario',
821
898
  'modal.leaveChat.title': 'Salir del chat',
@@ -852,6 +929,22 @@ const es: UiStringTable = {
852
929
  'settings.notifications.title': 'Notificaciones',
853
930
  'settings.notifications.push': 'Notificaciones push',
854
931
  'settings.notifications.pushHint': 'Los mensajes y llamadas nuevos despiertan este dispositivo incluso con la aplicación cerrada.',
932
+ 'settings.language.app': 'Idioma de la aplicación',
933
+ 'settings.language.appHint': 'El idioma de la interfaz.',
934
+ 'settings.language.chat': 'Idioma del chat',
935
+ 'settings.language.chatHint': 'Los mensajes se traducen a este idioma.',
936
+ 'settings.password.title': 'Cambiar contraseña',
937
+ 'settings.password.current': 'Contraseña actual',
938
+ 'settings.password.new': 'Nueva contraseña',
939
+ 'settings.password.again': 'Repetir la nueva contraseña',
940
+ 'settings.password.submit': 'Cambiar contraseña',
941
+ 'settings.password.changed': 'Contraseña cambiada',
942
+ 'settings.password.wrongCurrent': 'La contraseña actual no es correcta',
943
+ 'settings.password.failed': 'No se pudo cambiar la contraseña',
944
+ 'settings.password.tooShort': 'Al menos {min} caracteres',
945
+ 'settings.password.sameAsCurrent': 'Debe ser distinta de la contraseña actual',
946
+ 'settings.password.mismatch': 'Las contraseñas no coinciden',
947
+ 'settings.password.hint': 'Usa la nueva contraseña la próxima vez que inicies sesión.',
855
948
  'settings.visibility.title': 'Visibilidad',
856
949
  'settings.visibility.profileLabel': 'Visibilidad del perfil',
857
950
  'settings.visibility.open': 'Abierto (predeterminado)',
@@ -880,6 +973,11 @@ const es: UiStringTable = {
880
973
  const pt: UiStringTable = {
881
974
  'search.placeholder': 'Pesquisar...',
882
975
  'roomList.title': 'Conversas',
976
+ 'menu.newChat': 'Nova conversa',
977
+ 'menu.profile': 'Perfil',
978
+ 'menu.settings': 'Definições',
979
+ 'menu.logout': 'Terminar sessão',
980
+ 'menu.logoutConfirm': 'Tem a certeza de que quer terminar a sessão?',
883
981
  'search.members': 'Pesquisar membros',
884
982
  'search.noResults': 'Ninguém encontrado',
885
983
  'input.placeholder': 'Escreva uma mensagem',
@@ -889,6 +987,10 @@ const pt: UiStringTable = {
889
987
  'date.yesterday': 'Ontem',
890
988
  'message.deleted': 'Mensagem eliminada',
891
989
  'message.edited': 'editado',
990
+ 'message.sending': 'a enviar...',
991
+ 'message.failedRetry': '! Falhou — toque para tentar de novo',
992
+ 'thread.title': 'Tópico',
993
+ 'thread.alsoSendTo': 'Enviar também para',
892
994
  'e2ee.roomEncrypted': 'Criptografado de ponta a ponta',
893
995
  'e2ee.notEncrypted': 'Sem criptografia',
894
996
  'e2ee.notEncryptedHint': 'Enviado sem criptografia de ponta a ponta, porque nenhum destinatário publicou uma chave ainda.',
@@ -1037,6 +1139,10 @@ const pt: UiStringTable = {
1037
1139
  'settings.menu.title': 'Definições',
1038
1140
  'modal.filePreview.title': 'Pré-visualização do ficheiro',
1039
1141
  'toast.error': 'Erro',
1142
+ 'toast.failedToCreateRoom': 'Falha ao criar a sala',
1143
+ 'toast.copied': 'Copiado para a área de transferência!',
1144
+ 'toast.copyFailed': 'Falha ao copiar',
1145
+ 'toast.saveSuccessful': 'Guardado com sucesso',
1040
1146
  'toast.couldNotOpenPrivateChat': 'Não foi possível abrir o novo chat privado',
1041
1147
  'toast.couldNotResolveRecipient': 'Não foi possível identificar o destinatário',
1042
1148
  'toast.failedToCreatePrivateChat': 'Falha ao criar o chat privado',
@@ -1104,6 +1210,8 @@ const pt: UiStringTable = {
1104
1210
  'modal.chatProfile.removeMember': 'Remover deste chat?',
1105
1211
  'modal.chatProfile.memberListUnavailable': 'Lista de membros indisponível',
1106
1212
  'modal.deleteChat.title': 'Eliminar este chat?',
1213
+ 'modal.deleteMessage.title': 'Eliminar mensagem',
1214
+ 'modal.deleteMessage.description': 'Tem a certeza de que quer eliminar esta mensagem?',
1107
1215
  'modal.addMembers.title': 'Adicionar novo membro',
1108
1216
  'modal.addMembers.userIdPlaceholder': 'Introduza o ID do utilizador',
1109
1217
  'modal.leaveChat.title': 'Sair do chat',
@@ -1140,6 +1248,22 @@ const pt: UiStringTable = {
1140
1248
  'settings.notifications.title': 'Notificações',
1141
1249
  'settings.notifications.push': 'Notificações push',
1142
1250
  'settings.notifications.pushHint': 'Novas mensagens e chamadas acordam este dispositivo mesmo com o aplicativo fechado.',
1251
+ 'settings.language.app': 'Idioma do aplicativo',
1252
+ 'settings.language.appHint': 'O idioma da interface.',
1253
+ 'settings.language.chat': 'Idioma do chat',
1254
+ 'settings.language.chatHint': 'As mensagens são traduzidas para este idioma.',
1255
+ 'settings.password.title': 'Alterar senha',
1256
+ 'settings.password.current': 'Senha atual',
1257
+ 'settings.password.new': 'Nova senha',
1258
+ 'settings.password.again': 'Repetir a nova senha',
1259
+ 'settings.password.submit': 'Alterar senha',
1260
+ 'settings.password.changed': 'Senha alterada',
1261
+ 'settings.password.wrongCurrent': 'A senha atual não está correta',
1262
+ 'settings.password.failed': 'Não foi possível alterar a senha',
1263
+ 'settings.password.tooShort': 'Pelo menos {min} caracteres',
1264
+ 'settings.password.sameAsCurrent': 'Deve ser diferente da senha atual',
1265
+ 'settings.password.mismatch': 'As senhas não coincidem',
1266
+ 'settings.password.hint': 'Use a nova senha na próxima vez que entrar.',
1143
1267
  'settings.visibility.title': 'Visibilidade',
1144
1268
  'settings.visibility.profileLabel': 'Visibilidade do perfil',
1145
1269
  'settings.visibility.open': 'Aberto (predefinição)',
@@ -1168,6 +1292,11 @@ const pt: UiStringTable = {
1168
1292
  const ht: UiStringTable = {
1169
1293
  'search.placeholder': 'Chèche...',
1170
1294
  'roomList.title': 'Chat yo',
1295
+ 'menu.newChat': 'Nouvo chat',
1296
+ 'menu.profile': 'Pwofil',
1297
+ 'menu.settings': 'Paramèt',
1298
+ 'menu.logout': 'Dekonekte',
1299
+ 'menu.logoutConfirm': 'Èske ou sèten ou vle dekonekte?',
1171
1300
  'search.members': 'Chèche manm',
1172
1301
  'search.noResults': 'Pa jwenn pèsonn',
1173
1302
  'input.placeholder': 'Ekri yon mesaj',
@@ -1177,6 +1306,10 @@ const ht: UiStringTable = {
1177
1306
  'date.yesterday': 'Yè',
1178
1307
  'message.deleted': 'Mesaj efase',
1179
1308
  'message.edited': 'modifye',
1309
+ 'message.sending': 'ap voye...',
1310
+ 'message.failedRetry': '! Echwe — tape pou eseye ankò',
1311
+ 'thread.title': 'Fil',
1312
+ 'thread.alsoSendTo': 'Voye tou nan',
1180
1313
  'e2ee.roomEncrypted': 'Chifre bout-a-bout',
1181
1314
  'e2ee.notEncrypted': 'Pa chifre',
1182
1315
  'e2ee.notEncryptedHint': 'Voye san chifraj bout-a-bout, paske okenn moun ki resevwa a poko pibliye yon kle.',
@@ -1325,6 +1458,10 @@ const ht: UiStringTable = {
1325
1458
  'settings.menu.title': 'Paramèt',
1326
1459
  'modal.filePreview.title': 'Apèsi fichye',
1327
1460
  'toast.error': 'Erè',
1461
+ 'toast.failedToCreateRoom': 'Echwe pou kreye sal la',
1462
+ 'toast.copied': 'Kopye nan clipboard la!',
1463
+ 'toast.copyFailed': 'Kopi a echwe',
1464
+ 'toast.saveSuccessful': 'Anrejistre avèk siksè',
1328
1465
  'toast.couldNotOpenPrivateChat': 'Pa t kapab louvri nouvo chat prive a',
1329
1466
  'toast.couldNotResolveRecipient': 'Pa t kapab jwenn destinatè a',
1330
1467
  'toast.failedToCreatePrivateChat': 'Echèk pou kreye chat prive a',
@@ -1392,6 +1529,8 @@ const ht: UiStringTable = {
1392
1529
  'modal.chatProfile.removeMember': 'Retire nan chat sa a?',
1393
1530
  'modal.chatProfile.memberListUnavailable': 'Lis manm yo pa disponib',
1394
1531
  'modal.deleteChat.title': 'Efase chat sa a?',
1532
+ 'modal.deleteMessage.title': 'Efase mesaj',
1533
+ 'modal.deleteMessage.description': 'Èske ou sèten ou vle efase mesaj sa a?',
1395
1534
  'modal.addMembers.title': 'Ajoute yon nouvo manm',
1396
1535
  'modal.addMembers.userIdPlaceholder': 'Antre ID itilizatè a',
1397
1536
  'modal.leaveChat.title': 'Kite chat la',
@@ -1428,6 +1567,22 @@ const ht: UiStringTable = {
1428
1567
  'settings.notifications.title': 'Notifikasyon',
1429
1568
  'settings.notifications.push': 'Notifikasyon push',
1430
1569
  'settings.notifications.pushHint': 'Nouvo mesaj ak apèl reveye aparèy sa a menm lè aplikasyon an fèmen.',
1570
+ 'settings.language.app': 'Lang aplikasyon an',
1571
+ 'settings.language.appHint': 'Lang entèfas la.',
1572
+ 'settings.language.chat': 'Lang chat la',
1573
+ 'settings.language.chatHint': 'Mesaj yo tradui nan lang sa a.',
1574
+ 'settings.password.title': 'Chanje modpas',
1575
+ 'settings.password.current': 'Modpas aktyèl',
1576
+ 'settings.password.new': 'Nouvo modpas',
1577
+ 'settings.password.again': 'Repete nouvo modpas la',
1578
+ 'settings.password.submit': 'Chanje modpas',
1579
+ 'settings.password.changed': 'Modpas chanje',
1580
+ 'settings.password.wrongCurrent': 'Modpas aktyèl la pa kòrèk',
1581
+ 'settings.password.failed': 'Modpas la pa t ka chanje',
1582
+ 'settings.password.tooShort': 'Omwen {min} karaktè',
1583
+ 'settings.password.sameAsCurrent': 'Dwe diferan de modpas aktyèl la',
1584
+ 'settings.password.mismatch': 'Modpas yo pa menm',
1585
+ 'settings.password.hint': 'Sèvi ak nouvo modpas la pwochen fwa ou konekte.',
1431
1586
  'settings.visibility.title': 'Vizibilite',
1432
1587
  'settings.visibility.profileLabel': 'Vizibilite pwofil la',
1433
1588
  'settings.visibility.open': 'Louvri (default)',
@@ -1456,6 +1611,11 @@ const ht: UiStringTable = {
1456
1611
  const zh: UiStringTable = {
1457
1612
  'search.placeholder': '搜索...',
1458
1613
  'roomList.title': '聊天',
1614
+ 'menu.newChat': '新聊天',
1615
+ 'menu.profile': '个人资料',
1616
+ 'menu.settings': '设置',
1617
+ 'menu.logout': '退出登录',
1618
+ 'menu.logoutConfirm': '确定要退出登录吗?',
1459
1619
  'search.members': '搜索成员',
1460
1620
  'search.noResults': '未找到成员',
1461
1621
  'input.placeholder': '输入消息',
@@ -1465,6 +1625,10 @@ const zh: UiStringTable = {
1465
1625
  'date.yesterday': '昨天',
1466
1626
  'message.deleted': '消息已删除',
1467
1627
  'message.edited': '已编辑',
1628
+ 'message.sending': '发送中...',
1629
+ 'message.failedRetry': '! 发送失败 — 点击重试',
1630
+ 'thread.title': '话题',
1631
+ 'thread.alsoSendTo': '同时发送到',
1468
1632
  'e2ee.roomEncrypted': '端到端加密',
1469
1633
  'e2ee.notEncrypted': '未加密',
1470
1634
  'e2ee.notEncryptedHint': '未使用端到端加密发送,因为还没有收件人发布密钥。',
@@ -1613,6 +1777,10 @@ const zh: UiStringTable = {
1613
1777
  'settings.menu.title': '设置',
1614
1778
  'modal.filePreview.title': '文件预览',
1615
1779
  'toast.error': '错误',
1780
+ 'toast.failedToCreateRoom': '创建房间失败',
1781
+ 'toast.copied': '已复制到剪贴板!',
1782
+ 'toast.copyFailed': '复制失败',
1783
+ 'toast.saveSuccessful': '保存成功',
1616
1784
  'toast.couldNotOpenPrivateChat': '无法打开新的私聊',
1617
1785
  'toast.couldNotResolveRecipient': '无法识别收件人',
1618
1786
  'toast.failedToCreatePrivateChat': '创建私聊失败',
@@ -1680,6 +1848,8 @@ const zh: UiStringTable = {
1680
1848
  'modal.chatProfile.removeMember': '将其移出该聊天?',
1681
1849
  'modal.chatProfile.memberListUnavailable': '成员列表不可用',
1682
1850
  'modal.deleteChat.title': '删除此聊天?',
1851
+ 'modal.deleteMessage.title': '删除消息',
1852
+ 'modal.deleteMessage.description': '确定要删除这条消息吗?',
1683
1853
  'modal.addMembers.title': '添加新成员',
1684
1854
  'modal.addMembers.userIdPlaceholder': '输入用户 ID',
1685
1855
  'modal.leaveChat.title': '退出聊天',
@@ -1716,6 +1886,22 @@ const zh: UiStringTable = {
1716
1886
  'settings.notifications.title': '通知',
1717
1887
  'settings.notifications.push': '推送通知',
1718
1888
  'settings.notifications.pushHint': '即使应用已关闭,新消息和来电也会唤醒此设备。',
1889
+ 'settings.language.app': '应用语言',
1890
+ 'settings.language.appHint': '界面语言。',
1891
+ 'settings.language.chat': '聊天语言',
1892
+ 'settings.language.chatHint': '消息会翻译成此语言。',
1893
+ 'settings.password.title': '修改密码',
1894
+ 'settings.password.current': '当前密码',
1895
+ 'settings.password.new': '新密码',
1896
+ 'settings.password.again': '再次输入新密码',
1897
+ 'settings.password.submit': '修改密码',
1898
+ 'settings.password.changed': '密码已修改',
1899
+ 'settings.password.wrongCurrent': '当前密码不正确',
1900
+ 'settings.password.failed': '无法修改密码',
1901
+ 'settings.password.tooShort': '至少 {min} 个字符',
1902
+ 'settings.password.sameAsCurrent': '必须与当前密码不同',
1903
+ 'settings.password.mismatch': '两次输入的密码不一致',
1904
+ 'settings.password.hint': '下次登录时请使用新密码。',
1719
1905
  'settings.visibility.title': '可见性',
1720
1906
  'settings.visibility.profileLabel': '资料可见性',
1721
1907
  'settings.visibility.open': '公开(默认)',
package/src/i18n/useT.ts CHANGED
@@ -34,13 +34,15 @@ import { interpolate, resolveStringTable } from './strings';
34
34
  * own locale - the right default.
35
35
  */
36
36
  export const useUiLocale = (): string | undefined => {
37
- const { config, langSource } = useChatSettingState();
38
- return config?.i18n?.locale || langSource || undefined;
37
+ const { config, langSource, uiLocale } = useChatSettingState();
38
+ return uiLocale || config?.i18n?.locale || langSource || undefined;
39
39
  };
40
40
 
41
41
  export const useT = () => {
42
- const { config, langSource } = useChatSettingState();
43
- const locale = config?.i18n?.locale || langSource;
42
+ // The user's own pick from Settings first (`uiLocale`), then the host's
43
+ // default, then the reader's chat language.
44
+ const { config, langSource, uiLocale } = useChatSettingState();
45
+ const locale = uiLocale || config?.i18n?.locale || langSource;
44
46
  const overrides = config?.i18n?.strings;
45
47
 
46
48
  const table = useMemo(
@@ -59,6 +59,15 @@ export function updateMe(data: any) {
59
59
  return http.put('/v1/users', data);
60
60
  }
61
61
 
62
+ /**
63
+ * Change the account password. The backend checks `currentPassword` and
64
+ * answers with a 4xx and a reason when it does not match or the new one is
65
+ * not acceptable; the caller shows that reason.
66
+ */
67
+ export function changePassword(currentPassword: string, newPassword: string) {
68
+ return http.put('/v2/users/me/password', { currentPassword, newPassword });
69
+ }
70
+
62
71
  export async function updateProfile(fd: FormData): Promise<{ user: User }> {
63
72
  const token = store.getState().chatSettingStore.user.token || '';
64
73
 
@@ -132,6 +132,7 @@ const onRealtimeMessage = async (stanza: Element) => {
132
132
  // (stanza handler, not a component), so resolve the locale from the
133
133
  // store directly and call `translateKey` instead of `useT()`.
134
134
  const locale =
135
+ state.chatSettingStore.uiLocale ||
135
136
  state.chatSettingStore.config?.i18n?.locale ||
136
137
  state.chatSettingStore.langSource;
137
138
  const overrides = state.chatSettingStore.config?.i18n?.strings;
@@ -38,6 +38,12 @@ export interface ChatState {
38
38
  themePreference?: ThemePreference;
39
39
  /** Push notifications toggle from Settings. `undefined` = on. */
40
40
  pushEnabled?: boolean;
41
+ /**
42
+ * The interface language the user picked in Settings (BCP-47, e.g.
43
+ * "fr-CA") - the backend's `user.appLanguage`. Wins over the host's
44
+ * `config.i18n.locale`, which is the default for a user who never chose.
45
+ */
46
+ uiLocale?: string;
41
47
  }
42
48
 
43
49
  export const unpackAndTransform = (input?: User): User => {
@@ -219,6 +225,12 @@ const reducers = {
219
225
  ) => {
220
226
  state.translateMode = action.payload;
221
227
  },
228
+ setUiLocale: (
229
+ state: WritableDraft<ChatState>,
230
+ action: PayloadAction<string | undefined>
231
+ ) => {
232
+ state.uiLocale = action.payload || undefined;
233
+ },
222
234
  };
223
235
 
224
236
  export const chatSlice: Slice<ChatState, typeof reducers, 'chat'> = createSlice({
@@ -242,6 +254,7 @@ export const {
242
254
  setTranslateMode,
243
255
  setThemePreference,
244
256
  setPushEnabled,
257
+ setUiLocale,
245
258
  } = chatSlice.actions;
246
259
 
247
260
  export default chatSlice.reducer;
@@ -0,0 +1,143 @@
1
+ // The user's two languages, as the backend keeps them on the profile:
2
+ //
3
+ // appLanguage - the interface: the chat's captions (`uiLocale`).
4
+ // chatLanguage - what incoming messages are translated into, and the
5
+ // language declared on the user's own messages (`langSource`).
6
+ //
7
+ // Both are per account, not per device: a choice made here is written to the
8
+ // profile with `PUT /v1/users` - one field per request, as the web app does,
9
+ // so writing one never rewrites the other - and the profile's values are
10
+ // applied when a session starts. The last choice is also kept on the device,
11
+ // so the interface comes up in the right language before the profile loads.
12
+ //
13
+ // Translation itself happens on the server, per the reader's chatLanguage:
14
+ // that is why the choice has to reach the profile for a translation to ever
15
+ // arrive on a message.
16
+
17
+ import { store } from '../roomStore';
18
+ import { setLangSource, setUiLocale, updateUser } from '../roomStore/chatSettingsSlice';
19
+ import { savePreferences } from '../helpers/preferencesStorage';
20
+ import { updateMe } from '../networking/api-requests/user.api';
21
+ import { LANGUAGE_OPTIONS } from '../helpers/constants/LANGUAGE_OPTIONS';
22
+ import type { IConfig } from '../types/types';
23
+
24
+ export interface LanguageChoice {
25
+ /** BCP-47 tag, e.g. "fr-CA". */
26
+ id: string;
27
+ /** The language's own name for itself. */
28
+ name: string;
29
+ }
30
+
31
+ const AUTONYMS: Record<string, string> = {
32
+ en: 'English',
33
+ es: 'Español',
34
+ fr: 'Français',
35
+ pt: 'Português',
36
+ ht: 'Kreyòl ayisyen',
37
+ zh: '中文',
38
+ de: 'Deutsch',
39
+ it: 'Italiano',
40
+ uk: 'Українська',
41
+ ru: 'Русский',
42
+ pl: 'Polski',
43
+ tr: 'Türkçe',
44
+ ar: 'العربية',
45
+ hi: 'हिन्दी',
46
+ ja: '日本語',
47
+ ko: '한국어',
48
+ nl: 'Nederlands',
49
+ sv: 'Svenska',
50
+ };
51
+
52
+ /** "en-ca" -> "en-CA"; anything that is not a language tag -> undefined. */
53
+ export const canonicalLocale = (value: unknown): string | undefined => {
54
+ const raw = String(value ?? '').trim();
55
+ const match = /^([A-Za-z]{2,3})(?:[-_]([A-Za-z0-9]{2,8}))?$/.exec(raw);
56
+ if (!match) {return undefined;}
57
+ const [, language, region] = match;
58
+ const base = language!.toLowerCase();
59
+ if (!region) {return base;}
60
+ return `${base}-${region.length === 2 ? region.toUpperCase() : region}`;
61
+ };
62
+
63
+ export const languageLabel = (id: string): string => {
64
+ const exact = LANGUAGE_OPTIONS.find((o) => o.id.toLowerCase() === id.toLowerCase());
65
+ if (exact) {return exact.name;}
66
+ return AUTONYMS[id.split('-')[0]!.toLowerCase()] || id;
67
+ };
68
+
69
+ const toChoices = (ids: string[] | undefined): LanguageChoice[] | undefined => {
70
+ const list = (ids || []).map(canonicalLocale).filter((id): id is string => !!id);
71
+ return list.length ? list.map((id) => ({ id, name: languageLabel(id) })) : undefined;
72
+ };
73
+
74
+ export const languagesEnabled = (config?: IConfig | null): boolean =>
75
+ config?.settings?.languages?.enabled === true;
76
+
77
+ /** What the interface can be shown in. */
78
+ export const appLanguageChoices = (config?: IConfig | null): LanguageChoice[] =>
79
+ toChoices(config?.settings?.languages?.appLanguages) ??
80
+ LANGUAGE_OPTIONS.map((o) => ({ id: o.id, name: o.name }));
81
+
82
+ /** What messages can be translated into. */
83
+ export const chatLanguageChoices = (config?: IConfig | null): LanguageChoice[] =>
84
+ toChoices(config?.settings?.languages?.chatLanguages) ??
85
+ LANGUAGE_OPTIONS.map((o) => ({ id: o.id, name: o.name }));
86
+
87
+ async function writeToProfile(field: 'appLanguage' | 'chatLanguage', value: string) {
88
+ try {
89
+ await updateMe({ [field]: value });
90
+ // The field alone: replacing the whole user with the backend's profile
91
+ // would drop what only this side holds (tokens, the XMPP password).
92
+ store.dispatch(updateUser({ updates: { [field]: value } }));
93
+ return true;
94
+ } catch (error) {
95
+ // The local choice stands: a flaky network degrades to a device-only
96
+ // setting rather than silently reverting what the user just picked.
97
+ console.warn(`[languages] could not save ${field}`, error);
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /** The interface language. Applied at once; written to the profile behind. */
103
+ export async function setAppLanguage(id: string): Promise<boolean> {
104
+ const locale = canonicalLocale(id);
105
+ if (!locale) {return false;}
106
+ store.dispatch(setUiLocale(locale));
107
+ void savePreferences({ uiLocale: locale });
108
+ if (!languagesEnabled(store.getState().chatSettingStore.config)) {return true;}
109
+ return writeToProfile('appLanguage', locale);
110
+ }
111
+
112
+ /** The language messages are translated into. */
113
+ export async function setChatLanguage(id: string): Promise<boolean> {
114
+ const locale = canonicalLocale(id);
115
+ if (!locale) {return false;}
116
+ store.dispatch(setLangSource(locale as any));
117
+ void savePreferences({ chatLanguage: locale });
118
+ if (!languagesEnabled(store.getState().chatSettingStore.config)) {return true;}
119
+ return writeToProfile('chatLanguage', locale);
120
+ }
121
+
122
+ /**
123
+ * The profile's languages, applied when a session starts. The profile is the
124
+ * account's truth; what the device remembered only bridged the gap until
125
+ * now. A profile that has never chosen leaves the device's value alone.
126
+ */
127
+ export function applyProfileLanguages(
128
+ user: { appLanguage?: string | null; chatLanguage?: string | null } | null | undefined,
129
+ config?: IConfig | null
130
+ ): void {
131
+ if (!user || !languagesEnabled(config)) {return;}
132
+ const app = canonicalLocale(user.appLanguage);
133
+ if (app && app !== store.getState().chatSettingStore.uiLocale) {
134
+ store.dispatch(setUiLocale(app));
135
+ void savePreferences({ uiLocale: app });
136
+ }
137
+ // Unset means "follow the app language" - the backend's own rule.
138
+ const chat = canonicalLocale(user.chatLanguage) || app;
139
+ if (chat && chat !== store.getState().chatSettingStore.langSource) {
140
+ store.dispatch(setLangSource(chat as any));
141
+ void savePreferences({ chatLanguage: chat });
142
+ }
143
+ }
@@ -54,6 +54,10 @@ export interface User {
54
54
  isAssetsOpen?: boolean;
55
55
  isAgreeWithTerms?: boolean;
56
56
  isSuperAdmin?: boolean;
57
+ /** Interface language on the profile (BCP-47). */
58
+ appLanguage?: string | null;
59
+ /** Language messages are translated into (BCP-47); unset = follow appLanguage. */
60
+ chatLanguage?: string | null;
57
61
  }
58
62
 
59
63
  export interface ConfigUser {
@@ -811,6 +811,27 @@ export interface IConfig {
811
811
  hideAppearance?: boolean;
812
812
  /** Hide the push-notifications toggle. */
813
813
  hidePushToggle?: boolean;
814
+ /**
815
+ * A "Language" card with two pickers, off by default: the interface
816
+ * language (the backend's `user.appLanguage`) and the language messages
817
+ * are translated into (`user.chatLanguage`). A choice is applied at
818
+ * once, kept on the device, and written to the profile with
819
+ * `PUT /v1/users`, one field per request; the profile's values are
820
+ * applied when a session starts. Translation itself is the server's,
821
+ * per the reader's chat language - so `translates.enabled` is what
822
+ * shows the result. Options default to the built-in list (en-CA, es-US,
823
+ * fr-CA); pass BCP-47 tags to offer others.
824
+ */
825
+ languages?: {
826
+ enabled: boolean;
827
+ appLanguages?: string[];
828
+ chatLanguages?: string[];
829
+ };
830
+ /**
831
+ * A "Change password" screen, off by default: the current password and
832
+ * the new one, sent to `PUT /v2/users/me/password`.
833
+ */
834
+ changePassword?: boolean;
814
835
  };
815
836
 
816
837
  // ----- event hooks -----
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/sync-version.mjs on `npm version`.
2
2
  // Edit package.json's version instead of this file.
3
- export const ETHORA_CHAT_COMPONENT_VERSION = '26.8.7';
3
+ export const ETHORA_CHAT_COMPONENT_VERSION = '26.8.8';