Merge branch 'develop' into andybalaam/fix-receipt-flakes
This commit is contained in:
commit
d37790c460
59 changed files with 371 additions and 283 deletions
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"jsx": "react",
|
||||
"lib": ["es2020", "dom", "dom.iterable"],
|
||||
"lib": ["es2021", "dom", "dom.iterable"],
|
||||
"types": ["cypress", "cypress-axe", "@percy/cypress", "@testing-library/cypress"],
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
|
@ -49,6 +49,7 @@ import { SettingLevel } from "./settings/SettingLevel";
|
|||
import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import PlatformPeg from "./PlatformPeg";
|
||||
import { formatList } from "./utils/FormattingUtils";
|
||||
|
||||
export interface IMatrixClientCreds {
|
||||
homeserverUrl: string;
|
||||
|
@ -356,15 +357,9 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
|||
if (name) return name;
|
||||
|
||||
if (names.length === 2 && count === 2) {
|
||||
return _t("user1_and_user2", {
|
||||
user1: names[0],
|
||||
user2: names[1],
|
||||
});
|
||||
return formatList(names);
|
||||
}
|
||||
return _t("user_and_n_others", {
|
||||
user: names[0],
|
||||
count: count - 1,
|
||||
});
|
||||
return formatList(names, 1);
|
||||
}
|
||||
|
||||
private inviteeNamesToRoomName(names: string[], count: number): string {
|
||||
|
|
|
@ -20,7 +20,7 @@ import React, { ComponentProps, ReactNode } from "react";
|
|||
import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import { isValid3pidInvite } from "../../../RoomInvite";
|
||||
import GenericEventListSummary from "./GenericEventListSummary";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
|
@ -131,7 +131,7 @@ export default class EventListSummary extends React.Component<
|
|||
return EventListSummary.getDescriptionForTransition(t.transitionType, userNames.length, t.repeats);
|
||||
});
|
||||
|
||||
const desc = formatCommaSeparatedList(descs);
|
||||
const desc = formatList(descs);
|
||||
|
||||
return _t("timeline|summary|format", { nameList, transitionList: desc });
|
||||
});
|
||||
|
@ -150,7 +150,7 @@ export default class EventListSummary extends React.Component<
|
|||
* included before "and [n] others".
|
||||
*/
|
||||
private renderNameList(users: string[]): string {
|
||||
return formatCommaSeparatedList(users, this.props.summaryLength);
|
||||
return formatList(users, this.props.summaryLength);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,7 +33,7 @@ import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollRe
|
|||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||
|
@ -100,7 +100,7 @@ export function findTopAnswer(pollEvent: MatrixEvent, voteRelations: Relations):
|
|||
|
||||
const bestAnswerTexts = bestAnswerIds.map(findAnswerText);
|
||||
|
||||
return formatCommaSeparatedList(bestAnswerTexts, 3);
|
||||
return formatList(bestAnswerTexts, 3);
|
||||
}
|
||||
|
||||
export function isPollEnded(pollEvent: MatrixEvent, matrixClient: MatrixClient): boolean {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
@ -123,7 +123,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
|||
undefined;
|
||||
}
|
||||
|
||||
const reactors = formatCommaSeparatedList(senders, 6);
|
||||
const reactors = formatList(senders, 6);
|
||||
if (content) {
|
||||
label = _t("timeline|reactions|label", {
|
||||
reactors,
|
||||
|
|
|
@ -19,7 +19,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import { unicodeToShortcode } from "../../../HtmlUtils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
|
||||
|
@ -66,7 +66,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
|
|||
},
|
||||
{
|
||||
reactors: () => {
|
||||
return <div className="mx_Tooltip_title">{formatCommaSeparatedList(senders, 6)}</div>;
|
||||
return <div className="mx_Tooltip_title">{formatList(senders, 6)}</div>;
|
||||
},
|
||||
reactedWith: (sub) => {
|
||||
if (!shortName) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import ErrorDialog from "../dialogs/ErrorDialog";
|
|||
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
|
||||
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
|
@ -82,58 +83,46 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
|
|||
{_t("action|view")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
let names: string = knockMembers
|
||||
.slice(0, 2)
|
||||
.map((knockMember) => knockMember.name)
|
||||
.join(", ");
|
||||
let names = formatList(
|
||||
knockMembers.map((knockMember) => knockMember.name),
|
||||
3,
|
||||
true,
|
||||
);
|
||||
let link: ReactNode = null;
|
||||
switch (knockMembersCount) {
|
||||
case 1: {
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canKick || disabled}
|
||||
kind="icon_primary_outline"
|
||||
onClick={() => handleDeny(knockMembers[0].userId)}
|
||||
title={_t("action|deny")}
|
||||
>
|
||||
<XIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canInvite || disabled}
|
||||
kind="icon_primary"
|
||||
onClick={() => handleApprove(knockMembers[0].userId)}
|
||||
title={_t("action|approve")}
|
||||
>
|
||||
<CheckIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
|
||||
link = knockMembers[0].events.member?.getContent().reason && (
|
||||
if (knockMembersCount === 1) {
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_link"
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={handleOpenRoomSettings}
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canKick || disabled}
|
||||
kind="icon_primary_outline"
|
||||
onClick={() => handleDeny(knockMembers[0].userId)}
|
||||
title={_t("action|deny")}
|
||||
>
|
||||
{_t("action|view_message")}
|
||||
<XIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canInvite || disabled}
|
||||
kind="icon_primary"
|
||||
onClick={() => handleApprove(knockMembers[0].userId)}
|
||||
title={_t("action|approve")}
|
||||
>
|
||||
<CheckIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
|
||||
link = knockMembers[0].events.member?.getContent().reason && (
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_link"
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={handleOpenRoomSettings}
|
||||
>
|
||||
{_t("action|view_message")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -95,9 +95,9 @@
|
|||
"Delete widget": "Изтрий приспособлението",
|
||||
"Create new room": "Създай нова стая",
|
||||
"Home": "Начална страница",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s и %(count)s други",
|
||||
"one": "%(items)s и още един"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> и %(count)s други",
|
||||
"one": "<Items/> и още един"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
|
||||
"collapse": "свий",
|
||||
|
|
|
@ -99,9 +99,9 @@
|
|||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "La supressió d'un giny l'elimina per a tots els usuaris d'aquesta sala. Esteu segur que voleu eliminar aquest giny?",
|
||||
"Delete widget": "Suprimeix el giny",
|
||||
"Home": "Inici",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s i %(count)s altres",
|
||||
"one": "%(items)s i un altre"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> i %(count)s altres",
|
||||
"one": "<Items/> i un altre"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s i %(lastItem)s",
|
||||
"collapse": "col·lapsa",
|
||||
|
|
|
@ -132,9 +132,9 @@
|
|||
"Invalid file%(extra)s": "Neplatný soubor%(extra)s",
|
||||
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Budete přesměrováni na stránku třetí strany k ověření svého účtu pro používání s %(integrationsUrl)s. Chcete pokračovat?",
|
||||
"Something went wrong!": "Něco se nepodařilo!",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s a %(count)s další",
|
||||
"one": "%(items)s a jeden další"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> a %(count)s další",
|
||||
"one": "<Items/> a jeden další"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s a také %(lastItem)s",
|
||||
"And %(count)s more...": {
|
||||
|
|
|
@ -73,9 +73,9 @@
|
|||
"Preparing to send logs": "Forbereder afsendelse af logfiler",
|
||||
"Permission Required": "Tilladelse påkrævet",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s. %(monthName)s %(fullYear)s",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s og %(count)s andre",
|
||||
"one": "%(items)s og en anden"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> og %(count)s andre",
|
||||
"one": "<Items/> og en anden"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
|
||||
"Please contact your homeserver administrator.": "Kontakt venligst din homeserver administrator.",
|
||||
|
|
|
@ -145,9 +145,9 @@
|
|||
},
|
||||
"Delete Widget": "Widget löschen",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Das Löschen des Widgets entfernt es für alle in diesem Raum. Wirklich löschen?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s und %(count)s andere",
|
||||
"one": "%(items)s und ein weiteres Raummitglied"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> und %(count)s andere",
|
||||
"one": "<Items/> und ein weiteres Raummitglied"
|
||||
},
|
||||
"Restricted": "Eingeschränkt",
|
||||
"%(duration)ss": "%(duration)ss",
|
||||
|
|
|
@ -167,9 +167,9 @@
|
|||
"Your homeserver has exceeded its user limit.": "Ο διακομιστής σας ξεπέρασε το όριο χρηστών.",
|
||||
"Use app": "Χρησιμοποιήστε την εφαρμογή",
|
||||
"Use app for a better experience": "Χρησιμοποιήστε την εφαρμογή για καλύτερη εμπειρία",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s και ένα ακόμα",
|
||||
"other": "%(items)s και %(count)s άλλα"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> και ένα ακόμα",
|
||||
"other": "<Items/> και %(count)s άλλα"
|
||||
},
|
||||
"Ask this user to verify their session, or manually verify it below.": "Ζητήστε από αυτόν τον χρήστη να επιβεβαιώσει την συνεδρία του, ή επιβεβαιώστε την χειροκίνητα παρακάτω.",
|
||||
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "Ο %(name)s (%(userId)s) συνδέθηκε σε μία νέα συνεδρία χωρίς να την επιβεβαιώσει:",
|
||||
|
|
|
@ -764,15 +764,10 @@
|
|||
"error_database_closed_title": "Database unexpectedly closed",
|
||||
"error_database_closed_description": "This may be caused by having the app open in multiple tabs or due to clearing browser data.",
|
||||
"empty_room": "Empty room",
|
||||
"user1_and_user2": "%(user1)s and %(user2)s",
|
||||
"user_and_n_others": {
|
||||
"other": "%(user)s and %(count)s others",
|
||||
"one": "%(user)s and 1 other"
|
||||
},
|
||||
"inviting_user1_and_user2": "Inviting %(user1)s and %(user2)s",
|
||||
"inviting_user_and_n_others": {
|
||||
"other": "Inviting %(user)s and %(count)s others",
|
||||
"one": "Inviting %(user)s and 1 other"
|
||||
"one": "Inviting %(user)s and one other"
|
||||
},
|
||||
"empty_room_was_name": "Empty room (was %(oldName)s)",
|
||||
"notifier": {
|
||||
|
@ -1403,20 +1398,14 @@
|
|||
"mixed_content": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||
"tls": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests."
|
||||
},
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s and %(count)s others",
|
||||
"one": "%(items)s and one other"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> and %(count)s others",
|
||||
"one": "<Items/> and one other"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||
"%(space1Name)s and %(space2Name)s": "%(space1Name)s and %(space2Name)s",
|
||||
"in_space1_and_space2": "In spaces %(space1Name)s and %(space2Name)s.",
|
||||
"%(spaceName)s and %(count)s others": {
|
||||
"other": "%(spaceName)s and %(count)s others",
|
||||
"one": "%(spaceName)s and %(count)s other"
|
||||
},
|
||||
"in_space_and_n_other_spaces": {
|
||||
"other": "In %(spaceName)s and %(count)s other spaces.",
|
||||
"one": "In %(spaceName)s and %(count)s other space."
|
||||
"one": "In %(spaceName)s and one other space."
|
||||
},
|
||||
"in_space": "In %(spaceName)s.",
|
||||
"name_and_id": "%(name)s (%(userId)s)",
|
||||
|
@ -2571,11 +2560,6 @@
|
|||
"Public space": "Public space",
|
||||
"Private space": "Private space",
|
||||
"Private room": "Private room",
|
||||
"%(names)s and %(name)s": "%(names)s and %(name)s",
|
||||
"%(names)s and %(count)s others": {
|
||||
"other": "%(names)s and %(count)s others",
|
||||
"one": "%(names)s and %(count)s other"
|
||||
},
|
||||
"%(count)s people asking to join": {
|
||||
"other": "%(count)s people asking to join",
|
||||
"one": "Asking to join"
|
||||
|
|
|
@ -91,9 +91,9 @@
|
|||
"Delete widget": "Forigi fenestraĵon",
|
||||
"Create new room": "Krei novan ĉambron",
|
||||
"Home": "Hejmo",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s kaj %(count)s aliaj",
|
||||
"one": "%(items)s kaj unu alia"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> kaj %(count)s aliaj",
|
||||
"one": "<Items/> kaj unu alia"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s kaj %(lastItem)s",
|
||||
"collapse": "maletendi",
|
||||
|
|
|
@ -177,9 +177,9 @@
|
|||
"Delete Widget": "Eliminar accesorio",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Al borrar un accesorio, este se elimina para todos usuarios de la sala. ¿Estás seguro?",
|
||||
"Popout widget": "Abrir accesorio en una ventana emergente",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s y otros %(count)s",
|
||||
"one": "%(items)s y otro más"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> y otros %(count)s",
|
||||
"one": "<Items/> y otro más"
|
||||
},
|
||||
"collapse": "encoger",
|
||||
"expand": "desplegar",
|
||||
|
|
|
@ -442,9 +442,9 @@
|
|||
"Identity server URL does not appear to be a valid identity server": "Isikutuvastusserveri aadress ei tundu viitama kehtivale isikutuvastusserverile",
|
||||
"Looks good!": "Tundub õige!",
|
||||
"Failed to re-authenticate due to a homeserver problem": "Uuesti autentimine ei õnnestunud koduserveri vea tõttu",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s ja %(count)s muud",
|
||||
"one": "%(items)s ja üks muu"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> ja %(count)s muud",
|
||||
"one": "<Items/> ja üks muu"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s ja %(lastItem)s",
|
||||
"Please contact your homeserver administrator.": "Palun võta ühendust koduserveri haldajaga.",
|
||||
|
|
|
@ -153,9 +153,9 @@
|
|||
},
|
||||
"Delete Widget": "Ezabatu trepeta",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Trepeta ezabatzean gelako kide guztientzat kentzen da. Ziur trepeta ezabatu nahi duzula?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s eta beste %(count)s",
|
||||
"one": "%(items)s eta beste bat"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> eta beste %(count)s",
|
||||
"one": "<Items/> eta beste bat"
|
||||
},
|
||||
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Ezin izango duzu hau aldatu zure burua mailaz jaisten ari zarelako, zu bazara gelan baimenak dituen azken erabiltzailea ezin izango dira baimenak berreskuratu.",
|
||||
"Replying": "Erantzuten",
|
||||
|
|
|
@ -1011,10 +1011,9 @@
|
|||
"IRC display name width": "عرض نمایش نامهای IRC",
|
||||
"This event could not be displayed": "امکان نمایش این رخداد وجود ندارد",
|
||||
"Edit message": "ویرایش پیام",
|
||||
"%(items)s and %(lastItem)s": "%(items)s و %(lastItem)s",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s و یکی دیگر",
|
||||
"other": "%(items)s و %(count)s دیگر"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> و یکی دیگر",
|
||||
"other": "<Items/> و %(count)s دیگر"
|
||||
},
|
||||
"Clear personal data": "پاککردن دادههای شخصی",
|
||||
"Verify your identity to access encrypted messages and prove your identity to others.": "با تائید هویت خود به پیامهای رمزشده دسترسی یافته و هویت خود را به دیگران ثابت میکنید.",
|
||||
|
|
|
@ -151,9 +151,9 @@
|
|||
"expand": "laajenna",
|
||||
"collapse": "supista",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Sovelman poistaminen poistaa sen kaikilta huoneen käyttäjiltä. Haluatko varmasti poistaa tämän sovelman?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s ja %(count)s muuta",
|
||||
"one": "%(items)s ja yksi muu"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> ja %(count)s muuta",
|
||||
"one": "<Items/> ja yksi muu"
|
||||
},
|
||||
"Sunday": "Sunnuntai",
|
||||
"Notification targets": "Ilmoituksen kohteet",
|
||||
|
|
|
@ -142,9 +142,9 @@
|
|||
"Jump to read receipt": "Aller à l’accusé de lecture",
|
||||
"Delete Widget": "Supprimer le widget",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Supprimer un widget le supprime pour tous les utilisateurs du salon. Voulez-vous vraiment supprimer ce widget ?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s et %(count)s autres",
|
||||
"one": "%(items)s et un autre"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> et %(count)s autres",
|
||||
"one": "<Items/> et un autre"
|
||||
},
|
||||
"And %(count)s more...": {
|
||||
"other": "Et %(count)s autres…"
|
||||
|
|
|
@ -94,9 +94,9 @@
|
|||
"Delete widget": "Eliminar widget",
|
||||
"Create new room": "Crear unha nova sala",
|
||||
"Home": "Inicio",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s e %(count)s outras",
|
||||
"one": "%(items)s e outra máis"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> e %(count)s outras",
|
||||
"one": "<Items/> e outra máis"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
|
||||
"collapse": "comprimir",
|
||||
|
|
|
@ -307,10 +307,9 @@
|
|||
"Vietnam": "וייטנאם",
|
||||
"Venezuela": "ונצואלה",
|
||||
"Vatican City": "ותיקן",
|
||||
"%(items)s and %(lastItem)s": "%(items)s ו%(lastItem)s אחרון",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s ועוד אחד אחר",
|
||||
"other": "%(items)s ו%(count)s אחרים"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> ועוד אחד אחר",
|
||||
"other": "<Items/> ו%(count)s אחרים"
|
||||
},
|
||||
"Not Trusted": "לא אמין",
|
||||
"Ask this user to verify their session, or manually verify it below.": "בקש ממשתמש זה לאמת את ההתחברות שלו, או לאמת אותה באופן ידני למטה.",
|
||||
|
|
|
@ -145,9 +145,9 @@
|
|||
},
|
||||
"Delete Widget": "Kisalkalmazás törlése",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "A kisalkalmazás törlése minden felhasználót érint a szobában. Biztos, hogy törli a kisalkalmazást?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s és még %(count)s másik",
|
||||
"one": "%(items)s és még egy másik"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> és még %(count)s másik",
|
||||
"one": "<Items/> és még egy másik"
|
||||
},
|
||||
"Restricted": "Korlátozott",
|
||||
"%(duration)ss": "%(duration)s mp",
|
||||
|
|
|
@ -526,9 +526,9 @@
|
|||
"Sending": "Mengirim",
|
||||
"Spaces": "Space",
|
||||
"Connecting": "Menghubungkan",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s dan satu lainnya",
|
||||
"other": "%(items)s dan %(count)s lainnya"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> dan satu lainnya",
|
||||
"other": "<Items/> dan %(count)s lainnya"
|
||||
},
|
||||
"Disconnect from the identity server <idserver />?": "Putuskan hubungan dari server identitas <idserver />?",
|
||||
"Disconnect identity server": "Putuskan hubungan server identitas",
|
||||
|
|
|
@ -686,10 +686,9 @@
|
|||
"Enable desktop notifications": "Virkja tilkynningar á skjáborði",
|
||||
"Don't miss a reply": "Ekki missa af svari",
|
||||
"Review to ensure your account is safe": "Yfirfarðu þetta til að tryggja að aðgangurinn þinn sé öruggur",
|
||||
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s og einn til viðbótar",
|
||||
"other": "%(items)s og %(count)s til viðbótar"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> og einn til viðbótar",
|
||||
"other": "<Items/> og %(count)s til viðbótar"
|
||||
},
|
||||
"Private space": "Einkasvæði",
|
||||
"Mentions only": "Aðeins minnst á",
|
||||
|
|
|
@ -97,9 +97,9 @@
|
|||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "L'eliminazione di un widget lo rimuove per tutti gli utenti della stanza. Sei sicuro di eliminare il widget?",
|
||||
"Delete widget": "Elimina widget",
|
||||
"Home": "Pagina iniziale",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s e altri %(count)s",
|
||||
"one": "%(items)s e un altro"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> e altri %(count)s",
|
||||
"one": "<Items/> e un altro"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
|
||||
"collapse": "richiudi",
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
"Delete widget": "ウィジェットを削除",
|
||||
"Popout widget": "ウィジェットをポップアウト",
|
||||
"Home": "ホーム",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)sと他%(count)s人",
|
||||
"one": "%(items)sともう1人"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/>と他%(count)s人",
|
||||
"one": "<Items/>ともう1人"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s, %(lastItem)s",
|
||||
"collapse": "折りたたむ",
|
||||
|
|
|
@ -115,9 +115,9 @@
|
|||
"Set up Secure Messages": "Sbadu iznan iɣelsanen",
|
||||
"Logs sent": "Iɣmisen ttewaznen",
|
||||
"Not Trusted": "Ur yettwattkal ara",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s d %(count)s wiyaḍ",
|
||||
"one": "%(items)s d wayeḍ-nniḍen"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> d %(count)s wiyaḍ",
|
||||
"one": "<Items/> d wayeḍ-nniḍen"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s d %(lastItem)s",
|
||||
"Encryption upgrade available": "Yella uleqqem n uwgelhen",
|
||||
|
|
|
@ -173,9 +173,9 @@
|
|||
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "자기 자신을 강등하는 것은 되돌릴 수 없고, 자신이 마지막으로 이 방에서 특권을 가진 사용자라면 다시 특권을 얻는 건 불가능합니다.",
|
||||
"Jump to read receipt": "읽은 기록으로 건너뛰기",
|
||||
"Share room": "방 공유하기",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s님 외 한 명",
|
||||
"other": "%(items)s님 외 %(count)s명"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/>님 외 한 명",
|
||||
"other": "<Items/>님 외 %(count)s명"
|
||||
},
|
||||
"Permission Required": "권한 필요",
|
||||
"Copied!": "복사했습니다!",
|
||||
|
|
|
@ -1190,10 +1190,9 @@
|
|||
"other": "%(spaceName)s ແລະ %(count)s ອື່ນໆ"
|
||||
},
|
||||
"%(space1Name)s and %(space2Name)s": "%(space1Name)s ແລະ %(space2Name)s",
|
||||
"%(items)s and %(lastItem)s": "%(items)s ແລະ %(lastItem)s",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s ແລະ ອີກນຶ່ງລາຍການ",
|
||||
"other": "%(items)s ແລະ %(count)s ອື່ນໆ"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> ແລະ ອີກນຶ່ງລາຍການ",
|
||||
"other": "<Items/> ແລະ %(count)s ອື່ນໆ"
|
||||
},
|
||||
"Email (optional)": "ອີເມວ (ທາງເລືອກ)",
|
||||
"Just a heads up, if you don't add an email and forget your password, you could <b>permanently lose access to your account</b>.": "ກະລຸນາຮັບຊາບວ່າ, ຖ້າທ່ານບໍ່ເພີ່ມອີເມວ ແລະ ລືມລະຫັດຜ່ານຂອງທ່ານ, ທ່ານອາດ <b>ສູນເສຍການເຂົ້າເຖິງບັນຊີຂອງທ່ານຢ່າງຖາວອນ</b>.",
|
||||
|
|
|
@ -177,9 +177,9 @@
|
|||
"No backup found!": "Nerasta jokios atsarginės kopijos!",
|
||||
"Failed to decrypt %(failedCount)s sessions!": "Nepavyko iššifruoti %(failedCount)s seansų!",
|
||||
"Explore rooms": "Žvalgyti kambarius",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s ir %(count)s kiti(-ų)",
|
||||
"one": "%(items)s ir dar vienas"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> ir %(count)s kiti(-ų)",
|
||||
"one": "<Items/> ir dar vienas"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s ir %(lastItem)s",
|
||||
"General": "Bendrieji",
|
||||
|
|
|
@ -158,9 +158,9 @@
|
|||
"other": "Un par %(count)s vairāk..."
|
||||
},
|
||||
"This room is not public. You will not be able to rejoin without an invite.": "Šī istaba nav publiska un jūs nevarēsiet atkārtoti pievienoties bez uzaicinājuma.",
|
||||
"%(items)s and %(count)s others": {
|
||||
"one": "%(items)s un viens cits",
|
||||
"other": "%(items)s un %(count)s citus"
|
||||
"<Items/> and %(count)s others": {
|
||||
"one": "<Items/> un viens cits",
|
||||
"other": "<Items/> un %(count)s citus"
|
||||
},
|
||||
"Sunday": "Svētdiena",
|
||||
"Notification targets": "Paziņojumu adresāti",
|
||||
|
|
|
@ -248,9 +248,9 @@
|
|||
"Upgrade your encryption": "Oppgrader krypteringen din",
|
||||
"Verify this session": "Verifiser denne økten",
|
||||
"Not Trusted": "Ikke betrodd",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s og %(count)s andre",
|
||||
"one": "%(items)s og én annen"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> og %(count)s andre",
|
||||
"one": "<Items/> og én annen"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
|
||||
"Show more": "Vis mer",
|
||||
|
|
|
@ -151,9 +151,9 @@
|
|||
"Replying": "Aan het beantwoorden",
|
||||
"Unnamed room": "Naamloze kamer",
|
||||
"Banned by %(displayName)s": "Verbannen door %(displayName)s",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s en %(count)s andere",
|
||||
"one": "%(items)s en één ander"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> en %(count)s andere",
|
||||
"one": "<Items/> en één ander"
|
||||
},
|
||||
"collapse": "dichtvouwen",
|
||||
"expand": "uitvouwen",
|
||||
|
|
|
@ -109,9 +109,9 @@
|
|||
"Delete widget": "Slett widgeten",
|
||||
"Create new room": "Lag nytt rom",
|
||||
"Home": "Heim",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s og %(count)s til",
|
||||
"one": "%(items)s og ein til"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> og %(count)s til",
|
||||
"one": "<Items/> og ein til"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
|
||||
"collapse": "Slå saman",
|
||||
|
|
|
@ -210,9 +210,9 @@
|
|||
"other": "I %(count)s więcej…"
|
||||
},
|
||||
"Delete Backup": "Usuń kopię zapasową",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s i %(count)s innych",
|
||||
"one": "%(items)s i jedna inna osoba"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> i %(count)s innych",
|
||||
"one": "<Items/> i jedna inna osoba"
|
||||
},
|
||||
"Add some now": "Dodaj teraz kilka",
|
||||
"Continue With Encryption Disabled": "Kontynuuj Z Wyłączonym Szyfrowaniem",
|
||||
|
|
|
@ -151,9 +151,9 @@
|
|||
"Delete Widget": "Apagar widget",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Remover um widget o remove para todas as pessoas desta sala. Tem certeza que quer remover este widget?",
|
||||
"Delete widget": "Remover widget",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s e %(count)s outras",
|
||||
"one": "%(items)s e uma outra"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> e %(count)s outras",
|
||||
"one": "<Items/> e uma outra"
|
||||
},
|
||||
"collapse": "recolher",
|
||||
"expand": "expandir",
|
||||
|
|
|
@ -145,9 +145,9 @@
|
|||
},
|
||||
"Delete Widget": "Удалить виджет",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Удаление виджета действует для всех участников этой комнаты. Вы действительно хотите удалить этот виджет?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s и ещё %(count)s участника(-ов)",
|
||||
"one": "%(items)s и ещё кто-то"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> и ещё %(count)s участника(-ов)",
|
||||
"one": "<Items/> и ещё кто-то"
|
||||
},
|
||||
"Restricted": "Ограниченный пользователь",
|
||||
"%(duration)ss": "%(duration)s сек",
|
||||
|
|
|
@ -85,9 +85,9 @@
|
|||
"Delete widget": "Vymazať widget",
|
||||
"Create new room": "Vytvoriť novú miestnosť",
|
||||
"Home": "Domov",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s a %(count)s ďalší",
|
||||
"one": "%(items)s a jeden ďalší"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> a %(count)s ďalší",
|
||||
"one": "<Items/> a jeden ďalší"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s a tiež %(lastItem)s",
|
||||
"Custom level": "Vlastná úroveň",
|
||||
|
|
|
@ -94,9 +94,9 @@
|
|||
"Something went wrong!": "Diçka shkoi ters!",
|
||||
"Create new room": "Krijoni dhomë të re",
|
||||
"Home": "Kreu",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s dhe %(count)s të tjerë",
|
||||
"one": "%(items)s dhe një tjetër"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> dhe %(count)s të tjerë",
|
||||
"one": "<Items/> dhe një tjetër"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s dhe %(lastItem)s",
|
||||
"collapse": "tkurre",
|
||||
|
|
|
@ -94,9 +94,9 @@
|
|||
"Delete widget": "Обриши виџет",
|
||||
"Create new room": "Направи нову собу",
|
||||
"Home": "Почетна",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s и %(count)s других",
|
||||
"one": "%(items)s и још један"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> и %(count)s других",
|
||||
"one": "<Items/> и још један"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
|
||||
"collapse": "скупи",
|
||||
|
|
|
@ -173,9 +173,9 @@
|
|||
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.",
|
||||
"Confirm Removal": "Bekräfta borttagning",
|
||||
"Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s och %(count)s till",
|
||||
"one": "%(items)s och en till"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> och %(count)s till",
|
||||
"one": "<Items/> och en till"
|
||||
},
|
||||
"collapse": "fäll ihop",
|
||||
"expand": "fäll ut",
|
||||
|
|
|
@ -228,9 +228,9 @@
|
|||
"Your password has been reset.": "Parolanız sıfırlandı.",
|
||||
"General failure": "Genel başarısızlık",
|
||||
"Create account": "Yeni hesap",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s ve diğer %(count)s",
|
||||
"one": "%(items)s ve bir diğeri"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> ve diğer %(count)s",
|
||||
"one": "<Items/> ve bir diğeri"
|
||||
},
|
||||
"Clear personal data": "Kişisel veri temizle",
|
||||
"That matches!": "Eşleşti!",
|
||||
|
|
|
@ -172,9 +172,9 @@
|
|||
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) починає новий сеанс без його звірення:",
|
||||
"Ask this user to verify their session, or manually verify it below.": "Попросіть цього користувача звірити сеанс, або звірте його власноруч унизу.",
|
||||
"Not Trusted": "Не довірений",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s та ще %(count)s учасників",
|
||||
"one": "%(items)s і ще хтось"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> та ще %(count)s учасників",
|
||||
"one": "<Items/> і ще хтось"
|
||||
},
|
||||
"Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.",
|
||||
"Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив одне із своїх обмежень ресурсів.",
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
"Restricted": "Bị hạn chế",
|
||||
"Moderator": "Điều phối viên",
|
||||
"Reason": "Lý do",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s và %(count)s mục khác",
|
||||
"one": "%(items)s và một mục khác"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> và %(count)s mục khác",
|
||||
"one": "<Items/> và một mục khác"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s và %(lastItem)s",
|
||||
"Please contact your homeserver administrator.": "Vui lòng liên hệ quản trị viên homeserver của bạn.",
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
"Restricted": "Beperkten toegank",
|
||||
"Moderator": "Moderator",
|
||||
"Reason": "Reedn",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s en %(count)s andere",
|
||||
"one": "%(items)s en één ander"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> en %(count)s andere",
|
||||
"one": "<Items/> en één ander"
|
||||
},
|
||||
"%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s",
|
||||
"Please contact your homeserver administrator.": "Gelieve contact ip te neemn me den beheerder van je thuusserver.",
|
||||
|
|
|
@ -124,9 +124,9 @@
|
|||
"Jump to read receipt": "跳到阅读回执",
|
||||
"Unnamed room": "未命名的房间",
|
||||
"Delete Widget": "删除挂件",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s 和其他 %(count)s 人",
|
||||
"one": "%(items)s 与另一个人"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> 和其他 %(count)s 人",
|
||||
"one": "<Items/> 与另一个人"
|
||||
},
|
||||
"collapse": "折叠",
|
||||
"expand": "展开",
|
||||
|
|
|
@ -150,9 +150,9 @@
|
|||
"Banned by %(displayName)s": "被 %(displayName)s 封鎖",
|
||||
"Delete Widget": "刪除小工具",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "刪除小工具會將它從此聊天室中所有使用者的收藏中移除。您確定您要刪除這個小工具嗎?",
|
||||
"%(items)s and %(count)s others": {
|
||||
"other": "%(items)s 與其他 %(count)s 個人",
|
||||
"one": "%(items)s 與另一個人"
|
||||
"<Items/> and %(count)s others": {
|
||||
"other": "<Items/> 與其他 %(count)s 個人",
|
||||
"one": "<Items/> 與另一個人"
|
||||
},
|
||||
"collapse": "收折",
|
||||
"expand": "展開",
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
import { ReactElement, ReactNode } from "react";
|
||||
import { useIdColorHash } from "@vector-im/compound-web";
|
||||
|
||||
import { _t, getCurrentLanguage } from "../languageHandler";
|
||||
import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler";
|
||||
import { jsxJoin } from "./ReactUtils";
|
||||
const locale = getCurrentLanguage();
|
||||
|
||||
|
@ -92,34 +92,42 @@ export function getUserNameColorClass(userId: string): string {
|
|||
* @returns {string} a string constructed by joining `items` with a comma
|
||||
* between each item, but with the last item appended as " and [lastItem]".
|
||||
*/
|
||||
export function formatCommaSeparatedList(items: string[], itemLimit?: number): string;
|
||||
export function formatCommaSeparatedList(items: ReactElement[], itemLimit?: number): ReactElement;
|
||||
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode;
|
||||
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode {
|
||||
const remaining = itemLimit === undefined ? 0 : Math.max(items.length - itemLimit, 0);
|
||||
if (items.length === 0) {
|
||||
return "";
|
||||
} else if (items.length === 1) {
|
||||
return items[0];
|
||||
} else {
|
||||
let lastItem;
|
||||
if (remaining > 0) {
|
||||
items = items.slice(0, itemLimit);
|
||||
} else {
|
||||
lastItem = items.pop();
|
||||
export function formatList(items: string[], itemLimit?: number, includeCount?: boolean): string;
|
||||
export function formatList(items: ReactElement[], itemLimit?: number, includeCount?: boolean): ReactElement;
|
||||
export function formatList(items: ReactNode[], itemLimit?: number, includeCount?: boolean): ReactNode;
|
||||
export function formatList(items: ReactNode[], itemLimit = items.length, includeCount = false): ReactNode {
|
||||
let remaining = Math.max(items.length - itemLimit, 0);
|
||||
if (items.length <= 1) {
|
||||
return items[0] ?? "";
|
||||
}
|
||||
|
||||
const formatter = new Intl.ListFormat(getUserLanguage(), { style: "long", type: "conjunction" });
|
||||
if (remaining > 0) {
|
||||
if (includeCount) {
|
||||
itemLimit--;
|
||||
remaining++;
|
||||
}
|
||||
|
||||
let joinedItems;
|
||||
items = items.slice(0, itemLimit);
|
||||
let joinedItems: ReactNode;
|
||||
if (items.every((e) => typeof e === "string")) {
|
||||
joinedItems = items.join(", ");
|
||||
} else {
|
||||
joinedItems = jsxJoin(items, ", ");
|
||||
}
|
||||
|
||||
if (remaining > 0) {
|
||||
return _t("%(items)s and %(count)s others", { items: joinedItems, count: remaining });
|
||||
} else {
|
||||
return _t("%(items)s and %(lastItem)s", { items: joinedItems, lastItem });
|
||||
}
|
||||
return _t("<Items/> and %(count)s others", { count: remaining }, { Items: () => joinedItems });
|
||||
}
|
||||
|
||||
if (items.every((e) => typeof e === "string")) {
|
||||
return formatter.format(items as string[]);
|
||||
}
|
||||
|
||||
const parts = formatter.formatToParts(items.map((_, i) => `${i}`));
|
||||
return jsxJoin(
|
||||
parts.map((part) => {
|
||||
if (part.type === "literal") return part.value;
|
||||
return items[parseInt(part.value, 10)];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/matrix";
|
|||
import SpaceStore from "../stores/spaces/SpaceStore";
|
||||
import { _t } from "../languageHandler";
|
||||
import DMRoomMap from "./DMRoomMap";
|
||||
import { formatList } from "./FormattingUtils";
|
||||
|
||||
export interface RoomContextDetails {
|
||||
details: string | null;
|
||||
|
@ -39,7 +40,7 @@ export function roomContextDetails(room: Room): RoomContextDetails | null {
|
|||
const space1Name = room.client.getRoom(parent)?.name;
|
||||
const space2Name = room.client.getRoom(secondParent)?.name;
|
||||
return {
|
||||
details: _t("%(space1Name)s and %(space2Name)s", { space1Name, space2Name }),
|
||||
details: formatList([space1Name ?? "", space2Name ?? ""]),
|
||||
ariaLabel: _t("in_space1_and_space2", { space1Name, space2Name }),
|
||||
};
|
||||
} else if (parent) {
|
||||
|
@ -47,7 +48,7 @@ export function roomContextDetails(room: Room): RoomContextDetails | null {
|
|||
const count = otherParents.length;
|
||||
if (count > 0) {
|
||||
return {
|
||||
details: _t("%(spaceName)s and %(count)s others", { spaceName, count }),
|
||||
details: formatList([spaceName, ...otherParents], 1),
|
||||
ariaLabel: _t("in_space_and_n_other_spaces", { spaceName, count }),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
import EventListSummary from "../../../../src/components/views/elements/EventListSummary";
|
||||
import { Layout } from "../../../../src/settings/enums/Layout";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import * as languageHandler from "../../../../src/languageHandler";
|
||||
|
||||
describe("EventListSummary", function () {
|
||||
const roomId = "!room:server.org";
|
||||
|
@ -136,6 +137,7 @@ describe("EventListSummary", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||
import * as languageHandler from "../../../../src/languageHandler";
|
||||
|
||||
const CHECKED = "mx_PollOption_checked";
|
||||
const userId = "@me:example.com";
|
||||
|
@ -58,6 +59,7 @@ describe("MPollBody", () => {
|
|||
|
||||
mockClient.getRoom.mockReturnValue(null);
|
||||
mockClient.relations.mockResolvedValue({ events: [] });
|
||||
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
|
||||
});
|
||||
|
||||
it("finds no votes if there are none", () => {
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../test-utils";
|
||||
import * as languageHandler from "../../../../src/languageHandler";
|
||||
|
||||
describe("RoomKnocksBar", () => {
|
||||
const userId = "@alice:example.org";
|
||||
|
@ -127,6 +128,7 @@ describe("RoomKnocksBar", () => {
|
|||
jest.spyOn(state, "hasSufficientPowerLevelFor").mockReturnValue(true);
|
||||
jest.spyOn(Modal, "createDialog");
|
||||
jest.spyOn(dis, "dispatch");
|
||||
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
|
||||
});
|
||||
|
||||
it("does not render if user can neither approve nor deny", () => {
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { formatCount, formatCountLong } from "../../src/utils/FormattingUtils";
|
||||
|
||||
jest.mock("../../src/dispatcher/dispatcher");
|
||||
|
||||
describe("FormattingUtils", () => {
|
||||
describe("formatCount", () => {
|
||||
it.each([
|
||||
{ count: 999, expectedCount: "999" },
|
||||
{ count: 9999, expectedCount: "10K" },
|
||||
{ count: 99999, expectedCount: "100K" },
|
||||
{ count: 999999, expectedCount: "1M" },
|
||||
{ count: 9999999, expectedCount: "10M" },
|
||||
{ count: 99999999, expectedCount: "100M" },
|
||||
{ count: 999999999, expectedCount: "1B" },
|
||||
{ count: 9999999999, expectedCount: "10B" },
|
||||
])("formats $count as $expectedCount", ({ count, expectedCount }) => {
|
||||
expect(formatCount(count)).toBe(expectedCount);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatCountLong", () => {
|
||||
it("formats numbers according to the locale", () => {
|
||||
expect(formatCountLong(1000)).toBe("1,000");
|
||||
});
|
||||
});
|
||||
});
|
103
test/utils/FormattingUtils-test.tsx
Normal file
103
test/utils/FormattingUtils-test.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { formatList, formatCount, formatCountLong } from "../../src/utils/FormattingUtils";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
|
||||
jest.mock("../../src/dispatcher/dispatcher");
|
||||
|
||||
describe("FormattingUtils", () => {
|
||||
describe("formatCount", () => {
|
||||
it.each([
|
||||
{ count: 999, expectedCount: "999" },
|
||||
{ count: 9999, expectedCount: "10K" },
|
||||
{ count: 99999, expectedCount: "100K" },
|
||||
{ count: 999999, expectedCount: "1M" },
|
||||
{ count: 9999999, expectedCount: "10M" },
|
||||
{ count: 99999999, expectedCount: "100M" },
|
||||
{ count: 999999999, expectedCount: "1B" },
|
||||
{ count: 9999999999, expectedCount: "10B" },
|
||||
])("formats $count as $expectedCount", ({ count, expectedCount }) => {
|
||||
expect(formatCount(count)).toBe(expectedCount);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatCountLong", () => {
|
||||
it("formats numbers according to the locale", () => {
|
||||
expect(formatCountLong(1000)).toBe("1,000");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatList", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue("en-GB");
|
||||
});
|
||||
|
||||
it("should return empty string when given empty list", () => {
|
||||
expect(formatList([])).toEqual("");
|
||||
});
|
||||
|
||||
it("should return only item when given list of length 1", () => {
|
||||
expect(formatList(["abc"])).toEqual("abc");
|
||||
});
|
||||
|
||||
it("should return expected sentence in English without item limit", () => {
|
||||
expect(formatList(["abc", "def", "ghi"])).toEqual("abc, def and ghi");
|
||||
});
|
||||
|
||||
it("should return expected sentence in German without item limit", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue("de");
|
||||
expect(formatList(["abc", "def", "ghi"])).toEqual("abc, def und ghi");
|
||||
});
|
||||
|
||||
it("should return expected sentence in English with item limit", () => {
|
||||
expect(formatList(["abc", "def", "ghi", "jkl"], 2)).toEqual("abc, def and 2 others");
|
||||
expect(formatList(["abc", "def", "ghi", "jkl"], 3)).toEqual("abc, def, ghi and one other");
|
||||
});
|
||||
|
||||
it("should return expected sentence in English with item limit and includeCount", () => {
|
||||
expect(formatList(["abc", "def", "ghi", "jkl"], 3, true)).toEqual("abc, def and 2 others");
|
||||
expect(formatList(["abc", "def", "ghi", "jkl"], 4, true)).toEqual("abc, def, ghi and jkl");
|
||||
});
|
||||
|
||||
it("should return expected sentence in ReactNode when given 2 React children", () => {
|
||||
expect(formatList([<span key="a">a</span>, <span key="b">b</span>])).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should return expected sentence in ReactNode when given more React children", () => {
|
||||
expect(
|
||||
formatList([
|
||||
<span key="a">a</span>,
|
||||
<span key="b">b</span>,
|
||||
<span key="c">c</span>,
|
||||
<span key="d">d</span>,
|
||||
]),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should return expected sentence in ReactNode when using itemLimit", () => {
|
||||
expect(
|
||||
formatList(
|
||||
[<span key="a">a</span>, <span key="b">b</span>, <span key="c">c</span>, <span key="d">d</span>],
|
||||
2,
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
48
test/utils/__snapshots__/FormattingUtils-test.tsx.snap
Normal file
48
test/utils/__snapshots__/FormattingUtils-test.tsx.snap
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given 2 React children 1`] = `
|
||||
<span>
|
||||
<span>
|
||||
a
|
||||
</span>
|
||||
and
|
||||
<span>
|
||||
b
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given more React children 1`] = `
|
||||
<span>
|
||||
<span>
|
||||
a
|
||||
</span>
|
||||
,
|
||||
<span>
|
||||
b
|
||||
</span>
|
||||
,
|
||||
<span>
|
||||
c
|
||||
</span>
|
||||
and
|
||||
<span>
|
||||
d
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = `
|
||||
<span>
|
||||
<span>
|
||||
<span>
|
||||
a
|
||||
</span>
|
||||
,
|
||||
<span>
|
||||
b
|
||||
</span>
|
||||
</span>
|
||||
and 2 others
|
||||
</span>
|
||||
`;
|
|
@ -56,7 +56,7 @@ describe("roomContextDetails", () => {
|
|||
new Set([parent1.roomId, parent2.roomId, parent3.roomId]),
|
||||
);
|
||||
const res = roomContextDetails(room);
|
||||
expect(res!.details).toMatchInlineSnapshot(`"Alpha and 1 other"`);
|
||||
expect(res!.ariaLabel).toMatchInlineSnapshot(`"In Alpha and 1 other space."`);
|
||||
expect(res!.details).toMatchInlineSnapshot(`"Alpha and one other"`);
|
||||
expect(res!.ariaLabel).toMatchInlineSnapshot(`"In Alpha and one other space."`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"outDir": "./lib",
|
||||
"declaration": true,
|
||||
"jsx": "react",
|
||||
"lib": ["es2020", "dom", "dom.iterable"],
|
||||
"lib": ["es2021", "dom", "dom.iterable"],
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
|
|
Loading…
Reference in a new issue