Merge branch 'develop' into andybalaam/fix-receipt-flakes

This commit is contained in:
Andy Balaam 2023-09-25 12:41:05 +01:00 committed by GitHub
commit d37790c460
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 371 additions and 283 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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);
}
/**

View file

@ -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 {

View file

@ -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,

View file

@ -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) {

View file

@ -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,13 +83,13 @@ 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: {
if (knockMembersCount === 1) {
buttons = (
<>
<AccessibleButton
@ -122,18 +123,6 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
{_t("action|view_message")}
</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 });
}
return (

View file

@ -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": "свий",

View file

@ -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",

View file

@ -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...": {

View file

@ -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.",

View file

@ -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",

View file

@ -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) συνδέθηκε σε μία νέα συνεδρία χωρίς να την επιβεβαιώσει:",

View file

@ -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"

View file

@ -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",

View file

@ -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",

View file

@ -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.",

View file

@ -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",

View file

@ -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.": "با تائید هویت خود به پیام‌های رمزشده دسترسی یافته و هویت خود را به دیگران ثابت می‌کنید.",

View file

@ -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",

View file

@ -142,9 +142,9 @@
"Jump to read receipt": "Aller à laccusé 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…"

View file

@ -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",

View file

@ -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.": "בקש ממשתמש זה לאמת את ההתחברות שלו, או לאמת אותה באופן ידני למטה.",

View file

@ -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",

View file

@ -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",

View file

@ -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 á",

View file

@ -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",

View file

@ -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": "折りたたむ",

View file

@ -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",

View file

@ -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!": "복사했습니다!",

View file

@ -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>.",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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 сек",

View file

@ -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ň",

View file

@ -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",

View file

@ -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": "скупи",

View file

@ -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",

View file

@ -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!",

View file

@ -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.": "Ваш домашній сервер перевищив одне із своїх обмежень ресурсів.",

View file

@ -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.",

View file

@ -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.",

View file

@ -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": "展开",

View file

@ -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": "展開",

View file

@ -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] ?? "";
}
let joinedItems;
const formatter = new Intl.ListFormat(getUserLanguage(), { style: "long", type: "conjunction" });
if (remaining > 0) {
if (includeCount) {
itemLimit--;
remaining++;
}
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)];
}),
);
}

View file

@ -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 }),
};
}

View file

@ -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(() => {

View file

@ -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", () => {

View file

@ -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", () => {

View file

@ -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");
});
});
});

View 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();
});
});
});

View 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>
`;

View file

@ -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."`);
});
});

View file

@ -12,7 +12,7 @@
"outDir": "./lib",
"declaration": true,
"jsx": "react",
"lib": ["es2020", "dom", "dom.iterable"],
"lib": ["es2021", "dom", "dom.iterable"],
"strict": true
},
"include": [