@micha.bigler/ui-core-micha 3.6.0 → 3.7.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.
- package/DESIGN.md +1 -1
- package/README.md +2 -0
- package/WORK_ORDERS.md +7 -0
- package/dist/auth/UserMenu.js +23 -1
- package/dist/components/EmptyState.js +17 -0
- package/dist/components/UserListComponent.js +2 -1
- package/dist/i18n/authTranslations.js +196 -0
- package/dist/i18n/uiCoreTranslations.js +19 -0
- package/dist/index.js +2 -1
- package/dist/messaging/Composer.js +4 -2
- package/dist/messaging/MessageBubble.js +2 -1
- package/dist/messaging/ReadTicks.js +5 -3
- package/dist/messaging/linkifyText.js +39 -0
- package/dist/pages/PasswordInvitePage.js +23 -8
- package/dist/pages/SignUpPage.js +4 -4
- package/dist/theme/kitSxRegistry.js +5 -1
- package/package.json +1 -1
- package/src/auth/UserMenu.jsx +26 -1
- package/src/components/EmptyState.jsx +38 -0
- package/src/components/UserListComponent.jsx +2 -0
- package/src/i18n/authTranslations.ts +197 -0
- package/src/i18n/uiCoreTranslations.js +10 -0
- package/src/index.js +2 -1
- package/src/messaging/Composer.jsx +7 -3
- package/src/messaging/MessageBubble.jsx +2 -1
- package/src/messaging/ReadTicks.jsx +4 -3
- package/src/messaging/linkifyText.jsx +37 -0
- package/src/pages/PasswordInvitePage.jsx +36 -7
- package/src/pages/SignUpPage.jsx +79 -68
- package/src/theme/kitSxRegistry.js +9 -1
- package/tests/EmptyState.test.jsx +62 -0
- package/tests/PasswordInvitePage.test.jsx +135 -0
- package/tests/SignUpPage.test.jsx +15 -2
- package/tests/UserListComponent.test.jsx +85 -0
- package/tests/UserMenu.test.jsx +40 -4
- package/tests/i18nGermanVariant.test.js +65 -0
- package/tests/messagingComposer.test.jsx +25 -0
- package/tests/messagingLinkify.test.jsx +51 -0
- package/tests/messagingReadTicksTooltip.test.jsx +36 -0
- package/tests/userScreenTranslations.test.jsx +165 -0
- package/work-orders/UCM-AUTH-9.md +207 -0
- package/work-orders/UCM-I18N-4.md +226 -0
- package/work-orders/UCM-MSG-20.md +192 -0
- package/work-orders/UCM-PRIM-2.md +114 -0
- package/work-orders/UCM-SHELL-7.md +134 -0
- package/work-orders/UCM-THEME-13.md +69 -0
- package/work-orders/UCM-THEME-14.md +140 -0
|
@@ -59,6 +59,7 @@ export const authTranslations = {
|
|
|
59
59
|
|
|
60
60
|
"Auth.INVITE_PERMISSION_DENIED": {
|
|
61
61
|
"de": "Sie haben keine Berechtigung, Einladungen zu versenden.",
|
|
62
|
+
"de_informal": "Du hast keine Berechtigung, Einladungen zu versenden.",
|
|
62
63
|
"fr": "Vous n'avez pas l'autorisation d'envoyer des invitations.",
|
|
63
64
|
"en": "You do not have permission to send invitations.",
|
|
64
65
|
"sw": "Huna ruhusa ya kutuma mialiko."
|
|
@@ -115,6 +116,7 @@ export const authTranslations = {
|
|
|
115
116
|
|
|
116
117
|
"Auth.RESET_LINK_VALID": {
|
|
117
118
|
"de": "Der Link ist gültig. Sie können ein neues Passwort setzen.",
|
|
119
|
+
"de_informal": "Der Link ist gültig. Du kannst ein neues Passwort setzen.",
|
|
118
120
|
"fr": "Le lien est valide. Vous pouvez définir un nouveau mot de passe.",
|
|
119
121
|
"en": "The link is valid. You can set a new password.",
|
|
120
122
|
"sw": "Viungo ni sahihi. Unaweza kuweka nenosiri jipya."
|
|
@@ -129,6 +131,7 @@ export const authTranslations = {
|
|
|
129
131
|
|
|
130
132
|
"Auth.RESET_PASSWORD_REQUIRED": {
|
|
131
133
|
"de": "Bitte geben Sie ein neues Passwort ein.",
|
|
134
|
+
"de_informal": "Bitte gib ein neues Passwort ein.",
|
|
132
135
|
"fr": "Veuillez saisir un nouveau mot de passe.",
|
|
133
136
|
"en": "Please enter a new password.",
|
|
134
137
|
"sw": "Tafadhali ingiza nenosiri jipya."
|
|
@@ -149,6 +152,7 @@ export const authTranslations = {
|
|
|
149
152
|
},
|
|
150
153
|
"Auth.GENERIC_ERROR": {
|
|
151
154
|
"de": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
|
|
155
|
+
"de_informal": "Es ist ein Fehler aufgetreten. Bitte versuche es erneut.",
|
|
152
156
|
"fr": "Une erreur s'est produite. Veuillez réessayer.",
|
|
153
157
|
"en": "An error occurred. Please try again.",
|
|
154
158
|
"sw": "Hitilafu imetokea. Tafadhali jaribu tena."
|
|
@@ -316,6 +320,7 @@ export const authTranslations = {
|
|
|
316
320
|
},
|
|
317
321
|
"Auth.PAGE_CHANGE_PASSWORD_SUBTITLE": {
|
|
318
322
|
"de": "Geben Sie Ihr aktuelles und ein neues Passwort ein.",
|
|
323
|
+
"de_informal": "Gib dein aktuelles und ein neues Passwort ein.",
|
|
319
324
|
"fr": "Saisissez votre mot de passe actuel et un nouveau mot de passe.",
|
|
320
325
|
"en": "Enter your current password and a new password.",
|
|
321
326
|
"sw": "Ingiza nenosiri lako la sasa na nenosiri jipya."
|
|
@@ -328,6 +333,7 @@ export const authTranslations = {
|
|
|
328
333
|
},
|
|
329
334
|
"Auth.PAGE_CHECKING_LINK_TEXT": {
|
|
330
335
|
"de": "Wir prüfen gerade die Gültigkeit Ihres Links.",
|
|
336
|
+
"de_informal": "Wir prüfen gerade die Gültigkeit deines Links.",
|
|
331
337
|
"fr": "Nous vérifions la validité de votre lien.",
|
|
332
338
|
"en": "We are validating your link.",
|
|
333
339
|
"sw": "Tunathibitisha uhalali wa kiungo chako."
|
|
@@ -341,6 +347,7 @@ export const authTranslations = {
|
|
|
341
347
|
},
|
|
342
348
|
"Auth.PAGE_INVITE_SUBTITLE": {
|
|
343
349
|
"de": "Bitte wählen Sie ein Passwort, um auf Ihr Konto zuzugreifen.",
|
|
350
|
+
"de_informal": "Bitte wähle ein Passwort, um auf dein Konto zuzugreifen.",
|
|
344
351
|
"fr": "Veuillez choisir un mot de passe pour accéder à votre compte.",
|
|
345
352
|
"en": "Please choose a password to access your account.",
|
|
346
353
|
"sw": "Tafadhali chagua nenosiri ili kufikia akaunti yako."
|
|
@@ -354,6 +361,7 @@ export const authTranslations = {
|
|
|
354
361
|
},
|
|
355
362
|
"Auth.PAGE_RESET_PASSWORD_SUBTITLE": {
|
|
356
363
|
"de": "Bitte wählen Sie ein neues Passwort.",
|
|
364
|
+
"de_informal": "Bitte wähle ein neues Passwort.",
|
|
357
365
|
"fr": "Veuillez choisir un nouveau mot de passe.",
|
|
358
366
|
"en": "Please choose a new password.",
|
|
359
367
|
"sw": "Tafadhali chagua nenosiri jipya."
|
|
@@ -361,60 +369,70 @@ export const authTranslations = {
|
|
|
361
369
|
|
|
362
370
|
"Auth.RESET_PASSWORD_SUCCESS_INVITE": {
|
|
363
371
|
"de": "Das Passwort wurde gesetzt. Sie können sich nun anmelden.",
|
|
372
|
+
"de_informal": "Das Passwort wurde gesetzt. Du kannst dich nun anmelden.",
|
|
364
373
|
"fr": "Le mot de passe a été défini. Vous pouvez maintenant vous connecter.",
|
|
365
374
|
"en": "Password set successfully. You can now log in.",
|
|
366
375
|
"sw": "Nenosiri limewekwa. Sasa unaweza kuingia."
|
|
367
376
|
},
|
|
368
377
|
"Auth.RESET_PASSWORD_SUCCESS_RESET": {
|
|
369
378
|
"de": "Das Passwort wurde geändert. Sie können sich nun anmelden.",
|
|
379
|
+
"de_informal": "Das Passwort wurde geändert. Du kannst dich nun anmelden.",
|
|
370
380
|
"fr": "Le mot de passe a été modifié. Vous pouvez maintenant vous connecter.",
|
|
371
381
|
"en": "Password changed successfully. You can now log in.",
|
|
372
382
|
"sw": "Nenosiri limebadilishwa. Sasa unaweza kuingia."
|
|
373
383
|
},
|
|
374
384
|
"Auth.PAGE_RESET_REQUEST_SUBTITLE": {
|
|
375
385
|
"de": "Geben Sie Ihre E-Mail-Adresse ein, um den Link zum Zurücksetzen zu erhalten.",
|
|
386
|
+
"de_informal": "Gib deine E-Mail-Adresse ein, um den Link zum Zurücksetzen zu erhalten.",
|
|
376
387
|
"fr": "Saisissez votre adresse e-mail pour recevoir le lien de réinitialisation.",
|
|
377
388
|
"en": "Enter your email address to receive the reset link.",
|
|
378
389
|
"sw": "Ingiza anwani yako ya barua pepe ili kupokea kiungo cha kuweka upya."
|
|
379
390
|
},
|
|
380
391
|
"Auth.EMAIL_REQUIRED": {
|
|
381
392
|
"de": "Bitte geben Sie eine E-Mail-Adresse ein.",
|
|
393
|
+
"de_informal": "Bitte gib eine E-Mail-Adresse ein.",
|
|
382
394
|
"fr": "Veuillez saisir une adresse e-mail.",
|
|
383
395
|
"en": "Please enter an email address.",
|
|
384
396
|
"sw": "Tafadhali ingiza anwani ya barua pepe."
|
|
385
397
|
},
|
|
386
398
|
"Auth.PAGE_SIGNUP_SUBTITLE": {
|
|
387
399
|
"de": "Wählen Sie eine Registrierungsart und geben Sie die benötigten Angaben ein.",
|
|
400
|
+
"de_informal": "Wähle eine Registrierungsart und gib die benötigten Angaben ein.",
|
|
388
401
|
"fr": "Choisissez une méthode d'inscription et saisissez les informations requises.",
|
|
389
402
|
"en": "Choose a sign-up method and enter the required details.",
|
|
390
403
|
"sw": "Chagua njia ya kujisajili na uingize taarifa zinazohitajika."
|
|
391
404
|
},
|
|
392
405
|
"Auth.PAGE_SIGNUP_SUBTITLE_ACCESS_CODE": {
|
|
393
406
|
"de": "Geben Sie Ihre E-Mail-Adresse und den erhaltenen Zugangscode ein.",
|
|
407
|
+
"de_informal": "Gib deine E-Mail-Adresse und den erhaltenen Zugangscode ein.",
|
|
394
408
|
"fr": "Saisissez votre adresse e-mail et le code d'accès reçu.",
|
|
395
409
|
"en": "Enter your email address and the access code you received.",
|
|
396
410
|
"sw": "Ingiza anwani yako ya barua pepe na msimbo wa ufikiaji uliopokea."
|
|
397
411
|
},
|
|
398
412
|
"Auth.PAGE_SIGNUP_SUBTITLE_OPEN": {
|
|
399
413
|
"de": "Geben Sie Ihre E-Mail-Adresse ein, um sich direkt zu registrieren.",
|
|
414
|
+
"de_informal": "Gib deine E-Mail-Adresse ein, um dich direkt zu registrieren.",
|
|
400
415
|
"fr": "Saisissez votre adresse e-mail pour vous inscrire directement.",
|
|
401
416
|
"en": "Enter your email address to sign up directly.",
|
|
402
417
|
"sw": "Ingiza anwani yako ya barua pepe ili kujisajili moja kwa moja."
|
|
403
418
|
},
|
|
404
419
|
"Auth.PAGE_SIGNUP_SUBTITLE_EMAIL_DOMAIN": {
|
|
405
420
|
"de": "Verwenden Sie eine E-Mail-Adresse aus einer freigegebenen Domain.",
|
|
421
|
+
"de_informal": "Verwende eine E-Mail-Adresse aus einer freigegebenen Domain.",
|
|
406
422
|
"fr": "Utilisez une adresse e-mail provenant d'un domaine autorisé.",
|
|
407
423
|
"en": "Use an email address from an approved domain.",
|
|
408
424
|
"sw": "Tumia anwani ya barua pepe kutoka kwenye domeni iliyoidhinishwa."
|
|
409
425
|
},
|
|
410
426
|
"Auth.PAGE_SIGNUP_SUBTITLE_QR": {
|
|
411
427
|
"de": "Öffnen Sie diese Seite über einen gültigen QR-Link und geben Sie dann Ihre E-Mail-Adresse ein.",
|
|
428
|
+
"de_informal": "Öffne diese Seite über einen gültigen QR-Link und gib dann deine E-Mail-Adresse ein.",
|
|
412
429
|
"fr": "Ouvrez cette page via un lien QR valide, puis saisissez votre adresse e-mail.",
|
|
413
430
|
"en": "Open this page from a valid QR link, then enter your email address.",
|
|
414
431
|
"sw": "Fungua ukurasa huu kupitia kiungo halali cha QR, kisha ingiza anwani yako ya barua pepe."
|
|
415
432
|
},
|
|
416
433
|
"Auth.PAGE_SIGNUP_SUBTITLE_QR_READY": {
|
|
417
434
|
"de": "Geben Sie Ihre E-Mail-Adresse ein, um die Registrierung mit diesem QR-Link abzuschließen.",
|
|
435
|
+
"de_informal": "Gib deine E-Mail-Adresse ein, um die Registrierung mit diesem QR-Link abzuschließen.",
|
|
418
436
|
"fr": "Saisissez votre adresse e-mail pour terminer l'inscription avec ce lien QR.",
|
|
419
437
|
"en": "Enter your email address to complete sign-up with this QR link.",
|
|
420
438
|
"sw": "Ingiza anwani yako ya barua pepe ili kukamilisha kujisajili kwa kutumia kiungo hiki cha QR."
|
|
@@ -477,6 +495,7 @@ export const authTranslations = {
|
|
|
477
495
|
|
|
478
496
|
"Auth.SIGNUP_ALREADY_HAVE_ACCOUNT": {
|
|
479
497
|
"de": "Sie haben bereits ein Konto?",
|
|
498
|
+
"de_informal": "Du hast bereits ein Konto?",
|
|
480
499
|
"fr": "Vous avez déjà un compte ?",
|
|
481
500
|
"en": "Already have an account?",
|
|
482
501
|
"sw": "Je, tayari una akaunti?"
|
|
@@ -489,6 +508,7 @@ export const authTranslations = {
|
|
|
489
508
|
},
|
|
490
509
|
"Auth.SIGNUP_ACCESS_CODE_REQUIRED": {
|
|
491
510
|
"de": "Bitte geben Sie einen Zugangscode ein.",
|
|
511
|
+
"de_informal": "Bitte gib einen Zugangscode ein.",
|
|
492
512
|
"fr": "Veuillez saisir un code d'accès.",
|
|
493
513
|
"en": "Please enter an access code.",
|
|
494
514
|
"sw": "Tafadhali ingiza msimbo wa ufikiaji."
|
|
@@ -508,6 +528,7 @@ export const authTranslations = {
|
|
|
508
528
|
},
|
|
509
529
|
"Auth.PAGE_LOGIN_SUBTITLE": {
|
|
510
530
|
"de": "Melden Sie sich mit Ihrem Konto an.",
|
|
531
|
+
"de_informal": "Melde dich mit deinem Konto an.",
|
|
511
532
|
"fr": "Connectez-vous avec votre compte.",
|
|
512
533
|
"en": "Sign in with your account.",
|
|
513
534
|
"sw": "Ingia na akaunti yako."
|
|
@@ -1160,6 +1181,7 @@ export const authTranslations = {
|
|
|
1160
1181
|
},
|
|
1161
1182
|
"Support.RECOVERY_APPROVE_CONFIRM_QUESTION": {
|
|
1162
1183
|
"de": "Sind Sie sicher, dass Sie diese Wiederherstellungsanfrage bewilligen möchten?",
|
|
1184
|
+
"de_informal": "Bist du sicher, dass du diese Wiederherstellungsanfrage bewilligen möchtest?",
|
|
1163
1185
|
"fr": "Êtes-vous sûr de vouloir approuver cette demande de récupération ?",
|
|
1164
1186
|
"en": "Are you sure you want to approve this recovery request?",
|
|
1165
1187
|
"sw": "Una uhakika unataka kuidhinisha ombi hili la urejesho?"
|
|
@@ -1172,6 +1194,7 @@ export const authTranslations = {
|
|
|
1172
1194
|
},
|
|
1173
1195
|
"Support.RECOVERY_APPROVE_NOTE_HELP": {
|
|
1174
1196
|
"de": "Beschreiben Sie kurz, warum Sie diese Anfrage bewilligen.",
|
|
1197
|
+
"de_informal": "Beschreibe kurz, warum du diese Anfrage bewilligst.",
|
|
1175
1198
|
"fr": "Décrivez brièvement pourquoi vous approuvez cette demande.",
|
|
1176
1199
|
"en": "Briefly describe why you are approving this request.",
|
|
1177
1200
|
"sw": "Eleza kwa ufupi kwa nini unaidhinisha ombi hili."
|
|
@@ -1184,12 +1207,14 @@ export const authTranslations = {
|
|
|
1184
1207
|
},
|
|
1185
1208
|
"Auth.MFA_HELP_DIALOG_DESCRIPTION": {
|
|
1186
1209
|
"de": "Beschreiben Sie kurz, warum Sie keine der verfügbaren Methoden verwenden können. Eine Supportperson wird Ihre Anfrage prüfen.",
|
|
1210
|
+
"de_informal": "Beschreibe kurz, warum du keine der verfügbaren Methoden verwenden kannst. Eine Supportperson wird deine Anfrage prüfen.",
|
|
1187
1211
|
"fr": "Décrivez brièvement pourquoi vous ne pouvez utiliser aucune des méthodes disponibles. Un membre du support examinera votre demande.",
|
|
1188
1212
|
"en": "Briefly describe why you cannot use any of the available methods. A support person will review your request.",
|
|
1189
1213
|
"sw": "Eleza kwa ufupi kwa nini huwezi kutumia yoyote ya mbinu zinazopatikana. Mtu wa usaidizi atakagua ombi lako."
|
|
1190
1214
|
},
|
|
1191
1215
|
"Auth.MFA_HELP_MESSAGE_LABEL": {
|
|
1192
1216
|
"de": "Ihre Nachricht an den Support",
|
|
1217
|
+
"de_informal": "Deine Nachricht an den Support",
|
|
1193
1218
|
"fr": "Votre message au support",
|
|
1194
1219
|
"en": "Your message to support",
|
|
1195
1220
|
"sw": "Ujumbe wako kwa usaidizi"
|
|
@@ -1209,6 +1234,7 @@ export const authTranslations = {
|
|
|
1209
1234
|
},
|
|
1210
1235
|
"Support.RECOVERY_NOTE_HELP": {
|
|
1211
1236
|
"de": "Beschreiben Sie kurz, warum Sie diese Anfrage genehmigen oder ablehnen.",
|
|
1237
|
+
"de_informal": "Beschreibe kurz, warum du diese Anfrage genehmigst oder ablehnst.",
|
|
1212
1238
|
"fr": "Décrivez brièvement pourquoi vous approuvez ou rejetez cette demande.",
|
|
1213
1239
|
"en": "Briefly describe why you are approving or rejecting this request.",
|
|
1214
1240
|
"sw": "Eleza kwa ufupi kwa nini unaidhinisha au kukataa ombi hili."
|
|
@@ -1239,6 +1265,7 @@ export const authTranslations = {
|
|
|
1239
1265
|
},
|
|
1240
1266
|
"Support.RECOVERY_REQUESTS_DESCRIPTION": {
|
|
1241
1267
|
"de": "Benutzer, die die MFA nicht abschliessen können, können hier eine Anfrage stellen. Sie können die Anfrage prüfen und anschliessend akzeptieren oder ablehnen.",
|
|
1268
|
+
"de_informal": "Benutzer, die die MFA nicht abschliessen können, können hier eine Anfrage stellen. Du kannst die Anfrage prüfen und anschliessend akzeptieren oder ablehnen.",
|
|
1242
1269
|
"fr": "Les utilisateurs qui ne peuvent pas terminer la MFA peuvent envoyer ici une demande. Vous pouvez examiner la demande puis l’accepter ou la refuser.",
|
|
1243
1270
|
"en": "Users who cannot complete MFA can submit a request here. You can review the request and then approve or reject it.",
|
|
1244
1271
|
"sw": "Watumiaji ambao hawawezi kukamilisha MFA wanaweza kutuma ombi hapa. Unaweza kukagua ombi na kisha kulikubali au kulikataa."
|
|
@@ -1348,12 +1375,14 @@ export const authTranslations = {
|
|
|
1348
1375
|
},
|
|
1349
1376
|
"Auth.MFA_IDENTIFIER_REQUIRED": {
|
|
1350
1377
|
"de": "Bitte geben Sie eine E-Mail-Adresse an.",
|
|
1378
|
+
"de_informal": "Bitte gib eine E-Mail-Adresse an.",
|
|
1351
1379
|
"fr": "Veuillez indiquer une adresse e-mail.",
|
|
1352
1380
|
"en": "Please provide an email address.",
|
|
1353
1381
|
"sw": "Tafadhali toa anwani ya barua pepe."
|
|
1354
1382
|
},
|
|
1355
1383
|
"Auth.MFA_HELP_REQUESTED": {
|
|
1356
1384
|
"de": "Falls ein Konto mit dieser E-Mail existiert, wurde Ihre Anfrage an den Support weitergeleitet.",
|
|
1385
|
+
"de_informal": "Falls ein Konto mit dieser E-Mail existiert, wurde deine Anfrage an den Support weitergeleitet.",
|
|
1357
1386
|
"fr": "Si un compte existe avec cette adresse e-mail, votre demande a été transmise au support.",
|
|
1358
1387
|
"en": "If an account with this email exists, your request has been forwarded to support.",
|
|
1359
1388
|
"sw": "Ikiwa akaunti iliyo na barua pepe hii ipo, ombi lako limetumwa kwa usaidizi."
|
|
@@ -1414,6 +1443,7 @@ export const authTranslations = {
|
|
|
1414
1443
|
},
|
|
1415
1444
|
"Auth.AUTH_FACTOR_HINT": {
|
|
1416
1445
|
"de": "Legen Sie fest, wie viele Authentifizierungsfaktoren mindestens für die Anmeldung erforderlich sind.",
|
|
1446
|
+
"de_informal": "Lege fest, wie viele Authentifizierungsfaktoren mindestens für die Anmeldung erforderlich sind.",
|
|
1417
1447
|
"fr": "Définissez le nombre minimal de facteurs d'authentification requis pour la connexion.",
|
|
1418
1448
|
"en": "Define the minimum number of authentication factors required for sign-in.",
|
|
1419
1449
|
"sw": "Weka idadi ya chini ya vipengele vya uthibitishaji vinavyohitajika ili kuingia."
|
|
@@ -1444,6 +1474,7 @@ export const authTranslations = {
|
|
|
1444
1474
|
},
|
|
1445
1475
|
"Auth.REGISTRATION_METHODS_HINT": {
|
|
1446
1476
|
"de": "Wählen Sie aus, welche Registrierungs- und Einladungswege für diese App aktiv sind.",
|
|
1477
|
+
"de_informal": "Wähle aus, welche Registrierungs- und Einladungswege für diese App aktiv sind.",
|
|
1447
1478
|
"fr": "Choisissez quels flux d'inscription et d'invitation sont actifs pour cette application.",
|
|
1448
1479
|
"en": "Choose which signup and invite flows are active for this app.",
|
|
1449
1480
|
"sw": "Chagua njia zipi za kujisajili na za mwaliko zimewashwa kwa programu hii."
|
|
@@ -1504,6 +1535,7 @@ export const authTranslations = {
|
|
|
1504
1535
|
},
|
|
1505
1536
|
"Auth.ALLOWED_EMAIL_DOMAINS_HINT": {
|
|
1506
1537
|
"de": "Eine Domain pro Zeile, z. B. example.org. Sie können das Feld vorübergehend leer lassen.",
|
|
1538
|
+
"de_informal": "Eine Domain pro Zeile, z. B. example.org. Du kannst das Feld vorübergehend leer lassen.",
|
|
1507
1539
|
"fr": "Un domaine par ligne, p. ex. example.org. Vous pouvez laisser ce champ vide temporairement.",
|
|
1508
1540
|
"en": "One domain per line, e.g. example.org. You can leave this empty temporarily.",
|
|
1509
1541
|
"sw": "Domeni moja kwa kila mstari, kwa mfano example.org. Unaweza kuiacha tupu kwa muda."
|
|
@@ -1516,6 +1548,7 @@ export const authTranslations = {
|
|
|
1516
1548
|
},
|
|
1517
1549
|
"Auth.SIGNUP_QR_MANAGER_HINT": {
|
|
1518
1550
|
"de": "Erzeugen und teilen Sie hier QR-Registrierungslinks.",
|
|
1551
|
+
"de_informal": "Erzeuge und teile hier QR-Registrierungslinks.",
|
|
1519
1552
|
"fr": "Générez et partagez ici des liens d'inscription QR.",
|
|
1520
1553
|
"en": "Generate and share QR signup links below.",
|
|
1521
1554
|
"sw": "Tengeneza na ushiriki viungo vya usajili wa QR hapa chini."
|
|
@@ -1528,6 +1561,7 @@ export const authTranslations = {
|
|
|
1528
1561
|
},
|
|
1529
1562
|
"Auth.SIGNUP_QR_VALIDITY_HINT": {
|
|
1530
1563
|
"de": "Legen Sie die Standard-Gültigkeit für neu erzeugte QR-Registrierungslinks fest.",
|
|
1564
|
+
"de_informal": "Lege die Standard-Gültigkeit für neu erzeugte QR-Registrierungslinks fest.",
|
|
1531
1565
|
"fr": "Définissez la validité par défaut des nouveaux liens d'inscription QR générés.",
|
|
1532
1566
|
"en": "Set the default validity for newly generated QR signup links.",
|
|
1533
1567
|
"sw": "Weka muda wa kawaida wa uhalali wa viungo vipya vya usajili wa QR."
|
|
@@ -1606,12 +1640,14 @@ export const authTranslations = {
|
|
|
1606
1640
|
},
|
|
1607
1641
|
"Auth.SIGNUP_QR_PDF_NOT_READY": {
|
|
1608
1642
|
"de": "Das QR-Bild ist noch nicht bereit. Bitte versuchen Sie es erneut.",
|
|
1643
|
+
"de_informal": "Das QR-Bild ist noch nicht bereit. Bitte versuche es erneut.",
|
|
1609
1644
|
"fr": "L'image QR n'est pas encore prête. Veuillez réessayer.",
|
|
1610
1645
|
"en": "The QR image is not ready yet. Please try again.",
|
|
1611
1646
|
"sw": "Picha ya QR bado haijawa tayari. Tafadhali jaribu tena."
|
|
1612
1647
|
},
|
|
1613
1648
|
"Auth.SIGNUP_QR_PDF_BLOCKED": {
|
|
1614
1649
|
"de": "Popup blockiert. Bitte erlauben Sie Popups, um die QR-Karte als PDF zu speichern.",
|
|
1650
|
+
"de_informal": "Popup blockiert. Bitte erlaube Popups, um die QR-Karte als PDF zu speichern.",
|
|
1615
1651
|
"fr": "Fenêtre contextuelle bloquée. Veuillez autoriser les popups pour enregistrer la carte QR en PDF.",
|
|
1616
1652
|
"en": "Popup blocked. Please allow popups to save the QR card as PDF.",
|
|
1617
1653
|
"sw": "Dirisha ibukizi limezuiwa. Tafadhali ruhusu madirisha ibukizi ili kuhifadhi kadi ya QR kama PDF."
|
|
@@ -1714,6 +1750,7 @@ export const authTranslations = {
|
|
|
1714
1750
|
},
|
|
1715
1751
|
"Account.ACCESS_CODES_HINT": {
|
|
1716
1752
|
"de": "Verwalten Sie Zugangscodes für die Selbstregistrierung.",
|
|
1753
|
+
"de_informal": "Verwalte Zugangscodes für die Selbstregistrierung.",
|
|
1717
1754
|
"fr": "Gérer les codes d'accès pour l'auto-inscription.",
|
|
1718
1755
|
"en": "Manage access codes for self-registration.",
|
|
1719
1756
|
"sw": "Dhibiti misimbo ya ufikiaji kwa kujisajili mwenyewe."
|
|
@@ -1882,6 +1919,12 @@ export const authTranslations = {
|
|
|
1882
1919
|
"en": "The user's role could not be updated.",
|
|
1883
1920
|
"sw": "Jukumu la mtumiaji halikuweza kusasishwa."
|
|
1884
1921
|
},
|
|
1922
|
+
"UserList.ROLE_UPDATE_SUCCESS": {
|
|
1923
|
+
"de": "Die Rolle wurde aktualisiert.",
|
|
1924
|
+
"fr": "Le rôle a été mis à jour.",
|
|
1925
|
+
"en": "Role updated.",
|
|
1926
|
+
"sw": "Jukumu limesasishwa."
|
|
1927
|
+
},
|
|
1885
1928
|
"Auth.USER_SUPPORT_UPDATE_FAILED": {
|
|
1886
1929
|
"de": "Der Support-Status des Benutzers konnte nicht aktualisiert werden.",
|
|
1887
1930
|
"fr": "Le statut de support de l'utilisateur n'a pas pu être mis à jour.",
|
|
@@ -1943,6 +1986,158 @@ export const authTranslations = {
|
|
|
1943
1986
|
"sw": "Nenosiri si sahihi."
|
|
1944
1987
|
},
|
|
1945
1988
|
|
|
1989
|
+
"UserList.DELETE_CONFIRM": {
|
|
1990
|
+
"de": "Möchten Sie diesen Benutzer wirklich löschen?",
|
|
1991
|
+
"de_informal": "Möchtest du diesen Benutzer wirklich löschen?",
|
|
1992
|
+
"fr": "Voulez-vous vraiment supprimer cet utilisateur ?",
|
|
1993
|
+
"en": "Are you sure you want to delete this user?",
|
|
1994
|
+
"sw": "Je, una uhakika unataka kumfuta mtumiaji huyu?"
|
|
1995
|
+
},
|
|
1996
|
+
"Common.OPERATION_FAILED": {
|
|
1997
|
+
"de": "Vorgang fehlgeschlagen.",
|
|
1998
|
+
"fr": "L’opération a échoué.",
|
|
1999
|
+
"en": "Operation failed.",
|
|
2000
|
+
"sw": "Uendeshaji umeshindwa."
|
|
2001
|
+
},
|
|
2002
|
+
"UserList.NEW": {
|
|
2003
|
+
"de": "Neu",
|
|
2004
|
+
"fr": "Nouveau",
|
|
2005
|
+
"en": "New",
|
|
2006
|
+
"sw": "Mpya"
|
|
2007
|
+
},
|
|
2008
|
+
"UserList.SUCCESSFUL_LOGIN": {
|
|
2009
|
+
"de": "Erfolgreiche Anmeldung",
|
|
2010
|
+
"fr": "Connexion réussie",
|
|
2011
|
+
"en": "Successful Login",
|
|
2012
|
+
"sw": "Kuingia kwa mafanikio"
|
|
2013
|
+
},
|
|
2014
|
+
"UserList.ROLE": {
|
|
2015
|
+
"de": "Rolle",
|
|
2016
|
+
"fr": "Rôle",
|
|
2017
|
+
"en": "Role",
|
|
2018
|
+
"sw": "Jukumu"
|
|
2019
|
+
},
|
|
2020
|
+
"Common.ACTIONS": {
|
|
2021
|
+
"de": "Aktionen",
|
|
2022
|
+
"fr": "Actions",
|
|
2023
|
+
"en": "Actions",
|
|
2024
|
+
"sw": "Vitendo"
|
|
2025
|
+
},
|
|
2026
|
+
"UserList.TITLE": {
|
|
2027
|
+
"de": "Alle Benutzer",
|
|
2028
|
+
"fr": "Tous les utilisateurs",
|
|
2029
|
+
"en": "All Users",
|
|
2030
|
+
"sw": "Watumiaji wote"
|
|
2031
|
+
},
|
|
2032
|
+
"Common.SEARCH": {
|
|
2033
|
+
"de": "Suche",
|
|
2034
|
+
"fr": "Rechercher",
|
|
2035
|
+
"en": "Search",
|
|
2036
|
+
"sw": "Tafuta"
|
|
2037
|
+
},
|
|
2038
|
+
"UserList.SEARCH_PLACEHOLDER": {
|
|
2039
|
+
"de": "Benutzer suchen...",
|
|
2040
|
+
"fr": "Rechercher des utilisateurs...",
|
|
2041
|
+
"en": "Search users...",
|
|
2042
|
+
"sw": "Tafuta watumiaji..."
|
|
2043
|
+
},
|
|
2044
|
+
"UserList.NO_USERS": {
|
|
2045
|
+
"de": "Keine Benutzer gefunden.",
|
|
2046
|
+
"fr": "Aucun utilisateur trouvé.",
|
|
2047
|
+
"en": "No users found.",
|
|
2048
|
+
"sw": "Hakuna watumiaji waliopatikana."
|
|
2049
|
+
},
|
|
2050
|
+
"UserList.ROWS_PER_PAGE": {
|
|
2051
|
+
"de": "Zeilen pro Seite:",
|
|
2052
|
+
"fr": "Lignes par page :",
|
|
2053
|
+
"en": "Rows per page:",
|
|
2054
|
+
"sw": "Safu kwa ukurasa:"
|
|
2055
|
+
},
|
|
2056
|
+
"Auth.INVITE_SENT_SUCCESS": {
|
|
2057
|
+
"de": "Einladung gesendet.",
|
|
2058
|
+
"fr": "Invitation envoyée.",
|
|
2059
|
+
"en": "Invitation sent.",
|
|
2060
|
+
"sw": "Mwaliko umetumwa."
|
|
2061
|
+
},
|
|
2062
|
+
"Auth.INVITE_TITLE": {
|
|
2063
|
+
"de": "Neuen Benutzer einladen",
|
|
2064
|
+
"fr": "Inviter un nouvel utilisateur",
|
|
2065
|
+
"en": "Invite a new user",
|
|
2066
|
+
"sw": "Mwalike mtumiaji mpya"
|
|
2067
|
+
},
|
|
2068
|
+
"Account.BULK_INVITE_NO_EMAILS": {
|
|
2069
|
+
"de": "Keine gültigen E-Mail-Adressen in der CSV-Datei gefunden.",
|
|
2070
|
+
"fr": "Aucune adresse e-mail valide trouvée dans le fichier CSV.",
|
|
2071
|
+
"en": "No valid email addresses found in CSV.",
|
|
2072
|
+
"sw": "Hakuna anwani halali za barua pepe zilizopatikana kwenye CSV."
|
|
2073
|
+
},
|
|
2074
|
+
"Account.BULK_INVITE_PARSE_FAILED": {
|
|
2075
|
+
"de": "CSV-Datei konnte nicht gelesen werden.",
|
|
2076
|
+
"fr": "Impossible de lire le fichier CSV.",
|
|
2077
|
+
"en": "Could not read CSV file.",
|
|
2078
|
+
"sw": "Imeshindikana kusoma faili ya CSV."
|
|
2079
|
+
},
|
|
2080
|
+
"Account.BULK_INVITE_DONE": {
|
|
2081
|
+
"de": "{{ok}} / {{total}} Einladungen gesendet.",
|
|
2082
|
+
"fr": "{{ok}} / {{total}} invitations envoyées.",
|
|
2083
|
+
"en": "{{ok}} / {{total}} invites sent.",
|
|
2084
|
+
"sw": "Mialiko {{ok}} / {{total}} imetumwa."
|
|
2085
|
+
},
|
|
2086
|
+
"Account.BULK_INVITE_TITLE": {
|
|
2087
|
+
"de": "Masseneinladung per CSV",
|
|
2088
|
+
"fr": "Invitations groupées par CSV",
|
|
2089
|
+
"en": "Bulk Invite via CSV",
|
|
2090
|
+
"sw": "Mialiko ya wingi kupitia CSV"
|
|
2091
|
+
},
|
|
2092
|
+
"Account.BULK_INVITE_HINT": {
|
|
2093
|
+
"de": "CSV-Datei mit E-Mail-Adressen hochladen. Header \"email\" wird unterstützt.",
|
|
2094
|
+
"fr": "Téléversez un fichier CSV contenant des adresses e-mail. L’en-tête \"email\" est pris en charge.",
|
|
2095
|
+
"en": "Upload a CSV file containing email addresses. Header \"email\" is supported.",
|
|
2096
|
+
"sw": "Pakia faili ya CSV yenye anwani za barua pepe. Kichwa \"email\" kinatumika."
|
|
2097
|
+
},
|
|
2098
|
+
"Account.BULK_INVITE_UPLOAD": {
|
|
2099
|
+
"de": "CSV hochladen",
|
|
2100
|
+
"fr": "Téléverser un CSV",
|
|
2101
|
+
"en": "Upload CSV",
|
|
2102
|
+
"sw": "Pakia CSV"
|
|
2103
|
+
},
|
|
2104
|
+
"Account.BULK_INVITE_SEND": {
|
|
2105
|
+
"de": "Einladungen senden",
|
|
2106
|
+
"fr": "Envoyer les invitations",
|
|
2107
|
+
"en": "Send Invites",
|
|
2108
|
+
"sw": "Tuma mialiko"
|
|
2109
|
+
},
|
|
2110
|
+
"Account.BULK_INVITE_COUNT": {
|
|
2111
|
+
"de": "{{count}} E-Mail-Adressen geladen",
|
|
2112
|
+
"fr": "{{count}} adresses e-mail chargées",
|
|
2113
|
+
"en": "{{count}} emails loaded",
|
|
2114
|
+
"sw": "Anwani {{count}} za barua pepe zimepakiwa"
|
|
2115
|
+
},
|
|
2116
|
+
"Common.STATUS": {
|
|
2117
|
+
"de": "Status",
|
|
2118
|
+
"fr": "Statut",
|
|
2119
|
+
"en": "Status",
|
|
2120
|
+
"sw": "Hali"
|
|
2121
|
+
},
|
|
2122
|
+
"Common.DETAILS": {
|
|
2123
|
+
"de": "Details",
|
|
2124
|
+
"fr": "Détails",
|
|
2125
|
+
"en": "Details",
|
|
2126
|
+
"sw": "Maelezo"
|
|
2127
|
+
},
|
|
2128
|
+
"Account.BULK_INVITE_PROGRESS": {
|
|
2129
|
+
"de": "{{done}} / {{total}} verarbeitet",
|
|
2130
|
+
"fr": "{{done}} / {{total}} traitées",
|
|
2131
|
+
"en": "{{done}} / {{total}} processed",
|
|
2132
|
+
"sw": "{{done}} / {{total}} zimechakatwa"
|
|
2133
|
+
},
|
|
2134
|
+
"Account.BULK_INVITE_SUCCESS_COUNT": {
|
|
2135
|
+
"de": "{{count}} erfolgreich",
|
|
2136
|
+
"fr": "{{count}} réussies",
|
|
2137
|
+
"en": "{{count}} successful",
|
|
2138
|
+
"sw": "{{count}} zimefaulu"
|
|
2139
|
+
},
|
|
2140
|
+
|
|
1946
2141
|
// Raw backend auth error codes (allauth-headless + Django password validators).
|
|
1947
2142
|
// Keyed by the exact code string so the existing `t(err.code)` call sites resolve
|
|
1948
2143
|
// as a drop-in, without any component change. See AUTH-2.
|
|
@@ -1966,6 +2161,7 @@ export const authTranslations = {
|
|
|
1966
2161
|
},
|
|
1967
2162
|
"too_many_login_attempts": {
|
|
1968
2163
|
"de": "Zu viele fehlgeschlagene Anmeldeversuche. Bitte versuchen Sie es später erneut.",
|
|
2164
|
+
"de_informal": "Zu viele fehlgeschlagene Anmeldeversuche. Bitte versuche es später erneut.",
|
|
1969
2165
|
"fr": "Trop de tentatives de connexion échouées. Veuillez réessayer plus tard.",
|
|
1970
2166
|
"en": "Too many failed login attempts. Please try again later.",
|
|
1971
2167
|
"sw": "Majaribio mengi ya kuingia yameshindwa. Tafadhali jaribu tena baadaye."
|
|
@@ -2008,6 +2204,7 @@ export const authTranslations = {
|
|
|
2008
2204
|
},
|
|
2009
2205
|
"password_too_similar": {
|
|
2010
2206
|
"de": "Das Passwort ist Ihren persönlichen Daten zu ähnlich.",
|
|
2207
|
+
"de_informal": "Das Passwort ist deinen persönlichen Daten zu ähnlich.",
|
|
2011
2208
|
"fr": "Le mot de passe est trop similaire à vos informations personnelles.",
|
|
2012
2209
|
"en": "The password is too similar to your personal information.",
|
|
2013
2210
|
"sw": "Nenosiri linafanana sana na taarifa zako binafsi."
|
|
@@ -15,3 +15,13 @@ export const uiCoreTranslations = {
|
|
|
15
15
|
...sectionNavTranslations,
|
|
16
16
|
...userMenuTranslations,
|
|
17
17
|
};
|
|
18
|
+
|
|
19
|
+
export function createUiCoreTranslations({ germanVariant = 'formal' } = {}) {
|
|
20
|
+
return Object.fromEntries(
|
|
21
|
+
Object.entries(uiCoreTranslations).map(([key, value]) => {
|
|
22
|
+
const { de_informal, ...rest } = value;
|
|
23
|
+
if (germanVariant === 'informal' && de_informal) return [key, { ...rest, de: de_informal }];
|
|
24
|
+
return [key, rest];
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
}
|
package/src/index.js
CHANGED
|
@@ -63,6 +63,7 @@ export { QrSignupManager } from './components/QrSignupManager';
|
|
|
63
63
|
export { MobileBottomNav } from './components/MobileBottomNav';
|
|
64
64
|
export { default as StatTile } from './components/StatTile';
|
|
65
65
|
export { default as SoftChip } from './components/SoftChip';
|
|
66
|
+
export { default as EmptyState } from './components/EmptyState';
|
|
66
67
|
|
|
67
68
|
// --- 6. Charts ---
|
|
68
69
|
export { ChartFrame } from './components/charts/ChartFrame';
|
|
@@ -140,4 +141,4 @@ export { notificationsTranslations } from './i18n/notificationsTranslations';
|
|
|
140
141
|
export { onboardingTranslations } from './i18n/onboardingTranslations';
|
|
141
142
|
export { chartsTranslations } from './i18n/chartsTranslations';
|
|
142
143
|
export { messagingTranslations } from './i18n/messagingTranslations';
|
|
143
|
-
export { uiCoreTranslations } from './i18n/uiCoreTranslations';
|
|
144
|
+
export { createUiCoreTranslations, uiCoreTranslations } from './i18n/uiCoreTranslations';
|
|
@@ -11,6 +11,8 @@ import { compressImageForUpload } from './composerImageCompression';
|
|
|
11
11
|
import { extractApiErrorMessage, useMessaging } from './MessagingProvider';
|
|
12
12
|
|
|
13
13
|
export const COMPOSER_EMOJI_BUTTON_SX = { fontSize: '1.15rem' };
|
|
14
|
+
export const COMPOSER_MESSAGE_FIELD_SX = { flex: 1, minWidth: 120 };
|
|
15
|
+
export const COMPOSER_SEND_BUTTON_SX = { flexShrink: 0 };
|
|
14
16
|
|
|
15
17
|
// A wider, Composer-only set (MSG-6i) -- deliberately separate from
|
|
16
18
|
// ReactionBar's own QUICK_EMOJIS (5, curated for fast reactions). Mobile
|
|
@@ -136,14 +138,16 @@ export function Composer({ conversationId, conversation, replyTarget = null, onR
|
|
|
136
138
|
</Stack>)}
|
|
137
139
|
</Stack>}
|
|
138
140
|
{files.length > 0 && <Typography variant="caption">{t('MessagingComposer.FILES_SELECTED', { count: files.length })}</Typography>}
|
|
139
|
-
<Stack direction="row" spacing={1}
|
|
141
|
+
<Stack direction="row" spacing={1}>
|
|
140
142
|
<input ref={inputRef} hidden type="file" multiple onChange={(event) => setFiles(Array.from(event.target.files || []))} />
|
|
141
143
|
<IconButton type="button" aria-label={t('MessagingComposer.ADD_ATTACHMENT')} onClick={() => inputRef.current?.click()}><AttachFileIcon /></IconButton>
|
|
142
144
|
<IconButton type="button" aria-label={t('MessagingComposer.ADD_EMOJI')} onClick={(event) => setEmojiAnchor(event.currentTarget)}><EmojiEmotionsOutlinedIcon /></IconButton>
|
|
143
145
|
<IconButton type="button" aria-label={t('MessagingPoll.CREATE')} disabled={pollOpen || files.some(isImage)} onClick={() => setPollOpen(true)}><PollOutlinedIcon /></IconButton>
|
|
144
146
|
{canAnnounce && <IconButton type="button" aria-label={t('MessagingAnnouncement.CREATE')} onClick={() => setAnnouncementOpen(true)}><CampaignOutlinedIcon /></IconButton>}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
</Stack>
|
|
148
|
+
<Stack direction="row" spacing={1} alignItems="flex-end">
|
|
149
|
+
<TextField inputRef={messageInputRef} fullWidth multiline minRows={2} value={body} onChange={(event) => setBody(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) { event.preventDefault(); submit(); } }} label={t('MessagingComposer.MESSAGE')} disabled={sending} sx={COMPOSER_MESSAGE_FIELD_SX} />
|
|
150
|
+
<Button type="submit" variant="contained" disabled={sending || (!body.trim() && !files.length)} sx={COMPOSER_SEND_BUTTON_SX}>{t('MessagingComposer.SEND')}</Button>
|
|
147
151
|
</Stack>
|
|
148
152
|
{sending && files.length > 0 && <Stack spacing={0.5} role="status">
|
|
149
153
|
<Stack direction="row" spacing={1} alignItems="center">
|
|
@@ -7,6 +7,7 @@ import { AttachmentList } from './AttachmentList';
|
|
|
7
7
|
import { extractApiErrorMessage, useOptionalMessaging } from './MessagingProvider';
|
|
8
8
|
import { ReactionBar } from './ReactionBar';
|
|
9
9
|
import { PollCard } from './PollCard';
|
|
10
|
+
import { linkifyText } from './linkifyText';
|
|
10
11
|
|
|
11
12
|
export const MESSAGE_BUBBLE_ROOT_SX = {
|
|
12
13
|
position: 'relative',
|
|
@@ -146,7 +147,7 @@ export function MessageBubble({ message, replyTo, conversation, onReply, onJumpT
|
|
|
146
147
|
{!deleted && message.kind === 'announcement' && message.title && <Typography variant="subtitle1" fontWeight={700}>{message.title}</Typography>}
|
|
147
148
|
{!deleted && message.kind === 'announcement' && message.link_target && onAnnouncementLink && <Button type="button" size="small" onClick={() => onAnnouncementLink(message.link_target)}>{t('MessagingAnnouncement.OPEN_LINK')}</Button>}
|
|
148
149
|
{error && <Alert severity="error" role="alert">{error}</Alert>}
|
|
149
|
-
{editing ? <Stack spacing={0.75}><TextField label={t('MessagingActions.EDIT')} value={draft} onChange={(event) => setDraft(event.target.value)} multiline minRows={2} autoFocus disabled={saving} /><Stack direction="row" spacing={1}><Button type="button" onClick={saveEdit} disabled={saving}>{t('MessagingActions.SAVE')}</Button><Button type="button" onClick={() => setEditing(false)} disabled={saving}>{t('MessagingActions.CANCEL')}</Button></Stack></Stack> : <Typography sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{deleted ? t('MessagingThread.DELETED') : (message.kind === 'announcement' ? message.body : (message.body || message.title)) || ''}</Typography>}
|
|
150
|
+
{editing ? <Stack spacing={0.75}><TextField label={t('MessagingActions.EDIT')} value={draft} onChange={(event) => setDraft(event.target.value)} multiline minRows={2} autoFocus disabled={saving} /><Stack direction="row" spacing={1}><Button type="button" onClick={saveEdit} disabled={saving}>{t('MessagingActions.SAVE')}</Button><Button type="button" onClick={() => setEditing(false)} disabled={saving}>{t('MessagingActions.CANCEL')}</Button></Stack></Stack> : <Typography sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{deleted ? t('MessagingThread.DELETED') : linkifyText((message.kind === 'announcement' ? message.body : (message.body || message.title)) || '')}</Typography>}
|
|
150
151
|
{!deleted && <Stack spacing={0.75}>
|
|
151
152
|
{attachments.length > 0 && <AttachmentList attachments={attachments} />}
|
|
152
153
|
{message.poll && <PollCard message={message} />}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import DoneAllOutlinedIcon from '@mui/icons-material/DoneAllOutlined';
|
|
2
2
|
import DoneOutlinedIcon from '@mui/icons-material/DoneOutlined';
|
|
3
|
-
import { ButtonBase, List, ListItem, Popover, Typography } from '@mui/material';
|
|
3
|
+
import { ButtonBase, List, ListItem, Popover, Tooltip, Typography } from '@mui/material';
|
|
4
4
|
import { useEffect, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
@@ -48,6 +48,7 @@ export function ReadTicks({ messageId, conversation }) {
|
|
|
48
48
|
const hasCounts = typeof status.read_count === 'number' && typeof status.recipient_count === 'number';
|
|
49
49
|
|
|
50
50
|
if (!hasCounts && conversation?.kind !== 'direct') return null;
|
|
51
|
+
if (hasCounts && status.recipient_count === 0) return null;
|
|
51
52
|
|
|
52
53
|
// The server never returns DM detail; enforce the same carve-out defensively.
|
|
53
54
|
const detail = conversation?.kind === 'direct' ? null : recipientDetail;
|
|
@@ -71,8 +72,8 @@ export function ReadTicks({ messageId, conversation }) {
|
|
|
71
72
|
// next read-status fetch, not live from a read_state frame.
|
|
72
73
|
const label = t('MessagingReadTicks.READ_RATIO', { read: status.read_count, total: status.recipient_count });
|
|
73
74
|
const ratio = <Typography component="span" variant="caption" color="inherit">{status.read_count}/{status.recipient_count}</Typography>;
|
|
74
|
-
if (!recipients.length) return <span role="img" aria-label={label}>{ratio}</span>;
|
|
75
|
-
return <><ButtonBase aria-label={label} aria-haspopup="dialog" aria-expanded={Boolean(anchorEl)} onClick={(event) => setAnchorEl(event.currentTarget)} sx={{ display: 'inline-flex' }}>{ratio}</ButtonBase><Popover open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={() => setAnchorEl(null)}><List aria-label={t('MessagingReadTicks.RECIPIENTS')}>{recipients.map((recipient) => <ListItem key={recipient}>{recipient}</ListItem>)}</List></Popover></>;
|
|
75
|
+
if (!recipients.length) return <Tooltip title={label} describeChild><span role="img" aria-label={label}>{ratio}</span></Tooltip>;
|
|
76
|
+
return <><Tooltip title={label} describeChild><ButtonBase aria-label={label} aria-haspopup="dialog" aria-expanded={Boolean(anchorEl)} onClick={(event) => setAnchorEl(event.currentTarget)} sx={{ display: 'inline-flex' }}>{ratio}</ButtonBase></Tooltip><Popover open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={() => setAnchorEl(null)}><List aria-label={t('MessagingReadTicks.RECIPIENTS')}>{recipients.map((recipient) => <ListItem key={recipient}>{recipient}</ListItem>)}</List></Popover></>;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const label = allRead ? t('MessagingReadTicks.ALL_READ') : t('MessagingReadTicks.SENT');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from '@mui/material';
|
|
3
|
+
|
|
4
|
+
const HTTP_URL_PATTERN = /(https?:\/\/[^\s<>"']+)/g;
|
|
5
|
+
|
|
6
|
+
function splitTrailingPunctuation(rawUrl) {
|
|
7
|
+
let url = rawUrl;
|
|
8
|
+
let trailing = '';
|
|
9
|
+
while (/[.,!?]$/.test(url)) {
|
|
10
|
+
trailing = `${url.slice(-1)}${trailing}`;
|
|
11
|
+
url = url.slice(0, -1);
|
|
12
|
+
}
|
|
13
|
+
const openingParentheses = (url.match(/\(/g) || []).length;
|
|
14
|
+
const closingParentheses = (url.match(/\)/g) || []).length;
|
|
15
|
+
if (closingParentheses > openingParentheses) {
|
|
16
|
+
trailing = `)${trailing}`;
|
|
17
|
+
url = url.slice(0, -1);
|
|
18
|
+
}
|
|
19
|
+
return { url, trailing };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function linkifyText(text) {
|
|
23
|
+
const value = String(text ?? '');
|
|
24
|
+
const parts = [];
|
|
25
|
+
let lastIndex = 0;
|
|
26
|
+
for (const match of value.matchAll(HTTP_URL_PATTERN)) {
|
|
27
|
+
const rawUrl = match[0];
|
|
28
|
+
const start = match.index ?? 0;
|
|
29
|
+
if (start > lastIndex) parts.push(value.slice(lastIndex, start));
|
|
30
|
+
const { url, trailing } = splitTrailingPunctuation(rawUrl);
|
|
31
|
+
parts.push(<Link key={`url-${start}`} href={url} target="_blank" rel="noopener noreferrer">{url}</Link>);
|
|
32
|
+
if (trailing) parts.push(trailing);
|
|
33
|
+
lastIndex = start + rawUrl.length;
|
|
34
|
+
}
|
|
35
|
+
if (lastIndex < value.length) parts.push(value.slice(lastIndex));
|
|
36
|
+
return parts.length ? parts : [value];
|
|
37
|
+
}
|